天天看点

数据结构在游戏中的应用

链表

链表主要的优点就是可以方便的进行插入,删除操作。

雷电游戏中,飞机发射导弹时子弹是要频繁的出现和消失(飞离当前屏幕)的,其个数也是难以预料的,所以这种场景适合使用链表。

// 子弹坐标
struct CPOINT{
  int x;  // X轴坐标
  int y;  // Y轴坐标
};

// 子弹链表
struct BULLET{
  struct BULLE* next;  // 指向下一个子弹
  CPOINT bulletpos;   // 子弹的坐标
  int m_ispeed;     // 子弹的速度
}

// 飞机类
class CMYPLANE{
public:
  void AddBullet(struct BULLET*);    // 加入子弹的函数,每隔一定时间加弹 
  void RefreshBullet();        // 刷新子弹
privated:
  struct BULLET *st_llMyBullet;    // 声明飞机的子弹链表
}

// AddBullet() 我们要做的操作只是将一个结点插入链表中,并且每隔一段时间加入,就会产生连续发弹的效果。
// RefreshBullet() 中,我们只要将链表历遍一次就行,将子弹的各种数据更新。      

经过上面的分析,在游戏中,链表主要应用在有大规模删除,添加的应用上。不过,它也有相应的缺点,就是查询是顺序查找,比较耗费时间,并且存储密度较小,对空间的需求较大。如果通过对游戏数据的一些控制,限定大规模的添加,也就是确定了内存需求的上限,可以应用顺序表来代替链表,在某些情况下,顺序表可以弥补链表时间性能上的损失。

顺序表

在RPG地图系统中(特指砖块地图系统),主要使用数据结构中最简单的成员 — 顺序表。

规定一个最简单的砖块地图系统,视角为俯视90度,并由许多个顺序连接的图块拼成,早期RPG的地图系统大概就是这样。

我们这样定义每个图块:

struct TILE{
  int m_iAcesse;    // 纪录此图块是否可以通过(0不能通过,1能通过)
  ……           // 其中有每个图块的图片指针等纪录
}      

最后生成如下地图,并做简单的初始化:​

​TILE TheMapTile[10][5];​

数据结构在游戏中的应用

从上图看到这个地图用顺序表表示非常直接,当我们控制人物在其中走动时,把人物将要走到的下一个图块进行判断,看其是否能通过。

栈和队列

栈和队列是两种特殊的线性结构,在游戏当中,一般应用在脚本引擎,操作界面,数据判定当中。

我们在设置脚本文件的时候,通常会规定一些基本语法,这就需要一个解读语法的编译程序。这里列出的是一个语法检查函数,主要功能是检查“()”是否配对。我们规定在脚本语句中可以使用“()”嵌套,那么,便有如下的规律,左括号和右括号配对一定是先有左括号,后有右括号,并且,在嵌套使用中,左括号允许单个或连续出现,并与将要出现的有括号配对销解,左括号在等待右括号出现的过程中可以暂时保存起来。当右括号出现后,找不到左括号,则发生不配对现象。从程序实现角度讲,左括号连续出现,则后出现的左括号应与最先到来的右括号配对销解。左括号的这种保存和与右括号的配对销解的过程和栈中后进先出原则是一致的。我们可以将读到的左括号压入设定的栈中,当读到右括号时就和栈中的左括号销解,如果在栈顶弹不出左括号,则表示配对出错,或者,当括号串读完,栈中仍有左括号存在,也表示配对出错。

// 栈结构
struct SeqStack{
  int m_iData[100];    // 数据段
  int m_iTop;      // 通常规定栈底位置在向量低端
};

// 语法检擦函数
int Check(SeqStack *stack);      

总之,凡在游戏中出现先进后出(栈),先进先出(队列)的情况,就可以运用这两种数据结构。

二叉树

在游戏中通常有很多分类判断,设主角的生命值d,当怪物碰到主角后,怪物的反应遵从下规则:

数据结构在游戏中的应用

正常情况下我们第一时间想到的肯定是这种逻辑:

if(d < 100) state = 嘲笑&单挑;
else if(d < 200) state = 单挑;
else if(d < 300) state = 嗜血魔法;
else if(d < 400) state = 呼唤同伴;
else state = 逃跑;      

但这个代码时间性能差,一般,在即时战略游戏中,对此类判定算法会有较高的时间性能要求。二叉树可以帮助我们提高时间性能。首先,分析主角生命值通常的特点,即预测出每种条件占总条件的百分比,将这些比值作为权值来构造最优二叉树(哈夫曼树),作为判定树来设定算法。假设这些百分比为:

数据结构在游戏中的应用
数据结构在游戏中的应用
if(d >= 200 && d < 300) state = 嗜血魔法;
else if(d >= 300 && d < 500) state = 呼唤同伴;
else if(d >= 100 && d < 200) state=单挑;
else if(d < 100) state = 嘲笑&单挑;
else state = 逃跑;      

在游戏中,大多数应用图的地方是路径搜索(A* 算法),所以这里介绍图的另一种应用。

在一个游戏中,可能包含很多分支情节,在这些分支情节之间,会存在着一定的先决条件约束,即有些分支情节必须在其他分支情节完成后方可开始发展,而有些分支情节没有这样的约束。

假如我们手头有这样的情节:

数据结构在游戏中的应用

那么以上情节用图的形式表现为:

数据结构在游戏中的应用

用邻接矩阵表示此有向图:

struct MGRAPH{
  int Vexs[MaxVex];        // 顶点信息(存储在情节文件中)
  int Arcs[MaxLen][MaxLen];    // 邻接矩阵
  ....
};      

将给出的情节表示成邻接矩阵:

数据结构在游戏中的应用

我们规定,各个情节之间有先后关系,但没有被玩家发展的,用1表示。当情节被发展的话,就用2表示,比如,我们已经发展了遭遇强盗的情节,那么,C1与C2顶点之间的关系就可以用2表示,注意,并不表示C2已经发展,只是表示C2可以被发展了。

class CRelation{
public:
  CRelation(char *filename);          // 构造函数,将情节信息文件读入到缓存中.
  void SetRelation(int ActionRelation);    // 设定此情节已经发展
  BOOL SearchRelation(int ActionRelation);  // 寻找此情节是否已发展
  BOOL SaveBuf(char *filename);        // 保存缓存到文件中

privated:
  char* buf;                    // 邻接矩阵的内存缓冲
};      

我们将表示情节先后关系的邻接矩阵放到缓冲内,通过接口函数进行情节关系的修改,在BOOL SearchRelation(int ActionRelation)函数中,我们可以利用广度优先搜索方法进行搜索。

继续阅读