技能系统 流程图
Skill 数据类 SkillCfg:
拥有伤害值、动画切片名等等基础信息。
拥有技能类型、碰撞关系的技能逻辑体需要用到的枚举信息。
拥有BuffList、BulletCfg用于动态创建Buff和Bullet。
逻辑类 Skill:
Buff
数据类 BuffCfg。给策划配表用,只有数值(碰撞关系、伤害数值)。
逻辑类 Buff。本身用状态机实现。
复杂的Buff自己派生BuffCfg、Buff来实现数据、逻辑。
Bullet
目标追踪型:如果Skill是指向型的、且存在BulletCfg,就会被当作指向型弹道技能。
位置确定型:如果Skill是非指向型的、且存在BulletCfg,就会被当作非指向型弹道技能。
Bullet结构自身并不附带任何Buff。
子弹伤害、效果在Bullet的命中回调中通过读取Skill伤害、Skill的BuffList实现。
技能实现 // TODO 梳理一整个战斗网络包(位移、技能)Send-Rsp-Tick过程:所有的本地设备(包括操纵者自己)其实就是同步演算+播放器
技能前摇、后摇 指向技能实现 查找目标 指向 = 有目标的、追踪的。需要额外的查找目标代码。
非弹道型 前摇结束时目标仍在技能范围内,就判定释放成功。比如亚瑟这类近战的普攻。
弹道型 追踪子弹。比如后裔这类射手的普攻。
非指向技能实现 非弹道型 通过buff动态确定目标,其实大部分属于纯buff型技能。比如亚瑟2技能。
弹道型 自判定子弹,通过SweepVolume算法 确定是否命中。比如后裔的大招。
纯buff型技能 很多,比如替换普攻的技能、亚瑟的2技能、亚瑟的被动都属于这一类。
不需要通过技能逻辑代码来执行,直接通过buff逻辑(比如Buff.Tick、Buff对Skill添加回调等)。
金克斯技能拆解 被动 击杀防御塔、英雄就进行加速175%,持续3秒。
技能一
枪炮交响曲·Q:金克丝切换武器
鱼骨头——火箭发射器:普通攻击会对目标和目标身边的所有敌人造成110%的伤害,攻击距离提升75/100/125/150/175,并且耗费法力值。
砰砰枪——轻机枪:普通攻击会获得攻击速度加成,持续2.5秒。攻速加成可叠加3次,总攻速加成为30/55/80/105/130%。叠加效果在同一时间只会消耗一层,并且在金克丝切换至火箭发射器后,只有第一次攻击会享受加速效果。
金克斯最复杂的一个技能。
普攻、技能一自身替换 ,通过技能替换buff实现。
持续2.5秒的可叠加被动,是作为英雄被动技能实现的:buff初期化时给英雄的技能释放成功添加回调来刷新计时、叠加层数,在buff状态机内不断迭代时间是否满足2.5秒。但是普攻回调中会检测目前的普攻ID,如果是替换了的火箭发射器ID,就会把攻速加成提前取消。这样同时实现了“只有第一次攻击会享受加速效果”。
火箭发射器形态普攻 ,当作指向性弹道技能实现。爆炸是通过给技能添加一个 静态范围伤害buff 来实现的爆炸效果。在子弹命中回调中创建buff,传入子弹及时Pos,形成一个检测碰撞区来获取被命中的敌人。
技能二
振荡电磁波·W:金克丝0.6-0.4秒后发射一束震荡波,对命中的第一个目标造成10/60/110/160/210物理伤害,让非潜行的目标暴露在视野内,并让目标减速30/40/50/60/70%,减速效果持续2秒。
由于demo还没有实现战争迷雾系统,暴露视野暂不实现。
非指向弹道技能 + 减速buff。
技能三
金克丝扔出3颗陷阱手雷。手雷一旦布置完毕,就会在接触到敌方英雄时爆炸,将敌方英雄束缚1.5秒并阻止在该位置上的进行中的移动技能,立刻对周围的敌人造成共80/135/190/245/300魔法伤害。手雷持续5秒。
可以通过非指向非弹道技能实现。通过轮盘确定投放位置后,对位置生成3个静态范围寻找目标buff实现。给buff添加buffEffect实现夹子特效。
技能四
超究极死神飞弹·R:金克丝发射一枚飞弹。在发射后的一秒里,飞弹的伤害会根据飞行的距离而持续增加。飞弹会在首次命中敌方英雄后爆炸,对目标造成25/35/45到250/350/450物理伤害,并附带相当于目标已损失生命值的25/30/35%的额外伤害。附近的敌人也会受到80%伤害。
非指向弹道技能 + 静态计算碰撞范围+记录距离计算伤害+损失生命计算百分比伤害buff。因为比较特殊3个buff的数据是存在依赖(后2个buff的伤害计算 ->.前1个buff的伤害基准值)的,所以直接放到一个新buff里实现。
技能机制 技能分析 目标 = 指向;弹道 = 借助bullet;无前摇 = 瞬发型。
英雄
亚瑟
后羿
普攻
目标技能 + 非弹道、有前摇
目标技能 + 弹道、延迟
技能1
非目标技能 buff替换普攻Cfg + 非弹道、无前摇
技能2
非目标技能 + 非弹道、无前摇
技能3
目标技能 + 非弹道、有前摇
非目标技能(非指向) 1.buff替换普攻Cfg技能实现(亚瑟一技能) 亚瑟技能1有许多buff附加到下一个普攻上,直接修改普攻得buffArr使其变更数据会让代码复杂,所以直接采用技能替换普攻的方法,将其作为一个技能。
之后每1次普攻,其实就相当于在释放1次技能了,所以技能的buff 也会随着每一次普攻而重新Create。
直到时间结束或者攻击次数用完,技能被替换回原先的普攻。
2.后裔三技能实现 目标技能(指向) 通用的寻找目标实现
根据配置,查找到所有活着的可作为目标的单位(比如红队的英雄)
根据技能配置规则,确定最终目标单位(比如最近的单个英雄)
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 public enum TargetTeamEnum{ Dynamic, Friend, Enemy } public enum UnitTypeEnum{ Hero, Soldier, Tower, } public enum SelectRuleEnum{ None, MinHpValue, MinHpPercent, TargetClosestSingle, PositionClosestSingle, TargetClosestMulti, PositionClosestMulti, Hero, }
然后让我们看一下2中实现目标查找的关键部分代码,找最近单体目标的核心代码。其他的实现比如查找符合目标的群体算法,核心部分逻辑也是一样的:
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 static MainLogicUnit FindMinDisTarget (MainLogicUnit self,List<MainLogicUnit> targetList, PEInt range ){ if (targetList == null || targetList.Count < 1 ) { return null ; } MainLogicUnit target = null ; CodingKVector3 selfPos = self.LogicPos; PEInt len = 0 ; for (int i = 0 ; i < targetList.Count; i++) { PEInt sumRaius = targetList[i].ud.unitCfg.colliCfg.mRadius + self.ud.unitCfg.colliCfg.mRadius; PEInt tmpLen = (targetList[i].LogicPos - selfPos).magnitude - sumRaius; if (target == null || tmpLen < len) { len = tmpLen; target = targetList[i]; } } return len < range ? target : null ; }
1.亚瑟普攻实现
寻找目标
切换技能状态到SKillState.SpellStart
播放音效
修改朝向:修改为lockTarget.LogicPos - owner.LogicPos
的CodingKVector3向量。
播放动画:先取消移动,再播放攻击动画。
2.后裔普攻实现 技能构造 技能Config
血条和伤害UI 血条 数据结构 个体状态枚举类
1 2 3 4 5 6 7 8 9 10 11 12 13 public enum StateEnum{ None, Silenced, Knockup, Stunned, Invincible, Restricted, }
抽象血条类结构
继承关系:公用抽象血条类 => 塔 ; 公用抽象血条类 => 小兵 => 英雄
1 2 3 4 5 6 public RectTransform rect;public Image imgPrg; protected bool isFriend;protected Transform rootTrans;protected int hpVal;
UI映射 血条
血条映射
1 2 3 4 5 6 7 8 9 10 private void Update ( ){ if (rootTrans) { float scaleRate = 1.0f * ClientConfig.ScreenStandardHeight / Screen.height; var screenPos = Camera.main.WorldToScreenPoint(rootTrans.position); rect.anchoredPosition = screenPos * scaleRate; } }
伤害飘字 数据结构 缓存池
需要用到伤害飘字的单位比较多,频繁销毁创建会消耗性能,所以首先建立一个缓存池,限制个数为50,用到的时候从池中取出播放动画。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ``` **跳字类型枚举类** ```csharp public enum JumpTypeEnum{ None, SkillDamage, BuffDamage, Cure, SlowSpeed, }
跳字动画枚举类
1 2 3 4 5 6 7 public enum JumpAniEnum{ None, LeftCurve, RightCurve, CenterUp, }
飘字预制体脚本 结构
全部公开,方便在unity面板内调参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class JumpNum : MonoBehaviour { public RectTransform rect; public Animator ani; public Text txt; public int MaxFont; public int MinFont; public int MaxFontValue; public Color skillDamageColor; public Color buffDamageColor; public Color cureDamageColor; public Color slowDamageColor; }
通用技能定时器 总体流程 用于CD计时器。一共分3个部分:延迟、回调函数(循环)执行、结束回调(1次or不)执行。
定时器代码 只展示重要成员 和使用的接口 ,内部直接看代码(在github开源了)。
回调函数和3个部分的回调签名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private Action<int > cbAction;private int loopCounter;private Action<bool , float , float > prgAction;float prgLoopRate = 0 ;float prgAllRate = 0 ;private Action endAction;
对外接口(构造器):
1 2 3 4 5 6 7 8 9 10 11 12 public MonoTimer ( Action<int > cbAction, float intervalTime, int loopCount = 1 , Action<bool , float , float > prgAction = null , Action endAction = null , float delayTime = 0 ) { ... this .IsActive = true ; this .prgAllTime = delayTime + intervalTime * loopCount; }
定时器测试示例 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 public void ClickTestBtn ( ) { SetText(txtTime, 5 ); testTimer?.DisableTimer(); testTimer = CreateMonoTimer( (loopCount) => { this .ColorLog(LogColor.Green, "Loop:" + loopCount); SetText(txtTime, 5 - loopCount); }, 1000 , 5 , (isDelay, loopPrg, allPrg) => { SetActive(imgPrgLoop); if (isDelay) { SetActive(txtTime, false ); } else { SetActive(txtTime); } imgPrgLoop.fillAmount = 1 - loopPrg; imgPrgAll.fillAmount = allPrg; }, () => { SetActive(imgPrgLoop, false ); imgPrgAll.fillAmount = 0 ; this .ColorLog(LogColor.Green, "Loop End" ); }, 3000 ); }
延迟圈转完后进行倒计时轮转,下面的进度条prg= 已过时长 / (延迟时长+回调时长)。
逻辑定时器 逻辑定时器(基于定点数),可以跑在服务器上,不依赖于Mono。
在本项目中,由 NetSvc.GetServiceMessageAndHandle(GameMsg msg) > BattleSys.NotifyOpKey(GameMsg msg) > FightMgr.Tick() > MainLogicUnit.LogicTick() > MainLogicUnit.TickSkill() > LogicTimer.Tick() 驱动。
下面贴重要成员 和使用的接口 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public LogicTimer (Action cb, PEInt delayTime, int loopTime = 0 ) { ... delta = Configs.ServerLogicFrameIntervelMs; IsActive = true ; } PEInt delta; PEInt delayTime; PEInt loopTime; Action cb;
Buff 贴一下核心属性和对应的Enum类。
SubLogicUnit类:辅助类逻辑单元基类 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 public enum SubUnitState{ None, Delay, Start, Tick, End } public abstract class SubLogicUnit : LogicUnit { public MainLogicUnit source; protected Skill skill; protected int delayTime; public SubUnitState unitState; public override void LogicInit ( ) { } public override void LogicTick ( ) { switch (unitState) { case SubUnitState.Delay: break ; case SubUnitState.End: End(); unitState = SubUnitState.None; break ; case SubUnitState.None: default : break ; } } public override void LogicUnInit ( ) { } protected abstract void Start ( ) ; protected abstract void Tick ( ) ; protected abstract void End ( ) ; }
BuffCfg类 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 58 59 60 61 62 public enum BuffTypeEnum { None, ModifySkill, MoveSpeed_Single, Silense, ArthurMark, HPCure, MoveSpeed_DynamicGroup, MoveAttack, } public enum StaticPosTypeEnum { None, SkillCasterPos, SkillLockTargetPos, BulletHitTargetPos, UIInputPos, } public enum AttachTypeEnum { None, Caster, Indie, Target, Bullet, } public class BuffCfg { public int buffId; public string buffName; public BuffTypeEnum buffType; public AttachTypeEnum attacher; public StaticPosTypeEnum staticPosType; public TargetCfg impacter; public int buffDelay; public int buffInterval; public int buffDuration; public string buffAudio; public string buffEffect; public string hitTickAudio; }
Buff类 使用关系:
正常一种类得buff有 xxBuff类 and xxBuffCfg类
,xxBuffCfg拥有该buff特有的属性,xxBuff会包含xxBuffCfg在初期化通过ResSvc加载获取它。
继承关系:
xxBuff < Buff < SubLogicUnit < LogicUnit
xxBuffCfg < BuffCfg
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 public class Buff : SubLogicUnit { public MainLogicUnit owner; public BuffCfg cfg; protected int buffId; protected object [] args; protected List<MainLogicUnit> targetList; public override void LogicInit ( ) { cfg = ResSvc.Instance().GetBuffConfigById(buffId); base .LogicInit(); } public override void LogicTick ( ) { base .LogicTick(); switch (unitState) { case SubUnitState.Start: Start(); break ; case SubUnitState.Tick: if (cfg.buffInterval > 0 ) { tickCount += Configs.ServerLogicFrameIntervelMs; if (tickCount >= cfg.buffInterval) { tickCount -= cfg.buffInterval; Tick(); } } durationCount += Configs.ServerLogicFrameIntervelMs; if (durationCount >= buffDuration && buffDuration != -1 ) { unitState = SubUnitState.End; } break ; } } protected override void Start ( ) { } protected override void Tick ( ) { } protected override void End ( ) { } }
用于替换技能的特别buff 前面提到的亚瑟技能1实现得具体buff,进行技能之间的替换
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 public class CommonModifySkillBuffCfg : BuffCfg { public int originalID; public int replaceID; } public class CommonModifySkillBuff : Buff { public int originalID; public int replaceID; private Skill modifySkill; public override void LogicInit ( ) { base .LogicInit(); } protected override void Start ( ) { base .Start(); modifySkill.ReplaceSkillCfg(replaceID); modifySkill.SpellSuccCallback += ReplaceSkillReleaseDone; } void ReplaceSkillReleaseDone (Skill skillReleased ) { if (skillReleased.cfg.isNormalAttack) { unitState = SubUnitState.End; } } protected override void End ( ) { base .End(); modifySkill.ReplaceSkillCfg(originalID); modifySkill.SpellSuccCallback -= ReplaceSkillReleaseDone; } } public class Skill { public void ReplaceSkillCfg (int replaceId ) { skillCfg = ResSvc.Instance().GetSkillConfigById(replaceId); spellTime = skillCfg.spellTime; skillTime = skillCfg.skillTime; if (skillCfg.isNormalAttack) { owner.InitAttackSpeedRate(1000 / skillTime); } } }
人物状态:沉默、眩晕、击飞 比如亚瑟1技能的沉默,技能是附加了沉默buff,buff那块只要对 英雄/小兵 脚本的对应字段进行改变就行了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class SilenseBuff_Single : Buff { protected override void Start ( ) { base .Start(); owner.SilenceCount += 1 ; } protected override void End ( ) { base .End(); owner.SilenceCount -= 1 ; } }
对应的 英雄/小兵 的接口:
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 public partial class MainLogicUnit { int silenceCount; public int SilenceCount { get { return silenceCount; } set { silenceCount = value ; if (IsSilenced()) { OnStateChange?.Invoke(StateEnum.Silenced, true ); } else { OnStateChange?.Invoke(StateEnum.Silenced, false ); } } } bool IsSilenced ( ) { return silenceCount != 0 ; } } public class MainViewUnit { public void UpdateState (StateEnum state, bool show ) { if (state == StateEnum.Knockup || state == StateEnum.Silenced || state == StateEnum.Silenced) { if (mainLogicUnit.IsPlayerSelf() && show) { playWindow.SetAllSkillForbidState(); } } hpWindow.SetStateInfo(mainLogicUnit, state, show); } }
为目标标记上受击标记 其实就是MarkBuff,类似于亚瑟技能1的标记buff:标记目标,持续5秒,技能和普攻会对标记目标可额外造成目标最大生命1%的伤害。
使用对Onhurt事件 委托链添加委托实现。
调用回溯:from serive > InputKey() > MainLogicSkill.InputSkillKey() >Skill.ReleaseSkill() > Skill.CalcSkillAttack() > Skill.HitTarget() >target.CreateSkillBuff() > ArthurMarkBuff new()
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 public class ArthurMarkBuffCfg : BuffCfg { public int damagePct; } public class ArthurMarkBuff : Buff { PEInt damagePct; MainLogicUnit target; public override void LogicInit ( ) { base .LogicInit(); ArthurMarkBuffCfg ambc = cfg as ArthurMarkBuffCfg; damagePct = ambc.damagePct; target = skill.lockTarget; } protected override void Start ( ) { base .Start(); target.OnHurt += GetHurt; } void GetHurt ( ) { target.GetDamageByBuff(damagePct / 100 * target.ud.unitCfg.hp, this , false ); } protected override void End ( ) { base .End(); target.OnHurt -= GetHurt; } }
为目标标记一个立场标记,友军群体加速 亚瑟技能1标记,会让附近 range<5f 的友军会增加10%的移速。这像是一个立场,需要按照服务端的逻辑帧逐帧计算敌人是否在范围内从而为其加速。
核心是维护一个targetList:使用类似于前面写的通用的寻找目标实现 查找算法,逐帧计算附近 range<5f 的对象,将其加入到targetList队列中。
下面对Buff整体生命流程中,仅对于核心targetList的处理与使用进行展示:
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 public class MoveSpeedBuff_DynamicGroup : Buff { private PEInt speedOffset; public override void LogicInit ( ) { ... targetList = new List<MainLogicUnit>(); targetList.AddRange(CalcRule.FindMulipleTargetByRule(owner, cfg.impacter, skill.skillArgs)); } protected override void Start ( ) { ... ModifyTargetsMoveSpeed(speedOffset, true ); } protected override void Tick ( ) { ... ModifyTargetsMoveSpeed(-speedOffset); targetList.Clear(); targetList.AddRange(CalcRule.FindMulipleTargetByRule(owner, cfg.impacter, CodingKVector3.zero)); ModifyTargetsMoveSpeed(speedOffset); } protected override void End ( ) { ... ModifyTargetsMoveSpeed(-speedOffset); targetList.Clear(); } void ModifyTargetsMoveSpeed (PEInt value , bool showJump = false ) { } }
自动寻找目标攻击的通用buff 实现功能示意图:
部分代码演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 var MoveAttackBuff = new BuffCfg() {{ buffId = 999999 , buffName = "移动攻击" , buffType = BuffTypeEnum.MoveAttack, attacher = AttachTypeEnum.Caster, impacter = null , buffDelay = 0 , buffInterval = 66 , buffDuration = 5000 , }; ... public class MoveAttackBuff : Buff { }
查找算法示意图:
后裔的被动技能
普攻成功后增加攻速 后裔被动,每次平砍增加5%攻速,最高3次,持续3秒。如果释放2、3技能(1技能强化普攻可以)就直接失效。
1 2 3 4 5 public class HouyiPasvAttackSpeedBuffCfg : BuffCfg { public int overCount; public int speedAddtion; public int resetTime; }
buff代码就不贴了,具体逻辑是 buff类 给技能释放成功的回调注册一个事件,事件实现技能效果。
普攻3次后变成3连击 由于技能会跳转(如图),所以不能使用前面制作的替换技能buff,那个太简单了。
Bullet SweepVolume 体积扫描检测 用于逻辑帧计算子弹是否命中用的。记录上一帧的位置,与这一帧的位置连线,它的轨迹是一个矩形(粉色),查看矩形是否穿过目标碰撞体(蓝色),也就是矩形与圆形求相交的问题。
计算 AB中点位置 = (LogicPos + lastPos) / 2
计算 向量A->B = LogicPos - lastPos
由1与2模拟出 矩形碰撞体CodingKBoxCollider
由3创建出的矩形,与目标的 圆形碰撞体CodingKCylinderCollider 进行
具体怎么创建碰撞体,是自制的定点数物理碰撞计算库,在其他文章中详解。
Debug 弹道显示方案 想要debug看到弹道规矩,可以在每一个Tick里创建一个Cube模拟矩形,也就是子弹轨迹。
子弹Config 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class BulletCfg { public BulletTypeEnum bulletType; public string bulletName; public string resPath; public float bulletSpeed; public float bulletSize; public float bulletHeight; public float bulletOffset; public int bulletDelay; public bool canBlock; public TargetCfg impacter; public int bulletDuration; } public enum BulletTypeEnum { UIDirection, UIPosition, SkillTarget, BuffSearch, }
实现子弹曲线 图像分解
![image-20220115105710617](C:\Users\YAN JUNJIE\AppData\Roaming\Typora\typora-user-images\image-20220115105710617.png)
理解 我 -> 目标 连线的向量,是最初的弹道向量,也就是一开始的红色向量。
对 最初的弹道向量 + 我的中心点,可以取出一个唯一的垂直平面 ,对这个垂直平面上,以我的中心点为起始点,任取一个模(偏移量) 数值随机的向量,作为偏移向量,也就是绿箭头。
绿箭头的模确定后就不再改变,持续施加,称作固定值影响向量 。
而红色向量始终指向目标,所以会不停改变方向,称作方向矫正向量 。
最终,使用 固定值影响向量、方向矫正向量 的向量之和,也就是某个时间点下子弹的方向向量 了。
实现 实际就并不取用这么复杂过程了。
① 固定值影响向量 取 我->目标 的向量 + 我的up方向向量 的叉乘Cross 向量,然后规格化,就得到了准确的方向向量。
想要往上,只要对结果添加up方向上的向量就可以。当然,这需要一个随机数。
随机数 因为同步问题,不能只使用随机,所以通过传播随机种子 来确定性地随机,让多个端末都能正确计算出同样的效果。
然后再偷个懒,种子也不传播了,固定666。
Bug & QA 移动攻击Bugs 1.攻击动画没播放 是因为移动攻击时方向变更,导致动画状态立即恢复“free”从而导致攻击动画被跳过了。
解决方案是方向变更时,将是否在技能前摇 flag一起加入判定条件,来决定是否变化动画。
2.人物朝向没变化 也是因为方向被UI输入朝向重制了,关键是看ViewUnit.viewTargetDir属性。
解决方案一样,将是否在技能前摇 flag作为判定条件来决定是否根据UI改变朝向。
3.移动攻击时会滑动 是因为UI输入方向被服务端传来的最新UI移动请求给覆盖了,关键看MainLogic.InputDir属性。
解决方案是在接收到服务端传来的最新UI移动请求时,进行是否在技能前摇、是否被控制 flag作为判定条件来决定是否改变InputDir。
4.移动攻击完会僵直平移一段距离 是因为技能前摇时有一个定时器,根据技能总时长来将角色设回“free”动画。
解决方案是在收到服务器逻辑方向改变请求改变LogicUnit.LogicDir属性时,将过去的定时器动画Callback都清除掉。