Replay
The replay feature should allow people to watch a race they've just finished, but also to save a race to be able to load it later, perhaps share it with friends etc. It is not supposed to be a frame by frame history, which might be needed to debug certain bugs (e.g. kart falling through track, problems with physics, AI, ...), it should only give the same (or a very close) visual recreation of a race. Mid term these files can be utilised for an online highscore lists: having people to submit a complete replay of the race makes sure other people can have a look at the races, and cheating (e.g. a patched STK program, changed physics) will be detected.
Another usage would be to allow a 'ghost opponent' (as apparently in Moorhuhn Kart): you can use a (downloaded) replay file and race with that kart on the same track. The downloaded kart would be a 'ghost' (e.g. slightly foggy?), and you can't interact with the kart. But it might be fun trying to beat a certain highscore, or learn from other players. Perhaps we would need a kind of filter to extract a single kart from a replay file(?).
And we should consider replay of Grand Prixs as well, e.g. a collection of races. Perhaps the right approach would be that a replay file is a collection of single race data (this would then include a single race, but would also be prepared to do Grand Prix as well).
Replaying should enable people to switch camera positions, e.g. select the karts, or a view from high above, or perhaps even to fly around(?).
The data files saved should be rather small, so I would suggest not to use any existing formats like XML, since they could easily double the file size, instead define a simple STK only ASCII file format. ASCII might be a bit bigger than a binary format (e.g. in binary a floating point number for STK can be saved in 4 bytes, while an ASCII representation might be 6 bytes (limited precision would be enough), but it has a few advantages:
- it can easily be ported among different architectures, while binary suffers from problems such as endianness
- it is easier to debug
- we can always add compression to the text files
Some design ideas for the file format:
- Save data only at a certain frame rate, e.g. 30 times per seconds. The actual value might be changed (and should probably be stored in the file). The replay can either just sleep for a certain amount of time to give the same real-time impression, or interpolate between frames for smoother visuals. Some experimenting is needed.
- Interpolation might actually need more information (e.g. speed), would be more work to implement, but might save space since it might allow a larger timestep to be used. Just displaying the position at each timestamp would get away with less information (e.g. we wouldn't need speed information, except perhaps forward speed for the speed display), but might need a smaller timestamp for a smooth visual experience.
- For each kart we need at least 6 coordinates for the location (x/y/z; H/P/R), 3 for the speed (perhaps even angular speed, e.g. the equivalent of h/p/r)? Not sure if acceleration might be worth storing as well.
- We should store a version number, in case that we have to extend the format later on
- In between the kart information at each frame, all events should be included and probably time stamped. Events include
- We need information about steering as well, to turn the wheel appropriately.
- If we support animations, we might need some information about the position of the characters as well.
- collision with the track
- collision with another kart
- collision with movable physic objects (for bullet physics)
- getting a collectable
- getting a banana
- using a collectable
- 'using' a banana (e.g. passing the bomb from one kart to another)
- finishing a lap/race
- Generally: whatever causes a sound effect to be played
An example file format might be (using # to indicate comments):
... # preamble, to be defined
... # containing information about what race,
# number of karts, difficulty, numer of laps, ...
POS @10.1 k 1 1.0,2.0,3.0,4.0,5.0,6.0 7.0,8.,9. # position and velocity for kart 1 at time 10.1
k 2 1.0,2.0,3.0,4.0,5.0,6.0 7.0,8.,9. # dito for kart 2, indentation is optional
POS @10.13 k 1 ... # information for timestep 10.13
k 2 ...
COLL @10.14 k 1 zipper
CRASH @10.145 k 2 track 1.0,1.0,1.0 # where it crashed, perhaps specify triangle?
POS @10.16 k 1 ...
k 2 ...
USE @10.17 k 1 rocket 2 # rockets etc. would get an internal number, so that:
EXPL @10.18 rocket 1 # the right rocket can be exploded and replaced
We need events like explode and collide, since (because of minor error in the physics engine depending on time step size) an event like this might not occur 'naturally': while in the original game a rocket just hit a kart, in the play it might otherwise just miss the kart. And the final file format should be easy to parse.
Comments welcome! --hiker 03:59, 8 May 2007 (CEST)
First decision would be: do we interpolate, or not? My preferences would be not to interpolate, since this is less implementation work. This way we could quickly see how big files can become etc. --hiker 05:49, 10 May 2007 (CEST)
today i take the challenge and start implementing it .. ;)
1. memory-requirements for recording a game / memory-management
- buffers should not be static, since we never know how long the game will be in future and how many *events* will be there
- Agreed. --Hiker 00:10, 19 September 2007 (PDT)
- object in buffers, i.e. recorded vertices, events .. should not be copied, if buffer runs out of memory and we have to reallocate, since that could be bad for the game-performance
- Agreed again --Hiker 00:10, 19 September 2007 (PDT)
for the beginning i'm experimenting with an array of allocated buffers
simplified it looks like this:
struct Frame
{
// absolute time of frame
float time;
// this will be an array with number of karts, pointing to another buffer
// for simplication here it is just one kart
sgCoord kart;
};
// buffers of frames
Frame **m_pp_blocks_frames;
before we start recording the game, we initialize the first element of the array pointing to an allocated buffer:
// we start with one buffer m_pp_blocks_frames = new Frame*[ 1 ]; // number of frames stored in one buffer // assuming 10 minutes with 50 frames per second const int BUFFER_PREALLOCATE_FRAMES = 10 * 50 * 60; m_pp_blocks_frames[ 0 ] = new Frame[ BUFFER_PREALLOCATE_FRAMES ];
if that buffer is not big enough for one game, here is how we allocate a new buffer without copying all the old frames to the new alloated buffer
Frame **pp_blocks_frames_old = m_pp_blocks_frames;
m_pp_blocks_frames = new Frame*[ 2 ];
// copy pointer(s) to old frame-buffer
for( tmp = 0; tmp < 1; ++tmp )
{
m_pp_blocks_frames[tmp] = pp_blocks_frames_old[tmp];
}
// create new buffer of frames
m_pp_blocks_frames[1] = new Frame[ BUFFER_PREALLOCATE_FRAMES ];
this *memory-code* is gonna be hidden in templates, since we need different buffers, and it's gonna look like we are working with usual arrays, which have a self-growing-mode for recording, if an index is bigger than the array-size.
- Yes, makes sense as well. We might get away with a single array if we only store events, and we use a similar structure (well, a union, or object hierarchies) to store the events. Not sure if it's worth the effort, we would have to think about what events we need to store, and how much memory we are wasting (since the memory used will be the size of the largest event). --Hiker 00:10, 19 September 2007 (PDT)
2. Replay-Files
I'm gonna make experiments with both human readable and binary files, if we decide to use binary-files, we have to take care of little/big endians.
- I think human readable would be best as a start. It's easier to debug, and avoids endianes issues. If the files are going to be too big, we can think of either compressing them, or using binary, ... --Hiker 00:10, 19 September 2007 (PDT)
What do you think about a new folder to store replays?
- Sure --Hiker 00:10, 19 September 2007 (PDT)
3. Compiler-Switch to activate replay while deveopment
As long as its not implemented in a usable way, i'm gonna put ifdef's called HAVE_GHOST_REPLAY. that way the trunk is not gonna be unusable ..
- Yes, that's what I usually do, too (see current bullet development) - putting it in a branch just causes a big merge effort at the end. --Hiker 00:10, 19 September 2007 (PDT)
4. Sample-Frequency
for the time being i start recording with the normal frame-rate, but i guess for *good* results we can define a lower frequency later.
- Just to avoid a misunderstanding: It might be best to use a fixed 'event rate' per second (e.g. 30 events per second), independent of the FPS the simulation is running (e.g. we should't use 'store every 10th frame' or so,even if this 10 is computed using the specs of the computer - the frame rate can vary quite significantly, depending on how many karts are in view, how many rockets are flying, ...). --Hiker 00:10, 19 September 2007 (PDT)
i'm curious about your ideas and feedback ..
--Ike 14:09, 18 Sep 2007
- Sounds good to me! --Hiker 00:10, 19 September 2007 (PDT)