在游戏中,游戏物体经常会因为玩家输入或者事件从一种状态转变为另一种状态,从而使其行为也发生变化。对于以上情况最简单的办法就是使用if-else或者switch-case语句,但这样做不利于程序的拓展和维护。
通过状态模式(State Pattern)可以很好的解决这个问题。在状态模式(State Pattern)中,物体的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。
我们使用状态模式来实现一个根据其内部状态改变而改变行为的系统。因此游戏环境(玩家输入或事件)的变化会引起行为的变化。
这个系统由三部分组成:
该模式的类图
在这个系统中,客户端通过Context类设置状态,并且发出一个状态变化(Transition)的请求。因此Context类一直知道它所管理的物体的目前状态,但是并不用知道该物体的每一个存在的状态。所以我们可以任意添加和删除状态但并不用更改Context中的任何的内容。
为了方便写代码构建上述状态模式,我们假设目前有一辆车,这个车有停止,启动和转弯三种状态,通过构建一个应用状态模式的系统在Unity中控制这三种状态。
首先定义一个ICarState的接口,该接口有一个Handle方法。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace StatePattern
{
public interface ICarState
{
void Handle(CarController controller);
}
}
在Handle方法中我们传入了一个CarController类型(下文中有该类定义)的参数,这个参数可以使状态类访问和修改CarContoller中的公共属性。
然后实现Context类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace StatePattern
{
public class CarStateContext
{
public ICarState CurrentState
{
get;
set;
}
private readonly CarController _carController;
public CarStateContext(CarController carController)
{
_carController = carController;
}
public void Transition()
{
//调用接口,处理对应状态
CurrentState.Handle(_carController);
}
public void Transition(ICarState state)
{
CurrentState = state;
CurrentState.Handle(_carController);
}
}
}
在Context类中,公开了CurrentState属性并且可以调用Transition方法来改变当前属性。
接下来我们来实现CarContoller类,该类负责初始化Context和状态,并且触发状态的改变。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace StatePattern
{
///
/// 用来控制车的各种状态
///
public class CarController : MonoBehaviour
{
public float maxSpeed = 2.0f;
public float turnDistance = 2.0f;
public float CurrentSpeed
{
get;
set;
}
public Direction CurrentTurnDirection
{
get;
private set;
}
private ICarState _startState, _stopState, _turnState;
private CarStateContext _carStateContext;
// Start is called before the first frame update
void Start()
{
_carStateContext = new CarStateContext(this);
_startState = gameObject.AddComponent();
_stopState = gameObject.AddComponent();
_turnState = gameObject.AddComponent();
_carStateContext.Transition(_stopState);
}
///
/// 切换到开始状态
///
public void StartCar()
{
_carStateContext.Transition(_startState);
}
///
/// 切换到停止状态
///
public void StopCar()
{
_carStateContext.Transition(_stopState);
}
///
/// 切换到左转弯/右转弯状态
///
///
public void Turn(Direction direction)
{
CurrentTurnDirection = direction;
_carStateContext.Transition(_turnState);
}
}
}
如果我们不把各个状态封装起来独立于控制器类的话,那么控制器类就会变得臃肿且难以维护。因此,采用状态模式,使控制器类更小、更容易维护
接下来我们来继承之前定义的抽象的状态接口ICarState来定义具体的类
首先是Stop状态,在Stop状态下我们只需把车的速度设置为0即可。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace StatePattern
{
public class CarStopState : MonoBehaviour, ICarState
{
private CarController _carController;
public void Handle(CarController carController)
{
if (!_carController)
_carController = carController;
_carController.CurrentSpeed = 0;
}
}
}
然后是Start状态,该状态下把汽车速度设置于预定速度,并根据速度不断更新车的位置。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace StatePattern
{
public class CarStartState : MonoBehaviour, ICarState
{
private CarController _carController;
public void Handle(CarController carController)
{
if (!_carController)
_carController = carController;
_carController.CurrentSpeed = _carController.maxSpeed;
}
// Update is called once per frame
void Update()
{
if(_carController)
{
if(_carController.CurrentSpeed > 0)
{
_carController.transform.Translate(Vector3.forward *
(_carController.CurrentSpeed * Time.deltaTime));
}
}
}
}
}
最后就是转弯状态,该状态下有两种情况:左转和右转
首先枚举定义转弯方向
namespace Chapter.State
{
public enum Direction
{
Left = -1,
Right = 1
}
}
然后是定义转弯状态的代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace StatePattern
{
public class CarTurnState : MonoBehaviour, ICarState
{
private CarController _carController;
private Vector3 _turnDirection;
public void Handle(CarController carController)
{
if (!_carController)
_carController = carController;
_turnDirection.x = (float)_carController.CurrentTurnDirection;
if(_carController.CurrentSpeed > 0)
{
transform.Translate(_turnDirection * _carController.turnDistance);
}
}
}
}
至此,所有状态都已经定义好的。接下来把这些脚本应用到Unity中
我们先写一个类来方便控制各个状态的变化:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace StatePattern
{
public class ClientState : MonoBehaviour
{
private CarController _carController;
// Start is called before the first frame update
void Start()
{
_carController = (CarController)FindObjectOfType(typeof(CarController));
}
private void OnGUI()
{
if (GUILayout.Button("StartCar"))
_carController.StartCar();
if (GUILayout.Button("TurnLeft"))
_carController.Turn(Direction.Left);
if (GUILayout.Button("TurnRight"))
_carController.Turn(Direction.right);
if (GUILayout.Button("StopCar"))
_carController.StopCar();
}
}
}
之后我们在Unity中创建一个Cube,在Cube上挂载上CarContoller和ClientState脚本,运行游戏。可以通过屏幕上的四个按钮控制前进,停止,左转弯,右转弯。
一个简易的状态模式的系统就这样完成了,我们不难发现,状态模式易于维护和拓展,但是也有很多问题,不能实现动画之间的过渡,在改变状态时没有条件限制,如果要增加转换条件,需要非常多的代码来实现。但是Unity提供的原生的动画系统可以很好的解决这个问题.
动画系统实际上是一个有限状态机,我们可以在每个状态上添加脚本,来根据状态的变化调整物体的反应。
想要了解更多,请查看Unity官方关于动画系统的文档:https://docs.unity3d.com/Manual/AnimationSection.htmlhttps://docs.unity3d.com/Manual/AnimationSection.html
(水平有限,只是想记录学习过的知识,如果有错请指正)