状态模式
对于一个对象,用许多个flag去判断他处于什么状态,然后再使用if条件分支来执行对应
的行为,这听起来不是很明智。因此,我们可以借助状态机来合理的描述对象的行为和其变化。
1、将状态定义为接口,对于每个状态,让他实现接口
2、在状态中定义转移状态的方法(通过条件触发转移,if分支,switch分支都可)
3、在对象的属性中添加一个指针,指向现在的状态,添加状态转移方法(该方法调用指向的
状态实例的方法)。
通过这些,实现了将主对象的输入,委托给状态机处理。
注:一个简略的状态模式,可以由switch分支实现,不需要类,将各种状态集合为一个枚举类
,switch这个枚举类的对象,通过字段来转移状态。
/*---------------------------------状态模式-----------------------------
*/
// -------------------------------Demo-----------------------------//
// 状态接口(基类)
class State
// 注:不将State作为基类,把某个具体状态作为基类,这就是分层状态机
{
public:
// 静态的状态实例,用于在转移状态时,供状态指针指向这里
static StandingState standing;
static JumpingState jumping;
virtual void stateTransfer(gameObject gameObject, Input input) = 0;
};
// 具体状态STANDING
class StandingState : public State
{
public:
virtual void stateTransfer(gameObject gameObject, Input input) override
{
// 从Standing转换为Jumping
if (input == J)
{
// do sth,如更改贴图啥的
gameObject._state = State::jumping;
}
// else if 更多状态的转换
}
};
// 具体状态JUMPING
class JumpingState : public State
{
public:
virtual void stateTransfer(gameObject gameObject, Input input) override
{
// 从Jumping转换为Standing
if (input == S)
{
// do sth,如更改贴图啥的
gameObject._state = State::standing;
}
// else if 更多状态的转换
}
};
// 游戏对象
class GameObject
{
private:
// 状态指针,指向当前状态
State *_state;
// 一些改进:1、将状态指针变成一个指针栈,这样就可以记录历史状态,这叫下推自动机
// 2、构造两个指针变量,来指向不同的状态机,比如一个状态机决定移动方式,另一个决定是否攻击
public:
virtual void stateTransfer(Input input)
{
// 直接调用指向的状态对象的状态转移方法即可
_state->stateTransfer(*this, input);
}
// 注:可以对更改贴图的代码解耦,状态转移只负责转移状态,更改贴图的代码通过读取状态来显示贴图
};
扩展:一个用switch简化的状态模式
enum State
{
STATE_STANDING,
STATE_JUMPING
};
class gameObject
{
private:
State _state;
public:
void stateTransfer(Input input)
{
switch (_state)
{
case STATE_STANDING:
if (input == J)
{
// do sth
_state = STATE_JUMPING;
}
break;
case STATE_JUMPING:
if (input == S)
{
// do sth
_state = STATE_STANDING;
}
break;
}
}
};
脏位模式
在游戏中,场景图是分层的,也就是说场景中的物体会绑定在一个父物体上。物体拥有绝对位置
和相对位置,位置即“变换”,移动父物体时,子物体也要移动。渲染是以世界变换为参数渲染的,
因此需要将相对位置转为绝对位置。
举个例子:场景里有4层,第一层是世界,船这个对象在世界上移动,海盗在船上移动,鹦鹉在海
盗上移动。因此,船的本地变换就是世界变换,海盗的世界变换=海盗的相对变换*船的相对变换,
鹦鹉的世界变换=鹦鹉的相对变换*海盗的相对变换*船的相对变换。
如果这三个对象都需要在某一帧内移动,则船的相对变换要计算3次,海盗的要计算2次,对于这
种情况,我们可以把物体的世界变换缓存起来,如果物体不变换,那么我们就使用这个缓存的变
换即可。这就是脏位模式。
/*---------------------------------脏位模式-----------------------------
*/
// -------------------------------Demo-----------------------------//
class Transform
{
public:
// 原点坐标
static Transform orgin();
// combine可以通过父节点的世界变换得到自己的世界变换,其实就是矩阵乘法
Transform combine(const Transform &other);
};
class Object
{
private:
// 相对变换
Transform _localTransform;
// 绝对变换
Transform _worldTransform;
// 脏标识,默认为true,因为
bool _isDirty = true;
// 子对象数组
Object *_children[10];
public:
// 构造方法,默认相对变换在相对的“原点”
Object() : _localTransform(Transform::orgin()) {}
// 计算这一帧所有物体的世界坐标
void calWorldTransform(Transform &parent, bool isDirty)
{
// 或上父物体的脏标识,父物体的继承链上有一个脏标识,isDirty = True
isDirty |= _isDirty;
// 如果脏标识为true,说明物体的绝对位置和上次不一样
if (_isDirty)
{
// 更新世界坐标
_worldTransform = _localTransform.combine(parent);
_isDirty = false;
}
else // 此时脏标识为False,使用老的世界坐标
// 遍历子物体列表,计算他们的世界坐标
for (auto &&i : _children)
i->calWorldTransform(_worldTransform, isDirty);
}
// 改变坐标
void setTransform(Transform &local)
{
_localTransform = local;
_isDirty = true;
}
// 在指定的世界坐标处渲染物体
void render(Transform &transform) {}
};
对于游戏引擎来说,对某个对象Object调用setTransform(), 会改变他的脏标识,
同时,引擎每帧调用calWorldTransform()来计算各个物体的实时坐标来渲染
好了,暂时就总结到这里,以后有时间会继续完善一些代码部分。本人只是初学,若有大佬指正,不胜感激。
这本书的pdf放在这里:
链接:https://pan.baidu.com/s/1OFDVrZcZqKSAo77HfDvuYA?pwd=7xt3
提取码:7xt3
有需要的自取