《游戏编程模式》笔记
前言
本文无参考价值,主要记录博主在学习本书时,觉得有用的东西
不代表这些知识对你有用,也不代表没记录的东西对你没用,想学习请看上面的原文。
设计模式
状态机
有限状态机
层次状态机:部分状态可以通过继承,来处理通用的状态。比如在地面上按B,和在空中按B
下推自动机:用栈来存储状态的变化,比如从站立变成开火,结束开火时,出栈,变成站立
序列模式
双缓冲模式
通过两个帧缓冲实例控制数据的刷新。
第一个数据表示旧数据,用来给其他实例读取,比如gpu
第二个数据表示新数据,用来更新
当一帧结束之后,两个数据交换地址,继而往复。
这样可以保持数据刷新的连贯性,而不会因为多线程问题,导致数据刷新断层(比如屏幕刷新,一次性读取所有数据刷新,而不是读一个刷一个)
行为模式
类型对象
主要思维:将决定对象的关键数据,以引用(组件)的方式,集成到class里面。
比如所有怪物都是monster类,里面的类型、hp、数值等数据,都是组合进去随意修改的。这样新增类型就不用开新的子类,直接走表配置即可
何时使用:
在任何你需要定义不同“种”事物,但是语言自身的类型系统过于僵硬的时候使用该模式。
- 你不知道你后面还需要什么类型
- 想不改变代码或者重新编译就能修改或添加新类型。
解耦模式
事件队列
事件队列在队列中按先入先出的顺序存储一系列通知或请求。 发送通知时,将请求放入队列并返回。 处理请求的系统之后稍晚从队列中获取请求并处理。 这解耦了发送者和接收者,既静态又及时。
用于解耦【及时】
会陷于反馈系统环路中
同步消息会因为死循环崩溃,但是异步消息不会,要避免在处理事件的代码中发送事件
服务定位器
服务 类定义了一堆操作的抽象接口。 具体的服务提供者实现这个接口。 分离的服务定位器提供了通过查询获取服务的方法,同时隐藏了服务提供者的具体细节和定位它的过程。
- 具体使用:
- 1、日志装饰器:给audio加上日志,然后传入,而不必修改原本的audio类
class LoggedAudio : public Audio
{
public:
LoggedAudio(Audio &wrapped)
: wrapped_(wrapped)
{}
virtual void playSound(int soundID)
{
log("play sound");
wrapped_.playSound(soundID);
}
Audio &wrapped_;
}
优化模式
数据局部性
打包数据:在update操作中,将命中率更高的数据优先放在一起
void ParticleSystem::activateParticle(int index)
{
// 不应该已被激活!
assert(index >= numActive_);
// 将它和第一个未激活的粒子交换
Particle temp = particles_[numActive_];
particles_[numActive_] = particles_[index];
particles_[index] = temp;
// 现在多了一个激活粒子
numActive_++;
}
冷/热分割:将经常需要更新的数据放在一起,不怎么更新的数据放在另一边
脏标识模式
如果一组数据,计算过程代价昂贵,而且改动比较频繁的话,可以将计算和取值操作分离,即记录下所有计算操作,在取值操作时再一次性全量计算一遍。
如果计算消耗太大,可能会造成可见的卡顿,这样就需要隐藏在加载画面或者过场动画之后处理
对象池模式
主要解决频繁创建和删除对象,导致的内存碎片化的问题
在可见的事物上使用的比较多,比如游戏实体和视觉效果,也可在类似声音播放这样的非视觉化数据中使用
池容量优化:管理者拥有一系列池,池的块大小不相同。 当你申请分配一块,它会从合适块大小的池中取出一块,然后分配给你。
注:对象池在支持垃圾回收的系统中很少见,因为内存管理系统通常会为你处理这些碎片。
// 示例
class ParticlePool
{
private:
static const int POOL_SIZE = 100;
Particle particles_[POOL_SIZE];
};
对象的生命周期
传递所有权
只能存在一个,给别人的时候,自己就不再拥有。对应C++中的unique_ptr<T>
共享所有权
引用计数的方式,有引用就会存在,被遗忘时自动释放。对应C++中的shared_ptr<T>
永久所有权
永远存在,需要时获取。对应对象池模式