序
在线阅读地址:
命令模式 · Design Patterns Revisited · 游戏设计模式 (tkchu.me)
参考文章:
GameDesignPattern_U3D_Version/Assets/002FlyweightPattern at master · TYJia/GameDesignPattern_U3D_Version · GitHub
看到了没见过的观点:
-
抽象和解耦让扩展代码更快更容易,但除非确信需要灵活性,否则不要在这上面浪费时间。
-
在整个开发周期中为性能考虑并做好设计,但是尽可能推迟那些底层的,基于假设的优化,那会锁死代码。
相信我,发布前两个月不是开始思考“游戏运行只有1FPS”这种问题的时候。
-
快速地探索游戏的设计空间,但不要跑得太快,在身后留下烂摊子。毕竟你总得回来打扫。
-
如果打算抛弃这段代码,就不要尝试将其写完美。摇滚明星将旅店房间弄得一团糟,因为他们知道明天就走人了。
-
但最重要的是,如果你想要做出让人享受的东西,那就享受做它的过程。
不止是要学,还要知道为什么要学,学了不仅是为了用,也是为了不用
重访设计模式
我认为有些模式被过度使用了(单例模式), 而另一些被冷落了(命令模式)。 有些模式在这里是因为我想探索其在游戏上的特殊应用(享元模式和观察者模式)。 最后,我认为看看有些模式在更广的编程领域是如何运用的是很有趣的(原型模式和状态模式)。
命令模式
将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作
当你有接口只包含一个没有返回值的方法时,很可能你可以使用命令模式
一些代码(输入控制器或者AI)产生一系列命令放入流中。 另一些代码(调度器或者角色自身)调用并消耗命令。 通过在中间加入队列,我们解耦了消费者和生产者。
缺点:可能会产生大量的类,从而浪费内存
拓展:可以用享元模式代替大量的类
C++虚函数,纯虚函数与抽象类
-
1、纯虚函数声明如下: virtual void funtion1()=0; 纯虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。
-
2、虚函数声明如下:virtual ReturnType FunctionName(Parameter) 虚函数必须实现,如果不实现,编译器将报错,错误提示为:
error LNK****: unresolved external symbol "public: virtual void __thiscall ClassName::virtualFunctionName(void)"
-
3、对于虚函数来说,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。
-
4、实现了纯虚函数的子类,该纯虚函数在子类中就变成了虚函数,子类的子类即孙子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。
-
5、虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。
-
6、在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的。
-
7、友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚拟函数。但可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题。
-
8、析构函数应当是虚函数,将调用相应对象类型的析构函数,因此,如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数。
有纯虚函数的类是抽象类,不能生成对象,只能派生。他派生的类的纯虚函数没有被改写,那么,它的派生类还是个抽象类。
定义纯虚函数就是为了让基类不可实例化化,因为实例化这样的抽象数据结构本身并没有意义,或者给出实现也没有意义。
实际上我个人认为纯虚函数的引入,是出于两个目的:
- 1、为了安全,因为避免任何需要明确但是因为不小心而导致的未知的结果,提醒子类去做应做的实现。
- 2、为了效率,不是程序执行的效率,而是为了编码的效率。
享元模式
比如森林中所有的相同的树,就可以将相同的部分分离出来放到一个类中共享,节约了重复的空间,这样每个树的实例都只需要一个这个共享类的引用就可以了
当共享对象没有有效定义的实体时,使用这种模式就不那么明显,使用它就越发显得精明
通过分享对象来节约内存的这种优化,不应该影响到应用的显性行为,因此,享元对象几乎总是不可变的
当有一些东西是固定的,或者说是上下文无关的,此时就可以使用享元模式
比如一个地形数组,可以不用每个块都储存对应的类,可以从中抽出地形类,然后写草地实例,河流实例,丘陵实例,然后在数组中存储指向实例的指针
class World
{
public:
World()
: grassTerrain_(1, false, GRASS_TEXTURE),
hillTerrain_(3, false, HILL_TEXTURE),
riverTerrain_(2, true, RIVER_TEXTURE)
{}
private:
Terrain grassTerrain_;
Terrain hillTerrain_;
Terrain riverTerrain_;
// 其他代码……
};
指针通常不必枚举大
性能如何?
通过解引用指针获取地形需要一次间接跳转。 为了获得移动开销这样的地形数据,你首先需要跟着网格中的指针找到地形对象, 然后再找到移动开销。跟踪这样的指针会导致缓存不命中,降低运行速度。
但是产生一堆对象也会拖慢速度(我的理解
参见
-
在区块的例子中,我们只是为每种地形创建一个实例然后存储在
World
中。 这也许能更好找到和重用这些实例。 但是在多数情况下,你不会在一开始就创建所有享元。如果你不能预料哪些是实际上需要的,最好在需要时才创建。 为了保持共享的优势,当你需要一个时,首先看看是否已经创建了一个相同的实例。 如果确实如此,那么只需返回那个实例。
这通常意味需要将构造函数封装在查询对象是否存在的接口之后。 像这样隐藏构造指令是工厂方法的一个例子。
-
为了返回一个早先创建的享元,需要追踪那些已经实例化的对象池。 正如其名,这意味着对象池是存储它们的好地方。
-
当使用状态模式时, 经常会出现一些没有任何特定字段的“状态对象”。 这个状态的标识和方法都很有用。 在这种情况下,你可以使用这个模式,然后在不同的状态机上使用相同的对象实例。