Skip to content

Latest commit

 

History

History
150 lines (109 loc) · 5.84 KB

File metadata and controls

150 lines (109 loc) · 5.84 KB

基础


1 导论

1.1 典型游戏团队的结构

  • 工程师
    • 运行时程序员
    • 工具程序员
  • 艺术家
    • 概念艺术家(concept artist)
    • 三维建模师(3D modeler)
    • 纹理艺术家(texture artist)
    • 灯光师(lighting artist)
    • 动画师(animator)
    • 动画捕捉演员(motion capture actor)
    • 音效设计师(sound designer)
    • 配音演员(voice actor)
    • 作曲家(composer)
  • 游戏设计师(game designer)
  • 制作人(producer)

1.3 游戏引擎是什么

大部分游戏引擎是针对特定游戏及特定硬件平台所精心制作及微调的. 就算是一些最通用的游戏引擎, 其实也只适合制作某类型游戏. 游戏引擎或中间件组件越通用, 在特定平台运行特定游戏的性能就越一般.

出现这种现象, 是因为设计高效的软件总是需要取舍

1.6 运行时引擎架构

如同所有软件系统, 游戏引擎也是以软件层(software layer)构建的. 通常上层依赖下层, 下层不依赖上层. 当下层依赖上层时, 称为循环依赖(circular dependency). 在任何软件中, 循环依赖都要极力避免, 不然会导致系统间复杂的耦合(coupling), 也会使软件难以测试, 并妨碍代码重用.

3 游戏软件工程基础

3.1 重温 C++ 及最佳实践

  • 菱形继承问题(diamond problem)

在菱形继承问题中, 一个派生类最终包含了两份祖父类. C++ 可以使用虚继承(virtual inheritance) 去掉重复祖父类的数据.

菱形继承

大多数 C++ 软件开发者都会完全避免使用多重继承, 或只容许有限制的使用. 常见的惯例是, 只容许从一个单继承层次结构中多重继承一些简单且无父的类. 这些类有时被称为嵌入类(mix-in class), 因为它可在类树中任何位置加入新功能.

嵌入类

  • 多态(polymorphism) 多态是一种语言特征, 允许采用单一共同接口操作一组不同类型的对象. 共同接口能使异质的(heterogeneous) 对象集合从使用接口的代码来看显得是同质的(homogeneous).

例如, 一个二维绘图程序要把一个形状绘制到屏幕上, 列表里有不同的形状. 绘出这个异质形状集合中的其中一个方法是, 按不同的形状类型, 用 switch 语句区分并执行不同的绘图指令.

void drawShapes(std::list<Shape*> shapes)
{
  std::list<Shape*>::iterator shapeItr = shapes.begin();
  std::list<Shape*>::iterator shapeEnd = shapes.end();

  for(; shapeItr != shapeEnd; ++shapeItr)
  {
    switch ((*shapeItr)->mType)
    {
      case CIRCLE:
        // 绘制图形
        break;
      case RECTANGLE:
        // 绘制矩形
        break;
      case TRIANGLE:
        // 绘制三角形
        break;
      // ......
    }
  }
}

这种方法的问题是, drawShapes() 函数需要知悉所有可以绘画的形状类型. 在简单例子里还好, 但随着代码量的增加和代码复杂度的提高, 在系统里新增形状类型变得越来越困难. 每当加入一个新类型系统, 必须搜索并修改所有"知悉"各种形状类型的代码.

解决方法是把类型的内容从大部分代码中隔离出来, 为了实施这个隔离, 可以把每种要支持的形状定义为类. 而所有这些类, 都继承自一个共同的基类 shape. 在基类 Shape 中可以定义名为 Draw() 的虚函数(C++ 语言的多态机制), 并在每个不同形状的类中, 以不同方式实现这个函数. 绘画时不需要"知悉"给予的是何种形状, 只需逐一简单的调用形状对象的 Draw() 函数.

struct Shape
{
  virtual void Draw() = 0;   //纯虚函数
};

struct Circle : public Shape
{
  virtual void Draw()
  {
    // 绘制图形
  }
};

struct Rectangle : public Shape
{
  virtual void Draw()
  {
    // 绘制矩形
  }
};

struct Triangle : public Shape
{
  virtual void Draw()
  {
    // 绘制三角形
  }
};

void drawShapes(std::list<Shape*> shapes)
{
  std::list<Shape*>::iterator shapeItr = shapes.begin();
  std::list<Shape*>::iterator shapeEnd = shapes.end();

  for(; shapeItr != shapeEnd; ++shapeItr)
  {
    (*shapeItr)->Draw();
  }
}
  • 合成(composition)及聚合(aggregation)

合成是指用一组互动的对象去完成高阶任务. 合成在类之间建立"有一个(has-a)" 和 "用一个(use-a)" 的关系. (从技术上讲, "有一个"的关系称为合成, "用一个"的关系称为聚合). 缺乏面向对象经验的程序员常会过分依赖继承, 而忽视合成及聚合.

例如, 要设计一个图形界面用作游戏的前端, 假设定义了 window 类表示任何长方形的 GUI 元素, 另外也定义了 Rectangle 类封装数学上的长方形概念. 缺乏对经验的程序员可能会从 Rectangle 类派生出 window 类(window "是一个" Rectangle). 但一个更具弹性及封装更好的方法是, window 类引用或包含一个 Rectangle 类("用一个" 或 "有一个" 的关系). 这样, 每个类变得更简单, 更专注, 也更容易被测试, 调试, 重用.

3.1.2 编码标准

  • 接口为王: 保持接口(.h)文件整洁, 简单, 极小, 易于理解, 并有良好注释.
  • 好名字促进理解并避免混淆
  • 不要给名字空间添乱(宏会跨越全部 C/C++ 作用域及命名空间范围)
  • 遵从最好的 C++ 实践
3.2.1.3 定点记法(fixed-point)

定点记法用固定位数表示整数部分及小数部分. 定点记法的缺点在于, 它同时限制了可表示整数部分的范围及小数部分的精度.

3.2.1.4 浮点记法(floating-point)

在浮点记法里面, 小数点可以任意移动至不同位置, 此位置由指数控制.

若使用符号位 s, 尾数 m, 指数 e 去表达一个值 v, 则 $v = s \times 2^{(e-127)} \times (1 + m)$