游戏框架尽量用接口,将功能解耦。libgdx就是如此。
一年多前,曾经写过一篇关于Game Engine Framework的文章,当时基本上是为了巩固并加深对framework的理解。最近又做了一些关于framework的工作,对于framework的实现方式又有了些新的认识。虽然我现在做的已经完全不是game了,不过方式对于game也同样适用。
这篇文章主要希望通过一些示例性的C++代码介绍game framework的两种实现方式。首先,我还是搬出一年多前的那篇文章里的game流程图,以下的一些代码也主要基于这张图实现。对于图的细节在这里不再赘述,可以再去翻看之前的那篇文章。
1.通过继承
这是一种最传统的方式了,之前我一直使用这种方式。基本上是提供一个基类,基类封装并决定了整个程序控制流,同时基于该控制流,基类提供了一系列的接口(在C++里是虚函数),以供继承类override,并实现定制化的行为。就像下面的这个Game类,run函数决定了整个程序的执行逻辑,同时initialize,update,render等非纯虚函数则提供了接口以供用户定制并实现具体Game想要的行为。
1 | // 通过继承来实现的framework |
2 | class Game |
3 | { |
4 | public : |
5 | void run() |
6 | { |
7 | m_isQuit = false ; |
8 | initialize(); |
9 | while ( !m_isQuit ) |
10 | { |
11 | handleInput(); |
12 | update(); |
13 | render(); |
14 | } |
15 | destroy(); |
16 | } |
17 | |
18 | protected : |
19 | virtual void initialize() {} |
20 | virtual void update() {} |
21 | virtual void render() {} |
22 | virtual void destroy() {} |
23 | virtual void onMouse( Mouse mouse ) {} |
24 | virtual void onKeyboard( Keyboard keyboard ) {} |
25 | |
26 | private : |
27 | void handleInput() |
28 | { |
29 | Event e; |
30 | while ( popEvent( &e ) ) |
31 | { |
32 | switch ( e.type ) |
33 | { |
34 | case EVENT_QUIT: |
35 | m_isQuit = true ; |
36 | break ; |
37 | case EVENT_MOUSE; |
38 | onMouse( e.mouse ); |
39 | break ; |
40 | case EVENT_KEYBOARD: |
41 | onKeyboard( e.keyboard ); |
42 | break ; |
43 | default : |
44 | break ; |
45 | } |
46 | } |
47 | } |
48 | |
49 | private : |
50 | bool m_isQuit; |
51 | }; |
在上面的代码中,我把实现全部写在了类的定义头文件中,在这里只是为了省事,在现实中你最好还是分开在.h和.cpp文件中。而且在这里,Game类还可以做的更多,我这里只是为了说明实现方式,所以依照前面的流程图而尽量让它简单。另外,不要纠结这段代码中的Event,Mouse,Keyboard等几个类和popEvent方法,它们只是我为了将故事说得更圆满一点而假象出来的几个类,我想你应该能猜到它们是用来干嘛的。
基于此framework,一个具体的游戏只需要重写这些虚函数就可以了,像下面的这些代码。
1 | class ConcreteGame : public Game |
2 | { |
3 | private : |
4 | void initialize() |
5 | { |
6 | // do some real initialization |
7 | } |
8 | |
9 | void update() |
10 | { |
11 | // do some real update |
12 | } |
13 | |
14 | void render() |
15 | { |
16 | // do some real rendering |
17 | } |
18 |
19 | void destroy() |
20 | { |
21 | // do some real destroy |
22 | } |
23 | |
24 | void onMouse( Mouse mouse ) |
25 | { |
26 | // handle mouse event |
27 | } |
28 | |
29 | void onKeyboard( Keyboard keyboard ) |
30 | { |
31 | // handle keyboard event |
32 | } |
33 | }; |
34 |
35 | int main() |
36 | { |
37 | ConcreteGame mygame; |
38 | mygame.run(); |
39 | return 0; |
40 | } |
2.通过Delegation模式
前一种方法有一个缺点,就是将程序的控制流和具体行为紧耦合在了一起,而且还必须使用继承,不易于扩展。现代软件设计的一些方法告诉我们,要尽量使用接口,且尽量使用组合而非继承。Delegation模式就可以帮我们达到这一目的。
何为Delegation模式,wiki上的解释一语中的:
Delegation is the simple yet powerful concept of handing a task over to another part of the program.
Delegation将一些task委托给程序的另外一部分来处理,以达到了行为使用者和具体行为的松耦合。
以下是通过Delegation模式重新实现的framework。
1 | // 通过Delegation模式来实现的framework |
2 | class GameDelegation |
3 | { |
4 | public : |
5 | virtual void initialize() {} |
6 | virtual void update() {} |
7 | virtual void render() {} |
8 | virtual void destroy() {} |
9 | virtual void onMouse( Mouse mouse ) {} |
10 | virtual void onKeyboard( Keyboard keyboard ) {} |
11 | }; |
12 |
13 | class Game |
14 | { |
15 | public : |
16 | Game( GameDelegation *gameDelegation ) |
17 | { |
18 | m_gameDelegation = gameDelegation; |
19 | } |
20 | |
21 | void run() |
22 | { |
23 | m_isQuit = false ; |
24 | if ( m_gameDelegation == NULL ) |
25 | { |
26 | return ; |
27 | } |
28 | |
29 | m_gameDelegation->initialize(); |
30 | while ( !m_isQuit ) |
31 | { |
32 | handleInput(); |
33 | m_gameDelegation->update(); |
34 | m_gameDelegation->render(); |
35 | } |
36 | } |
37 | |
38 | private : |
39 | void handleInput() |
40 | { |
41 | Event e; |
42 | while ( popEvent( &e ) ) |
43 | { |
44 | switch ( e.type ) |
45 | { |
46 | case EVENT_QUIT: |
47 | m_isQuit = true ; |
48 | break ; |
49 | case EVENT_MOUSE; |
50 | m_gameDelegation->onMouse( e.mouse ); |
51 | break ; |
52 | case EVENT_KEYBOARD: |
53 | m_gameDelegation->onKeyboard( e.keyboard ); |
54 | break ; |
55 | default : |
56 | break ; |
57 | } |
58 | } |
59 | } |
60 | |
61 | private : |
62 | bool m_isQuit; |
63 | GameDelegation *m_gameDelegation; |
64 | }; |
基于此framework,当需要具体实现一个游戏的时候,只需要实现GameDelegation接口即可,然后将Game类的GameDelegation设置为你所实现的具体的ConcreteGameDelegation类,代码如下。
1 | class ConcreteGameDelegation : public GameDelegation |
2 | { |
3 | public : |
4 | void initialize() |
5 | { |
6 | // do some real initialization |
7 | } |
8 | |
9 | void update() |
10 | { |
11 | // do some real update |
12 | } |
13 | |
14 | void render() |
15 | { |
16 | // do some real rendering |
17 | } |
18 |
19 | void destroy() |
20 | { |
21 | // do some real destroy |
22 | } |
23 | |
24 | void onMouse( Mouse mouse ) |
25 | { |
26 | // handle mouse event |
27 | } |
28 | |
29 | void onKeyboard( Keyboard keyboard ) |
30 | { |
31 | // handle keyboard event |
32 | } |
33 | }; |
34 |
35 | int main() |
36 | { |
37 | ConcreteGameDelegation myGameDelegation; |
38 | Game mygame( &myGameDelegation ); |
39 | mygame.run(); |
40 | return 0; |
41 | } |
最近我正好做了一些iOS上开发的研究,发现Delegation在iOS框架中被普遍使用。比如,iOS中的每个应用程序对应的是一个UIApplication类,为每一个UIApplication,开发人员必须要实现一个特定的UIApplicationDelegate,并将它指定给当前的应用程序(在main函数中通过UIApplicationMain函数指定,或者是在nib文件中绑定)。在这个UIApplicationDelegate类中,开发人员就需要重写诸如didFinishLaunchingWithOptions,applicationWillTerminate这样的方法,就类似与上面game framework中的initialize,destroy等方法。当然iOS框架要复杂很多,它还用到其它一系列的设计模式,有空研究些这样设计是非常有趣的。
图片来自于这篇苹果官方关于iOS中delegation的介绍:http://developer.apple.com/library/ios/#documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html