这里介绍了传统Roguelike游戏开发中的几种处理时间消耗的算法。
参考网站是RogueBasin
像Roguelike这种回合制游戏,生物每次行动都需要消耗一定量的时间,这样就必须有一个通过消耗时间确定生物行动顺序的系统。
这个系统很简单,需要一个循环链表:
1
2
3
4
5
6
7
8
9
|
struct Entity {
int Update() { /*.. your update ..*/ } // 返回物体行动消耗的时间
int speed;
int action_points;
};
struct ETMSystem {
std::list<Entity*> entities; // 含有物体链表
};
|
因为是循环链表,我们得定义一个头节点,这里默认就在下标0处吧。
然后这个算法的核心就是:每次看头结点的物体action_points
是否大于0,如果大于0,就执行其Update()
函数进行更新,并且将action_points
减去消耗的时间。如果没有,就加上物体的speed
,然后继续看下一个物体。
整个流程的代码大致如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
struct ETMSystem {
std::list<Entity*> entities; // 含有物体链表
void RegistEntity(Entity* entity) {
entities.push_front(entity); // 新增加的放在头部
}
void Tick() { // 这个函数在游戏主循环中被调用,每帧调用过一次
if (!entities.empty()) {
auto& entity = entities.front();
std::rotate(entities.begin(), ++entities.begin(), entities.end()); // 这里为了方便用rotate将所有元素循环左移了一位
entity->action_points += entity->speed;
if (entity->action_points > 0) {
entity->action_points -= entity->Update();
}
}
}
};
|
如果你想要给你的游戏加上动画,这里有一个锁机制可以帮助你:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
struct ETMSystem {
void RegistEntity();
void Tick();
void Lock() { lockNum++; }
void Unlock() { lockNum--; }
int lockNum = 0;
};
// 然后在调用Tick的时候判断,如果锁上了就不掉用Tick
if (etm.lockNum == 0) {
etm.Tick();
}
/* 每一次动画开始时都需要Lock,然后动画播放完成就Unlock */
|
这种算法适合将Update函数写死在物体内的游戏。
这个很简单,就是将事件推入到优先队列中,然后每次从队列中取出需要耗费时间的最短的事件去处理,并且将余下的所有事件的剩余时间全部减掉。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
struct Event {
int costTime;
int id; // 为了辨别Event
// 定义operator<,优先队列需要
bool operator<(const Event& e) { return costTime < e.costTime; }
};
struct PriorityQueue {
std::vector<Event> events;
void Push(const Event& event) {
int i = 0;
while (i < events.size() && events[i + 1] > event) {
i++;
}
events.insert(event, events.begin() + i);
}
Event Pop() {
Event e = events[0];
events.erase(events.begin());
return e;
}
void DecCostTime(int time) {
for (auto& e : events)
e.coseTime -= time;
}
};
struct TurnSchedule {
PriorityQueue q;
Event NextEvent() {
Event e = q.Pop();
q.DecCostTime(e.time);
return event;
}
};
|
这里STL的优先队列不能满足我们的需求,我就写了个简单的。在每次的游戏循环中,都需要调用NextEvent()
函数获得Event并处理。
这种算法适合基于事件或者命令机制的游戏。