经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 游戏设计 » 查看文章
Unity《ATD》塔防RPG类3D游戏架构设计(一)
来源:cnblogs  作者:KillerAery  时间:2019/7/16 8:38:10  对本文有异议

《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机制
  • Skill机制(技能机制)

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. public bool isSleep; //睡眠
  18. public bool isBound; //束缚
  19. public bool isBurn; //点燃
  20. public bool isCharm; //魅惑
  21. public bool isIncreaseAttSpeed; //攻速提高
  22. public bool isPoisoning; //中毒
  23. public bool isImmuneControl; //免疫控制
  24. public bool isRevenge; //复仇
  25. public bool isTaunt; //嘲讽
  26. public bool isIncreaseHpReturn; //回血速度提高
  27. public bool isIncreaseAttack; //攻击力提高
  28. }

然后我们就可以用一个List

  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号索引,则表示它当前受一个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,使用这种完全映射会不会很影响空间占用或者效率。

首先,空间占用绝对不用担心,因为前面BuffDataBase机制保证同种Buff只有唯一BuffData副本,其所有BuffData总共占用量不过几kb而已。

其次,至于效率,例如说某个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技能类和召唤技能类。
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. public BuffSkill(int buffID,bool releasable = true,bool isAura = true, float range = 0.01f)
  11. {
  12. this.buffID = buffID;
  13. this.isAura = isAura;
  14. this.range = range;
  15. this.releasable = releasable;
  16. }
  17. public void InitSkill(Individual master)
  18. {
  19. if (!releasable && !isAura)
  20. {
  21. var individual = master.GetComponent<Individual>();
  22. master.GetComponent<MessageSystem>().SendMessage(2, individual.ID,buffID);
  23. }
  24. }
  25. public void ReleaseSkill(Individual master)
  26. {
  27. if (releasable && IsColdTimeEnd())
  28. {
  29. timer = 0.0f;
  30. Factory.TraversalIndividualsInCircle(
  31. (individual) => { master.GetComponent<MessageSystem>().SendMessage(2, individual.ID, buffID); }
  32. , master.transform.position, range);
  33. }
  34. }
  35. public void UpdateSkill(Individual master)
  36. {
  37. //增加计时
  38. timer =Mathf.Min(timer+Time.deltaTime, coldTime+0.1f);
  39. if (!releasable && isAura)
  40. {
  41. Factory.TraversalIndividualsInCircle(
  42. (individual) => { master.GetComponent<MessageSystem>().SendMessage(2, individual.ID, buffID); }
  43. , master.transform.position, range);
  44. }
  45. }
  46. public float GetColdTimePercent()
  47. {
  48. if (!releasable) return 1.0f;
  49. return timer / coldTime;
  50. }
  51. public bool IsColdTimeEnd()
  52. {
  53. return timer > coldTime;
  54. }
  55. }

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

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

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

  1. //TODO
  2. //目前硬编码给玩家赋予3个技能
  3. HeroSkills.Add(new BuffSkill(6, true, true, 5.0f)); //主动技能:嘲讽Buff
  4. HeroSkills.Add(new BuffSkill(0, false, false)); //被动技能:回血buff
  5. HeroSkills.Add(new BuffSkill(14, true, false)); //主动技能:攻速戒指buff

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


结语

《ATD》只是社团部门内提出的一个游戏项目,而我负责这个项目的程序架构设计,然而中途开发因为不少事,我们不得不放弃了这个项目。因此才想写点东西总结一下开发这个项目时的经验。

之后还会有新博文来更新这个系列,大概涉及《ATD》的游戏对象模型,全局游戏逻辑,UI/HUD/特效/声音管理,工具等,也同时会分享一些trick。

原文链接:http://www.cnblogs.com/KillerAery/p/11191222.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号