赞
踩
之前我们已经写过非常多的脚本控制玩家类,动画器复杂的连线和众多的判断条件bool类型之类 的让我们应接不暇,所以我们不妨使用unity常用的状态机脚本模式来编写代码,今天就从b站看到了一部状态机脚本教程,就试着跟着老师一起写一下。
[Unity] 平台游戏控制器 教程 Ep.00 教程简介_哔哩哔哩_bilibili
今天就把学到的状态机知识全部吐出来吧。
【项目资源包下载】(在老师的视频简介课看到)
- 百度网盘 - https://pan.baidu.com/s/1Xk6VwiKh2Jzun8HVCt97mg?pwd=j9aa 提取码:j9aa
- Google Drive - https://drive.google.com/file/d/1VAwWqFeKIiu-gpwJdfUsDnijJ-fFu_HF/view?usp=sharing
首先我们先创建一个3D项目,把刚刚下载好的素材拖进来
我们需要把没用的插件给删除掉,然后再下载几个插件,这些都是UNity自带的,例如cinemaChine,Poss-Poccessing,Input systems,最后在in Project中就这些
点开老师创建好的场景,会发现有地图,人物,和一些粒子系统
还有创建好的后处理系统
然而这些都不是我们这篇文章要讲的重点,最主要是我也不太会这些专业名字。
然后我们就开始编写脚本吧。
按这样管理好脚本的文件夹
然后我们先创建状态机系统吧,先在Base文件夹下创建一个IState接口来给玩家状态机继承。
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- public interface IState
- {
- void Enter();
- void Exit();
- void LogicUpdate();
- void PhysicsUpdate();
- }
我们还需要一个PlayerController控制玩家移动的
- using UnityEngine;
-
- public class PlayerController : MonoBehaviour
- {
-
- Rigidbody rigibody;
-
-
- private void Awake()
- {
-
- rigibody = GetComponent<Rigidbody>();
- }
-
- private void Start()
- {
- }
-
-
-
- public void SetVelocity(Vector3 velocityY)
- {
- rigibody.velocity = velocityY;
- }
-
- public void SetVelocityX(float velocityX)
- {
- rigibody.velocity = new Vector3(velocityX, rigibody.velocity.y);
- }
-
- public void SetVelocityY(float velocityY)
- {
- rigibody.velocity = new Vector3(rigibody.velocity.x, velocityY);
- }
- }
然后我们创建一个玩家状态机让它继承接口并且实现接口的方法。
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- public class PlayerStates : ScriptableObject, IState
- {
- protected PlayerController player;
-
-
- public void Initialize(PlayerController player)
- {
- this.player = player;
-
- }
- public virtual void Enter()
- {
-
- }
-
- public virtual void Exit()
- {
-
- }
-
- public virtual void LogicUpdate()
- {
-
- }
-
- public virtual void PhysicsUpdate()
- {
-
- }
- }
初始工作做完后,我们可以使用Input Systems继续做一个动作系统
之前的视频已经做过类似的了,所以我这里直接就将动作表贴出来了。
然后点击Generate C#脚本
他就生成了那个PlayerInputActions的脚本,然后我们再创建一个PlayerInput脚本,把我们上面创建的脚本放在Player上。
- using UnityEngine;
-
- public class PlayerInput : MonoBehaviour
- {
- PlayerInputActions playerInputActions;
-
- Vector2 Axes => playerInputActions.GamePlay.Axes.ReadValue<Vector2>();
- public bool Jump => playerInputActions.GamePlay.Jump.WasPerformedThisFrame();
- public bool StopJump => playerInputActions.GamePlay.Jump.WasReleasedThisFrame();
-
- public bool Move => AxisX != 0;
- public float AxisX => Axes.x;
- private void Awake()
- {
- playerInputActions = new PlayerInputActions();
- }
-
- public void EnableGamePlayInput()
- {
- playerInputActions.GamePlay.Enable();
- Cursor.lockState = CursorLockMode.Locked;
- }
- }
我们还需要一个StateMachine状态机脚本,它负责存储保存好的状态通过字典,然后切换退出开始状态,在它的Update函数中逐帧执行Istate的逻辑函数,然后再FixedUpdate上执行和物理运动相关的函数。
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- public class StateMachine : MonoBehaviour
- {
- IState currentState;
-
- protected Dictionary<System.Type, IState> stateTable;
-
- private void Update()
- {
- currentState.LogicUpdate();
- }
-
- private void FixedUpdate()
- {
- currentState.PhysicsUpdate();
- }
-
- protected void SwitchOn(IState newState)
- {
- currentState = newState;
- currentState.Enter();
- }
-
- public void SwitchState(IState newState)
- {
- currentState.Exit();
- SwitchOn(newState);
- }
-
- public void SwitchState(System.Type newStateType)
- {
- SwitchState(stateTable[newStateType]);
- }
- }
创建PlayerStateMachine让它继承这个类
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- public class PlayerStateMachine : StateMachine
- {
-
- [SerializeField] PlayerStates[] states;
-
- Animator animator;
-
- PlayerInput input;
- PlayerController player;
-
- private void Awake()
- {
- animator = GetComponentInChildren<Animator>();
- player = GetComponent<PlayerController>();
- input = GetComponent<PlayerInput>();
-
- stateTable = new Dictionary<System.Type, IState>(states.Length);
-
- foreach (PlayerStates state in states)
- {
- state.Initialize(animator,player, input ,this);
- stateTable.Add(state.GetType(),state);
- }
- }
-
- private void Start()
- {
- SwitchOn(stateTable[typeof(PlayerState_Idle)]);
- }
- }
包括:在Awake函数利用循环结构初始化我们的状态以及为字典添加状态。
最后在完善PlayerStates脚本
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- public class PlayerStates : ScriptableObject, IState
- {
- [SerializeField,Range(0f,1f)] float transitionDuration = 0.1f;
- [SerializeField] string stateName;
- int stateHash;
- float stateStartTime;
-
- protected float currentSpeed;
-
- protected Animator animator;
-
- protected PlayerController player;
-
- protected PlayerStateMachine playerStateMachine;
-
- protected PlayerInput input;
-
- protected float StateDuration => Time.time - stateStartTime;
- protected bool IsAnimationFinished => StateDuration >= animator.GetCurrentAnimatorStateInfo(0).length;
- private void OnEnable()
- {
- stateHash = Animator.StringToHash(stateName);
- }
- public void Initialize(Animator animator,PlayerController player,PlayerInput input,PlayerStateMachine playerStateMachine)
- {
- this.player = player;
- this.animator = animator;
- this.playerStateMachine = playerStateMachine;
- this.input = input;
- }
- public virtual void Enter()
- {
- //通过哈希值在内存中找到指定的对象
- animator.CrossFade(stateHash, transitionDuration);
- stateStartTime = Time.time;
- }
-
- public virtual void Exit()
- {
-
- }
-
- public virtual void LogicUpdate()
- {
-
- }
-
- public virtual void PhysicsUpdate()
- {
-
- }
- }
并且再完善一下PlayerController脚本
- using UnityEngine;
-
- public class PlayerController : MonoBehaviour
- {
- PlayerGroundDetector groundDetector;
- PlayerInput input;
- Rigidbody rigibody;
-
- public float MoveSpeed =>Mathf.Abs( rigibody.velocity.x);
- public bool isGrounded => groundDetector.isOnGrounded;
- public bool isFalling => rigibody.velocity.y < 0f && !isGrounded;
-
- private void Awake()
- {
- groundDetector = GetComponentInChildren<PlayerGroundDetector>();
- input = GetComponent<PlayerInput>();
- rigibody = GetComponent<Rigidbody>();
- }
-
- private void Start()
- {
- input.EnableGamePlayInput();
- }
-
- public void Move(float speed)
- {
- if (input.Move)
- {
- transform.localScale = new Vector3(input.AxisX, 1, 1);
- }
-
- SetVelocityX(speed * input.AxisX);
- }
-
- public void SetVelocity(Vector3 velocityY)
- {
- rigibody.velocity = velocityY;
- }
-
- public void SetVelocityX(float velocityX)
- {
- rigibody.velocity = new Vector3(velocityX, rigibody.velocity.y);
- }
-
- public void SetVelocityY(float velocityY)
- {
- rigibody.velocity = new Vector3(rigibody.velocity.x, velocityY);
- }
- }
这里的isGround设计到另一个脚本PlayerGroundDetector脚本,可以看到作者已经为我们添加好一个Gruonded Detector的空对象,我们就直接拖进来就完事了
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- public class PlayerGroundDetector : MonoBehaviour
- {
- [SerializeField] float detectionRadius = 0.1f;
-
- Collider[] colliders = new Collider[1];
-
- [SerializeField] LayerMask groundLayer;
- public bool isOnGrounded
- {
- get
- {
- //不会产生内存分配,也就不会有垃圾回收机制
- return Physics.OverlapSphereNonAlloc(transform.position, detectionRadius, colliders, groundLayer) != 0;
- }
- }
-
- private void OnDrawGizmos()
- {
- Gizmos.color = Color.red;
- Gizmos.DrawWireSphere(transform.position, detectionRadius);
- }
- }
最后我们依次为Player的每个动作添加一个状态脚本(如Idle,Run,Jump)这些我们通过特性CreateAssetMenu来为它们每个创建一个图标方便调用。
大伙先像我这样创建好各个脚本,然后我们梳理一下各个脚本的逻辑就开始编写吧。
- using UnityEngine;
-
- [CreateAssetMenu(menuName = "Data/StateMachine/PlayerState/Idle",fileName = "PlayerState_Idle")]
- public class PlayerState_Idle : PlayerStates
- {
- [SerializeField] float deceleration = 5f;
- public override void Enter()
- {
- base.Enter();
-
- currentSpeed = player.MoveSpeed;
- }
-
- public override void LogicUpdate()
- {
- if(input.Move)
- {
- playerStateMachine.SwitchState(typeof(PlayerState_Run));
- }
- if (input.Jump)
- {
- playerStateMachine.SwitchState(typeof(PlayerState_JumpUp));
- }
- if (!player.isGrounded)
- {
- playerStateMachine.SwitchState(typeof(PlayerState_Fall));
- }
-
- currentSpeed = Mathf.MoveTowards(currentSpeed, 0f, deceleration * Time.deltaTime);
- }
-
- public override void PhysicsUpdate()
- {
- player.SetVelocityX(currentSpeed * player.transform.localScale.x);
- }
- }
- using UnityEngine;
- [CreateAssetMenu(menuName = "Data/StateMachine/PlayerState/Run", fileName = "PlayerState_Run")]
- public class PlayerState_Run : PlayerStates
- {
- [SerializeField] float runSpeed = 5f;
- [SerializeField] float accleration = 5f;
- public override void Enter()
- {
- base.Enter();
-
- currentSpeed = player.MoveSpeed;
- }
- public override void LogicUpdate()
- {
- if (!input.Move)
- {
- playerStateMachine.SwitchState(typeof(PlayerState_Idle));
- }
- if (input.Jump)
- {
- playerStateMachine.SwitchState(typeof(PlayerState_JumpUp));
- }
- if (!player.isGrounded)
- {
- playerStateMachine.SwitchState(typeof(PlayerState_Fall));
- }
-
- currentSpeed = Mathf.MoveTowards(currentSpeed, runSpeed, accleration * Time.deltaTime);
- }
-
- public override void PhysicsUpdate()
- {
- player.Move(currentSpeed);
- }
-
- }
- using UnityEngine;
-
- [CreateAssetMenu(menuName = "Data/StateMachine/PlayerState/Fall", fileName = "PlayerState_Fall")]
- public class PlayerState_Fall : PlayerStates
- {
- [SerializeField] AnimationCurve speedCurve;
- [SerializeField] float moveSpeed = 5f;
- public override void LogicUpdate()
- {
- if (player.isGrounded)
- {
- playerStateMachine.SwitchState(typeof(PlayerState_Land));
- }
- }
-
- public override void PhysicsUpdate()
- {
- player.Move(moveSpeed);
- player.SetVelocityY( speedCurve.Evaluate(StateDuration));
- }
-
-
- }
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- [CreateAssetMenu(menuName = "Data/StateMachine/PlayerState/JumpUp", fileName = "PlayerState_JumpUp")]
- public class PlayerState_JumpUp : PlayerStates
- {
- [SerializeField] float jumpForce = 7f;
- [SerializeField] float moveSpeed = 5f;
- public override void Enter()
- {
- base.Enter();
-
- player.SetVelocityY(jumpForce);
- }
-
- public override void LogicUpdate()
- {
- if (input.StopJump || player.isFalling)
- {
- playerStateMachine.SwitchState(typeof(PlayerState_Fall));
- }
- }
- public override void PhysicsUpdate()
- {
- player.Move(moveSpeed);
- }
- }
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- [CreateAssetMenu(menuName = "Data/StateMachine/PlayerState/Land", fileName = "PlayerState_Land")]
- public class PlayerState_Land : PlayerStates
- {
- [SerializeField] float stiffTime = 0.2f;
- public override void Enter()
- {
- base.Enter();
-
- player.SetVelocity(Vector3.zero);
- }
-
- public override void LogicUpdate()
- {
- if (input.Jump)
- {
- playerStateMachine.SwitchState(typeof(PlayerState_JumpUp));
- }
- if (StateDuration < stiffTime)
- return;
-
- if (input.Move)
- {
- playerStateMachine.SwitchState(typeof(PlayerState_Run));
-
- }
- if (IsAnimationFinished)
- {
- playerStateMachine.SwitchState(typeof(PlayerState_Idle));
- }
- }
- }
别忘了给墙壁添加Ground 的Layer
给我们的Unity_Chan添加好动画组件后并且摆放好,我们终于终结了复杂的动画连线了。 最后再给Main Camera一个CinemaMachine.
最后我们设置好它们的State属性
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。