Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion 01-Introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
有人可能会问,市面上已经存在各种各样的游戏编程类的书。为什么还要写一本?

我看过的大多数的游戏编程书籍,无非就是下面两种类型:

- 介绍特定领域的书籍:这类书的写作范围一般比较狭小,通常专注于给你在游戏开发某些特定方面的深层次的指导。他们会教你比如:3D图形,实时渲染,物理世界模拟,人工智能或者音频处理方面的知识。这些都是从事游戏开发的工程师在职业发展阶段会碰到的专业技能。
- 全引擎类的书籍:不同的是,这类书籍试着跨度整个游戏引擎的不同组成部分。他们通常是面向构建一个完整的游戏引擎,来适合于特定类型的游戏,经常举的例子是三维下的第一人称射击游戏。

Expand Down Expand Up @@ -65,6 +65,44 @@

最后一个章节是本书的重中之重。提出了13个我认为很有用的设计模式。他们被安排到4个类别:序列模式,行为模式,解耦模式和优化模式。

- **含义**部分给出了这种设计模式的一个简要描述并告诉你这种模式可以解决哪种问题。把这部分放在前面可以帮助你迅速找到可以拓展你的知识的地方。

- **示例部分**提供了一个将要应用这种设计模式的一个例子。与具体的算法不同,通常,如果不把设计模式用在某个特定的问题上,模式的说明将会很抽象。教授一种设计模式而不用例子来说明类似于教授烘培而不提生面团。这一部分提供了稍后用来烘培的面团。

- **模式**部分脱离前面所给的例子提取了这个设计模式的精华部分。如果你想要一个枯燥的教科书般的描述的话,就是这一部分了。如果你已经很熟悉这种设计模式了,那么这一部分可以帮助你复习以确保你不会忘记。

- 目前为止,我们仅就一个例子对这个设计模式进行阐述,但是,你怎么知道这种设计模式对解决你的问题会很有效呢?而**用武之地**部分会告诉你什么时候有效,什么时候最好避免使用它。**铭记于心**部分则指出了使用这种设计模式的后果和风险。

- 像我,如果你需要一个具体的例子进行学习**实例代码**部分就是这么一个部分了,通过一步一步地实现这种设计模式,你可以确切地看到模式是如何工作的。

- 设计模式与纯粹的算法不同,它是开放的。每一次你使用某种设计模式,你都可以用不同的方式实现它。下一个部分,**设计与决策**会就这一方面进行讨论并向你展示使用这种模式时可以做某些的选择。

- 为了圆满结束这一部分,有一个简单的部分——**更多请参阅**。它向你展示了与这个设计模式相关的东西并指出了一些现实世界中使用这种设计模式的开源代码。


# 关于示例代码

在本书中示例代码是用C++写的,但是这并不意味着设计模式只能对这种程序设计语言(C++)有效,也不是说用C++比其他语言好。几乎任何语言都可以很好地工作,但是一些设计模式会假设你的语言中有“对象”和“类”。

我选择C++有几个理由。首先在商业游戏中最流行用C++(译者:中国大概是Java?),它是行业语言。更重要的是C/C++式语法在诸如Java,C#,Javascript和其他语言中被广泛应用。在这种状况下,尽管你不了解C++,只要你稍微下点功夫你就可以理解示例代码了。

这本书的目标并不是教你C++,所以示例代码就写的尽可能的简单,这些代码并不代表优秀的C++编程风格。去读出代码所要表现出的想法,而不是去阅读代码是怎样表现这种思想的(译者:大概也就是代码无所谓,思想最重要)。

特别地,示例代码并不是用“现代”风格——C++11或更高——编写的,也没有用到标准库,极少用到模板。希望通过这样做可以让代码尽可能的简单,这将会对那些使用C,Object-C,Java或其他程序设计语言的人更亲近。

为避免在代码上浪费空间(译者:这里指的是纸质书籍的空间浪费,网页上怎么可能有这种问题?!),你已经看到过或是与这种设计模式无关的代码都会被省略,被省略的部分有省略号,还会告诉你省去的代码到哪里去找。

试想这里有一个做某个工作并返回一个值的函数。如果解释设计模式时只用考虑到返回值的时候而不是做了什么的时候,示例代码就会像下面一样:

```cpp
bool update()
{
// Do work...
return isDone();
}
```


# 何去何从#

模式是一个在软件开发过程中不断变化,不断扩大的部分。这本书的是从Gang of Four经典理论的分享与讨论中开始的,并且这个讨论、分享的过程还会在本书之外继续进行。
Expand Down
35 changes: 18 additions & 17 deletions 02.1-Command.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ Tags: 游戏编程 设计模式 游戏开发
我认为,用这句话来诠释Command模式,远比上面那些他们选取的句子更恰当。
当然了,不管怎么样的诠释,听起来总是很抽象,很朦胧。所以,我加入一些具体的例子,来补充解释Command模式。

#实例一:输入配置
#输入配置
每个游戏都有一部分代码是专门用来读取用户输入的——比如:按下button,键盘事件,点击鼠标等等。这些输入都需要被记录下来,然后转变成游戏中有意义的行为。

![此处输入图片的描述][1]

下面是一个简单的实现:
```
```cpp
void InputHandler::handleInput()
{
if (isPressed(BUTTON_X)) jump();
Expand All @@ -48,7 +48,7 @@ void InputHandler::handleInput()
为了能过实现这样的功能,我们需要将 *jump()* 和 *fireGun()* 之间的直接调用,改成一种能够置换的方式。所以,我们需要使用一个对象来表示游戏中的一个行为,这样,Command模式就来了。

我们定义一个基类来代表一个可以触发的游戏命令:
```
```cpp
class Command
{
public:
Expand All @@ -57,7 +57,7 @@ public:
};
```
然后,我们为不同的游戏行为定义相应的子类:
```
```cpp
class JumpCommand : public Command
{
public:
Expand All @@ -69,9 +69,10 @@ class FireCommand : public Command
public:
virtual void execute() { fireGun(); }
};
// You get the idea...
```
在我们的InputHandler类中,我们为每一个按键都保存了一个指向Command对象的指针:
```
```cpp
class InputHandler
{
public:
Expand All @@ -87,7 +88,7 @@ private:
};
```
现在的输入处理只是指向了相应的代理:
```
```cpp
void InputHandler::handleInput()
{
if (isPressed(BUTTON_X)) buttonX_->execute();
Expand All @@ -101,10 +102,10 @@ void InputHandler::handleInput()

以上就是Command模式的一个简单直观的例子了,如果你觉得自己已经掌握Command的真谛,那么下面的部分你就可以随便看看,当做巩固了。

#实例二:控制演员
#控制演员
前面给出的例子中,我们的代码勉强可以达到目的,不过作用还是相当有限的。原因是,上面的例子有一个假设作为前提,就是他们假定了有一些全局函数,例如*jump()*,*fireGun()*等等,这些函数能够直接得到一个控制器,然后很轻松的像控制木偶一样去控制主角。
这样的假定大大地限制了Command的使用范围。因为,在这种情况下,只有主角能够使用JumpCommand进行跳跃。让我们取消这样的假设来打破这层限制。我们不让调用的函数自己去找控制对象,而是将控制对象作为参数传给它。
```
```cpp
class Command
{
public:
Expand All @@ -113,7 +114,7 @@ public:
};
```
上面的代码中,GameActor是我们的游戏对象类,代表了游戏世界中的一个演员。我们把它传递给execute函数,这样那些Command子类就可以条用相应的方法了。就像这样:
```
```cpp
class JumpCommand : public Command
{
public:
Expand All @@ -122,7 +123,7 @@ public:
actor.jump();
}
};
```
```
如果这个actor是玩家角色的一个引用,那么就可以根据玩家的输入准确的控制主角,这样就能实现跟第一个例子同样的效果了。与此同时,在Command跟Actor之间插入了一个层,这让我们有了一个更加灵活的能力:我们可以通过改变这个actor参数,让玩家能够控制游戏世界中的所有演员。

实际上,虽然这不是一个通常意义上的特性,不过倒是会时不时的被使用到。当目前为止,我们只留意了玩家控制主角,但是,那么游戏世界中的其他演员呢?他们是被游戏AI所驱动的。我们可以使用同样的Command模式,作为AI引擎和演员之间的借口。如此一来,AI代码只需要简单地抛出命令对象就可以了。
Expand All @@ -137,7 +138,7 @@ public:
最后的这个例子是Command模式最为著名的一个用法了。如果一个Command对象能够完成一件事,那么离他撤销这件事就不远了。撤销可以用在一些策略游戏中,你可以撤销一些你不喜欢或者后悔做的一些操作。在制作游戏的工具中,撤销也是一个非常必要的功能。让你游戏策划恨你的最佳方式,就是提供一个让他不能进行撤销操作的关卡编辑器。
如果没有Command模式,那么撤销将是一件非常困难的事情。一旦有了它,那就是小菜一碟了。如比,当我们要做一个单人回合制游戏,我们会希望能够加入一个撤销操作,这样玩家就可以把注意力放到策略上,而不用做过多没有必要的猜测了。
前面,我们已经使用了Command,非常方便地将用户的输入抽象化,因此玩家的每一步操作都已经封装到了Command之中。例如,玩家移动一个单位的代码如下:
```
```cpp
class MoveUnitCommand : public Command
{
public:
Expand All @@ -162,7 +163,7 @@ private:
这里强调另一种Command模式的实现方法,在某些情况下,就像我们前两个例子,一个Command就是一个可以被复用的的对象,它代表了一个操作。前面我们的输入处理是在一个单独的Command对象中进行的,任何时候,一旦玩家按了正确的按键,它相应的execute()函数就会被调用。

在这里,这些Command就更加具体了。他们代表了在某一个特定的时间做了某件事。也就是说,每一次玩家选择一次移动,输入处理代码就会产生一个Command实例。就像这样:
```
```cpp
Command* handleInput()
{
Unit* unit = getSelectedUnit();
Expand All @@ -185,7 +186,7 @@ Command* handleInput()
}
```
实际上,这些Command只使用一次的优点马上就会体现出来。为了使Command能够被撤销,我们需要定义另外一个需要各个Command都实现的方法:
```
```cpp
class Command
{
public:
Expand All @@ -195,7 +196,7 @@ public:
};
```
undo()方法会恢复被execute()方法改变过的游戏状态。下面代码是我们前面定义的Move Command加入了对撤销的支持:
```
```cpp
class MoveUnitCommand : public Command
{
public:
Expand Down Expand Up @@ -238,15 +239,15 @@ private:
当玩家撤销,我们就撤销当前的Command,并将当前的引用向后移动一位。当他们重做,我们就向前移动指针,并且执行Command。当他们在撤销后选择了一步新的操作,那当前Command之后的所有Command就会被销毁。

当我第一次在一个关卡编辑器里面实现后,我感觉自己简直是个天才。我对它如此的简单明了,如此的运行顺畅感到吃惊。Command规定了所有的数据修改都通过一个个Command进行。但是一旦你确定了这个规则,剩下的就很容易了。
# 优雅与功能弱化
# 优雅亦或有缺陷?
之前,我说Command与一级函数或者闭包非常像,但是前面每一个例子,我都使用了类来定义。如果你对函数式编程比较熟悉,你可能会疑惑,说好的函数呢?

我用这种方式写例子,是因为C++对一级函数支持很弱。函数指针没有状态,仿函数很奇葩,并且同样需要定义类。C++11的lambdas表达式用起来很不顺手,原因是内存管理需要手动进行。

这些不是说你在其他语言中不能在Command模式中使用函数。如果你使用的语言有真正的闭包,一定用起来!某种意义上说,Command模式就是一个没有闭包的语言模仿闭包的方式。

例如,如果我们用Javascript开发游戏,我们可以像这样创建一个单位的Command:
```
```Javascript
function makeMoveUnitCommand(unit, x, y) {
// This function here is the command object:
return function() {
Expand All @@ -255,7 +256,7 @@ function makeMoveUnitCommand(unit, x, y) {
}
```
我们可以使用一对闭包来添加对撤销的支持:
```
```Javascript
function makeMoveUnitCommand(unit, x, y) {
var xBefore, yBefore;
return {
Expand Down
Loading