对象池模式
使用场景:
1)需要频繁创建和销毁对象时,用来避免内存碎片。
2)每个对象都封装了像数据库或网络连接这样的昂贵资源。
/*---------------------------------对象池模式-----------------------------
*/
// -------------------------------Demo-----------------------------//、
// 粒子对象
class Particle
{
private:
//
union
{
// 用一个结构体live包含粒子在使用时需要访问的属性
struct
{
double _x, _y;
double _xVel, _yVel;
} live;
// 一个指向下一个粒子的指针
Particle *next;
} _state;
// 用粒子的存活时间来判断是否正在使用
int _lifetime;
public:
// 构造粒子时,默认"不使用"
Particle() : _lifetime(0) {}
// 初始化,可以激活粒子,使其进入“使用”状态
void init(double x, double y, double xVel, double yVel, int lifetime)
{
_state.live._x = x;
_state.live._y = y;
_state.live._xVel = xVel;
_state.live._yVel = yVel;
_lifetime = lifetime;
}
// 用粒子的存活时间来判断是否正在使用
bool isUse() const { return _lifetime > 0; }
// 每帧调用一次animate()
bool animate()
{
// 当粒子“未激活”即空闲时,不执行状态更新,返回false
if (!isUse())
return false;
// lifetime应该减去每帧花费的现实时间
_lifetime--;
// 模拟粒子的运动
_state.live._x += _state.live._xVel;
_state.live._y += _state.live._yVel;
// 当粒子生命结束时,即正好空闲,返回true
return _lifetime == 0;
}
// 链表的API,get和set-->*next
Particle *getNext() const { return _state.next; }
void setNext(Particle *next) { _state.next = next; }
};
// 对象池
class ParticlePool
{
private:
static const int POOL_SIZE = 100;
// 用数组作为容器
Particle _container[POOL_SIZE];
Particle *_firstAvailableParticle;
public:
// 在初始化对象池的时候,设置好链表的连接
ParticlePool()
{
// 设置第一个空闲粒子
_firstAvailableParticle = &_container[0];
// 让每个粒子指向下一个,形成链表
for (int i = 0; i < POOL_SIZE - 1; i++)
_container[i].setNext(&_container[i + 1]);
// 设置链表最后一个节点指向null
_container[POOL_SIZE - 1].setNext(nullptr);
}
// create()不能写成传进来一个Particle&,因为这样的话不能控制对象在哪块内存生成
void create(double x, double y, double xVel, double yVel, int lifetime)
{
// 保证池没有满,即还有空闲粒子没被使用
assert(_firstAvailableParticle != nullptr);
// 找到一个空闲粒子
Particle *newParticle = _firstAvailableParticle;
// 初始化该粒子
newParticle->init(x, y, xVel, yVel, lifetime);
// 设置下一个空闲粒子的指针
_firstAvailableParticle = newParticle->getNext();
}
// 池内对象轮流调用一次animate()
void animate()
{
for (auto &&i : _container)
{
// 对象调用完一次animate()后,若返回ture,代表其正好到达空闲状态
if (i.animate())
{
// 将处于空闲状态的粒子加入链表的头部
i.setNext(_firstAvailableParticle);
// 让头节点指针(_firstAvailableParticle)指向这个新加入的节点
_firstAvailableParticle = &i;
}
}
}
};
游戏循环
游戏程序特有的模式。
这里将Unity的游戏循环作为例子。
https://gafferongames.com/post/fix_your_timestep/
http://www.koonsolo.com/news/dewitters-gameloop/
http://docs.unity3d.com/Manual/ExecutionOrder.html
组件模式
在使用并发编程的现代游戏中,将游戏分割到线程的一个通用的方法就是通过领域划分,比如在
第一个核上运行AI,在第二个播放声音,在第三个上渲染等等。因此需要解耦各个领域。
现在的软件设计趋势是用组件代替继承,不是让两个类继承一个类来分享属性,而是让他们拥有
同一个类的实例。
当有一个涉及控制多个领域的类出现,并且这多个领域相互隔绝时,使用组件模式。
(Unity就使用组件模式,一个GameObject通过添加组件来完善其功能)
/*---------------------------------组件模式-----------------------------
*/
// -------------------------------Demo-----------------------------//
// GameObject容器类
class GameObject
{
private:
InputComponent *_inputComp;
PhysicsComponent *_physicsComp;
GraphicsComponent *_graphicsComp;
public:
int _velocity;
int _xPos, _yPos;
/* 这里可以考虑结合对象池模式,将Component抽象成基类接口,输入组件、图
像组件等都实现这个接口,然后再GameObject里创建一个Component指针数组,
把各种接口放进这个对象池里。如何找到特定组件:来个模板方法,类似Unity的
GetComponent<T>()(可能并不太好?需要遍历数组才能找到)【动态添加组件】
如果是现在这种硬编码的方式,就需要将可能用到的组件全部先放在Gameobject类
中,需要用到的给指针,用不到的给null,找到特定组件倒是简单了。【静态添加组
件】
*/
// 用组件构造对象,没有这个组件就设为null
GameObject(InputComponent *inputComp = nullptr,
PhysicsComponent *physicsComp = nullptr,
GraphicsComponent *graphicsComp = nullptr)
: _inputComp(inputComp),
_physicsComp(physicsComp),
_graphicsComp(graphicsComp)
{
}
void update()
{
// 没有检测空指针,懒得写了
_inputComp->update();
_physicsComp->update();
_graphicsComp->update();
}
};
// 抽象基类接口
class Component
{
public:
virtual void update() = 0;
virtual ~Component() {}
};
// 接下来是三个组件的实现
class InputComponent : public Component
{
public:
virtual void update() override{
// 实现
};
};
class PhysicsComponent : public Component
{
public:
virtual void update() override{
// 实现
};
};
class GraphicsComponent : public Component
{
public:
virtual void update() override{
// 实现
};
};