Board game simulation (physics)

Hi all,

I’ve got a question regarding the physics in codea.
I’ve got inspired by the game ‘porTable’, which is developed in such a cool way. Have a look to the custom card deck configuration for the full card deck (you don’t have to buy it to see the configuration). With the ‘extended’ switch you can configure every single card in a full 54 cards deck. This is just an example how beautiful this is designed. The whole ‘game’ is designed in such a beautiful way. Every element can be touched and dragged around the table and it really feels ‘real’.
This realistic handling of objects is what I would like to do on my own.
I found an example in the forums (or it was one of the built-ins) which I tried to modify. I tweaked some parameters but it always feels far from being objects like domino bricks or something that are dragged around a table.

Did anybody try something similar or just have an idea how the physics parameters should be?

This is what my sample project looks like:

Thank you very much for your help in advance!

Regards,
Jan


supportedOrientations(LANDSCAPE_ANY)

function setup()
    watch("CurrentTouch.deltaX")
    inTouch = false
    ball = physics.body(CIRCLE, 50)
    ball.x = WIDTH / 2
    ball.y = HEIGHT / 2
    physics.gravity(0,0)
    walls = { 
        physics.body(EDGE, vec2(0, 0), vec2(WIDTH, 0)),
        physics.body(EDGE, vec2(0, 0), vec2(0, HEIGHT)),
        physics.body(EDGE, vec2(WIDTH, HEIGHT), vec2(WIDTH, 0)),
        physics.body(EDGE, vec2(WIDTH, HEIGHT), vec2(0, HEIGHT)),
    }
    for k, v in ipairs(walls) do
        v.restitution = 1
    end
    bodies = {}
    for i = 1, 5 do
        bodies[i] = physics.body(CIRCLE, 50)
        bodies[i].position = vec2(math.random(WIDTH), math.random(HEIGHT))
        -- bodies[i].mass = 100
        -- bodies[i].friction = 1000000
    end
end

function draw()
    background(0, 0, 0, 255)
    fill(255, 255, 0, 255)
    ellipse(ball.x, ball.y, 100)
    ball.linearVelocity = vec2(0,0)
    fill(255, 0, 0, 255)
    for k, v in ipairs(bodies) do
        ellipse(v.x, v.y, 100)
    end
end

function touched(touch)
    touchPoint = vec2(touch.x, touch.y)
    if touch.state == BEGAN and ball:testPoint(touchPoint) then
        inTouch = true
    end
    if touch.state == ENDED then
        inTouch = false
    end
    if touch.state == MOVING and inTouch then
        ball.linearVelocity = vec2(touch.deltaX, touch.deltaY) / DeltaTime
    end
end

Interesting. I felt the red balls felt a bit a light, adding a .density of about 10 to them seemed to help.

You also need to pretend there is friction with the board or something, this is nothing to do with .friction which is for physics bodies hitting each other, you’ll need to add some procedure for this not sure of the maths, but some function of reducing velocity to bring them to a stop.

Also, the behaviour of the yellow didn’t seem quite right, I think the DeltaTime isn’t deliverying value, I’d expect to be able to flick it and see it still working.

It’s the fact that you keep killing its velocity in the draw method. If you remove that line it works as a flick.

So for friction we want to calculate a force and apply it to the object in the opposite vector to it’s current movement using applyforce.

The force should notionally by F = uN where u is the friction coefficient and N is the force holding the object to the surface, or it’s weight in this case.

The interesting thing here is that it’s u is higher when it’s not moving than when it’s moving, and the friction force may need some extra control for when the object is close to stopping so it doesn’t over apply and wobble backwards and forwards.

http://www.school-for-champions.com/science/friction_equation.htm has a reasonable overview.

I’ll have a play with building something like that into the example, but it might be a few days before I have any time.

Working through it on a piece of paper it feels like it just needs a static acceleration applied opposite to the movement direction, and something to stop it.

Interesting. I will try the suggestions. I will also try to change it that way that I can touch and move every object. Not just the ball. Actually I will have to remove the ball.

I removed the ball velocity stopper and added a friction method with a parameter. I think it greatly improves the feel of the app.

One thing I haven’t done is control for draw speed, so the friction is affected by FPS which you wouldn’t want in a real game.


supportedOrientations(LANDSCAPE_ANY)

function setup()
    iparameter("friction", 1, 100, 20)
    inTouch = false
    ball = physics.body(CIRCLE, 50)
    ball.x = WIDTH / 2
    ball.y = HEIGHT / 2
    ball.restitution = .7
    ball.density = 1
    physics.gravity(0,0)
    walls = { 
        physics.body(EDGE, vec2(0, 0), vec2(WIDTH, 0)),
        physics.body(EDGE, vec2(0, 0), vec2(0, HEIGHT)),
        physics.body(EDGE, vec2(WIDTH, HEIGHT), vec2(WIDTH, 0)),
        physics.body(EDGE, vec2(WIDTH, HEIGHT), vec2(0, HEIGHT)),
    }
    for k, v in ipairs(walls) do
        v.restitution = .7
    end
    bodies = {}
    for i = 1, 5 do
        bodies[i] = physics.body(CIRCLE, 50)
        bodies[i].position = vec2(math.random(WIDTH), math.random(HEIGHT))
        bodies[i].density = 2
        bodies[i].restitution = .7
        -- bodies[i].mass = 100
        --bodies[i].friction = 1000000
    end
end

function draw()
    background(0, 0, 0, 255)
    fill(255, 255, 0, 255)
    applySurfaceFriction(ball)
    ellipse(ball.x, ball.y, 100)
    --ball.linearVelocity = vec2(0,0)
    fill(255, 0, 0, 255)
    for k, v in ipairs(bodies) do
        applySurfaceFriction(v)
        ellipse(v.x, v.y, 100)
    end
    
end

function applySurfaceFriction(body)
    --if it's slow, stop it dead
    if body.linearVelocity:len() < friction*2 then
        body.linearVelocity = vec2(0,0)
    else
        body.linearVelocity = body.linearVelocity - friction*body.linearVelocity:normalize()
    end
end



function touched(touch)
    touchPoint = vec2(touch.x, touch.y)
    if touch.state == BEGAN and ball:testPoint(touchPoint) then
        inTouch = true
    end
    if touch.state == ENDED then
        inTouch = false
    end
    if touch.state == MOVING and inTouch then
        ball.linearVelocity = vec2(touch.deltaX, touch.deltaY) / DeltaTime
    end
end

‘it might be a few days before I have any time’ ? :slight_smile: I’m eager to try it out but Can’t do that before tomorrow evening :slight_smile:
Thank you!

I hope you don’t mind if I keep on askink stupid questions.
I tried moving the body object into a class to be able to put in extended functionality later. It raises an error now. Something basic that I miss because LUA is so new to me. Must be something with the array/table…
In the next step I would like to implement the touch handling for every body object and remove the different colored ball. Then I could touch and move all objects. I put in some thoughts that are outcommented now in the touched function. Again I miss some knowledge about tables.
Does anybody have a good link helping me to start with arrays/tables in LUA?

Next planned steps:

-Implement touch and move for all objects.

-Draw the objects as sprites.

-Make the objects rectangles.

-Properly rotate the rectangles if they collide off the center of an edge.

-Allow multiple objects to be touched at the same time.

-Allow multiple touches inside a single object to rotate it.

Not sure about the order of 2, 3 and 4, yet.


supportedOrientations(LANDSCAPE_ANY)

function setup()
    iparameter("friction", 1, 100, 50)
    inTouch = {}
    ball = physics.body(CIRCLE, 50)
    ball.x = WIDTH / 2
    ball.y = HEIGHT / 2
    ball.restitution = .7
    ball.density = 1
    physics.gravity(0,0)
    walls = { 
        physics.body(EDGE, vec2(0, 0), vec2(WIDTH, 0)),
        physics.body(EDGE, vec2(0, 0), vec2(0, HEIGHT)),
        physics.body(EDGE, vec2(WIDTH, HEIGHT), vec2(WIDTH, 0)),
        physics.body(EDGE, vec2(WIDTH, HEIGHT), vec2(0, HEIGHT)),
    }
    for k, v in ipairs(walls) do
        v.restitution = .7
    end
    bodies = {}
    for i = 1, 5 do
        bodies[i] = Brick()
        --bodies[i] = physics.body(CIRCLE, 50)
        --bodies[i].position = vec2(math.random(WIDTH), math.random(HEIGHT))
        --bodies[i].density = 2
        --bodies[i].restitution = .7
    end
end

function draw()
    background(0, 0, 0, 255)
    fill(255, 255, 0, 255)
    applySurfaceFriction(ball)
    ellipse(ball.x, ball.y, 100)
    ball.linearVelocity = vec2(0,0)
    fill(255, 0, 0, 255)
    for k, v in ipairs(bodies) do
        applySurfaceFriction(v)
        ellipse(v.x, v.y, 100)
    end
end

function applySurfaceFriction(body)
    --if it's slow, stop it dead
    if body.linearVelocity:len() < friction * 2 then
        body.linearVelocity = vec2(0,0)
    else
        body.linearVelocity = body.linearVelocity - friction * body.linearVelocity:normalize()
    end
end

function touched(touch)
    touchPoint = vec2(touch.x, touch.y)
    if touch.state == BEGAN and ball:testPoint(touchPoint) then
        inTouch = true
    end
    if touch.state == ENDED then
        inTouch = false
    end
    if touch.state == MOVING and inTouch then
        ball.linearVelocity = vec2(touch.deltaX, touch.deltaY) / DeltaTime
    end
    
    --if touch.state == BEGAN then
    --    --Test every body for touchPoint and add touch.id an body to table
    --    for k, v in ipairs(bodies) do
    --        if v:testPoint(touchPoint) then
    --            inTouch.Add()
    --        end
    --    end
    --elseif touch.state == ENDED then
    --    --Remove body with id touch.id from table
    --    
    --elseif touch.state == MOVING then
    --    --Set body.linearVelocity
    --    
    --end
end

---------------

Brick = class()

function Brick:init()
    self.body = physics.body(CIRCLE, 50)
    self.body.position = vec2(math.random(WIDTH), math.random(HEIGHT))
    self.body.density = 2
    self.body.restitution = .7
end

You need to add body to the for loop in draw(). See below.


    for k, v in ipairs(bodies) do
        applySurfaceFriction(v.body)
        ellipse(v.body.x, v.body.y, 100)
    end

To make the objects rectangles do that in physics, this will mean you get proper bouncing/spinning for free
To construct:

local size = 10 --size of square
    square = physics.body(POLYGON, vec2(-size,-size), vec2(-size,size), vec2(size,size), vec2(size, -size)) --create a square
    square.position = vec2(math.random(20,WIDTH-300),HEIGHT - math.random(1,20)) --put it somewhere random

Then to draw it, similar to to circles, but you have to worry about rotation- if you replace it with a sprite a similar method may work, for position and rotation.
Basically the translate moves the logical coordinates to the objects location meaning we can then just draw it at 0,0 in coordinate space. Rotate then rotates the coordinate space so it draws it spun correctly. The pushMatrix, popMatrix calls localise translation and rotation to just what we are doing, so if you were doing this for a table of squares then the whole snippet below needs to be inside the for loop.

pushMatrix()
            translate(square.x, square.y)
            rotate(square.angle)
            rect(-v.size,-v.size, v.size*2, v.size*2)
popMatrix() 

@dave1707: How sure. Stupid me. :wink:

@spacemonkey: Will try that, thanks.

I found a nice tutorial linked at.

http://twolivesleft.com/Codea/Talk/discussion/1305/lua-tables/p1

Will try to implement the touch on all object with that.

For touch, for just being able to touch everything you’ve made it too complicated, just tweak it as below and you should be fine:

function touched(touch)
    touchPoint = vec2(touch.x, touch.y)

    if touch.state == MOVING then
        if ball:testPoint(touchPoint) then
            ball.linearVelocity = vec2(touch.deltaX, touch.deltaY) / DeltaTime
        end
        for k, v in ipairs(bodies) do
            if v.body:testPoint(touchPoint) then
                v.body.linearVelocity = vec2(touch.deltaX, touch.deltaY) / DeltaTime
            end
        end
    end

end

That should give you multitouch and touch all bodies.

As to multitouch rotating a body, that’s a little trickier, I’ve got some other code somewhere that seperates detecting touches from acting on them which might go some way, I’ll have a look for it.

Current state:

Friction is not applied during rotation. That looks a little funny. Actually if I spin the rectangles very fast the body shape seems to get out of sync with the drawn shape. I wonder how that can happen.

One other issue is if I push away one object with the object under my finger and my finger gets insite the bound of the pushed object I start to control the pushed object then. That is supposed to happen the way the touch function is implemented. I think I will find a way to solve that later.


supportedOrientations(LANDSCAPE_ANY)

function setup()
    iparameter("friction", 1, 100, 100)
    physics.gravity(0,0)
    walls = { 
        physics.body(EDGE, vec2(0, 0), vec2(WIDTH, 0)),
        physics.body(EDGE, vec2(0, 0), vec2(0, HEIGHT)),
        physics.body(EDGE, vec2(WIDTH, HEIGHT), vec2(WIDTH, 0)),
        physics.body(EDGE, vec2(WIDTH, HEIGHT), vec2(0, HEIGHT)),
    }
    for k, v in ipairs(walls) do
        v.restitution = .7
    end 
    bodies = {}
    for i = 1, 5 do
        bodies[i] = Brick()
    end
end

function draw()
    background(0, 0, 0, 255)
    for k, v in ipairs(bodies) do
        applySurfaceFriction(v.body)
        v:draw()
    end
end

function applySurfaceFriction(body)
    --if it's slow, stop it dead
    if body.linearVelocity:len() < friction * 2 then
        body.linearVelocity = vec2(0,0)
    else
        body.linearVelocity = body.linearVelocity - friction * body.linearVelocity:normalize()
    end
end

function touched(touch)
    if touch.state == MOVING then
        for k, v in ipairs(bodies) do
            if v.body:testPoint(vec2(touch.x, touch.y)) then
                v.body.linearVelocity = vec2(touch.deltaX, touch.deltaY) / DeltaTime
            end
        end
    end
end

---------------

Brick = class()

function Brick:init()
    self.width = 100
    self.height = 50
    self.colour = color(255, 0, 0, 255)
    
    --self.body = physics.body(CIRCLE, 50)
    self.body = physics.body(POLYGON, vec2(-self.width / 2, -self.height / 2),
        vec2(-self.width / 2, self.height / 2), vec2(self.width / 2, self.height / 2),
        vec2(self.width / 2, -self.height / 2))
    self.body.position = vec2(math.random(WIDTH), math.random(HEIGHT))
    self.body.density = 2
    self.body.restitution = .7
end

function Brick:draw()
    fill(self.colour)
    
    --ellipse(self.body.x, self.body.y, 100)
    pushMatrix()
            translate(self.body.x, self.body.y)
            rotate(self.body.angle)
            rect(-self.width / 2, -self.height, self.width, self.height)
    popMatrix() 
end

Tweaks:

  • friction is now applied to rotation as well, I didn’t actually research if this was a legit way of doing friction for rotating bodies

  • rotation was a bit off because you had missed a " / 2" in the drawing routine so it was drawing the rectangles off centre from their actual locations.

  • touch changed. Now you have touched an object if your touch starts on it, then your finger is attached that object regardless, this means your finger can end up off the object but still pushing it if it hits things. It also still does multi touch. I added a table of touches as if you want to then try and figure out how to two finger spin things you’ll definitely need that.

supportedOrientations(LANDSCAPE_ANY)

function setup()
    iparameter("linearFriction", 1, 100, 100)
    iparameter("angularFriction", 1, 100, 10)
    watch("bodies[1].body.angularVelocity")
    physics.gravity(0,0)
    walls = { 
        physics.body(EDGE, vec2(0, 0), vec2(WIDTH, 0)),
        physics.body(EDGE, vec2(0, 0), vec2(0, HEIGHT)),
        physics.body(EDGE, vec2(WIDTH, HEIGHT), vec2(WIDTH, 0)),
        physics.body(EDGE, vec2(WIDTH, HEIGHT), vec2(0, HEIGHT)),
    }
    for k, v in ipairs(walls) do
        v.restitution = .7
    end 
    bodies = {}
    for i = 1, 5 do
        bodies[i] = Brick()
    end
    bodyTouches = {}
end

function draw()
    background(0, 0, 0, 255)
    for k, v in ipairs(bodies) do
        applySurfaceFriction(v.body)
        v:draw()
    end
end

function applySurfaceFriction(body)
    --if it's slow, stop it dead
    --movement friction
    if body.linearVelocity:len() < linearFriction * 2 then
        body.linearVelocity = vec2(0,0)
    else
        body.linearVelocity = body.linearVelocity - linearFriction * body.linearVelocity:normalize()
    end
    --spinning friction
    if math.abs(body.angularVelocity) < angularFriction * 2 then
        body.angularVelocity = 0
    else
        if body.angularVelocity > 0 then
            body.angularVelocity = body.angularVelocity - angularFriction
        else
            body.angularVelocity = body.angularVelocity + angularFriction
        end
    end
end

function touched(touch)
    if touch.state == BEGAN then
        for k,v in ipairs(bodies) do
            if v.body:testPoint(vec2(touch.x, touch.y)) then
                bodyTouches[touch.id] = { body = k }
            end
        end
    end
    if touch.state == ENDED then
        if bodyTouches[touch.id] ~= nil then
            bodyTouches[touch.id] = nil
        end
    end 
    if touch.state == MOVING then
        if bodyTouches[touch.id] ~= nil then
            bodies[bodyTouches[touch.id].body].body.linearVelocity = vec2(touch.deltaX, touch.deltaY) / DeltaTime
        end
    end
end

---------------

Brick = class()

function Brick:init()
    self.width = 100
    self.height = 50
    self.colour = color(255, 0, 0, 255)

    --self.body = physics.body(CIRCLE, 50)
    self.body = physics.body(POLYGON, vec2(-self.width / 2, -self.height / 2),
        vec2(-self.width / 2, self.height / 2), vec2(self.width / 2, self.height / 2),
        vec2(self.width / 2, -self.height / 2))
    self.body.position = vec2(math.random(WIDTH), math.random(HEIGHT))
    self.body.density = 2
    self.body.restitution = .7
end

function Brick:draw()
    fill(self.colour)

    --ellipse(self.body.x, self.body.y, 100)
    pushMatrix()
            translate(self.body.x, self.body.y)
            rotate(self.body.angle)
            rect(-self.width / 2, -self.height / 2, self.width, self.height)
    popMatrix() 
end

I stumbled on this as well… there’s is a feature in Box2d (undocumented in Codea) for dampening movement. If you comment out the applySurfaceFriction call and instead add these lines in the Brick:init :

    self.body.linearDamping = 0.9
    self.body.angularDamping = 0.9

The number can be varied 0 being no dampening.

The advantage of this approach is that framerate won’t affect it, which is a current flaw in the applySurfaceFriction. Also it’s all handled for you magically. However, the damping seems to reduce speed proportionally to current speed, so as they slow down, they still keep moving at very low speeds, and the slow down varies a lot. I feel the applySurfaceFriction approach gives a more realistic feel, and dampening feels maybe more like an object would be slowed by friction with the air, as opposed to the idea of the object sliding on a surface.

That’s good. If I set the damping to something like 10 it looks good to me.

The touched implementation above is really cool, too. That’s how I wanted to implement it. I just didn’t know how to handle the tables… To be honest I don’t know yet, either… I don’t understand the setting of the table value

bodyTouches[touch.id] = { body = k }

I’d expect it to look like

bodyTouches[touch.id] = { v.body }

What’s the result of the comparison of ‘body’ and ‘k’? Or even more… There isn’t an object ‘body’…

I just implemented some elseif’s in the code.

bodyTouches is a table to hold the collection of touches.

Doing variable = { x=1, y=2, z=3 } effectively sets up a table for yur variable with named parameters, so you can look at variable.x variable.y etc

So in my code above I am effectively filling the bodyTouches table with tables, which is quite wasteful and probably because I was thinking of putting more stuff in there initially. body = k isn’t a cmparison, it’s an assignment.

It can be simplified as below. k is the key from your bodies which we use to affect the right object.

function touched(touch)
    if touch.state == BEGAN then
        for k,v in ipairs(bodies) do
            if v.body:testPoint(vec2(touch.x, touch.y)) then
                bodyTouches[touch.id] = k
            end
        end
    end
    if touch.state == ENDED then
        if bodyTouches[touch.id] ~= nil then
            bodyTouches[touch.id] = nil
        end
    end 
    if touch.state == MOVING then
        if bodyTouches[touch.id] ~= nil then
            bodies[bodyTouches[touch.id]].body.linearVelocity = vec2(touch.deltaX, touch.deltaY) / DeltaTime
        end
    end
end

For the two finger spin: the way it is done now multiple touches in the same object are aleeady registered and handled. Just the angular velocity is not set. Even with a single touch the angular velocity should be set. If me move the object holding it off the center (off the axis we are moving along, to be more specific) we have to apply a little angular velocity. I will think about that.

Ah I was confused. A comparison is == not a single =.
Is it right that you could also just add v.body to the table and directly use that object below instead of getting it from the bodies table again?

Yes, setting it with

bodyTouches[touch.id] = v.body

and applying the velocity with

bodyTouches[touch.id].linearVelocity = vec2(touch.deltaX, touch.deltaY) / DeltaTime

works. Looks easier. As long as we don’t need anything else than the body, of course.