Unity C#经典状态机基类设计2 小白学习笔记

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);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值