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
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.