做竞价网站 要注意什么,购物网站开发 书籍,企业门户网站建设,wordpress 菜单字体大小前言
最近在学习Unity游戏设计模式#xff0c;看到两本比较适合入门的书#xff0c;一本是unity官方的 《Level up your programming with game programming patterns》 ,另一本是 《游戏编程模式》
这两本书介绍了大部分会使用到的设计模式#xff0c;因此很值得学习
本… 前言
最近在学习Unity游戏设计模式看到两本比较适合入门的书一本是unity官方的 《Level up your programming with game programming patterns》 ,另一本是 《游戏编程模式》
这两本书介绍了大部分会使用到的设计模式因此很值得学习
本专栏暂时先记录一些教程部分深入阅读后续再更新 文章目录 前言有限状态机如何实现状态模式 有限状态机
在游戏中有一个可玩的角色通常情况他是站在地面上的当控制控制器他就会进入行走状态当按下跳跃则他会跳到半空下落后又会回到站立状态 如果你做过动画机Animator你一定能理解这个状态切换是如何实现的。上述的状态图类似于流程图但是有一些区别
它由多种状态构成且每个时间点只有一个状态是处于活动的每个状态可以根据运行条件转换为其他状态当发生状态转换时原来的状态由活动切换为不活动而转换的目标状态变为活动
我们将上图这样的状态模型称为有限状态机FSM有限状态机在角色AI程序设计尤其操作系统中十分常见。
有限状态机由数量有限的状态构成它有一个初始状态并包含了其他状态以及转换状态的条件。状态机在任意时刻都处于其中的某一状态并且在一定条件下会从一种状态切换为另一种状态以响应转换的外部输入。
状态模式不仅可以用于管理角色道具的状态甚至可以用于管理整个游戏系统的状态。包括Mono Behavior的生命周期实际上也可视作一种状态模式 如何实现状态模式
状态模式看起来似乎很简单我们只需要让对象进行状态判断根据状态来选择行为就行了。
那我是不是可以定义一个枚举类型来分出状态然后让角色根据他们所处的状态在内部进行行为切换就行了呢
public enum EnemyState
{Idle,Walk,Jump
}public class Enemy : MonoBehaviour
{private EnemyState state;private void Update(){GetInput();switch (state){case EnemyState.Idle:Idle();break;case EnemyState.Walk:Walk();break;case EnemyState.Jump:Jump();break;}}
}看起来实现了状态模式但显然这种实现是依托答辩。
首先难道我们每定义一个角色就需要在其内部管理它自身的状态 齐次如果我们每添加一个状态就需要一个Switch Case那代码会有多冗余 最后上述代码显然是高耦合的如果我们需要添加或者删去某状态那么所有使用了该状态的代码都需要被修改。
因此用枚举类型实现状态显然不合适记住设计模式的重要原则对拓展开放对修改关闭
因此同理让所有角色继承一个状态基类在基类中定义各种状态实现的方法并在子类中重写状态实现的虚方法也是不行的因为基类一旦改变子类也要改变。
所以我们需要在不修改角色代码的情况下既要实现状态的拓展和删除又要方便我们对每个状态的角色事件进行定义。一个想法就是让状态持有角色并在状态中完成业务处理逻辑而非角色根据状态来实现业务逻辑。
这个想法很像我之前学习的一个案例也许是工厂模式银行有很多业务但是如果每增加一个业务就需要修改银行类的代码显然违背了开闭原则因此银行应当只负责返回给用户相应的业务而具体的业务逻辑则需要业务类本身来执行。就方便对银行业务进行增减。
因此角色的状态事件则需要由状态类本身来进行定义好处是减少了耦合代码也会更加清晰。但坏处是我们可能要为每个角色类定义多个衍生出来的状态类类的数量会爆炸式的增长此时用命名空间和程序集来管理多个相关类的好处就凸显出来了
Unity 状态模式实例详解
// 定义抽象状态类
public abstract class CharacterState
{protected Character character;public void SetCharacter(Character _character){this.character _character;}// 抽象方法子类需要实现具体行为public abstract void Update();
}// 具体状态类IdleState
public class IdleState : CharacterState
{public override void Update(){Debug.Log(角色处于闲置状态);// 检查是否应该转换到其他状态如按下移动键则切换至MoveStateif (Input.GetKey(KeyCode.W)){character.ChangeState(new MoveState());}}
}// 具体状态类MoveState
public class MoveState : CharacterState
{public override void Update(){Debug.Log(角色正在移动);// 检查是否应返回闲置状态或切换至其他状态if (!Input.GetKey(KeyCode.W)){character.ChangeState(new IdleState());}}
}------------------------------------------------------// 角色类持有当前状态并处理状态切换
public class Character : MonoBehaviour
{private CharacterState currentState;public void ChangeState(CharacterState newState){if (currentState ! null){currentState.SetCharacter(null);}currentState newState;currentState.SetCharacter(this);}void Update(){currentState.Update();}
}
在上述例子中我们把状态的业务逻辑本身定义到了状态类中并将对应的持有角色传入状态类那么当角色进行状态改变时则行为逻辑也就切换为对应状态类提供的Update方法。由状态类中对角色逻辑进行处理。
为了进一步解除角色类和状态类的耦合角色未必需要有状态切换的需求可以创建一个抽象的上下文类Context由它来持有当前状态并处理状态之间的切换
管理StateSystem的文件
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace StatePattern.StateSystem
{/// summary/// 状态抽象基类/// /summarypublic abstract class State{public abstract void Handle();}/// summary/// 状态生命周期抽象基类/// /summarypublic abstract class StateBehaviour:State{// 状态持有者protected ContextBehaviour Context;// 几个用于状态生命周期调度的抽象方法public abstract void Update();public abstract void Enter();public abstract void Exit();}/// summary/// 管理状态的上下文基类/// /summarypublic class Context{// 当前状态private State _state;public void SetStateT(T state) where T:State{_state state;}public State GetState(){return _state;}public void Requst(){_state?.Handle();}}/// summary/// 上下文管理状态生命周期基类/// /summarypublic class ContextBehaviour : Context{// 当前持有状态private StateBehaviour _stateBehaviour;// 覆盖父类的获取状态方法public new void SetStateT(T state) where T:StateBehaviour{_stateBehaviour state;}public new StateBehaviour GetState(){return _stateBehaviour;}// 几个用于状态生命周期调度的虚方法public virtual void ChangeState(StateBehaviour stateBehaviour){_stateBehaviour.Exit();SetState(stateBehaviour);_stateBehaviour.Enter();}public virtual void Update(){_stateBehaviour.Update();}public virtual void NotifyStateEnter(){_stateBehaviour.Enter();}public virtual void NotifyStateExit(){_stateBehaviour.Exit();}}}
角色基类定义代码
using StatePattern.StateSystem;
using System;
using UnityEngine;
using UnityEngine.UI;namespace CharacterClass
{#region 基类定义/// summary/// 角色状态基类/// /summarypublic abstract class CharacterState : StateBehaviour { }/// summary/// 角色状态上下文基类/// /summarypublic class CharacterContext : ContextBehaviour { }/// summary/// 角色基类/// /summarypublic class Character : MonoBehaviour{private CharacterContext _context;public CharacterContext Context _context;public Button StateChangeBtn;private void Start(){var riginState new IdleState();_context new CharacterContext();_context.SetState(riginState);var newState new MoveState();StateChangeBtn.onClick.AddListener(() { ChangeState(newState); });}private void Update(){_context.Update();}public void ChangeState(CharacterState characterState){_context.ChangeState(characterState);}}#endregion/// summary/// 角色状态类IdleState/// /summarypublic class IdleState : CharacterState{public override void Update(){Debug.Log(处于IdleState);}public override void Enter(){Debug.Log(进入IdleState);}public override void Exit(){Debug.Log(退出IdleState);}public override void Handle(){Debug.Log(IdleState下执行事件);}}/// summary/// 角色状态类MoveState/// /summarypublic class MoveState : CharacterState{public override void Update(){Debug.Log(处于MoveState);}public override void Enter(){Debug.Log(进入MoveState);}public override void Exit(){Debug.Log(退出MoveState);}public override void Handle(){Debug.Log(MoveState下执行事件);}}}我们把Character脚本挂载然后传入Button用于手动切换状态 这样我们就实现状态模式了。上面的代码写的实在太漂亮了我都忍不住想夸我自己
我们还有更丧心病狂的想法如果我们需要管理的状态不是单个而是一系列的状态那么我们可能就需要维护一个状态队列或者状态栈此时一个状态切换上下文已经不够我们用了我们需要一个状态机 public class NullState : StateBehaviour{public override void Handle(){throw new System.NotImplementedException();}public override void Update(){throw new System.NotImplementedException();}public override void Enter(){throw new System.NotImplementedException();}public override void Exit(){throw new System.NotImplementedException();}}public class StateMachine{private ContextBehaviour _contextBehaviour;public ContextBehaviour ContextBehaviour _contextBehaviour;private NullState _nullState new NullState();private StateBehaviour _prevState new NullState();public StateMachine (ContextBehaviour contextBehaviour){_contextBehaviour contextBehaviour;}public StateMachine (ContextBehaviour contextBehaviour,StateBehaviour riginState){_contextBehaviour contextBehaviour;_contextBehaviour.SetState(riginState);}private QueueStateBehaviour _stateQueue new QueueStateBehaviour();public void StateEnQueue(StateBehaviour stateBehaviour){_stateQueue.Enqueue(stateBehaviour);}public StateBehaviour StateDeQueue(){if (_stateQueue.Count 0){return _stateQueue.Dequeue();}else{return _nullState;}}public void Update(){_contextBehaviour.Update();}public void NextState(){_prevState _contextBehaviour.GetState();_contextBehaviour.ChangeState(StateDeQueue());}public void PrevState(){_contextBehaviour.ChangeState(_prevState);}}
这样我们就不是让角色持有上下文而是让角色持有状态机本身。
在某些需要的时候更新状态机就可以处理一系列状态。我们就可以对状态进行各种操作例如回到上一个状态例如在一个事件中根据我们的需要传入一系列状态并按照我们的想法对状态机中的状态进行触发。甚至多个角色持有同个状态机一个状态机持有多个状态的上下文等等奇思妙想。