《ATD》 游戏简介


游戏类型:塔防+RPG的3D游戏

游戏要素:3D 塔防 英雄 建筑树 搭配

主体玩法:游戏里将会有一波波怪物进攻基地。玩家可以建造塔来防御敌人,同时也可以控制单独的个体英雄角色来攻击敌人。

游戏模式

  • 第三人称视角的RPG模式

  • 上帝视角的建造模式

控制方式:在游戏中使用Tab按键,切换这两种操作模式:

  • RPG模式下:WASD控制移动,Space跳跃,鼠标左键普通攻击。
  • 建造模式下:鼠标左键建造,E销毁已建造的建筑。
  • 数字键1,2,3,4,5,6控制物品栏,对应英雄技能或者建筑安放。

胜利条件:消灭所有敌人 或者 坚持到时间结束

失败条件:基地生命值为0 或者 英雄死亡

《ATD》 整体结构


一般来说,整个Unity游戏项目整体结构,我比较偏向分为如下5部分:

  • 场景对象 :不会产生互动的可视物体对象,例如地型/建筑/灯光。

  • 游戏对象 :参与互动的游戏对象,例如英雄/怪物/塔。

  • 游戏逻辑 :负责控制游戏的逻辑,其逻辑对象一般是单例的。

  • 非游戏性对象 :负责增强游戏效果,但不是直接的游戏逻辑,例如UI/HUD/特效/声音。

  • 工具 :负责辅助编码,例如日志工具,调试工具。

在《ATD》游戏项目里,我是这样设置游戏对象目录的:

注:“个体”在《ATD》里的术语表示游戏对象单位。

《ATD》 游戏机制


通过分析《ATD》策划案,确立了两种需要实现的基本游戏机制:

Buff机制

和策划商量后,策划制作了下面一张含所有Buff属性的Excel表:

由于策划还没想好Buff名字,直接套用装备或者技能名字来命名Buff。

首先,使用了一个数据类型BuffData,用于完全映射Buff在表格的所有属性:

  1. public class BuffData
  2. {
  3. public int ID;
  4. public string Name;
  5. public int HpChange; //血量变化
  6. public double HpChange_p; //血量百分比变化
  7. public int AttackChange; //攻击力变化
  8. public double AttackChange_p; //攻击力百分比变化
  9. public double AttSpeedChange_p; //攻击速度百分比变化
  10. public double SpeedChange_p; //速度百分比变化
  11. public int HpReturnChange; //血量恢复数值
  12. public double HpReturnChange_p; //血量百分比恢复数值
  13. public int AddReviveCount; //增加复活次数
  14. public bool isDecelerate; //减速
  15. public bool isVertigo; //眩晕
  16. public bool isParalysis; //麻痹
  17. //...等属性
  18. }

然后我们就可以用一个 List<BuffData>来存储表示所有Buff种类。

为了读取Excel表,并根据读入的所有Buff种类属性来初始化 List<BuffData>,于是就引入了一个BuffDataBase的全局单例类来负责此事:

  1. //全局单例类
  2. public class BuffDataBase : MonoBehaviour
  3. {
  4. //读取excel插件生成的json文件
  5. public TextAsset buffDataJson;
  6. //存储BuffData的列表
  7. private List<BuffData> buffDatas;
  8. //全局单例实现
  9. //...
  10. //根据ID获取相应的BuffData对象
  11. public BuffData GetBuffData(int ID){
  12. //...
  13. }
  14. }

为了表示游戏对象得到/失去一个Buff而从BuffDataBase找到并拷贝一份BuffData对象/释放掉一份BuffData对象显然是不明智的。(BuffData所占空间大,开销大)

正确的做法应该是使用索引/引用的方式,例如某个游戏对象持有3号索引,则表示它当前受一个ID为3的Buff影响。

为了引入Buff的时间有效性,则进一步封装索引,于是编写了下面一个Buff类:

  1. public class Buff
  2. {
  3. public int ID; //BuffData的ID(索引)
  4. public double time; //持续时间
  5. public int repeatCount; //重复次数
  6. public bool isTrigger; //是否触发类型
  7. }

因为每个Buff的时间有效性都有所不同:有些Buff是一次性触发Buff;也有一些是持续性Buff,持续N秒;还有一些是被动buff,永久生效。

所以我这里就总结了个规则,Buff主要分为两种类型:

  • 持续型(Non-Trigger):开始对属性造成生效影响一次,有效时间结束时造成失效影响一次。例如一段时间内增加攻速Buff
  • 触发性型(Trigger):有效时间内,每一帧对属性造成生效影响一次。例如一次性伤害Buff,光环Buff。

然后Buff的有效时间取决于2个属性:

  • 持续时间(time):每帧持续时间减少DeltaTime
  • 触发次数(repeatCount):每帧触发次数减一

当一个Buff对象,持续时间 <= 0 并且 触发次数为0,则应视为失效。特殊地,触发次数为-1时,表示无限时间。

这样Buff/BuffData/BuffDataBase基本构造就出现了:

整个游戏同种类Buff只用存储一份BuffData;但是可以有很多个对象持有索引/引用,指向这个BuffData。

游戏对象持有Buff对象,通过BuffDataBase访问BuffData的数据,然后利用这些数据对游戏对象属性造成影响。

看到这里,可能会有人想到前面有个问题:对于任意一种Buff,它往往有很多属性是false或者0,使用这种完全映射会不会很影响空间占用或者效率。

  1. 首先,空间占用绝对不用担心,因为前面BuffDataBase机制保证同种Buff只有唯一BuffData副本,其所有BuffData总共占用量不过几kb而已。
  2. 其次,至于效率,例如说某个Buff对某个游戏对象造成影响,因为是完全映射,所以需要对该游戏对象每个属性都要进行更新,其实这也并不是太糟糕。

    而且只要游戏对象有比较好的Buff计算方式,可以让一个Buff对象的整个有效周期只对对象造成两次影响计算(生效影响,失效影响),避免每帧出现影响多余的计算,这样就很不错了。

Skill机制(技能机制)

可以说技能是我比较头疼的部分。

看到那千奇百怪的Skill需求时,然后才总结出大概这几个分类:

  • 主动Buff技能 = 主动释放,生成一个Buff
  • 被动Buff技能 = 初始化时,生成一个Buff
  • 召唤技能 = 生成一个游戏对象
  • 指向性技能 = 主动释放,对锁定的目标生成一个Buff

最后我决定使用继承接口的方式来实现Skill:

技能接口:

  1. public interface ISkill
  2. {
  3. // 技能初始化接口
  4. void InitSkill(Individual user);
  5. // 使用技能接口
  6. void ReleaseSkill(Individual user);
  7. /// 技能每帧更新
  8. void UpdateSkill(Individual user);
  9. /// 技能是否冷却
  10. bool IsColdTimeEnd();
  11. // 技能冷却百分比
  12. float GetColdTimePercent();
  13. }

需要注意的一点是,技能并不是主动释放时调用一个自定义的技能函数即可完事:

例如持续性的范围技能,需要每帧调用散发Buff的函数。

所以一个ISkill对象 该有这3种重要的接口方法:初始化/主动释放/每帧更新

下面是其中一个派生类的具体实现:

由于一开始设计考虑不足,Buff技能类暂时包含了ActiveBuff技能类和PassiveBuff技能类的功能。

  1. // 示例:Buff技能类
  2. public class BuffSkill : ISkill
  3. {
  4. public int buffID; //目的Buff
  5. public bool isAura = true; //光环
  6. public bool releasable = true; //是否主动释放
  7. public float range = 0.01f; //范围
  8. private float coldTime = 5.0f; //冷却时间
  9. private float timer = 5.0f; //冷却计时
  10. //构造方法
  11. public BuffSkill(int buffID,bool releasable = true,bool isAura = true, float range = 0.01f)
  12. {
  13. this.buffID = buffID;
  14. this.isAura = isAura;
  15. this.range = range;
  16. this.releasable = releasable;
  17. }
  18. //初始化技能
  19. public void InitSkill(Individual master)
  20. {
  21. //非光环的被动buff
  22. if (!releasable && !isAura)
  23. {
  24. var individual = master.GetComponent<Individual>();
  25. master.GetComponent<MessageSystem>().SendMessage(2, individual.ID,buffID);
  26. }
  27. }
  28. //释放技能
  29. public void ReleaseSkill(Individual master)
  30. {
  31. //主动buff
  32. if (releasable && IsColdTimeEnd())
  33. {
  34. timer = 0.0f;
  35. Factory.TraversalIndividualsInCircle(
  36. (individual) => { master.GetComponent<MessageSystem>().SendMessage(2, individual.ID, buffID); }
  37. , master.transform.position, range);
  38. }
  39. }
  40. //技能每帧更新
  41. public void UpdateSkill(Individual master)
  42. {
  43. //增加计时
  44. timer =Mathf.Min(timer+Time.deltaTime, coldTime);
  45. //光环被动buff:每帧向周围range范围内的对象散发buff
  46. if (!releasable && isAura)
  47. {
  48. Factory.TraversalIndividualsInCircle(
  49. (individual) => { master.GetComponent<MessageSystem>().SendMessage(2, individual.ID, buffID); }
  50. , master.transform.position, range);
  51. }
  52. }
  53. //得到冷却时间百分比
  54. public float GetColdTimePercent()
  55. {
  56. if (!releasable) return 1.0f;
  57. return timer / coldTime;
  58. }
  59. //冷却时间是否结束
  60. public bool IsColdTimeEnd()
  61. {
  62. return timer > coldTime;
  63. }
  64. }

派生类的构造函数很重要,这样即使硬编码了4个技能派生类,通过不同的数据参数传入,也能产生更多不同的技能对象。

最后还应该再写一个SkillDataBase全局单例类,它负责读取策划写的技能配置文件,来初始化出来一些ISkill对象,以供游戏对象使用。

不过项目代码还没写完,因此项目目前是直接在SkillDataBase的初始化函数直接硬编码3个技能。


  1. public class SkillDataBase : MonoBehaviour
  2. {
  3. //技能json文件
  4. public TextAsset skillDataJson;
  5. //全局单例实现
  6. //...
  7. //读取文件,初始化ISkill对象给英雄使用
  8. public void InitSkillsData(){
  9. //...TODO:读取文件来初始化英雄的技能
  10. //目前硬编码给英雄赋予3个技能
  11. HeroSkills.Add(new BuffSkill(6, true, true, 5.0f)); //主动技能:嘲讽Buff
  12. HeroSkills.Add(new BuffSkill(0, false, false)); //被动技能:回血buff
  13. HeroSkills.Add(new BuffSkill(14, true, false)); //主动技能:攻速戒指buff
  14. }
  15. }

以后的话,SkillDataBase的初始化函数应该是读取某种配置文件,然后生成若干个对应的技能对象分配给游戏对象使用:

仇恨机制

待更新

《ATD》 游戏模型


策划案部分摘取





分析了策划案后,显而易见里面划分了这4种游戏模型:

英雄怪物陷阱

继承实现游戏模型

最初想到的是使用继承的方式来实现这些游戏模型(如图):

然而考虑到现在的英雄/怪物/陷阱/塔类型已经足够太多了,而且以后还可能会扩展更多。

若用继承的方式,其派生类数量将到达一个小团队难以维护的地步。

至于之前设计Skill机制的时候,为什么反而采用继承的方式,原因如下:

  1. 策划案里,Skill的种类只有8种,所以需要编写的派生类比较少,而英雄/怪物/陷阱/塔所有种类总共加起来有二十多种。
  2. Skill不是GameObject,没有Unity提供的GameObject-Component机制,不太方便接纳组件(除非自己再实现一套组件模式)。
  3. 实际上,还有个设计Skill的思路就是把Skill设计成一个行为树,通过组合节点来生成一个Skill。然而因为当时急于实现,于是抛弃了这个想法。

首先为了统一术语,避免游戏模型和Unity的GameObject弄混淆,我们定义了一个称之为 个体(Individual) 的名词,来表示一个游戏模型单位。

组合实现游戏模型

再想到Unity的GameObject-Component机制,于是最后我采用组合组件的方式来设计这几个游戏模型。

那么如何表示一个个体游戏对象呢?

首先我们需要编写一些个体游戏对象必要的组件脚本类。

对于一个个体游戏对象,它可能由如下图构成:

一般来说行为和输入都应该放在一起统称为控制器,然而实际上在游戏里,输入来源可能是玩家,也可能是AI,因此把个体对象行为和输入分离是个好的选择。

也就是说它得有属性,行为,操控行为的输入,还得可以容纳Buff机制,Skill机制和装备机制。

根据这些需求分化出来不少组件类:

然后为了解耦各组件的依赖关系,特别是跨游戏对象的组件依赖,于是还额外引入了一个 消息系统组件 ,实际上就是用于实现观察者模式。

每个个体对象都必须带一个消息系统组件,且其他编写的组件类基本上都依赖这个消息系统组件。

例如,A个体用指向性技能对B个体进行释放实际上的行为是:

由A个体的 技能系统组件 发送消息给A个体的 消息系统组件

然后A个体的 消息系统组件 把消息再转发给B个体的 消息系统组件

B个体的 消息系统组件 再把消息通知给 Buff系统组件 ,从而让B个体受到该Buff影响。

组合实现设计结构

最终个体游戏对象的组件依赖关系图:



然后通过一个GameObject然后添加好模型,然后放置一些组件从而组合出来一个个体游戏对象。

一个怪物个体游戏对象示例:

结语


《ATD》本来只是社团部门内提出的一个Unity游戏项目,而我负责这个项目的程序架构设计。

然而中途开发因为不少事,我们不得不放弃了这个项目。

感觉到有些可惜,因此才想得写点东西总结一下开发这个项目时的经验。

GitHub - ima-games/ATD: Unity RPG+塔防3D游戏

第二篇:Unity《ATD》塔防RPG类3D游戏架构设计(二) - KillerAery - 博客园

Unity《ATD》塔防RPG类3D游戏架构设计(一)的更多相关文章

  1. Unity《ATD》塔防RPG类3D游戏架构设计(二)

    目录 <ATD> 游戏模型 <ATD> 游戏逻辑 <ATD> UI/HUD/特效/音乐 结语 前篇:Unity<ATD>塔防RPG类3D游戏架构设计(一 ...

  2. 使用Unity创建塔防游戏(Part3)—— 项目总结

    之前我们完成了使用Unity创建塔防游戏这个小项目,在这篇文章里,我们对项目中学习到的知识进行一次总结. Part1的地址:http://www.cnblogs.com/lcxBlog/p/60759 ...

  3. 使用Unity创建塔防游戏(Part2)

    How to Create a Tower Defense Game in Unity – Part 2 原文地址:https://www.raywenderlich.com/107529/unity ...

  4. 3D游戏引擎设计 实时计算机图形学的应用方法 第2版 pdf 带索引书签目录

    3D游戏引擎设计  实时计算机图形学的应用方法  第2版 目录 第1章 概述1.1 图形硬件和游戏发展史1.2 本书版本与软件发展史1.3 章节导读 第2章 图形系统2.1 基础知识2.1.1 坐标系 ...

  5. 使用Unity创建塔防游戏(Part1)

    How to Create a Tower Defense Game in Unity - Part1 原文作者:Barbara Reichart 文章原译:http://www.cnblogs.co ...

  6. 使用unity创建塔防游戏(原译)(part1)

    塔防游戏非常地受欢迎,木有什么能比看着自己的防御毁灭邪恶的入侵者更爽的事了. 在这个包含两部分的教程中,你将使用Unity创建一个塔防游戏. 你将会学到如何: 创建一波一波的敌人 使敌人随着路标移动 ...

  7. 解构C#游戏框架uFrame兼谈游戏架构设计

    1.概览 uFrame是提供给Unity3D开发者使用的一个框架插件,它本身模仿了MVVM这种架构模式(事实上并不包含Model部分,且多出了Controller部分).因为用于Unity3D,所以它 ...

  8. 游戏引擎架构,3d游戏引擎设计、Unreal引擎技术等五本PDF推荐

    扫码时备注或说明中留下邮箱 付款后如未回复请至https://shop135452397.taobao.com/ 联系店主

  9. 使用 Unity 3D 开发游戏的架构设计难点

    Unity 3D 引擎对于开发者来说,入手非常快,因为它采用的是 C# 作为开发语言,这也大大降低了开发者的门槛.但凡只要懂一门编程语言的人都能使用 Unity 3D 引擎开发,另外 Unity 3D ...

随机推荐

  1. 【Web技术】314- 前端组件设计原则

    点击上方"前端自习课"关注,学习起来~ 译者:@没有好名字了译文:https://github.com/lightningminers/article/issues/36,http ...

  2. BIOS安全设置

    1.开机按F2进入BIOS 2.进入 Security 界面 3.Set user password 用户密码 开机密码 设置为123456 4.Set supervisor password 进BI ...

  3. JS基础知识——原型与原型链

    1.如何准确判断一个变量的数组类型 2.写一个原型链继承的例子 3.描述new一个对象的过程 4.zepto(或其他框架中如何使用原型链) 知识点: (1)构造函数 function Foo(name ...

  4. CCF-CSP题解 201812-4 数据中心

    题目要求最长边最小的生成树.好吧,这就是一道kruskal MST题. #include <bits/stdc++.h> const int maxn = 50000; const int ...

  5. poj 1511 Invitation Cards (最短路)

    Invitation Cards Time Limit: 8000MS   Memory Limit: 262144K Total Submissions: 33435   Accepted: 111 ...

  6. Real World CTF一日游

    今天去感受了长亭举办的RWCTF现场,参加了技术论坛,也学到了很多的知识 比较有印象的就是 智能安全在Web防护中的探索和实践 阿里云安全防护构建的AI架构体系: 基线检测 基础过滤 异常检测 攻击识 ...

  7. js获取当前时间的年月日时分秒以及时间的格式化

    1.获取当前时间 var myDate = new Date(); 2.获取时间中的年月日时分秒 myDate.getYear(); // 获取当前年份(2位) myDate.getFullYear( ...

  8. Java面试题_第三阶段(Spring、MVC、IOC、AOP、DI、MyBatis、SSM、struts2)

    1.1 何为Spring Bean容器?Spring Bean容器与Spring IOC 容器有什么不同吗? 答:1)用于创建bean对象,管理bean对象的那个容器. 2)Spring IOC 容器 ...

  9. 非关系型数据库--redis

    0.1 新单词 expire 美 /ɪk'spaɪɚ/ 到期 range 美 /rendʒ/ 范围 idle美 /'aɪdl/ 闲置的 0.2 面试题:mysql和redis和memcached区别? ...

  10. Android 仿真器 无法启动排查

    从命令行启动仿真器,可以查看其输出. Microsoft Windows [版本 10.0.18362.145] (c) 2019 Microsoft Corporation.保留所有权利. C:\U ...