游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

分享制作游戏重放系统的问题及解决方法

发布时间:2013-04-01 11:24:41 Tags:,,,,

作者:Catherine Levesque

《Bollywood Wannabe》能够吸引玩家,主要归功于它的视觉效果。这款游戏的关卡细节相当丰富和色彩十足鲜明。游戏角色穿着各式风格的服装,从印度的传统服装到万圣节的戏服,令人眼花缭乱。更令人称奇叫好的是舞蹈本身。然而,当你正在玩游戏时,你很难欣赏到这么精彩的表演。关卡难度越高,玩家就越需要全神贯注,所以就更加无暇顾及自己编排出来的精彩舞蹈了。如何让玩家欣赏自己的辛苦成果,甚至与好友分享?我得想个办法。我想得越多,就越觉得添加重放系统不仅是个好主意,而且本来就应该是游戏中必不可少的部分。

bollywood wannabe(from gamasutra)

bollywood wannabe(from gamasutra)

游戏中的重放系统并不算什么新概念。关于制作重放系统的好教程也有很多。所以请读者们务必记住,我撰写本文的目的只是与大家分享我自己制作重放系统的过程和经验,而不是为了详尽地指导如何制作。

从哪开始?

经过一番快速搜索,我发现制作重放系统的方法主要有三种:一是实时记录视频,二是记录关卡中所有对象的信息,三是只记录玩家的输入。这三种方法各有优缺点。

记录视频

这个技术不难理解,也就是定时捕捉画面并将其保存在内存中,再加上音乐和声音,做成视频。如果玩家想观看重放,只要加载视频数据就可以了。

优点:

-可以随意快进、跳过或倒回。

-画面效果总是很理想。

缺点:

-因为视频数据通常是压缩的,所以最终结果可能不像原来的那么好。

-拖慢游戏速度,因为视频需要实时记录,而记录本身又很占内存。

-保存视频会占用大量空间。

-观看重放时的加载比较慢。

记录对象

这个技术也很简单,也就是保存当前关卡中的各个对象的所有可用信息(游戏邦注:如位置、旋转、大小、动画等)和游戏各帧需要的信息(如当前得分、UI状态等)。重放时,就是加载数据并用其替换所有该关卡的对象。

优点:

-面画效果总是很理想。

-可以随意快进、跳过或倒回。

缺点:

-拖慢游戏速度,因为要保存各帧的各个对象的信息。

-当观看重放时,可能会有一点慢,因为各帧的各个对象的信息都必需更新。

-必须编写大量用于保存和加载所有必要信息的代码。

-保存信息占用大量空间(即使不如保存视频那么多)。

记录玩家输入

也就是保存每一帧中玩家的活动(如果在该帧中玩家有输入的话)。玩家活动通常就是按下或松开按键。保存后的玩家活动可以作为通关过程。

只有当游戏代码是确定性的,这个技术才管用。当输入是完全相同时,确定性程序就会产生完全相同的结果。不过,说起来容易做起来难。

优点:

-不影响游戏速度,因为需要保存的信息量小。

-重放速度快,因为使用了相同的代码。

-不需要太多空间,因为需要保存的信息量小。

-需要编写的代码很少。

-当多玩家模式时,也可以重复利用同一套代码。

缺点:

-一点点小漏洞也会导致重放结果与原来的效果相去甚远。

-不可以跳过或倒回;快进也受限。

怎么办?

我决定采用最后一个办法。为了保证游戏的重放效果完美,我制作了一个特殊的测试模式。这个模式会记录当前关卡部的所有对象的状态,包括所有角色的位置;之后将这些值与重放时的值相比较,如果发现不同,无论是多小的差异,都会发出警告。

借助这个工具,我现在可以开始测试重放,并保证我的代码的所有部分都能完美运作。需要注意的有两个部分,即节奏管理和关卡。

节奏

一开始,节奏条是与音乐同步的。这意味着节拍会与音乐完美契合。但是,因为音乐是实时播放的,虽然游戏的其他部分会与原来的通关过程的时间一致,但重放中还是存在一个时间问题。时间上任何微小误差都意味着重放中的节拍可能与音乐不同步,即使在原来的游戏过程中是同步的。通过强制节奏与重放保持相同的时间,这个问题解决了。为了避免这个问题,我们强制游戏与重放同步,不过,在这个过程中很难产生伪延时。

关卡

这当然是最成问题的一部分。关卡中的对象互动作用基本上是受物理引擎Box2D控制的。为了制作完美的重放效果,引擎自身必须是确定性的。在文件和论坛中搜索一番后,我发现似乎所有人都同意这个说法。

但为了达到完美的模拟效果,你必须以相同的顺序执行相同的事件。直到我在每个关卡中添加引入部分,这个问题才解决。为了不惹怒玩家,游戏允许玩家跳过引入部分。跳过引入部分意味着引擎没有执行到很多步骤。即使我确保所有对象都与执行引入部分后保持相同的位置和状态,模拟结果也不一样。我没有办法,只好每一次都执行引入部分。为了留住跳过选项,我必须以快进的方式执行引入部分,并将结果隐藏到淡出效果之后。

多一些惊喜!

从一开始,我就希望每次玩游戏都会有一点儿不同。我期望玩家每一次都觉得惊喜。为此,我必须避免每一次都使用相同的角色放在相同的位置。游戏必须有点儿随机性,但重放仍然必须与原来的游戏过程一致。对于知道电脑程序如何执行随机性的人来说,解决这个问题不难。然而,程序中绝对不存在真正的随机性,这才是真相!生成“随机”数的最普遍方式就是,对原来的数字,也就是种子,运用一个数学公式,然后使用算法产生的结果作为种子,以产生下一个随机数。想进一步了解随机数生成的知识,自己去查维基百科吧。另外,网上可以找到大量关于制作随机功能的教程。现在,我要做的就是保存重放数据中的种子,以便在重放时使用它。

问题

在开发《Bollywood Wannabe》的重放系统时,我并没有遇到太多问题,但遇到问题都比较棘手。依靠确定性系统来管理重放的最大问题就是,即使是小小的差异也会导致完全不同的重放结果。有一次,我甚至看到我的角色卡在墙里,仍然走着跳着,好像墙不存在似的,最后竟落入我之前做好的一个关卡里。在这次测试里,我发现关卡中的有些对象使用了不同的时钟。解决办法就是使用相同的时钟。然而,问题虽然显而易见,对重放执行排错却常常是漫长的过程。有时候,两个版本之间的差异实在太小了,以致于我一时无法重制它。只要重放产生错误,我就可以重启关卡做测试,现在一切都运作良好。我用来排错的代码可以找到非常小的差异,所以任何肉眼可见的问题都逃不过。在数个小时中不断寻找差异的根源,人很容易产生懈怠的心理,打算得过且过。但最终,我还是抵住诱惑,修复了所有问题,使重放系统运作得非常好。

总结

制作一个实用的重放系统非常费功夫,主要是因为测试,必须确保重放结果与原来的游戏过程一致。然而,这次测试的意外收获是,我被迫对游戏做了一次彻底的排错工作。之前忽略掉的小问题都在测试中发现了,因为重放系统使小漏洞变得更明显了。最后,这成为我测试游戏事件和视觉效果的有利工具。

当然,最大的收获应该是,玩家终于可以看到自己在游戏中的精彩表现了。我自为干得不错,是吧?(本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

What just happened? – Creating a replay system

by Catherine Levesque

A lot of the appeal of Bollywood Wannabe comes from the visuals. The levels are full of little details and bright colors. The characters wear a wide variety of clothes in various styles, from
traditional Indian clothes to Halloween costumes. There are also surprise events happening and the choreography itself is worth admiring. However, it’s hard to appreciate it all when you are
playing. The higher difficulty levels require a lot of concentration and you can’t take the time to admire the result of your amazing choreography. I needed a way to allow the player to see the
result of his hard work and maybe even share it with his friends. The more I thought about it, the more I felt adding a replay was not only a good idea, but a necessary component of the game.

Replay systems in games are not a new concept. There are already a lot of very good tutorial for creating one. Please keep in mind that this article is not an in-deep tutorial on how to create a
replay system. The goal of this article is simply to give an overview of the process and share my own experience on the matter.

Where to start

After a quick search, I discovered there was mostly three way of creating a replay: recording a video in real time, recording the information about every object in the game and recording only the user inputs. Each technique has its own advantage and drawback.

Recording a video

This technique is simple to understand. A picture of the screen is regularly captured and saved in memory, along with the music and sounds, to create a video. If the user wants to watch the replay, you simply load the video data and play it.

Pros

You can fast forward, skip or rewind at will.

The replay is always perfect.

Cons

Since video data is usually compressed, the result may not be as pretty as the original.

Makes the game a lot slower since the video needs to be recorded in real time and recording is very resource intensive.

Use a lot of memory to store the video.

Slow to load when you want to see the replay.

Recording everything

This technique is also very simple. You save all the information available for each object in the level (position, rotation, scale, state of its animation, etc.) and all the information needed by
the game (current score, state of the UI, etc.) for each frame. If the user wants to watch the replay you load the data and use it to place all the objects in the level.

Pros

The replay is always perfect.

You can fast forward, skip or rewind at will.

Cons

Makes the game a little slower since information about every object needs to be saved for each frame.

Might be a bit slow when you want to see the replay since the information about each object need to be updated for each frame.

A lot of code must be created to save and load all the information required.

Use a lot of memory to store all the information needed (though probably less than the video).

Recording only user inputs

For each frame you save the user actions, if any. A user action generally consists of pressing or releasing a button. The saved user actions are then used to simulate a playthrough.

This technique only works when the game’s code is determined. A deterministic program will give the exact same result if the exact same inputs are provided. This is harder to achieve than it may seems.

Pros

Do not affect the speed of the game since very little information is saved.

The replay is as fast as the original since the same code is used.

Doesn’t need a lot of memory since very little information is saved.

Very little code must be created.

The code can be reused for multiplayer functions.

Cons

A small bug may create a big difference between the original play and the replay.

You cannot skip or rewind. Fast forward is also limited.

Let’s do this

I decided to go with the last option. To make sure that the game’s replays were perfect, I created a special test mode. In this mode, I recorded the state of every object in the level including
the position of every character. Those values would be compared with the one produced during the replay and if a difference was found, no matter how small, a warning was shown.

With this tool, I could now start testing the replay and make sure that every part of my code worked perfectly. Two part of the game needed attention, the rhythm management and the level.

Rhythm

Originally, the rhythm bar was synchronized with the music. This meant that the beats would follow the music perfectly. This created a problem in the replay since the music was played in real time, but the rest of the game followed the timing of the original playthrough. A small difference in timing meant that the replay could show a beat was missed when the original was on time. This was fixed by forcing the beats to follow the same time as the replay. However, this sometime caused the music to be out of sync with the rest of the replay. To avoid this problem we forced the game to synchronize with the replay even if it had to create false lag in the process.

Level

This was certainly the most problematic part. The objects interactions in a level are mostly controlled by a physic engine, Box2D. To be able to create a perfect replay, the engine itself needs to be deterministic. After a quick search through the documentation and the forum, it seemed like everyone agreed that it was.

But in order to achieve a perfect simulation, you need to execute the exact same events in the same order. This wasn’t a problem until I added an intro to every level. To avoid annoying the
player, the intro can be skipped. Skipping the intro meant that a lot of steps weren’t executed anymore by the engine. Even if I made sure every object was exactly in the same position and state as it would be after executing the intro, the simulation wasn’t exactly the same anymore. I had no choice but to execute the intro every time. To keep the skipping option, I had to execute the intro in a fast forward way and hide the result behind a fade.

Adding some surprises

From the start, I wanted each play session to be a bit different. I wanted the player to be surprised by what was happening on the screen. To do that, I had to avoid having the exact same character entering the screen and positioning itself in the exact same way every time. The game needed a little bit of randomness, but the replay still needed to be identical to the original. The solution to this problem is easy for anyone who knows how randomness works in computer programs. The truth is that true randomness can’t exist in a program. The most common way to generate a “random” number is to apply a mathematic formula to a pre-existing number, the seed, and use the result as the seed for the next random number needed. For a complete explanation of how this works, you can check the Wikipedia article about random number generator. There are also lots of tutorials on how to create custom random functions and, alternatively, one can simply set the seed of the standard C++ random function. Now all I had to do was save the seed in the replay data and use it in the replay.

Problems

I didn’t face a lot of problems developing Bollywood Wannabe replay system, but those I experienced were hard to fixes. The biggest problem with relying on a determinist system to manage a replay is that the smallest difference can send the replay in a completely different direction. I once tested a replay only to see my character stuck behind a wall, trying to walk and dance like the obstacle didn’t exists and failing a level I had previously completed. In this case, I discovered that some of the moving objects in the level used a different clock then the rest of the game.

Using the same clock solved this problem. However, as obvious as the problems were once I found them, debugging the replay was often an incredibly long process. Sometimes the difference was so small between the original and the replay that I couldn’t reproduce it half the time. The replay would produce an error once, I would redo the level to test it again and everything was now working flawlessly. The code I used to debug the replay could detect difference so small that they often didn’t cause any visible problem. It’s often tempting, after hours of trying to find the source of that tiny difference, to just stop everything and decide this is “good enough”. In the end though, I was able to fix all the problems and the replay worked fine.

Conclusion

Creating a functional replay system is a lot or work, mostly because of the testing involved in making sure the replay is identical to the original playthough. However, one of the hidden benefits of this testing is that it forced me to thoroughly debug the game. A lot of small bugs that would probably have been overlooked were found because the replay system magnified them. It also gave me a good tool to test the events and visuals in the game.

Of course, the biggest benefit is allowing the player to watch just how well he played that level. I think I played very well, don’t you think?(source:gamasutra)


上一篇:

下一篇: