Recording gameplay

DM6
Unregistered
 
Post: #1
Hi all.

The game I'm writing has to take a very specific certain amount of time to play; about 30 mins. So in order to get the best data I can from my testers, I want to implement a system for recording the keypresses over time, so I can play them back on my own machine and watch their games to see where it's too easy, too hard, etc. and maybe statistical methods to judge the learning curve over several games.

I am using SDL with C++. I'm thinking I write SDL key events and their corresponding timer values to an array or two during the game loop and then dump it to a file after the game is finished. I am only an average programmer and am not sure I know whether there are any problems with this way of doing it.

Also there is a problem with reproducing the gameplay on another machine because of random numbers in the game. But the pseudorandom numbers are generated by the timer right? So if I get the absolute time values that aren't relative to the start of the game I can presumably generate the same random numbers every time...
Any advice on implementation/feasability would be soooooo helpful

Thanks
-Duncan
Quote this message in a reply
Moderator
Posts: 916
Joined: 2002.10
Post: #2
as for the random number solving thing, just record the seed that you use
Quote this message in a reply
DM6
Unregistered
 
Post: #3
oooooooh riiiight...

Thanks skyhawk.
My only possible concern is that the array or vector would get pretty long. I don't want to write to a file on the fly since it will probably slow down the game. Should I go ahead and use a vector? Another type?

-Duncan
Quote this message in a reply
Sage
Posts: 1,482
Joined: 2002.09
Post: #4
If you are using the standard C library rand() function then there is no problem with reproducing the numbers. What you need to do is only seed the generator once at the beginning of the game and record the seed number. With the same seed number, the sequence of "random" numbers will always be the same. Try it.

And I wouldn't worry about the length of the list getting to long. Just make a buffer and stick it in there. Think about how much space the graphics take up... a 256x256 32bit has 65 thousand pixels and uses up a quarter of a meg. Now imagining that a SDL_Event is something like 64bits (I'm guessing, to lazy to look it up.) that would give you 120+ thousand events before using up a meg of memory. Even if you are using mouse input it can't possibly have more than 30 or so events per second. That would give you about 4000 seconds of gameplay. (over an hour)

So with that in mind, stick it in memory and write the file when done.

Scott Lembcke - Howling Moon Software
Author of Chipmunk Physics - A fast and simple rigid body physics library in C.
Quote this message in a reply
Moderator
Posts: 916
Joined: 2002.10
Post: #5
DM6 Wrote:oooooooh riiiight...

Thanks skyhawk.
My only possible concern is that the array or vector would get pretty long. I don't want to write to a file on the fly since it will probably slow down the game. Should I go ahead and use a vector? Another type?

-Duncan
record key ups and key downs and use a linked list.
Code:
typedef struct recordy recordy;
struct recordy
{
  unsigned short key; // key pressed
  bool up; // 1 for up 0 for down
  unsigned int time; // the time it was pressed
  recordy *prev, *next;
}
Quote this message in a reply
DM6
Unregistered
 
Post: #6
Thank you both, your comments helped a lot.
-Duncan
Quote this message in a reply
Sage
Posts: 1,482
Joined: 2002.09
Post: #7
I'd just store the whole SDL_Event, it would be marginally simpler and more flexible.

Scott Lembcke - Howling Moon Software
Author of Chipmunk Physics - A fast and simple rigid body physics library in C.
Quote this message in a reply
Moderator
Posts: 771
Joined: 2003.04
Post: #8
Does your game logic run at a fixed FPS? Otherwise, playing back keyboard events, even if at the exact same moment, won't work very well (especially after 30 min.)
Quote this message in a reply
Nibbie
Posts: 2
Joined: 2009.01
Post: #9
well depending on your game complexity you could save a snapshot of the game state every 20 seconds to keep it as accurate as possible, surely there cant be too much error at non-fixed fps in only 20 seconds?
Quote this message in a reply
DM6
Unregistered
 
Post: #10
Dumping the actual SDL_Events was my plan so I wouldn't have to mess with converting to and from other types. Using a linked list is a good idea that I hadn't even considered. For timing i'm using the SDL timer and updating the scene every n ticks. Sometimes I get slowdowns because of my 550 G4 but the physics and logic and frame all get updated simultaneously so I don't think anything will get out of sync. If this is wrong please let me know.
Have any of you guys tried this kind of thing before, or something like it?
Thanks
-Duncan
Quote this message in a reply
Moderator
Posts: 771
Joined: 2003.04
Post: #11
DM6 Wrote:For timing i'm using the SDL timer and updating the scene every n ticks. Sometimes I get slowdowns because of my 550 G4 but the physics and logic and frame all get updated simultaneously so I don't think anything will get out of sync.

That's a fixed FPS Wink . The only problem is, if the machine in wich the game is being "recorded" skips a few frames due to lack of speed, then your characters/objects "think(...)" or "checkColisions(), etc. will end up being called less frequently than say, on a G5, so you may not get the exact same playback (you object will get the chance to "think" or "collide" before the keypress is played back.

DM6 Wrote:Have any of you guys tried this kind of thing before, or something like it?

I tried to do this, using SDL, so I could record a QT movie of my game, but I finally gave up. It would have been easier if I had designed the game with a client-server architecture to begin with... Sad
Quote this message in a reply
DM6
Unregistered
 
Post: #12
No I never skip frames,I just draw them as quick as they come. so i'm sure it'll work fine.

-Duncan
Quote this message in a reply
w_reade
Unregistered
 
Post: #13
It won't be fine, I'm afraid; if you record the timestamp of an event, the time (in-game) that the event is actually processed will not be the same from one computer to another -- or even from one session to another -- because you're just processing frames as fast as you can.

If, however, you make your game logic run at a fixed speed, and record which "tick" an event got to the game in -- not worrying at all about the timestamp -- then you can replay it perfectly, even on computers of very different speeds.

Even better, it's pretty simple to do. This is Carbon; use InstallEventLoopTimer() to call DoFrame() a set number of times per second when you RunApplicationEventLoop(). Any number of code samples will show you a simple Carbon application using RAEL(). So, with comments interspersed at random:

Code:
// measured in seconds; so, the game logic will run at 100fps.
// the actual frames _drawn_ per second depends on the rate
// at which you ask the timer to fire (and your machine's speed)
const double k_timestep = 0.01;

const UInt32 k_max_UInt32 = 0xFFFFFFFF;

pascal void DoFrame (EventLoopTimerRef  /*theTimer*/, void */*userData*/)
    {
    UnsignedWide timer;
    Microseconds(&timer);

    static double last_time = timer.lo * kEventDurationMicrosecond;
    double this_time = timer.lo * kEventDurationMicrosecond;
    double diff_time = this_time - last_time;

    // handle possible wraparound

    if (diff_time < 0.0)
        diff_time += k_max_UInt32 * kEventDurationMicrosecond;

    // do however many game logic ticks we need to
    // bring the game timer level with the "real" time

    static double overflow = 0.0;
    overflow += diff_time;
    while (overflow > k_timestep)
        {

        // this gets called 100 times per second
        // (or whatever the value (1 / k_timestep) is)

        YouGameTickFunction(k_timestep);
        overflow -= k_timestep;
        }

    last_time = this_time;

    // ...but this only gets called as often as the
    // event loop timer fires

    YourDrawGameFunction();
    }


Remember, static variables are only set to the value in the code the first time the function is run; after that, they remember their values from the last time the function was executed.

If you want a deeper explanation, mail me and I'll try to provide one (copious free time permitting Wink).

Oh, also, if you run this on a machine that's too slow, you get sucked into a pit of rapidly-increasing frame times, which is fatal to any gameplay; it's pretty simple to detect and fix -- or, better, avoid -- and I leave that as the time-honoured exercise for the reader Rasp.
Quote this message in a reply
Moderator
Posts: 771
Joined: 2003.04
Post: #14
DM6 Wrote:For timing i'm using the SDL timer and updating the scene every n ticks.

DM6 Wrote:No I never skip frames,I just draw them as quick as they come. so i'm sure it'll work fine.

Basically, what you need to guarantee is that the event will be played back between the same 2 frames it originally was recorded on. If you don't use any time-delta info linking your keypress with the previous/next frame, then you can use an event record holding:

Code:
struct myEvent
{
    unsigned int frame; //(the frame after wich the event occured)
    SDLEvent event;
}
If you are using C++, you just declare your list of events as

Code:
std::vector<myEvent>myEventList

To add an event to the list (ie. record an event):

Code:
//currentEvent is declared as kind myEvent

//All this should go in your event loop
currentEvent.frame = currentFrame;
currentEvent.event = event;
myEventList.push_back(currentEvent)


Finally, to play back:

Code:
eventIndex = 0;
...
...
//Every frame
while (myEventList[eventIndex].frame <= currentFrame)
    SDLPushEvent(myEventList[eventIndex++].event)

Of course, this is a simplified code: it will record/play back any kind of event: key down, key up, mouse down, mouse up, mouse moved; you should only record those that you really need.
Quote this message in a reply
Post Reply