学习UnityUnity之连招系统制作
理理最近在试图制作连招系统,每次遇到这种逻辑稍微复杂的我都容易头痛,总感觉逻辑好乱啊,于是准备稍微系统性地记录思路,这样更好。
思路
采用“从大到小”的思路:首先我们需要一个连招表,用于记录不同连招逻辑,比如按三下左键打出什么组合。
然后需要思考,什么时候可以攻击,那么这就会引入bool变量CanAttack,先考虑不能攻击的时候:翻墙时、在空中时、被敌人攻击时、格挡时;可以攻击的时候:待机时、跑步时。
接下来考虑我们的攻击冷却时间和攻击距离。先是冷却时间,防止玩家快速点击鼠标键、导致角色攻击动画快速切换造成抽搐效果(比如有人用连点器一秒点999次)。然后是攻击距离,敌人在这个距离内可以被打到播放受伤动画,距离外就不可以。关于冷却时间这点,我们需要自己写个计时器,先做记录
再往下小的方面去考虑,该考虑怎么进行组合呢?我的想法是用ScriptableObject将不同动画文件储存起来,形成一套连招技能配置器
再往小的方面去考虑每个单一的动作,我们需要什么?首先是动作名字ComboName,然后是造成伤害Damage,接着是冷却时间ColdTime,还有攻击距离Distance,此外,每个攻击动作会对应不同的格挡动画和受伤动画,但假如一个动作里给了角色两拳,那么就有两个受伤动画和格挡动画了,这就用数组来表示。
再思考每次受伤伤害是Damage,多次受伤应该是Damage * n,那么就需要一个int值来表示一个动作文件给角色造成几次伤害。
为了方便配置,我们依旧让每个动作继承ScriptableObject,通过以下脚本去进行配置单个动作,代码如下写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| using UnityEngine;
[CreateAssetMenu(fileName = "ComboData", menuName = "Scriptable Objects/ComboData")] public class ComboData : ScriptableObject { [SerializeField] private string _ComboName; [SerializeField] private float _Damage; [SerializeField] private float _ColdTime; [SerializeField] private string[] _ComboHitName;//比如一个动画文件有两个动作,因此设置数组对应受伤动画和格挡动画 [SerializeField] private string[] _ComboParryName; [SerializeField] private float _ComboOffsetPostion;//动画距离 //我们给类配置一些参数(因为我们要用),以便使用时能直接类名后加点号点出来 public string ComboName => _ComboName; public float Damage => _Damage; public float ColdTime => _ColdTime; public string[] ComboHitName => _ComboHitName; public string[] ComboParryName => _ComboParryName; public float ComboOffsetPostion => _ComboOffsetPostion; public int ComboHitMax => _ComboHitName.Length; }
|
关于ColdTime我们会在另一篇文章里写一个计时器。
接下来我们需要一个技能编辑器,将我们不同动作拖入编辑器里边、来实现技能组合。于是我们就新建个继承ScriptableObject的脚本,将我们的动作拖入Inspector窗口实现编辑
技能的存储我们就用List来存起来,通过引入索引值index来判断我们出于什么动画下。同时给技能编辑器写一些方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| using NUnit.Framework; using NUnit.Framework.Internal; using System.Collections.Generic; using System.Linq; using UnityEngine;
[CreateAssetMenu(fileName = "Combo", menuName = "Scriptable Objects/Combo")] public class Combo : ScriptableObject { [SerializeField]private List<ComboData> comboData = new List<ComboData>(); private int index; public string TryGetComboName(int index) { if(comboData.Count == 0) return null; return comboData[index].ComboName; }
public string TryGetHitName(int index,int hitindex) { if (comboData.Count == 0) return null; if (comboData[index].ComboHitMax == 0) return null; return comboData[index].ComboHitName[hitindex]; }
public string TryGetParryName(int index,int hitindex) { if (comboData.Count == 0) return null; if (comboData[index].ComboHitMax == 0) return null; return comboData[index].ComboParryName[hitindex]; }
public float TryGetColdTime(int index) { if (comboData.Count == 0) return 0; return comboData[index].ColdTime; }
public int TryGetHitCount(int index) { if (comboData.Count == 0) return 0; return comboData[index].ComboHitMax; }
public float TryGetDamage(int index) { if (comboData.Count == 0) return 0; return comboData[index].Damage; }
public float TryGetComboOffsetPostion(int index) { if (comboData.Count == 0) return 0f; return comboData[index].ComboOffsetPostion;
} }
|
接下来,我们专门写一个脚本,与基础角色控制器分开,用来专门记录连招逻辑。
连招逻辑有什么?
1,被攻击、格挡、在空中、攀墙状态下不能攻击;
2,ColdTime内不能攻击(防止抽搐);
3,一段时间不攻击、进入idle状态后,再攻击会从第一招开始。
对于1,我们先写一个返回值为bool的函数吧,起名“CanAttack”;
对于2,在1的基础上补充,启动计时器、必须计时器工作完了、冷却时间过了CanAttack才为true;
对于3,我的想法是用有限状态机的知识,将idle和Attack设置为不同的状态。
未完待续…