Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Game loop / physics step question

edited October 2013 in Questions Posts: 53

So I'm building a physics game with Codea where two players take turns and there is a turn-tracking server in the cloud. When one player moves something I want to be able to replay the same move on the other player's app. This means I need to have perfectly replay-able physics.

So, thinking this through, I just need to make sure the framerate (calls to the draw() function) doesn't make a difference when doing things like adding bodies or applying forces to the Box2D running in the background (since Box2D itself is supposed to be perfectly predictable, right?). So my question is.... when is Box2D's physics step occurring? Can a variable number of steps occur between draw() calls..... or is it always:
1) draw
2) physics step
3) draw
4) physics step ... ?

Any help is appreciated!
- Mike


  • Jmv38Jmv38 Mod
    Posts: 3,280

    I dont know the details of box2d, but you cannot rely on 2 ipads having the same frame rate. And from time to time there is a small delay that will vary from ipad to ipad. So relying on box2d to have identical scenes is probably expecting too much.

  • Posts: 53

    Hmm, yeah the framerate would definitely vary... but I believe box2d is supposed to be pretty exact... like if I put a ball in a room, apply an exact force once, and replay that scene over and over... the ball should land in the exact same spot everytime, no matter what the framerate is, right?

    If that's true, it's only a question of when is the framerate making some difference... like it's common advice to not change a scene in a collision callback - you should save some marker to do the change in your next game loop.... my concern is the physics scene could progress in a varying number of steps before the next game loop. So if I knew there was exactly one step between game loop calls, I'd be okay.

    Does this make sense?
    - Mike

  • Posts: 1,595

    Neztec, I tried to accomplish this with my game splinerider but it just seems now that box2d wants to give different results every time, I managed to get it to being deterministic before but not now, I'll have another play with it and get back to you.

  • Posts: 1,976

    How about one main iPad, and another secondary iPad? The main could run all the calculations and handle Box2D, and frequently sync all the positions and force and rotations etc. to the cloud, and the secondary could run its own Box2D calculations so it's smooth movement, but also retrieves the data from the cloud and updates positions and force and rotations etc. frequently. Might require two projects though, one with the code to upload to the cloud, and one with the code to download from the cloud...

  • Posts: 157

    I think what you're going to have to do is store enough telemetry about the move in order to perfectly reproduce it at the other end.

    For example, take a game like Peggle. You'd have to store the starting condition of the board (which pegs are still alive) and the angle and force with which the ball is fired. Save the ball's velocity and position periodically and store each collision with another object.

    The key is to store enough data to tween the moving object reliably without having to store every single frame. 5-10 updates per second should be enough for a turn-based game.

  • Posts: 372

    @Luatee, weird it's working fine for me. Try this code atleast for three loops, I get the same results even if the FPS drops from 60 to 12.

    supportedOrientations(PORTRAIT_ANY) function setup() --clearLocalData() font("Courier") loop=0 tab1={} tab2={} restart=true fps = 60"fps") NumberOfCollisions = 5 print("Number Of Collisions Before Check = 5") print("change this value in setup if you wish too.") print("clear LocalData if you change the value") end function setup2() tab2={} count=0 loop=loop+1 print("loop: "..loop) if b1 then b1:destroy() b2:destroy() e1:destroy() e2:destroy() e3:destroy() e4:destroy() end WIDTH = 768 HEIGHT = 1024 e1=physics.body(EDGE,vec2(0,0),vec2(0,HEIGHT)) e2=physics.body(EDGE,vec2(0,HEIGHT),vec2(WIDTH,HEIGHT)) e3=physics.body(EDGE,vec2(WIDTH,HEIGHT),vec2(WIDTH,0)) e4=physics.body(EDGE,vec2(WIDTH,0),vec2(0,0)) b1=physics.body(CIRCLE,60) b1.gravityScale=0 b1.restitution=1 b1.linearDamping=0 b1.linearVelocity=vec2(380,700) b1.x=200 b1.y=600 b1.friction=0 b2=physics.body(CIRCLE,60) b2.gravityScale=0 b2.restitution=1 b2.linearDamping=0 b2.linearVelocity=vec2(620,450) b2.x=400 b2.y=200 b2.friction=0 end function draw() fps = .9*fps + .1/DeltaTime background(40,40,50) textMode(CORNER) if restart then setup2() -- setup for the physics objects restart=false end if loop == 3 then for i = 1,5 do print("Drop FPS") end end noFill() strokeWidth(4) stroke(255) ellipse(b1.x,b1.y,120) ellipse(b2.x,b2.y,120) fill(255) text("x,y loop 1 x,y loop 2,3,4 etc.",100,1000) text("loop "..loop,600,1000) text("Shows the x,y position every 5 collisions for 100 collisions.",100,300) text("Retains the values for loop 1 to compare to loops 2,3,4, etc.",100,260) text("White fill represents that it mathches the previous value.",100,220) text("Red fill represent the value doesnt match.",100,180) text("Local data is saved to check values of first loop.",100,140) for a,b in pairs(tab1) do pushStyle() if readLocalData("tab1x"..a) == b.x and readLocalData("tab1y"..a) == b.y then fill(255, 255, 255, 255) else fill(255, 0, 0, 255) end text(b.x.." "..b.y,100,HEIGHT-50-a*20) popStyle() end for a,b in pairs(tab2) do pushStyle() if b.x == tab1[a].x and b.y == tab1[a].y then fill(255, 255, 255, 255) else fill(255, 0, 0, 255) end text(b.x.." "..b.y,300,HEIGHT-50-a*20) popStyle() end text(fps, WIDTH - 100, HEIGHT - 50) end function collide(c) if c.state==BEGAN then count = count + 1 if count%NumberOfCollisions == 0 then if loop==1 then table.insert(tab1,vec2(b1.x,b1.y)) if readLocalData("tab1x"..#tab1) == nil then print("Saved Values") saveLocalData("tab1x"..#tab1, b1.x) saveLocalData("tab1y"..#tab1, b1.y) else print("Checked Values") end end if loop>1 then table.insert(tab2,vec2(b1.x,b1.y)) end end if count==20*NumberOfCollisions then restart=true end end end
  • dave1707dave1707 Mod
    Posts: 7,195

    @Saurabh Change b1 and b2 physics.body(CIRCLE,60) to (CIRCLE,85). Change ellipse(b1.x,b1.y,120) to ellipse(b1.x,b1.y,170) and change ellipse(b2.x,b2.y,120) to ellipse(b2.x,b2.y,170) and run it again. It might work sometimes for some values, but that doesn't mean it works for all values.

  • Posts: 372

    Oh!! I never tried that out. But how does it make any sense sometimes it's dead accurate in all loops and sometimes it isn't? It can't be coincidence that it gives the same value every loop.

  • Posts: 53

    Lots of interesting thoughts.... @Luatee - I found and read your other post about your game and the wacky physics two-path issue....

    I'm wondering... is there anywhere we can look at Codea's code? I'm really curious to see how the step is being called. Re-reading the box2d docs - they guarantee deterministic behavior on the same device (but not across two devices with different OSes due to floating point number calculation differences).

  • dave1707dave1707 Mod
    Posts: 7,195

    @Saurabh I never figured out what was happening when I originally wrote code to show the difference. I think that some variable isn't being reset properly when a restart is done so it kind of alternates.

Sign In or Register to comment.