游戏开发中有限状态机

有限状态机在游戏开发中是经常用到的数学模型,虽然我做的是"万年捕鱼",但是有幸在2014年用Unity开发《怒海潜江》(已经成为线上尸体)中研究学习了有限状态机并且在项目中成功使用,对BOSS状态的控制让我惊艳到了(虽然是自己开发的,哈哈)。时至今日很多时候都在做重复的工作,最近的招聘好多应聘者都谈到了有限状态机这个东西,那么就重新来学习一次吧。
有限状态机,( 英语 :Finite-state machine, FSM),又称 有限状态自动机 ,简称状态机,是表示有限个 状态 以及在这些状态之间的转移和动作等行为的 数学模型 。-------《百度百科》
首先我们谈谈对"有限状态自动机"这个名词的理解。首先"状态",就是指某个对象有多种状态,我们举个游戏中的例子,怪物有待机,行走,攻击,死亡等等这几个状态;“有限状态”就是指状态是有限个的,这个也很好理解;"自动机"的意思是说具有一定的AI能力。很多人都写过FSM,但是大多数人都是自己来控制状态的切换,并没有真正做到自动(AI)的程度。在有限个状态中自动切换,实现一个状态转换的死循环。
状态机由下列几部分组成:
状态集(States) ,事件(Event), 动作(Action), 转换(Transition)。
我们用实例一步一步来看FSM。如果让我们来实现一个游戏中的怪物,需求中怪物有饥饿,觅食,战斗,逃跑,玩耍这些状态 :

如果用(if else)或者(switch case)来实现的话,那么在Update中就充斥着大量的逻辑判断和跳转,如果增加一个状态或者减少一个状态,那么就要对应修改大量的逻辑判断,很容易造成bug,违反了高内聚低耦合的设计思维。
首先我们引入状态模式,把我们要实现的怪物状态进行封装:
#pragma once

#include "../HrState/State.h"

class HrFSMState : State
{
public:
	HrFSMState();
	~HrFSMState();

	virtual void Enter() override;

	virtual void Execute() override;

	virtual void Exit() override;
};

#pragma once

#include <memory>
#include "HrFSMState.h"

class HrMonsterEntity;

class HrMonsterState : public HrFSMState
{
public:
	HrMonsterState(std::shared_ptr<HrMonsterEntity> pEntity);
	~HrMonsterState();


	virtual void Enter() override;

	virtual void Execute() override;

	virtual void Exit() override;

protected:
	std::shared_ptr<HrMonsterEntity> m_pStateOwnerEntity;

};

class HrMonsterHungryState : public HrMonsterState
{
public:
	HrMonsterHungryState(std::shared_ptr<HrMonsterEntity> pEntity);
	~HrMonsterHungryState();

	virtual void Enter() override;

	virtual void Execute() override;

	virtual void Exit() override;

};

class HrMonsterPlayState : public HrMonsterState
{
public:
	HrMonsterPlayState(std::shared_ptr<HrMonsterEntity> pEntity);
	~HrMonsterPlayState();

	virtual void Enter() override;

	virtual void Execute() override;

	virtual void Exit() override;
};

class HrMonsterFightState : public HrMonsterState
{
public:
	HrMonsterFightState(std::shared_ptr<HrMonsterEntity> pEntity);
	~HrMonsterFightState();

	virtual void Enter() override;

	virtual void Execute() override;

	virtual void Exit() override;
};

class HrMonsterEatState : public HrMonsterState
{
public:
	HrMonsterEatState(std::shared_ptr<HrMonsterEntity> pEntity);
	~HrMonsterEatState();

	virtual void Enter() override;

	virtual void Execute() override;

	virtual void Exit() override;
};

class HrMonsterEscapeState : public HrMonsterState
{
public:
	HrMonsterEscapeState(std::shared_ptr<HrMonsterEntity> pEntity);
	~HrMonsterEscapeState();

	virtual void Enter() override;

	virtual void Execute() override;

	virtual void Exit() override;
};

class HrMonsterForagingState : public HrMonsterState
{
public:
	HrMonsterForagingState(std::shared_ptr<HrMonsterEntity> pEntity);
	~HrMonsterForagingState();

	virtual void Enter() override;

	virtual void Execute() override;

	virtual void Exit() override;
};
#include "stdafx.h"
#include "HrMonsterState.h"
#include "HrMonsterEntity.h"
#include "HrRandomUtil.h"
#include <iostream>

HrMonsterState::HrMonsterState(std::shared_ptr<HrMonsterEntity> pEntity)
{
	m_pStateOwnerEntity = pEntity;
}

HrMonsterState::~HrMonsterState()
{
}

void HrMonsterState::Enter()
{

}

void HrMonsterState::Execute()
{

}

void HrMonsterState::Exit()
{

}

// HungryState /

HrMonsterHungryState::HrMonsterHungryState(std::shared_ptr<HrMonsterEntity> pEntity) : HrMonsterState(pEntity)
{

}

HrMonsterHungryState::~HrMonsterHungryState()
{

}

void HrMonsterHungryState::Enter()
{
	std::cout << " HungryState: Enter" << std::endl;
}

void HrMonsterHungryState::Execute()
{
	std::cout << " HungryState: 饿了" << std::endl;
	m_pStateOwnerEntity->ChangeState(HrMonsterEntity::ENUM_STATE_FORAGING);
}

void HrMonsterHungryState::Exit()
{
	std::cout << " HungryState: Exit" << std::endl;
}

 PlaySate ///

HrMonsterPlayState::HrMonsterPlayState(std::shared_ptr<HrMonsterEntity> pEntity) : HrMonsterState(pEntity)
{

}

HrMonsterPlayState::~HrMonsterPlayState()
{

}

void HrMonsterPlayState::Enter()
{
	std::cout << " PlayState: Enter" << std::endl;
}

void HrMonsterPlayState::Execute()
{
	std::cout << " PlayState: Playing 妈妈睡了起来嗨" << std::endl;
	if (m_pStateOwnerEntity->IsHungry())
	{
		m_pStateOwnerEntity->ChangeState(HrMonsterEntity::ENUM_STATE_HUNGRY);
	}
	else if (HrRandomUtil::GetRandNum(1, 10000) <= 1000)
	{
		std::cout << " PlayState: 遭遇敌人。。。。" << std::endl;
		m_pStateOwnerEntity->ChangeState(HrMonsterEntity::ENUM_STATE_FIGHT);
	}
}

void HrMonsterPlayState::Exit()
{
	std::cout << " PlayState: Exit" << std::endl;
}

// FightState /

HrMonsterFightState::HrMonsterFightState(std::shared_ptr<HrMonsterEntity> pEntity) : HrMonsterState(pEntity)
{

}

HrMonsterFightState::~HrMonsterFightState()
{

}

void HrMonsterFightState::Enter()
{
	std::cout << " FightState: Enter" << std::endl;
}

void HrMonsterFightState::Execute()
{
	std::cout << " FightState: Execute" << std::endl;
	int nRandNum = HrRandomUtil::GetRandNum(1, 10000);
	if (nRandNum < 1000)
	{
		std::cout << " FightState: 战胜了!" << std::endl;
		if (m_pStateOwnerEntity->IsHungry())
			m_pStateOwnerEntity->ChangeState(HrMonsterEntity::ENUM_STATE_EAT);
		else
			m_pStateOwnerEntity->ChangeToPreviousState();
	}
	else if (nRandNum < 2000)
	{
		std::cout << " FightState: 战败了!" << std::endl;
		m_pStateOwnerEntity->ChangeState(HrMonsterEntity::ENUM_STATE_ESCAPE);
	}
	else
	{
		std::cout << " FightState: 战斗中。。。。" << std::endl;
	}
}

void HrMonsterFightState::Exit()
{
	std::cout << " FightState: Exit" << std::endl;
}

/ FightState 

HrMonsterEatState::HrMonsterEatState(std::shared_ptr<HrMonsterEntity> pEntity) : HrMonsterState(pEntity)
{

}

HrMonsterEatState::~HrMonsterEatState()
{

}

void HrMonsterEatState::Enter()
{
	std::cout << " EatState: Enter" << std::endl;
}

void HrMonsterEatState::Execute()
{
	std::cout << " EatState: Execute 吃" << std::endl;
	if (m_pStateOwnerEntity->Eat())
	{
		m_pStateOwnerEntity->ChangeState(HrMonsterEntity::ENUM_STATE_PLAY);
	}
}

void HrMonsterEatState::Exit()
{
	std::cout << " EatState: Exit" << std::endl;
}

// EscapeState //
HrMonsterEscapeState::HrMonsterEscapeState(std::shared_ptr<HrMonsterEntity> pEntity) : HrMonsterState(pEntity)
{
}

HrMonsterEscapeState::~HrMonsterEscapeState()
{

}

void HrMonsterEscapeState::Enter()
{
	std::cout << " EscapeState: Enter" << std::endl;
}

void HrMonsterEscapeState::Execute()
{
	std::cout << " EscapeState: Execute 逃跑中..." << std::endl;
	if (HrRandomUtil::GetRandNum(1, 10000) <= 1000)
	{
		if (m_pStateOwnerEntity->IsHungry())
		{
			m_pStateOwnerEntity->ChangeState(HrMonsterEntity::ENUM_STATE_HUNGRY);
		}
		else
		{
			m_pStateOwnerEntity->ChangeState(HrMonsterEntity::ENUM_STATE_PLAY);
		}
	}
}

void HrMonsterEscapeState::Exit()
{
	std::cout << " EscapeState: Exit" << std::endl;
}

// ForagingState //

HrMonsterForagingState::HrMonsterForagingState(std::shared_ptr<HrMonsterEntity> pEntity) : HrMonsterState(pEntity)
{
}

HrMonsterForagingState::~HrMonsterForagingState()
{

}

void HrMonsterForagingState::Enter()
{
	std::cout << " ForagingState: Enter" << std::endl;
}

void HrMonsterForagingState::Execute()
{
	std::cout << " ForagingState: Execute 寻找食物中。。。。" << std::endl;
	if (HrRandomUtil::GetRandNum(1, 10000) <= 1000)
	{
		std::cout << " ForagingState: 找到一个小羊羔~ " << std::endl;
		m_pStateOwnerEntity->ChangeState(HrMonsterEntity::ENUM_STATE_FIGHT);
	}
}

void HrMonsterForagingState::Exit()
{
	std::cout << " ForagingState: Exit" << std::endl;
}


对怪物状态的分析,我们针对每个状态进行封装,而具体的状态逻辑也封装在了每一个状态里,状态互不干扰,把耦合降到最低。而控制当前状态的是StateMachine来控制:
#pragma once
#include <memory>

class HrFSMEntity;
class HrFSMState;

class HrFSMStateMachine
{
public:
	HrFSMStateMachine();
	virtual ~HrFSMStateMachine();

	virtual void SetOwnerEntiry(std::shared_ptr<HrFSMEntity> pEntity);
	virtual void SetCurrentState(std::shared_ptr<HrFSMState> pState);
	virtual void SetPreviousState(std::shared_ptr<HrFSMState> pState);

	virtual void ChangeState(std::shared_ptr<HrFSMState> pState);
	virtual void ChangeToPreviousState();

	virtual void Update();

	virtual std::shared_ptr<HrFSMState> GetCurretnState(std::shared_ptr<HrFSMState> pState);
	virtual std::shared_ptr<HrFSMState> GetPreviousState(std::shared_ptr<HrFSMState> pState);
protected:
	std::shared_ptr<HrFSMEntity> m_pEntityOwer;

	std::shared_ptr<HrFSMState> m_pPreviousState;
	std::shared_ptr<HrFSMState> m_pCurrentState;
};

具体的实体,即怪物继承状态模式的Context,并且持有FSMStateMachine。在状态转换的实现时,我用ID来控制具体状态,并通过状态ID来实现状态的转化,这里和之前的状态模式稍有区别:

#pragma once

#include "../HrState/Context.h"

class HrFSMEntity : public Context
{
public:
	HrFSMEntity();
	~HrFSMEntity();

	virtual void ChangeState(std::shared_ptr<State> pState) override;
	virtual void ChangeState(int nStateID) override;
	virtual void ChangeToPreviousState() override;

	virtual void Execute() override;
};

#pragma once

#include "HrFSMEntity.h"
#include <iostream>

class HrFSMStateMachine;

class HrMonsterHungryState;
class HrMonsterEatState;
class HrMonsterEscapeState;
class HrMonsterFightState;
class HrMonsterForagingState;
class HrMonsterPlayState;

class HrMonsterEntity : public HrFSMEntity
{
public:
	HrMonsterEntity();
	~HrMonsterEntity();

	enum ENUM_MONSTER_STATE
	{
		ENUM_STATE_HUNGRY,
		ENUM_STATE_EAT,
		ENUM_STATE_ESCAPE,
		ENUM_STATE_FIGHT,
		ENUM_STATE_FORAGING,
		ENUM_STATE_PLAY,
	};

	void InitMonsterEntityState();

	virtual void ChangeState(int nStateID) override;
	virtual void ChangeToPreviousState() override;
	virtual void Execute() override;

	bool IsHungry();
	bool Eat();
protected:
	std::shared_ptr<HrFSMStateMachine> m_pStateMachine;

	std::shared_ptr<HrMonsterHungryState> m_pStateHungry;
	std::shared_ptr<HrMonsterEatState> m_pStateEat;
	std::shared_ptr<HrMonsterEscapeState> m_pStateEscape;
	std::shared_ptr<HrMonsterFightState> m_pStateFight;
	std::shared_ptr<HrMonsterForagingState> m_pStateForaging;
	std::shared_ptr<HrMonsterPlayState> m_pStatePlay;

	//最大值100 低于10饥饿
	int m_nDegreeOfStarvation;
};

#include "stdafx.h"
#include "HrMonsterEntity.h"
#include "HrFSMStateMachine.h"
#include "HrMonsterState.h" 

HrMonsterEntity::HrMonsterEntity()
{

	m_nDegreeOfStarvation = 10;
}

HrMonsterEntity::~HrMonsterEntity()
{
}

void HrMonsterEntity::InitMonsterEntityState()
{
	m_pStateMachine = std::make_shared<HrFSMStateMachine>();

	m_pStateHungry = std::make_shared<HrMonsterHungryState>(std::dynamic_pointer_cast<HrMonsterEntity>(shared_from_this()));
	m_pStateEat = std::make_shared<HrMonsterEatState>(std::dynamic_pointer_cast<HrMonsterEntity>(shared_from_this()));
	m_pStateEscape = std::make_shared<HrMonsterEscapeState>(std::dynamic_pointer_cast<HrMonsterEntity>(shared_from_this()));
	m_pStateFight = std::make_shared<HrMonsterFightState>(std::dynamic_pointer_cast<HrMonsterEntity>(shared_from_this()));
	m_pStateForaging = std::make_shared<HrMonsterForagingState>(std::dynamic_pointer_cast<HrMonsterEntity>(shared_from_this()));
	m_pStatePlay = std::make_shared<HrMonsterPlayState>(std::dynamic_pointer_cast<HrMonsterEntity>(shared_from_this()));

	m_pStateMachine->SetOwnerEntiry(std::dynamic_pointer_cast<HrFSMEntity>(shared_from_this()));
	m_pStateMachine->SetCurrentState(m_pStatePlay);
	m_pStateMachine->SetPreviousState(m_pStatePlay);

}

void HrMonsterEntity::ChangeState(int nStateID)
{
	switch (nStateID)
	{
	case ENUM_STATE_HUNGRY:
		m_pStateMachine->ChangeState(m_pStateHungry);
		break;
	case ENUM_STATE_EAT:
		m_pStateMachine->ChangeState(m_pStateEat);
		break;
	case ENUM_STATE_ESCAPE:
		m_pStateMachine->ChangeState(m_pStateEscape);
		break;
	case ENUM_STATE_FIGHT:
		m_pStateMachine->ChangeState(m_pStateFight);
		break;
	case ENUM_STATE_FORAGING:
		m_pStateMachine->ChangeState(m_pStateForaging);
		break;
	case ENUM_STATE_PLAY:
		m_pStateMachine->ChangeState(m_pStatePlay);
		break;
	default:
		std::cout << " Error State " << std::endl;
		break;
	}
}

void HrMonsterEntity::ChangeToPreviousState()
{
	m_pStateMachine->ChangeToPreviousState();
}

void HrMonsterEntity::Execute()
{
	--m_nDegreeOfStarvation;
	if (m_nDegreeOfStarvation < 0)
	{
		m_nDegreeOfStarvation = 0;
	}

	m_pStateMachine->Update();
}

bool HrMonsterEntity::IsHungry()
{
	return m_nDegreeOfStarvation <= 5;
}

bool HrMonsterEntity::Eat()
{
	return (m_nDegreeOfStarvation += 2 ) >= 10;
}


这样就实现了一个非常简单的状态机,各个状态之间的转换逻辑还非常简单,具体的转换要根据具体的需求来实现。具体的实现我觉得不必死板,只要保持着设计模式的思维,结构的改动要根据具体的需求。在之前的怒海项目中,我另外加入了一个全局状态来控制一个更大范围的状态,比如无敌状态,在无敌的时候各个状态会有另外的一系列行为。加入把无敌状态也对等封装的话需要封装更多的状态,增加了逻辑复杂度。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity有限状态机(FSM)是一种流程控制方法,它将一个对象或一个系统划分为多个状态,并且根据一定的条件和规则来决定何时转换到另一个状态。FSM在游戏开发非常常见,因为游戏有许多对象都需要根据不同的条件和规则进行状态转换,例如玩家角色、敌人AI、动画控制等。 在Unity,可以通过编写脚本来实现有限状态机。常见的实现方式是使用枚举类型来定义不同的状态,并在脚本编写转换条件和规则。例如,以下是一个简单的有限状态机脚本示例: ``` public enum PlayerState { Idle, Walk, Run, Jump, Attack } public class Player : MonoBehaviour { private PlayerState currentState; void Start() { currentState = PlayerState.Idle; } void Update() { switch(currentState) { case PlayerState.Idle: if(Input.GetKeyDown(KeyCode.Space)) { currentState = PlayerState.Jump; } else if(Input.GetAxis("Horizontal") != 0) { currentState = PlayerState.Walk; } break; case PlayerState.Walk: if(Input.GetKeyDown(KeyCode.Space)) { currentState = PlayerState.Jump; } else if(Input.GetAxis("Horizontal") == 0) { currentState = PlayerState.Idle; } else if(Input.GetKey(KeyCode.LeftShift)) { currentState = PlayerState.Run; } break; case PlayerState.Run: if(Input.GetKeyDown(KeyCode.Space)) { currentState = PlayerState.Jump; } else if(Input.GetAxis("Horizontal") == 0) { currentState = PlayerState.Idle; } else if(!Input.GetKey(KeyCode.LeftShift)) { currentState = PlayerState.Walk; } break; case PlayerState.Jump: if(transform.position.y < 0) { currentState = PlayerState.Idle; } break; } } } ``` 在这个例子,定义了五种不同的玩家状态:Idle、Walk、Run、Jump和Attack。在Start方法,将当前状态设置为Idle。在Update方法,根据当前状态不同的转换条件和规则,决定何时转换到另一个状态。例如,如果当前状态为Idle,并且玩家按下了空格键,就会转换到Jump状态。如果当前状态为Walk,并且玩家没有按下左Shift键,就会转换到Idle状态。 通过这种方式,可以轻松实现复杂的状态转换逻辑,从而使游戏对象在不同的状态下具有不同的行为和动画效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值