timing issues while creating replay of game (ghost for racing)

130 views Asked by At

I have created an iOS game in cocos2d that uses box2d for physics. I would like to have a replay function for this game (where you can race a ghost of your previous attempts) I know box2d is deterministic (well it is for the same hardware... so running this on your same phone would work) so this should be doable. For now, during testing, I have recorded the timing of the user input with

NSLog(@"%f", [[NSDate date] timeIntervalSince1970] - gameTime);

where gameTime is when the game started

and then I have done this to play the game back

[self performSelector:@selector(simulateFingerDown:) withObject:nil afterDelay:0.753302];
[self performSelector:@selector(simulateFingerUp:) withObject:nil afterDelay:1.382405];
[self performSelector:@selector(simulateFingerDown:) withObject:nil afterDelay:2.066786];
[self performSelector:@selector(simulateFingerUp:) withObject:nil afterDelay:2.800533];
[self performSelector:@selector(simulateFingerDown:) withObject:nil afterDelay:3.950479];
[self performSelector:@selector(simulateFingerUp:) withObject:nil afterDelay:4.933555];
[self performSelector:@selector(simulateFingerDown:) withObject:nil afterDelay:6.607358];
[self performSelector:@selector(simulateFingerUp:) withObject:nil afterDelay:8.067316];
[self performSelector:@selector(simulateFingerDown:) withObject:nil afterDelay:8.700970];
[self performSelector:@selector(simulateFingerUp:) withObject:nil afterDelay:8.934012];

Where simulateFingerUp/Down call the same thing that ccTouchBegan/Ended calls. The only function in my game is to touch the screen or not so I am merely hardcoding in a specific delay for when to touch or not (to test the "ghost" function) This process is working as expected... and the game is "simulated/replayed" however each run of the app is getting different results.. Presumably scheduling selectors is dependent on system resources or something like that so it would lead to different timings between runs (which can lead to big differences in my game)

What should I be using instead to simulate the replay? I can record the time of user input and it would only possibly be off by as long as it takes to calculate the subtraction [[NSDate date] timeIntervalSince1970] - gameTime however it seems that playing it back with performSelector is not reliable. Anyone have suggestions?

Also I think this might be relevant? The step function for box2d is always going to be: _world->Step(0, 8, 3);

I am not entirely sure why it is those values... I had taken some of the box2d code from a tutorial online that didn't explain those particular values

2

There are 2 answers

4
pixelmike On BEST ANSWER

One solution is to run the simulation at a fixed timestep. Let's say you choose 16ms per update. But you still want things to move scaled by actual time elapsed.

Now suppose a frame took 20ms. That means you'll do one 16ms update and have 4ms left over for the next frame. Then say the next frame takes 15ms. That means you'll need to do one 16ms update, and then have 3ms left over for the next frame, and so on. Then suppose you have a long frame that takes 40ms - you'll need to perform two 16ms updates for that one frame. Or maybe a frame takes 10ms and there isn't enough of a remainder to make 16ms - in this case you won't perform a physics update.

Now you have a (hopefully) deterministic*, re-playable update loop.

The problem with this implementation so far is that the motion won't appear perfectly smooth; you'll get little hiccups because the fixed 16 ms update is not quite lined up with your actual time elapsed.

In your render function, use the "remainder" time (eg 4ms) and interpolate things forward in time by that amount. So using the current speed and angular velocity of the car, render it at where you estimate it to be 4ms into the future. This should be accurate enough to "look" correct. Hopefully you can get the car velocity and angular velocity from the physics engine.

*I say hopefully, because floating point is a tricky thing and even when you think it should be deterministic, it might not be. I'm not sure if iOS FP math is deterministic, or if it depends on compiler settings or what.

2
YvesLeBorg On

Interesting , but wrong device for that! I would say (just a guess) that using a time base will be very difficult. Although it is the same device playing back a recording, there exists a certain amount of randomness in the run loop, both during recording and during playback. To convince yourself, just log in any 'update' method the dt parameter, and you will see (assuming you are playing at 60 fps) some frequent 'skipped frames' ...

.016 .016 .033 <- argh ... what just happened here ? possibly some other task in the device consumed enough resource to block this run loop long enough to miss a scheduling frame altogether !

Now, the randomness will be different during recording and play-back. For example you could be receiving a notification on your phone, or AdSheet might be busy doing something vital (like preparing to serve the next ad for an app in background) between two ticks ... etc. So, you will never be able to repeat a time-base exactly.

I think. Just a guess :)