Unity3D开发日志一:#2动作系统

一、关于状态和状态机

        1.定义

        状态:顾名思义就是角色当前的状态,例如“奔跑”“站立”“蹲下”等等,各状态独立

        状态机:将当前状态转变到下一状态的一种方法

优点缺点
每个状态都有自己独立的状态,互不干扰每个状态都有一个自己的文件
便于状态的增添更改

玩家一次只能处于一种状态,例如不能同属处于射击和移动状态,不过只需要创建两个状态机即可解决

无法继承Unity的“MonoBehaviour”类

        2.创建接口

        因为状态机无法继承“MonoBehavior”类,我们需要先创建一个接口,这只需将Unity创建的脚本内“class”改为“interface”。并添加两个状态机中比较简单的方法Enter()和Exit(),这两个方法的逻辑在我们从一个状态转换到另一个状态时运行

        Enter:每当状态变为当前状态时运行

        Exit:   每当状态变为上一状态时运行

这将便于我们“进入”或“退出”状态时设置和重置数据

public interface IState
    {
        public void Enter();
        public void Exit();
    }

        不过显然,这两种方法并不足以解决大多问题,我们通常有其他3种方法

public void HandleInput();
    //允许我们运行任何关于读取输入的逻辑
public void Update();
    //允许我们运行任何非物理相关的逻辑
public void PhysicsUpdate();
    //允许我们运行任何物理相关的逻辑

顺带一提,“Update”和“PhysicsUpdate”等同于“MonoBehavior”中的“Update”和“FixedUpdate”

        3.创建基本状态机抽象类

        使其成为一个抽象类,并从创建保存当前状态的变量开始,这是我们创建的每个状态机都将继承的类,此处的Protected是为了使其可以访问从该类继承的类

 public abstract class StateMachine
    {
        protected IState currentState;
    }

        ChangeState方法

        Exit()方法可以重置前面的状态,但若状态为空,Exit()则不会被调用,导致报错,而在C#中有一个方便的运算符“null-conditional”便可以做到若返回null,则C#不会调用Exit方法

     public void ChangeState(IState newState)
        {
            currentState?.Exit();
            currentState = newState;
            currentState.Enter();
        }

        再为每个状态逻辑创建一个方法,便可拥有一种方法可以从MonoBehaviour类调用当前状态的逻辑了

        public void HandleInput()
        {
            currentState?.HandleInput();
        }
         public void Update()
        {
            currentState?.Update();
        }
         public void PhysicsUpdate()
        {
            currentState?.PhysicsUpdate();
        }

二、创建运动状态机

        1.关于“缓存状态”和“实例化新状态”

        在ChangeState(IState newState)中,每当我们要调用ChangeState()方法,我们都需要传入我们的newState,我们可以通过每次更改状态时,创建新状态类中的新实例并缓存它的实例到我们的状态机中,在每次我们更改状态时创建一个新实例可以确保 如果一个状态未被使用,资源不会被不必要地浪费,因为他会被删除。

        “缓存实例”将始终使用内存等资源,因为实例仍然是必须的且不会自动删除,但我们不需要每次都实例化新状态。此外,创建一个新实例总会再次创建变量,因此他们的值将被重置为初始默认值,而缓存实例将始终保持原样。

        2.缓存初始状态

public class PlayerMovementStateMachine : StateMachine
    {
        public PlayerIdlingState IdlingState { get; }
        public PlayerWalkingState WalkingState { get; }
        public PlayerRunningState RunningState { get; }
        public PlayerSprintingState SprintingState { get; }
    }

        并创建构造将其实例化

    public PlayerMovementStateMachine(Player player)
        {
            IdlingState = new PlayerIdlingState();
            WalkingState = new PlayerWalkingState();
            RunningState = new PlayerRunningState();
            SprintingState = new PlayerSprintingState();
        }

        3.创建玩家脚本

        目前我们仍未继承MonoBehaviour类,那么他的逻辑在游戏中就永远不会运行,我们可以新建一个Player脚本,并创建一个状态机实例并调用其方法。创建Awake,Start,Update,FixedUpdate等常用方法,并将Player脚本挂载在Player物体上

三、创建玩家输入

        创建好文件夹后在文件夹中Create一个“InputActions”,命名并双击打开,这是Unity的新输入系统,将创建的Movement里ActionType改为Value,Control Type改为Vector2,将创建Movement时自带的<No Binding>删除,并ADD一个2DVector,再根据UpDown等输入该方向移动的WASD键,详细方法为点击“Up”,并在右边的Path中点击Listen并输入自己希望的按键

         现在我们已经保存了输入,那么就需要一种访问他们的方法,只需要在刚创建的InputAction中的Inspector中点击“Generate C# Class”即可自动生成一个脚本并提供一中方法

    public class PlayerInput : MonoBehaviour
    {
        public PlayerInputActions InputActions { get; private set; }
        public PlayerInputActions.PlayerActions PlayerActions { get; private set; }

        private void Awake()
        {
            InputActions = new PlayerInputActions();

            PlayerActions = InputActions.Player;
        }

        private void OnEnable()
        {
            InputActions.Enable();
        }

        private void OnDisable()
        {
            InputActions.Disable();
        }

    }

四、玩家移动

        开始的开始,当然是为玩家添加RIgidBody组件以及一个胶囊碰撞器,方法可以看上一篇文章

        通常,我们为玩家的移动添加一个力,都会设置刚体的“速度”变量,而在Unity的官方教程中,并不建议这么做,而是使用“AddForce”,因为添加力不是瞬间的,只会在下一次物理更新中发生,而Velocity是力的即使变化。但如果我们一直按下w键,玩家就会有一个一直向前的力,让我们的玩家速度无限加快,要解决该问题,我们只需从要添加的力,移除现有的速度

Vector3 currentPlayerHorizontalVelocity = GetPlayerHorizontalVelocity();
     protected Vector3 GetPlayerHorizontalVelocity()
        {
            Vector3 playerHorizontalVelocity = stateMachine.Player.Rigidbody.velocity;

            playerHorizontalVelocity.y = 0f;

            return playerHorizontalVelocity;
        }

五、添加相机

        我们将使用cinemachine,它可以让我i们轻松添加一个相机系统,在Hierarchy>cinemachine>Virtual Camera中创建相机,后我们需要在玩家上新建一个CameraLookPoint以便我们可以实现相机跟随,更多跟cinemachine的可以参考cinemachine教程

六、玩家旋转

        大多数游戏中,当我们旋转相机时,玩家将随之旋转,并向前移动到相机所注视的位置,因此,我们要让玩家朝着我们输入的方向加上相机旋转的总和移动

     private float Rotate(Vector3 direction)
        {
            float directionAngle = UpdateTargetRotation(direction);

            RotateTowardsTargetRotation();

            return directionAngle;
        }
        protected float UpdateTargetRotation(Vector3 direction, bool shouldConsiderCameraRotation = true)
        {
            float directionAngle = GetDirectionAngle(direction);

            if (shouldConsiderCameraRotation)
            {
                directionAngle = AddCameraRotationToAngle(directionAngle);
            }

            if (directionAngle != stateMachine.ReusableData.CurrentTargetRotation.y)
            {
                UpdateTargetRotationData(directionAngle);
            }

            return directionAngle;
        }

        private float GetDirectionAngle(Vector3 direction)
        {
            float directionAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg;

            if (directionAngle < 0f)
            {
                directionAngle += 360f;
            }

            return directionAngle;
        }

        private float AddCameraRotationToAngle(float angle)
        {
            angle += stateMachine.Player.MainCameraTransform.eulerAngles.y;

            if (angle > 360f)
            {
                angle -= 360f;
            }

            return angle;
        }

        private void UpdateTargetRotationData(float targetAngle)
        {
            stateMachine.ReusableData.CurrentTargetRotation.y = targetAngle;

            stateMachine.ReusableData.DampedTargetRotationPassedTime.y = 0f;
        }

        protected void RotateTowardsTargetRotation()
        {
            float currentYAngle = stateMachine.Player.Rigidbody.rotation.eulerAngles.y;

            if (currentYAngle == stateMachine.ReusableData.CurrentTargetRotation.y)
            {
                return;
            }

            float smoothedYAngle = Mathf.SmoothDampAngle(currentYAngle, stateMachine.ReusableData.CurrentTargetRotation.y, ref stateMachine.ReusableData.DampedTargetRotationCurrentVelocity.y, stateMachine.ReusableData.TimeToReachTargetRotation.y - stateMachine.ReusableData.DampedTargetRotationPassedTime.y);

            stateMachine.ReusableData.DampedTargetRotationPassedTime.y += Time.deltaTime;

            Quaternion targetRotation = Quaternion.Euler(0f, smoothedYAngle, 0f);

            stateMachine.Player.Rigidbody.MoveRotation(targetRotation);
        }

        protected Vector3 GetTargetRotationDirection(float targetRotationAngle)
        {
            return Quaternion.Euler(0f, targetRotationAngle, 0f) * Vector3.forward;
        }

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值