游戏开发中有限状态机

有限状态机在游戏开发中是经常用到的数学模型,虽然我做的是"万年捕鱼",但是有幸在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 
#include "HrFSMState.h"

class HrMonsterEntity;

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


	virtual void Enter() override;

	virtual void Execute() override;

	virtual void Exit() override;

protected:
	std::shared_ptr m_pStateOwnerEntity;

};

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

	virtual void Enter() override;

	virtual void Execute() override;

	virtual void Exit() override;

};

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

	virtual void Enter() override;

	virtual void Execute() override;

	virtual void Exit() override;
};

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

	virtual void Enter() override;

	virtual void Execute() override;

	virtual void Exit() override;
};

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

	virtual void Enter() override;

	virtual void Execute() override;

	virtual void Exit() override;
};

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

	virtual void Enter() override;

	virtual void Execute() override;

	virtual void Exit() override;
};

class HrMonsterForagingState : public HrMonsterState
{
public:
	HrMonsterForagingState(std::shared_ptr 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 

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

HrMonsterState::~HrMonsterState()
{
}

void HrMonsterState::Enter()
{

}

void HrMonsterState::Execute()
{

}

void HrMonsterState::Exit()
{

}

////////////////////// HungryState /////////////////////

HrMonsterHungryState::HrMonsterHungryState(std::shared_ptr 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 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 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 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 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 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 

class HrFSMEntity;
class HrFSMState;

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

	virtual void SetOwnerEntiry(std::shared_ptr pEntity);
	virtual void SetCurrentState(std::shared_ptr pState);
	virtual void SetPreviousState(std::shared_ptr pState);

	virtual void ChangeState(std::shared_ptr pState);
	virtual void ChangeToPreviousState();

	virtual void Update();

	virtual std::shared_ptr GetCurretnState(std::shared_ptr pState);
	virtual std::shared_ptr GetPreviousState(std::shared_ptr pState);
protected:
	std::shared_ptr m_pEntityOwer;

	std::shared_ptr m_pPreviousState;
	std::shared_ptr 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 pState) override;
	virtual void ChangeState(int nStateID) override;
	virtual void ChangeToPreviousState() override;

	virtual void Execute() override;
};

#pragma once

#include "HrFSMEntity.h"
#include 

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 m_pStateMachine;

	std::shared_ptr m_pStateHungry;
	std::shared_ptr m_pStateEat;
	std::shared_ptr m_pStateEscape;
	std::shared_ptr m_pStateFight;
	std::shared_ptr m_pStateForaging;
	std::shared_ptr 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();

	m_pStateHungry = std::make_shared(std::dynamic_pointer_cast(shared_from_this()));
	m_pStateEat = std::make_shared(std::dynamic_pointer_cast(shared_from_this()));
	m_pStateEscape = std::make_shared(std::dynamic_pointer_cast(shared_from_this()));
	m_pStateFight = std::make_shared(std::dynamic_pointer_cast(shared_from_this()));
	m_pStateForaging = std::make_shared(std::dynamic_pointer_cast(shared_from_this()));
	m_pStatePlay = std::make_shared(std::dynamic_pointer_cast(shared_from_this()));

	m_pStateMachine->SetOwnerEntiry(std::dynamic_pointer_cast(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;
}


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

你可能感兴趣的:(设计模式)