https://youtu.be/-jkT4oFi1vk?si=izRR0C1xHx3-I23E 教程来源,油管AdamCYounis
继续上回,对状态机本身进行了一个完善(直接照抄):
public abstract class State : MonoBehaviour
{
protected Animator animator;
protected Rigidbody2D rigidbody2;
protected PlayerScript input;
public bool isComplete { get; protected set; }
protected float startTime;
public float time => Time.time - startTime;
public virtual void Enter() { }
public virtual void Do() { }
public virtual void FixDo() { }
public virtual void Exit() { }
public void Setup(Rigidbody2D _rigidbody2,Animator _animator,PlayerScript _playerScript)
{
rigidbody2 = _rigidbody2;
animator = _animator;
input = _playerScript;
}
public void Initialize()
{
isComplete = false;
startTime = Time.time;
}
}
一、访问范围
1、protected(受保护)访问范围:声明它的类内部 + 所有子类内部
private访问范围:只能在声明它的类内部访问
public谁都能改。
2、如果要写一个谁都能用的方法,可以直接写一个public static的Helper类,不需要继承monobehavior,不用访问unity生命周期,如数值映射函数:
public static class Helper
{
public static float Map(float value, float min1, float max1, float min2, float max2, bool clamp = false)
{
float val = min2 + (max2 - min2) * ((value - min1) / (max1 - min1));
return clamp ? Mathf.Clamp(val, Mathf.Min(min2, max2), Mathf.Max(min2, max2)) : val;
}
}
二、初始化
1、状态机的实体需要别的类来提供,所以添加了setup功能,方便在相应的类(PlayerScript)中直接声明
PlayerScript
State state;
public AirState airState;
public RunState runState;
public IdleState idleState;
public KneelState kneelState;
idleState.Setup(rigidbody2, animator, this);
runState.Setup(rigidbody2, animator, this);
airState.Setup(rigidbody2, animator, this);
kneelState.Setup(rigidbody2, animator, this);
state = idleState;
//不能直接state.Setup(rigidbody2, animator, this);是因为这三个是public变量,直接拖拽到inspector中的
2、initialize方法
每次完成状态的时候,初始化对象,对状态赋值false(具体在角色类中)
三、state子类实现
需要遵循的规则(可以更改,人为规定):
1、某个state所拥有的变量,由那个state来初始化,如RunState有public float runSpeed = 5,从PlayerMovement中移到具体state中来了。当然这个标准是可以人为更换的,如你觉得这个应该属于角色的而不是属于某个状态的,那就见仁见智了。
2、尽量不要把代码写死,如animation播放时用的名称animator.Play(“Idle”);改为animator.Play(anim.name);初始化animation clip后直接拖进inspector里面去。
3、雷同的代码移到state父类去,如设置某个state的isComplete状态,setup等。
KneelState:
public class KneelState : State
{
public AnimationClip anim;
public override void Enter()
{
animator.Play(anim.name);
}
public override void Do()
{
if (!input.isGrounded )
isComplete = true;
}
public override void FixDo()
{
}
public override void Exit()
{
}
}
RunState:
public class RunState : State
{
public AnimationClip anim;
public float runSpeed = 5f;
public override void Enter()
{
animator.Play(anim.name);
}
public override void Do()
{
float velX = rigidbody2.velocity.x;
animator.speed = Mathf.Abs(velX)/runSpeed;
if (!input.isGrounded)
isComplete = true;
}
public override void FixDo()
{
}
public override void Exit()
{
}
}
IdleState:
public class IdleState : State
{
public AnimationClip anim;
public override void Enter()
{
animator.Play(anim.name);
}
public override void Do()
{
if (!input.isGrounded)
isComplete = true;
}
public override void FixDo()
{
}
public override void Exit()
{
}
}
AirState(在空中的情况)
public class AirState : State
{
public AnimationClip anim;
public float jumpForce = 17f;
public override void Enter()
{
//animator.Play(anim.name);
}
public override void Do()
{
float time = Helper.Map(rigidbody2.velocity.y, -jumpForce, jumpForce, 0, 1, true);
animator.Play(anim.name, 0, time);
animator.speed = 0;
if (input.isGrounded)
isComplete = true;
}
public override void FixDo()
{
}
public override void Exit()
{
}
}
四、PlayerScript(角色主体)
一些比较好的地方:
public bool isGrounded { get; private set; }
public float xInput { get; private set; }
public float yInput { get; private set; }
注:定义的时候直接弄成不能被其他类修改的变量,但是允许访问,是一种非常经典的写法,
void SelectState()
{
State oldState = state;
if (isGrounded)
{
if (yInput == -1&&Mathf.Abs(rigidbody2.velocity.x)<0.1)
{
state = kneelState;
}
else if (xInput == 0)
{
state = idleState;
}
else
{
state = runState;
}
}
else
{
state = airState;
}
if (state != oldState)//满足上述条件(状态不同),就更换状态
{
oldState.Exit();
state.Initialize();
state.Enter();
}
}
注:仅在转换状态时切换state,虽然这种方式会直接无视掉isComplete条件,不过具体看需求以及适用范围。
public void ApplyFriction()
{
if (isGrounded && xInput == 0 && yInput <= 0)
{
rigidbody2.velocity = new Vector2(rigidbody2.velocity.x * groundDecay, rigidbody2.velocity.y);
}
}
注:非常简单暴力的摩擦力,可能会受到帧率影响。
角色类完整代码:
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
public class PlayerScript : MonoBehaviour
{
public BoxCollider2D boxCollider2D;
public CapsuleCollider2D capsuleCollider2D;
public Animator animator;
private Rigidbody2D rigidbody2;
private float groundDecay;
public bool isGrounded { get; private set; }
public float xInput { get; private set; }
public float yInput { get; private set; }
private LayerMask groundLayer;
State state;
public AirState airState;
public RunState runState;
public IdleState idleState;
public KneelState kneelState;
void Start()
{
rigidbody2 = GetComponent<Rigidbody2D>();
groundLayer = LayerMask.GetMask("Ground");
idleState.Setup(rigidbody2, animator, this);
runState.Setup(rigidbody2, animator, this);
airState.Setup(rigidbody2, animator, this);
kneelState.Setup(rigidbody2, animator, this);
//不能直接state.Setup(rigidbody2, animator, this);是因为这三个是public变量,直接拖拽到inspector中的
state = idleState;
}
// Update is called once per frame
void Update()
{
FlipDetection2();
MoveWithInput();
ApplyFriction();
//注释掉之后,时刻选择state,不等Complete了再更换state。
//油管作者AdamCYounis的解释是:举个例子,Idle这种状态本身不包含已完成切换这种逻辑,所以要这样写,不过这也是一种尝试,实际上都可以。
//if (state.isComplete)
//{
SelectState();
//}
state.Do();
}
private void FixedUpdate()
{
CheckGround();
}
void MoveWithInput()
{
yInput = Input.GetAxisRaw("Vertical");
xInput = Input.GetAxisRaw("Horizontal");
float horizontalSpeed = xInput * runState.runSpeed;
//animator.SetFloat("speed", Mathf.Abs(horizontalSpeed));
if (Mathf.Abs(Input.GetAxisRaw("Horizontal")) > 0)
{
rigidbody2.velocity = new Vector2(horizontalSpeed, rigidbody2.velocity.y);
}
//垂直速度
if (Input.GetButtonDown("Jump") && isGrounded)
{
rigidbody2.velocity = new Vector2(rigidbody2.velocity.x, airState.jumpForce);
}
}
void CheckGround()
{
//Debug.Log(isGrounded);
int sumGround = Physics2D.OverlapAreaAll(boxCollider2D.bounds.min, boxCollider2D.bounds.max, groundLayer).Length;
if (sumGround > 0) {
isGrounded = true;
}
else
{
isGrounded = false;
}
}
void FlipDetection2()
{
if (xInput != 0)
{
float direction = Mathf.Sign(xInput);
//Mathf.Sign(float f); Returns a value of 1 when f is 0 or greater. Returns a value of -1 when f is negative.
transform.localScale = new Vector3( direction* Mathf.Abs(transform.localScale.x), transform.localScale.y, 1);
}
}
void SelectState()
{
State oldState = state;
if (isGrounded)
{
if (yInput == -1&&Mathf.Abs(rigidbody2.velocity.x)<0.1)
{
state = kneelState;
}
else if (xInput == 0)
{
state = idleState;
}
else
{
state = runState;
}
}
else
{
state = airState;
}
if (state != oldState)//满足上述条件(状态不同),就更换状态
{
oldState.Exit();
state.Initialize();
state.Enter();
}
}
public void ApplyFriction()
{
if (isGrounded && xInput == 0 && yInput <= 0)
{
rigidbody2.velocity = new Vector2(rigidbody2.velocity.x * groundDecay, rigidbody2.velocity.y);
}
}
}

500

被折叠的 条评论
为什么被折叠?



