FSM状态机以及超级控制器API的使用

2017-04-28  本文已影响192人  heibe

在上一篇文章留了一个FSM有限自动机状态机的坑,在这里填上,对这部分不感兴趣的可以直接看后面的API和使用方法

FSM有限自动状态机

简单来说状态机是一种维护状态迁移的精简并逻辑性很强的模式,在游戏开发中被广泛运用在AI逻辑和状态变化中,设想一种情况,我们需要维护角色运动状态的变化,如行走,跳跃,掉落等等,一种可行的方式是用一些if,else语句,如这种在某些教程中经常出现的代码:

if(Input.GetKeyDown(KeyCode.W))
{
     transform.positon += new Vector3(0,0,1); 
}
if(Input.GetKeyDown(KeyCode.S))
{
     transform.positon += new Vector3(0,0,-1); 
}

这样的代码在处理单纯的行走时是可行的,但是当动作增多、逻辑增多以后这样的代码变得不可行,就算只有行走、跳跃、掉落这三种动作,带来的逻辑也是相当多的,比如角色在输入跳跃后角色将跳跃,角色在空中时不可再跳跃,跳跃着地以后才能继续行走等等等
为了解决大量的状态变化,我们引入了状态机
事实上我相信每个Unity开发人员都接触过状态机,Unity编辑器中对动画的处理就是采用状态机的(5.x版本以后)


一个状态机的例子

这张图很好的说明了状态机在做什么,其实就是在管理状态以及状态之间的迁移,Idle状态在输入前进方向后会迁移到Walk状态,而Walk状态在脱离地面则会进入Fall状态等等
有两条原则是确保状态机可以简单实现的:

使用方法

添加动作

API

SuperCharacterController:

在跳跃和降落状态前记得应用

    controller.DisableClamping();
    controller.DisableSlopeLimit();

FSM:

FSMSystem:

FSMState:

使用

  FSMSystem fsm = new FSMSystem();
  void Update()
  {
      fsm.CurrentState.Reason();
      fsm.CurrentState.Act();
  }

示例

下面我拿Walk状态举例

public class CharacterStateMachine : FSMSystem {
public CharacterStateMachine(SuperCharacterController controller)
{
    AddState(new CharacterIdleState(controller,this));
    AddState(new CharacterWalkState(controller, this));
    AddState(new CharacterJumpState(controller, this));
    AddState(new CharacterFallState(controller, this));
}
}

首先我们需要定义一个类来继承FSMSystem类,并定义构造函数,在构造函数中添加所有我们需要的状态

public class CharacterWalkState : FSMState
{
public SuperCharacterController controller;
public CharacterWalkState(SuperCharacterController c, FSMSystem f)
{
    fsm = f;
    controller = c;
    stateID = StateID.CharacterWalk;
    AddTransition(Transition.CharacterWalkToIdle, StateID.CharacterIdle);
    AddTransition(Transition.CharacterJump, StateID.CharacterJump);
    AddTransition(Transition.CharacterFall, StateID.CharacterFall);
}

public override void Reason()
{
    if (!controller.MaintainingGround())
    {
        fsm.PerformTransition(Transition.CharacterFall);
    }
    if (InputController.GetKey<bool>("Jump"))
    {
        fsm.PerformTransition(Transition.CharacterJump);
    }
    if (InputController.GetKey<Vector2>("inputV").magnitude <= 0.1f)
    {
        fsm.PerformTransition(Transition.CharacterWalkToIdle);
    }
}

public override void Act()
{
    
    float walkSpeed = 5;
    float walkAcc = 1;
    float angleDelta = 30;
    Vector2 inputV = InputController.GetKey<Vector2>("inputV");
    Transform camera = Camera.main.transform;//模拟照相机
    Vector3 screenForword = controller.transform.position - camera.position;
    Vector3 pScreenForword = Math3d.ProjectVectorOnPlane(controller.up, screenForword);
    Quaternion inputQua = Quaternion.FromToRotation(new Vector3(0,0,1), new Vector3(inputV.x, 0, inputV.y));
    if(inputV == new Vector2(0,-1))
    {
        inputQua = Quaternion.AngleAxis(180, controller.up);
    }
    Vector3 target = inputQua * pScreenForword;
    controller.Ronate(Quaternion.FromToRotation(Vector3.forward, target), angleDelta);
    Vector2 direction = new Vector2(0, 1);
    controller.MoveHorizontal(direction, walkSpeed, walkAcc);
}
public override void DoBeforeEntering()
{
    controller.EnableClamping();
    controller.EnableSlopeLimit();
}
}

接下来定义一个Walk类继承自FSMState类,在构造函数中使用AddTransiton方法将所有可能的状态迁移添加进去。
重载Reason和Act函数,这两个函数分别代表状态在经历什么样的事件会进行状态迁移以及在这个状态下会执行什么样的逻辑代码

public override void Reason()
{
    if (!controller.MaintainingGround())
    {
        fsm.PerformTransition(Transition.CharacterFall);
    }
    if (InputController.GetKey<bool>("Jump"))
    {
        fsm.PerformTransition(Transition.CharacterJump);
    }
    if (InputController.GetKey<Vector2>("inputV").magnitude <= 0.1f)
    {
        fsm.PerformTransition(Transition.CharacterWalkToIdle);
    }
}

Reason函数的逻辑很简单,如果我们的角色脱离了地面将进入Fall状态,如果输入了跳跃动作将进入跳跃状态,如果输入的行走向量长度过小将进入静止状态

public override void Act()
{
    
    float walkSpeed = 5;
    float walkAcc = 1;
    float angleDelta = 30;
    Vector2 inputV = InputController.GetKey<Vector2>("inputV");
    Transform camera = Camera.main.transform;//模拟照相机
    Vector3 screenForword = controller.transform.position - camera.position;
    Vector3 pScreenForword = Math3d.ProjectVectorOnPlane(controller.up, screenForword);
    Quaternion inputQua = Quaternion.FromToRotation(new Vector3(0,0,1), new Vector3(inputV.x, 0, inputV.y));
    if(inputV == new Vector2(0,-1))
    {
        inputQua = Quaternion.AngleAxis(180, controller.up);
    }
    Vector3 target = inputQua * pScreenForword;
    controller.Ronate(Quaternion.FromToRotation(Vector3.forward, target), angleDelta);
    Vector2 direction = new Vector2(0, 1);
    controller.MoveHorizontal(direction, walkSpeed, walkAcc);
}
public override void DoBeforeEntering()
{
    controller.EnableClamping();
    controller.EnableSlopeLimit();
}
}

旋转

Act函数中执行了使角色移动的逻辑代码,我采用了模拟摇杆的方式,首先计算出从向量(0,0,1)到输入的摇杆向量的旋转变化值inputQua(对四元数不太清楚的读者可以暂时跳过),然后通过inputQua * pScreenForword计算要旋转到的目的方向(pScreenForword表示从摄像机位置到角色位置的向量在xz平面上的投影,如果角色处在摄像机中心位置的话,这样的旋转方式是很人性化的),最后使用Quaternion.FromToRotation(Vector3.forward, target)计算出这一旋转对应的四元数值,并调用Ronate方法进行旋转

移动

移动要简单得多,就是以一定速度和加速度调用MoveHorizontal方法使角色朝前方移动
最后的DoBeforeEntering即是字面理解的意思,这里使角色启用附着地面和坡度限制

上一篇 下一篇

猜你喜欢

热点阅读