Unity3D_从零开始设计一个RPG游戏(4)PlayerSystem-角色系统设计-03状态机系统实现人物的移动控制

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状态机系统实现人物的移动控制

上一篇:演示 ServletContext | 学习笔记


下一篇:uni-app 分享接口