您现在的位置是:首页 >技术杂谈 >unity进阶学习笔记:有限状态机网站首页技术杂谈

unity进阶学习笔记:有限状态机

Raine_Yang 2024-06-14 06:01:02
简介unity进阶学习笔记:有限状态机

一般来说,每一个游戏物体会有多种状态,每一个状态会对应一个特定动画。如一个游戏角色可能有静止状态,移动状态,攻击状态。每一个状态里都有对应的动画。如果我们只是简单使用一个个if语句判断玩家进行哪个控制来切换动画会让程序可读性和可维护性很差。利用FSM(有限状态机)可以将每一个状态单独封装,使得系统结构更加清晰

我们首先搭建一个游戏场景用于示例
在这里插入图片描述
导入素材Character Pack: Free Sample,该素材里包括一个人物模型和一系列人物动画。这里我们使用其中idle,run,wave动画
在这里插入图片描述
给人物添加动画器组件,idle和run之间靠boolean值IsRun切换,idle和wave之间用trigger Wave切换

1 将状态封装为方法

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum State {
    idle,
    run,
    wave
}

public class TestFSM_1 : MonoBehaviour
{

    private Animator ani;
    private State state = State.idle;
    // Start is called before the first frame update
    void Start()
    {
        ani = GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        switch (state) {
            case State.idle:
                idle();
                break;
            case State.run:
                run();
                break;
            case State.wave:
                wave();
                break;
        }
    }

    void idle() {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        if (dir == Vector3.zero) {
            ani.SetBool("IsRun", false);
        } else {
            state = State.run;
        }
        if (Input.GetKeyDown(KeyCode.Space)) {
            state = State.wave;
        }
    }

    void run() {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        if (dir != Vector3.zero) {
            ani.SetBool("IsRun", true);
            transform.rotation = Quaternion.LookRotation(dir);
            transform.Translate(Vector3.forward * 3 * Time.deltaTime);
        } else {
            state = State.idle;
        }
    }

    void wave() {
        ani.SetTrigger("Wave");
        if (!ani.GetCurrentAnimatorStateInfo(0).IsName("wave")) {
            state = State.idle;
        }
    }
}

首先我们创建枚举State用以保存各个状态名称

这里我们创建idle() run() wave()方法。在idle()方法中如果发现玩家按下方向键将状态切换为State.run,在按下空格时将状态切换为State.wave

在run()方法里我们实现了角色移动,如果方向为(0,0,0)时说明玩家松开方向键,将状态切换为idle

在wave()方法里设置Trigger Wave。这里我们要在挥手动画退出时将状态切换回idle。我们可以使用GetCurrentAnimatorStateInfo方法判断

2 将状态封装为类

状态基类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class FSMState
{
    public int StateID;
    public MonoBehaviour Mono;
    public FSMManager Manager;

    public FSMState(int stateID, MonoBehaviour mono, FSMManager manager) {
        StateID = stateID;
        Mono = mono;
        Manager = manager;
    }

    public abstract void OnEnter();
    public abstract void OnUpdate();
}

状态基类保存了状态的ID(即在State枚举中的值),Mono包括了使用该状态的游戏物体,Manager为该状态的管理器(在后面代码实现)

在状态基类里定义了OnEnter (在进入该状态时执行的方法) OnUpdate (在该状态中每一帧调用方法)。这两个方法在子类中实现

FSMManager类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FSMManager
{

    public List<FSMState> StateList = new List<FSMState>();
    public int CurrentIndex = -1;

    public void ChangeState(int StateID) {
        CurrentIndex = StateID;
        StateList[CurrentIndex].OnEnter();
    }

    public void Update() {
        if (CurrentIndex != -1) {
            StateList[CurrentIndex].OnUpdate();
        }
    }
}

在该类中我们使用列表StateList保存各个状态。ChangeState方法用于改变状态,在改变状态后调用一次新状态的OnEnter方法。Update方法用于执行当前状态的OnUpdate

创建好这两个类后,我们开始使用该状态机框架实现游戏程序

1 IdleState

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class IdleState : FSMState
{

    public IdleState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager) {

    }

    
    public override void OnEnter() {
        Mono.GetComponent<Animator>().SetBool("IsRun", false);
    }

    public override void OnUpdate() {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        if (dir != Vector3.zero) {
            Manager.ChangeState((int)State.run);
        }
        if (Input.GetKeyDown(KeyCode.Space)) {
            Manager.ChangeState((int)State.wave);
        }
    }

}

对于静止状态,在OnEnter中将IsRun参数设为false。在OnUpdate方法中检测如果按下方向键将状态切换为State.run,如果按下空格将状态切换为State.wave

2 RunState

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RunState : FSMState
{

    public RunState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager) {

    }

    public override void OnEnter() {
        Mono.GetComponent<Animator>().SetBool("IsRun", true);
    }

    public override void OnUpdate() {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        if (dir != Vector3.zero) {
            Mono.transform.rotation = Quaternion.LookRotation(dir);
            Mono.transform.Translate(Vector3.forward * 3 * Time.deltaTime);
        } else {
            Manager.ChangeState((int)State.idle);
        }
    }

}

在OnEnter方法里,我们将动画器参数IsRun设为true。在OnUpdate方法中控制角色移动,如果玩家没有按着方向键将状态切换为idle

3 WaveState

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class WaveState : FSMState
{
    public WaveState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager) {

    }

    public override void OnEnter() {
       Mono.GetComponent<Animator>().SetTrigger("Wave");
    }

    public override void OnUpdate() {
        if (!Mono.GetComponent<Animator>().GetCurrentAnimatorStateInfo(0).IsName("wave")) {
            Manager.ChangeState((int)State.idle);
        }
    }
}

在OnEnter方法中将Wave Trigger触发。在OnUpdate中进行检测当前动画并在wave动画播放完后将状态改为idle

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。