状态转换
本课属于 Coddy Unity 学习旅程的“Unity 游戏架构”单元。第 38 课,共 64 课。
谁来决定状态何时改变?如果由拥有者来决定,就会把每条规则都放回同一个 Update 中,就像之前的切换逻辑一样。通常的做法是让每个状态都知道如何退出:在自己的 Tick 中检查条件,并向状态机请求下一个状态。这个炸弹会等到玩家靠近,然后点燃引信;如果玩家跑开,就回到等待状态:
public class Bomb : MonoBehaviour
{
public Transform player;
public StateMachine machine = new StateMachine();
public IState idle;
public IState fuse;
void Start()
{
idle = new IdleState(this);
fuse = new FuseState(this);
machine.ChangeState(idle);
}
void Update() { machine.Tick(); }
public bool PlayerNear()
{
return Vector3.Distance(transform.position, player.position) < 3f;
}
}
public class IdleState : IState
{
private Bomb bomb;
public IdleState(Bomb bomb) { this.bomb = bomb; }
public void Enter() { }
public void Exit() { }
public void Tick()
{
if (bomb.PlayerNear()) bomb.machine.ChangeState(bomb.fuse);
}
}
public class FuseState : IState
{
private Bomb bomb;
private float timer;
public FuseState(Bomb bomb) { this.bomb = bomb; }
public void Enter() { timer = 2f; }
public void Exit() { }
public void Tick()
{
if (!bomb.PlayerNear())
{
bomb.machine.ChangeState(bomb.idle);
return;
}
timer -= Time.deltaTime;
if (timer <= 0f)
{
Debug.Log("Boom!");
Object.Destroy(bomb.gameObject);
}
}
}每个状态都会在构造函数中获取炸弹,并通过它访问状态机、玩家检测和其他状态。炸弹会在 Start 中创建每个状态一次,因此状态不会再用 new 创建其他状态:如果两个状态在各自的构造函数中创建对方,就会不断循环,直到堆栈溢出。炸弹的 Update 只会调用状态机的 Tick,规则则保存在所属的状态中:2 秒引信完全由 FuseState 负责。
ChangeState 会立即运行 FuseState 的 Exit 和 IdleState 的 Enter,但调用之后,FuseState 的 Tick 中其余部分仍会继续运行。return 会停止后续执行,否则引信会在状态已经结束后再多烧一帧。Destroy 写作 Object.Destroy:MonoBehaviour 从 Unity 的 Object 类继承了它,而普通类必须写出类名。
挑战
中等在 FlytrapStates.cs 中的每个 Tick 里编写捕蝇草的状态转换。
- Open:当满足
trap.PlayerInReach()时,调用trap.machine.ChangeState(trap.snap);。 - Snap:将
Time.deltaTime加到timer上;到达0.5时,如果玩家仍在范围内,就切换到trap.chew,否则切换到trap.open。 - Chew:让
timer从 3 开始倒计时;到 0 时,切换到trap.open。
按“开始”,走进陷阱,然后躲开它。接着按“检查”。
亲手试试
using UnityEngine;
public class OpenState : IState
{
private Flytrap trap;
public OpenState(Flytrap trap)
{
this.trap = trap;
}
public void Enter()
{
trap.Paint(new Color(0.3f, 0.8f, 0.2f));
}
public void Tick()
{
// 玩家进入攻击范围时,迅速合拢
}
public void Exit()
{
}
}
public class SnapState : IState
{
private Flytrap trap;
private float timer;
public SnapState(Flytrap trap)
{
this.trap = trap;
}
public void Enter()
{
timer = 0f;
trap.Paint(new Color(1f, 0.55f, 0.1f));
}
public void Tick()
{
// 经过 0.5 秒:如果玩家仍在攻击范围内,就开始咀嚼;否则重新张开
}
public void Exit()
{
}
}
public class ChewState : IState
{
private Flytrap trap;
private float timer;
public ChewState(Flytrap trap)
{
this.trap = trap;
}
public void Enter()
{
timer = 3f;
trap.Paint(new Color(0.5f, 0.05f, 0.1f));
Debug.Log("Chomp!");
}
public void Tick()
{
// 经过 3 秒:重新张开
}
public void Exit()
{
}
}
Unity 游戏架构中的所有课
自己练习: Unity 在线编辑器