Day21_一个技能框架的学习和搭建流程总结
制作框架的第一步就是要划分变化点,梳理业务逻辑,变化点划分清楚了,才有可能分类,业务逻辑清楚了才有可能梳理清各个模块之间的关系。
明白一个技能从产生到释放都需要什么
首先要明白的一点是,不同游戏类型对于技能的需求是不同的,这里以经典的MMORPG游戏为例:
MMORPG类型意味着其技能数量是庞大的,且技能是要频繁修改数值和效果的,不同技能有不同的攻击范围,效果,释放等,这里我们可以提取三个变化点:
- 技能与技能之间是不同的,所以不同技能不可以写在同一个类中。
- 技能效果是不同的,技能效果需要频繁更改,且往往一些技能效果是部分技能通用的。所以理论上不同技能效果应当是写在不同类中。
- 技能与技能之间的技能数据是不同的,且需要频繁更改,技能效果往往需要技能数据才可以使用,其中技能数据。
由以上三点变化点我们可以暂时得到下图结构:
这个结构会直接导致以下三个问题:
- 如果由技能释放者直接调用技能类来释放技能会导致技能类中的代码臃肿且违反了高内聚的思想。
- 理由:直接调用技能类将导致技能类即要实现技能释放工作,还要实现技能释放效果的调用。
- 技能释放者怎么知道自己可以释放什么技能。
- 技能释放者直接调用技能会导致角色输入控制类出现不属于角色控制类的代码——技能释放代码。
解决方案:
(解决方案不唯一,只有最适合你想法的,没有最完美的)
创建一个角色技能管理类挂载在角色物体上负责管理角色身上的可以释放的技能(可以使用技能数据数组来管理),并由技能管理类去调用技能释放器,这样就不需要在角色的输入控制中写大量技能释放相关的代码了。
我们可以给技能单独提取出一个类,名叫技能释放器,由技能释放器来负责技能技能释放和效果的调用。
调整之后的结构图:
此时,结构逐渐清晰,但要注意我们的技能释放器既要负责技能技能释放还要效果的调用,类的职责还是不够单一,所以我们可以将技能的释放调度到角色技能管理中,让技能管理在调用释放器时进行技能释放。
由于技能效果调用和技能释放现在分开做了,我们可以将技能释放器挂载在技能特效预制件上来使用(前期技能释放工作中包含调用技能特效预制件的过程)。
技能数据根据上面的解决方案来看是保存在角色技能管理中的,在技能释放器调用技能效果时是需要技能数据的,所以这里的技能数据将作为参数在类与类之间进行传递。我们可以将技能数据单独提成一个类来进行管理。
现在大体框架有了,我们就需要考虑一些细节问题:
- 技能释放器怎么知道要调用什么技能效果,技能范围,技能释放方式。可能有人会存在疑问,既然技能释放器挂载在了技能特效预制件上,那理应这些都是可以知道的,为什么还会出现这个问题?
- 理由:如果直接根据技能预制件来确定调用,那么如果后面要求更改某些技能的技能效果,你就不得不修改这里的调用代码,违反开闭原则。
- 技能在游戏中是需要大量使用的,如果任由CPU创建/销毁技能对象,将造成大量的性能消耗。
解决方案:
(解决方案不唯一,只有最适合你想法的,没有最完美的)
调用技能效果,技能范围就是一个创建对象的过程,所以我们可以使用反射来动态创建对象,考虑到反射糟糕的性能,我们需要在反射的同时使用一个字典来缓存创建的对象。为了支持动态创建,技能数据应当有字段来支持。
技能释放方式并不多,我们可以认为是可以穷举的,我们可以让不同技能释放封装成类继承技能释放器来使用。这样挂载在技能预制件上的是什么释放类就是什么释放方式。
技能预制件也应当使用对象池缓存起来,避免任由CPU创建/销毁技能对象,造成大量的性能消耗。
到这里我们就基本可以看到成型的结构图:
技能释放器
职责:负责技能释放时的操作
数据成员:
释放的技能的数据
技能使用的选区对象
技能使用的效果对象数组
方法成员:
根据选区计算受击目标
释放技能
回收技能
技能管理器
职责:管理角色技能
数据成员:
技能数据对象列表/数组
方法成员:
生成技能
准备技能
技能初始化
技能冷却
技能数据类
职责存储技能共性数据
}using System;
using UnityEngine;
/*
Editor:
Version:
The last time for modification:
Time for Creation:
*/
namespace SkillSystem.SkillInfo
{
/// <summary>
/// 技能数据类,内部存放一个技能表示所需要的所有数据类型
/// </summary>
[Serializable]
public class SkillData
{
/// <summary> 技能ID</summary>
public int skillId;
/// <summary> 技能名称</summary>
public string skillName;
/// <summary> 技能描述</summary>
public string skillDescription;
/// <summary> 技能冷却时间</summary>
public int coolDownTime;
/// <summary> 技能剩余冷却时间</summary>
public int coolDownRemain;
/// <summary> 技能法力值消耗量</summary>
public int mpCost;
/// <summary> 攻击距离</summary>
public float attackDistance;
/// <summary> 攻击角度</summary>
public float attackAngle;
/// <summary> 攻击目标标签数组</summary>
public string[] attackTargetTags;
/// <summary> 攻击目标变换数组</summary>
public Transform[] attackTargets;
/// <summary> 技能影响类型</summary>
public string[] impactTypes;
/// <summary> 下一个连击技能ID</summary>
public int nextSkillId;
/// <summary> 伤害比率</summary>
public float attackRate;
/// <summary> 技能持续时间</summary>
public float durationTime;
/// <summary> 伤害间隔</summary>
public float attackInterval;
/// <summary> 技能释放者游戏物体</summary>
[HideInInspector]
public GameObject skillOwner;
/// <summary> 技能预制体名称</summary>
public string skillPrefabName;
/// <summary> 技能预制体游戏物体</summary>
[HideInInspector]
public GameObject skillPrefab;
/// <summary> 技能动画名称</summary>
public string skillAnimationName;
/// <summary> 受击特效名称</summary>
public string hitVfxName;
/// <summary> 受击特效预制体游戏物体</summary>
public GameObject hitVfxPrefab;
/// <summary> 技能等级</summary>
public int skillLevel;
/// <summary> 技能释放类型:单体,群攻。。。。。</summary>
public SkillAttackType skillAttackType;
/// <summary> 技能范围类型</summary>
public SelectorType selectorType;
}
}
using(){}代码块
using(){}作为语句,用于定义一个范围,在此范围的末尾将释放对象。
using 语句允许程序员指定使用资源的对象应当何时释放资源。using 语句中使用的对象必须实现 IDisposable 接口。此接口提供了 Dispose 方法,该方法将释放此对象的资源。比如stringreader,可以使用using代码块来防止自己忘记关闭通道。