Unity3D_从零开始设计一个RPG游戏(4)PlayerSystem-角色系统设计-03状态机系统实现人物的移动控制
既然是使用状态机来做人物状态控制,那么肯定是需要实现一个人物的状态机,以及设计好人物的状态,首先创建一个人物状态机继承ai状态机,代码如下
using UnityEngine; using System.Collections; namespace ChuMeng { /// <summary> /// 人物角色状态机 /// </summary> public class HumanCharacter : AICharacter { /// <summary> /// 设置跑步动画 /// </summary> public override void SetRun () { var runName = "run"; if (WorldManager.worldManager.IsPeaceLevel ()) { runName = "run1"; } else { runName = "run2"; } if (!CheckAni (runName)) { runName = "run"; } if (!CheckAni (runName)) { runName = "walk"; } Log.AI ("RunAnimation name " + runName); PlayAni (runName, GetAttr ().ObjUnitData.WalkAniSpeed, WrapMode.Loop); } /// <summary> /// 播放要播放的动画 /// </summary> /// <param name="name">Name.</param> /// <param name="speed">Speed.</param> /// <param name="wm">Wm.</param> public override void PlayAni (string name, float speed, WrapMode wm) { var ani = GetAttr ().animation; ani [name].speed = speed; ani [name].wrapMode = wm; GetAttr ().GetComponent<AnimationController> ().PlayAnimation (ani [name]); } /// <summary> /// 设置要播放的动画 /// </summary> /// <param name="name">Name.</param> /// <param name="speed">Speed.</param> /// <param name="wm">Wm.</param> public override void SetAni (string name, float speed, WrapMode wm) { Log.Ani ("SetAni " + name); var ani = GetAttr ().animation; ani [name].speed = speed; ani [name].wrapMode = wm; GetAttr ().GetComponent<AnimationController> ().CrossfadeAnimation (ani [name], 0.2f); } /// <summary> /// 设置静止状态 /// </summary> public override void SetIdle () { var peace = WorldManager.worldManager.IsPeaceLevel (); var idleName = "idle"; if (peace) { if (CheckAni ("stand")) { idleName = "stand"; } } else { } PlayAni (idleName, 1, WrapMode.Loop); } } }
人物有哪些状态呢?从常识来看主要有:静止、移动、战斗、使用技能、死亡、眩晕等状态,所以一一实现他们(这里希望注意一点:在rpg游戏中,我们的状态机基类是面向所有角色的,状态基类也是提供了实现所有的状态接口)
人物静止状态HumanIdle
using UnityEngine; using System.Collections; using System.Collections.Generic; namespace ChuMeng { public class HumanIdle : IdleState { bool first= true; /// <summary> /// 重载的时候 子类 需要 执行父类的enterState exitState 函数 /// </summary> public override void EnterState () { Log.AI ("Enter Idle State"); base.EnterState (); SetAttrState (CharacterState.Idle); aiCharacter.SetIdle (); if(first){ first = false; } } /// <summary> /// 重载的时候 子类需要在while中判定是否quit状态 /// </summary> /// <returns>The logic.</returns> public override IEnumerator RunLogic () { var playerMove = GetAttr ().GetComponent<MoveController> (); var vcontroller = playerMove.vcontroller; while (!quit) { if(CheckEvent()) { yield break; } float v = 0; float h = 0; if (vcontroller != null) { h = vcontroller.inputVector.x;//CameraRight v = vcontroller.inputVector.y;//CameraForward } bool isMoving = Mathf.Abs (h) > 0.1 || Mathf.Abs (v) > 0.1; if(isMoving) { aiCharacter.ChangeState(AIStateEnum.MOVE); } yield return null; } } } }
战斗状态:HumanCombat
using UnityEngine; using System.Collections; using System.Collections.Generic; namespace ChuMeng { public class HumanCombat : CombatState { #region ConstVar float WindowTime = 0.5f; float rotateSpeed = 10; float walkSpeed = 1.5f; #endregion bool pressAttack = false; float attackPressTime; int attackId = 0; string attackAniName = null; SkillStateMachine skillStateMachine; SkillFullInfo activeSkill; //注册监听玩家的OnHit事件处理 public override void EnterState () { Log.AI ("Enter HumanCombat state"); base.EnterState (); pressAttack = false; attackPressTime = Time.time; attackId = 0; SetAttrState (CharacterState.Attacking); activeSkill = GetAttr ().GetComponent<SkillInfoComponent> ().GetActiveSkill (); } public override void ExitState () { Log.AI ("Push HideWeaponTrail"); MyEventSystem.myEventSystem.PushLocalEvent (GetAttr ().GetLocalId (), MyEvent.EventType.HideWeaponTrail); base.ExitState (); } string GetAttackAniName () { var name = string.Format ("rslash_{0}", attackId + 1); attackId++; attackId %= 4; return name; } //TODO:使用ObjectManager 来寻找目标敌人 public override bool CheckNextState (AIStateEnum next) { if (next == AIStateEnum.COMBAT) { Log.AI ("Combat receive attack again"); attackPressTime = Time.time; pressAttack = true; return false; } if (next == AIStateEnum.CastSkill) { return true; } return base.CheckNextState (next); } /* * 伤害计算过程 1:伤害对象判定 客户端做 2:伤害数值确定 服务端 或者客户端 3:伤害效果施展 例如击退 服务端 或者 客户端 */ //TODO:增加摇杆控制攻击方向功能 这样人物会根据摇杆方向来确定攻击目标 IEnumerator WaitForAttackAnimation (Animation animation) { var playerMove = GetAttr ().GetComponent<MoveController> (); var camRight = playerMove.camRight; var camForward = playerMove.camForward; var vcontroller = playerMove.vcontroller; var physics = playerMove.GetComponent<PhysicComponent> (); var rd = Random.Range (1, 3); BackgroundSound.Instance.PlayEffect ("onehandswinglarge" + rd); skillStateMachine = SkillLogic.CreateSkillStateMachine (GetAttr ().gameObject, activeSkill.skillData, GetAttr ().transform.position); Log.AI ("Wait For Combat Animation"); //GameObject enemy = NearestEnemy (); float passTime = 0; //var transform = GetAttr ().transform; bool hitYet = false; do { if (!hitYet && GetEvent ().onHit) { hitYet = true; } if (CheckEvent ()) { break; } float v = 0; float h = 0; if (vcontroller != null) { h = vcontroller.inputVector.x;//CameraRight v = vcontroller.inputVector.y;//CameraForward } if (Mathf.Abs (h) > 0.1f || Mathf.Abs (v) > 0.1f) { Vector3 moveDirection = playerMove.transform.forward; Vector3 targetDirection = h * camRight + v * camForward; if (targetDirection != Vector3.zero) { moveDirection = Vector3.RotateTowards (moveDirection, targetDirection, rotateSpeed * Time.deltaTime, 0); } moveDirection = moveDirection.normalized; //playerMove.transform.rotation = Quaternion.LookRotation (moveDirection); physics.TurnTo (moveDirection); var movement = moveDirection * walkSpeed; physics.MoveSpeed (movement); } if (passTime >= animation [attackAniName].length * 0.8f / animation [attackAniName].speed) { break; } passTime += Time.deltaTime; var vhValue = Mathf.Abs (v) + Mathf.Abs (h); if (hitYet && vhValue > 0.2f) { //stopAttack = true; break; } yield return null; } while(!quit); Log.Ani ("Animation is Playing stop " + attackAniName); skillStateMachine.Stop (); } public override IEnumerator RunLogic () { Log.AI ("Run HumanCombat Logic"); bool first = true; while (!quit) { attackAniName = GetAttackAniName (); var realAttackTime = activeSkill.skillData.AttackAniTime / GetAttr ().GetSpeedCoff (); var rate = GetAttr ().animation [attackAniName].length / realAttackTime; if (first) { PlayAni (attackAniName, rate, WrapMode.Once); first = false; } else { SetAni (attackAniName, rate, WrapMode.Once); } var rd = Random.Range (1, 10); if (rd <= 3) { BackgroundSound.Instance.PlayEffect ("destroyereffort" + rd); } Log.Ani ("Do ule Press Time " + attackAniName + " " + pressAttack + " " + attackPressTime + " " + Time.time + " " + WindowTime); yield return GetAttr ().StartCoroutine (WaitForAttackAnimation (GetAttr ().animation)); //stopAttack = false; if (pressAttack && ((Time.time - attackPressTime) < WindowTime)) { Log.AI ("Press Attack Again"); pressAttack = false; } else { break; } } Log.AI ("Combat Over "); aiCharacter.ChangeState (AIStateEnum.IDLE); } } }
至于剩下的状态也是如此设计,通过继承AIState,重写属于自己角色需要的状态接口,在状态接口内部,实现具体的动画切换逻辑即可,有了以上这些状态之后,我们可以通过HumanStateMachine来注册这些状态,然后在恰当的时机切换、更新这些状态,这样就很好的实现了人物状态的更新维护
using UnityEngine; using System.Collections; using System.Collections.Generic; using System; namespace ChuMeng { [RequireComponent(typeof(AnimationController))] [RequireComponent(typeof(SkillCombineBuff))] public class PlayerAIController : AIBase { void Awake () { attribute = GetComponent<NpcAttribute> (); //初始化角色ai状态机 ai = new HumanCharacter (); ai.attribute = attribute; ai.AddState (new HumanIdle ());//注册静止状态 ai.AddState (new HumanMove ());//注册移动状态 ai.AddState (new HumanCombat ());//注册战斗状态 ai.AddState (new HumanSkill ());//注册技能状态 ai.AddState (new HumanDead ());//注册死亡状态 ai.AddState (new MonsterKnockBack ());//注册怪物击退状态 ai.AddState (new HumanStunned ());//注册昏迷状态 this.regEvt = new List<MyEvent.EventType> () { MyEvent.EventType.EnterSafePoint,//进入安全点 MyEvent.EventType.ExitSafePoint,//退出安全点 }; RegEvent (); } List<Vector3> samplePos; IEnumerator CheckFall () { Vector3 originPos = attribute.OriginPos; samplePos = new List<Vector3> (){ originPos }; while (true) { var lastOne = transform.position; if (samplePos.Count > 0) { lastOne = samplePos [0]; } Log.Sys ("lastPos nowPos " + lastOne + " now " + transform.position); if (transform.position.y < (lastOne.y - 3)) { if (!inSafe) { transform.position = lastOne; } } else { if (inSafe) { samplePos.Clear (); } else { var pos = transform.position; samplePos.Add (pos); if (samplePos.Count > 4) { samplePos.RemoveAt (0); } } } yield return new WaitForSeconds (1); } } private bool inSafe = false; protected override void OnEvent (MyEvent evt) { if (evt.type == MyEvent.EventType.EnterSafePoint) { inSafe = true; samplePos.Clear (); } else if (evt.type == MyEvent.EventType.ExitSafePoint) { inSafe = false; samplePos.Add (transform.position); } Log.Sys ("InSafeNow " + inSafe + " evt " + evt); } void Start () { ai.ChangeState (AIStateEnum.IDLE); StartCoroutine (CheckFall ()); } } }
通过使用状态机可以很高效很方便。
Unity3D_从零开始设计一个RPG游戏(4)PlayerSystem-角色系统设计-03状态机系统实现人物的移动控制