Record and Playback System

Started by
13 comments, last by dmail 15 years, 10 months ago
I am looking to write a record/playback system for a game. I wanted to see if there were any good hints out there as to how to do it effectively. I need the system to be able to handle an arbitrary number of players, an arbitrary number of packet types, an arbitrary frame rate for each player, insertion of data, forward play, backward play, fast forward, rewind, etc. I plan to write everything out to a binary file, which is fine, but how best can I structure my data and file as to be able to move through it fast. I am not as concerned about disk space as I am performance and capability. Thoughts or lessons learned? Thanks.
Advertisement
I've never done this but the way I'd tackle it is record input. Any time you generate a random number (maybe for an enemy) record that. Then on a replay just insert what ever the input was and have the game play itself out.

You're going to need to write code that can ask itself "Is this real time, or a play back" and use the proper value for the instance.

Slow motion, just double the time each frame stays on screen.

Some problems arise when you want to start the play back from any where besides the start, or rewind, but I'm sure you'll think of something.

At the very worst you could save every variable from every frame, and well... that would probably be slow.
Yeah...trying to avoid "slow". I can't record input because of network players. I have an "obvious" solution, but I wanted to incorporate anyone's lessons learned or avoid some not-so-obvious pitfalls before I go too far down one path.
hmmm... I really don't have much to offer as I've never done it. However when you do, it's be really interesting to see how it turns out and what some of the challenges were.

Good luck!
Hi,
We have a system like that in the program I work on, and I'm going to rewrite it one day, so here are my thoughts about it :

I think the easiest way is to create 2 classes : one interface (IRecordable or something like that) which has a few virtual methods like "Record", "Playback", etc. And one Recorder class. Objects which you want to record should inherit from IRecordable, and implement its virtual methods. Recorder is responsible to get the data (via the IRecordable::Record) method, store it, and on playback, give it back to the objects (via IRecordable::Playback)

Then how you record and playback data is up to you. You could store inputs, only state changes, all states for all frames, all states each N frames, etc.

I think that if you want the same functionalities as a video player you probably need to store all states for all frames. That way you can start the playback at any frame you want, play backward, etc.
But the drawback is that it's a lot of data to store. So you will probably want to think of a real-time compression / decompression to reduce data transfert cost.

Or you could store all states each N frames, and only store state changes or differences for the other frames. I think DivX and many other video algorithms use a similar approach.

Hope it'll help a little :)
Right now my thought was to make a header struct with the packet type, packet size, and timestamp. Then follow every header struct with the actual data. Save the data at arbitrary rates, and keep a table in memory of "key frame" locations in the file to assist with searching for a specific time and being able to play from there. The biggest overhead I can see would be inserting new data in the middle of the file, which would mean updating the "key frame" table. It just seems that this problem must have been tackled a thousand times a thousand ways and I'd hate to repeat a bad design!
I don't understand why you'd need to insert new data ? In every game / app I've used, you record your current race / whatever, and replay it ... could you please give an example of when you'd need to insert data ?
Good question. There is a special mode where a player can act on what has already happened. For this to work, I will need to play back what has already happened and insert the player's new data as well. I would then want this new data available for future playback.
Have a layer that sits between your input (both local and network) and the rest of your engine. In record mode it captures and timestamps all of the input. In playback mode it feeds the recorded input back to the engine.

If you rely at all on calls to rand() (or similar) be sure to store the value your RNG was seeded with. Using the same seed should produce the same values in your subsequent rand() calls IIRC.
like commentaries? It might be best to then record extra stuff as a separate 'layer'.

I've done a replay feature and it's a real-big-major-painful-PITA.

I've done it the hard way too, as the engine was non deterministic and there was some random access required.

First, if you just do brute force and record the game state, the data that needs to be logged will be humongous. It really, really adds up very quickly.

second, recording game states is a real pain. Then you have to think of what is recordable, and how well they will replay. Many games are input driven (player inputs, scripts, network packet), so the obvious choice is to record inputs and replay them in the same order. However, your engine will have to be fully deterministic.

If it is not deterministic, then the real pain starts. You will have to record game states, as freeze-frames, (like you would do for a instant load/save feature), say every 20th second.

To record game states, you will need to implement serrialse/deserialise member functions for each entity you want to record.

If you record at a lower framerate, then you will also need some interpolation. You will also need to make awkward choices about what to record and what not to record. Entity states of course, but potentially events, scripted effects...

As for memory, you will be logging potentially 100's of kBytes a second (5K per game state, 20 frames a second). That is a LOT of memory. It also means that there is a limit to what you can record.

There are a lot of optimisations thast you can do to reduce the data size.

1) record only stuff close to the camera. That limits your replay viewpoint to the camera (or thereabout).

2) You can use delta-compression to record only what changes from state to state in each entity). that will reduce your data dramatically. The drawback is that it is not possible to read backwards. You can add keyframes along the replay file that contains the full state of entities, so you can jump back and forth to points in time, and base your subsequent deltas on keyframes.

A replay system like this is closely related to how FPS online games work. So theoretically, you could base your replay on a network engine.

It's a lot of work, but if your engine is deterministic, it's a lot easier, although harder to debug. All you have to do is identify what constitutes an input and needs to be recorded, and log those inputs precisely.

Everything is better with Metal.

This topic is closed to new replies.

Advertisement