Action games that require sophisticated collision response should definitely consider the Box2D library. Box2D is a rigid body physics library that does not provide any rendering functionality. In this tutorial, we'll look at how to run and render a Box2D simulation.
require ( 'Box2D' ) display:create ( "Physics Example", 800, 600, 32, true ) scene = Layer ( ) camera = Camera ( ) camera:set_scale ( 0.1, 0.1 ) scene:add_child ( camera ) display.viewport.camera = camera
There are a few ways to render a Box2D simulation using the AGen framework. The most efficient approach would be to create an individual sprite for each body. For this purpose we will introduce a few custom functions to the body object. The "OnCreate" function adds a new sprite representing the body and draws its existing shapes. After each step of the physics simulation, we need to call the "OnUpdate" function for every body in the world. It syncs the position and angle of each sprite to its associated body. Notice that Box2D uses radians instead of degrees and that bodies rotate in the opposite direction compared to sprites.
function b2.Body:OnCreate ( ) local sprite = Sprite ( ) scene:add_child ( sprite ) -- iterate and draw all the shapes of a body local fix = self:GetFixtureList ( ) while fix ~= nil do local shape = fix:GetShape ( ) local type = shape:GetType ( ) if type == "circle" then local localPosition = shape.position local radius = shape.radius sprite.canvas:move_to ( localPosition.x, localPosition.y ) sprite.canvas:circle ( radius ) sprite.canvas:rel_line_to ( 0, -radius ) elseif type == "polygon" then local vertices = shape.vertices local vertexCount = #shape.vertices sprite.canvas:move_to ( vertices.x, vertices.y ) for i = 1, vertexCount, 1 do sprite.canvas:line_to ( vertices[i].x, vertices[i].y ) end sprite.canvas:close_path ( ) end sprite.canvas:set_line_style ( 0.1, WHITE, 1 ) sprite.canvas:stroke ( ) fix = fix:GetNext ( ) end -- attach a sprite to the body self.sprite = sprite end function b2.Body:OnDestroy ( ) scene:remove_child ( self.sprite ) self.sprite = nil end function b2.Body:OnUpdate ( ) -- synchronize the sprite to the body local position = b2.Vec2 ( ) self:GetPosition ( position ) local angle = self:GetAngle ( ) self.sprite:set_position ( position.x, position.y ) self.sprite:set_rotation_r ( -angle ) end
gravity = b2.Vec2 ( 0, -10 ) world = b2.World ( gravity ) timer = Timer ( ) timer:start ( 16, true ) timer.on_tick = function ( timer ) -- update the physics simulation local seconds = timer:get_delta_ms ( ) / 1000 world:Step ( seconds, 10, 8 ) -- iterate all bodies and update their sprites local body = world:GetBodyList ( ) while body do body:OnUpdate ( ) body = body:GetNext ( ) end end
The physics simulation will run whenever we start the script. However, our Box2D world is still empty so let's add some bodies to it. First, we will make a ground body using a static rectangle shape. Box2D has a fairly specific way of creating new bodies and shapes that we won't get into for now. What you should know however is that shapes with a density of 0 are considered 'static'. Static shapes always remain immobile even when another body collides with them.
-- create ground object local def = b2.BodyDef ( ) def.type = "staticBody" def.position = b2.Vec2 ( 0, 0 ) ground = world:CreateBody ( def ) local def = b2.PolygonShape ( ) def:SetAsBox ( 10, 1 ) local fix = b2.FixtureDef ( ) fix.density = 0 fix.friction = 1 fix.restitution = 0 fix.isSensor = false fix.shape = def ground:CreateFixture ( fix )
To make this example interactive, we will let the user create circles by clicking with the mouse. We set the position for our new body based on the current location of the mouse cursor. You will notice that Box2D uses an object called b2.Vec2 to represent positions (and vectors). Also note that new shapes/fixtures require quite a few parameters. A few of them are of particular interest. Density (in kg/mē) affects the mass of the body. Friction (between 0 and 1) is the loss in velocity that occurs when two bodies are touching. Restitution (between 0 and 1) could be described as a measure of elasticity.
mouse.on_press = function ( mouse, button ) local x, y = camera:get_world_point ( mouse.xaxis, mouse.yaxis ) -- create a new body local def = b2.BodyDef ( ) def.type = "dynamicBody" def.position = b2.Vec2 ( x, y ) def.angle = 0 def.linearDamping = 0 def.angularDamping = 0 def.fixedRotation = false def.bullet = false def.allowSleep = false def.awake = true local body = world:CreateBody ( def ) -- add a circle shape to the body local def = b2.CircleShape ( ) def.radius = 1 def.position = b2.Vec2 ( 0, 0 ) local fix = b2.FixtureDef ( ) fix.density = 10 fix.friction = 1 fix.restitution = 0 fix.isSensor = false fix.shape = def body:CreateFixture ( fix ) body:OnCreate ( ) end
It should be pointed out that losing all references to a body from Lua does not remove it from the world. Bodies will remain in the simulation until an explicit call to "world:DestroyBody(body)".