How to use game physics in Solar2D

Andreas Löw
Last updated:
GitHub
How to use game physics in Solar2D

Welcome to this tutorial on how to use game physics and create 2d collisio shapes for Solar2D. Here's what you are going to learn:

  • How to enable game physics in Solar2D
  • How to easily create collision shapes for your sprites
  • How to handle collisions in your game

Full source code for this example project is available on GitHub. It also includes the assets used for this tutorial. It's ok to play and experiment with the sprites - just don't use them in your own projects without asking for permission first.

You are going to create a small simple application which will drop several items and let them collide. The title image of this post should give you an idea of the final result.

The assets

Let's have a look at the assets first: We've chosen these assets to explain different aspects of the physics engine. There are simple items (orange) and complex items like the cherries. Some of them will be used as static items (ground), or work without collisions (background).

background.png
background.png
ground.png
ground.png
banana.png
banana.png
cherries.png
cherries.png
crate.png
crate.png
orange.png
orange.png

For the simplicity of this tutorial, we don't use sprite sheets - as we should. If you want to learn about sprite sheets and how to optimize your game's performance, read How to create Sprite Sheets and Animations with Solar2D.

Create the physics body shapes

The physics engine in Solar2D - like all 2d physics engines - requires a vector description of the collision objects. The sprites are all pixel based - which means nothing that the physics engine can work with.

Solar2D can create physics shapes from the following base forms:

  • Circle
  • Box / Rectangle
  • Polygon

As one might expect, creating the physics shape for the orange is easy: It's almost a circle - so it's a perfect match for that sprite. The crate could be represented by a rectangle.

However, the other shapes are more complex. The ground or the banana can't be easily built from rectangles or circles. A polygon shape is required here.

Solar2D can create Polygonal Body - but you have to specify the coordinates for each point of the polygon. But how to do that?

This is where PhysicsEditor comes in play. It's a visual editor for 2d collision shapes for all kinds of game engines and it supports Solar2D out of the box!

Install PhysicsEditor

If you do not already have it installed get PhysicsEditor for your platform and install it. It runs on Windows, macOS and Linux.

After starting the first thing to do is to set the exporter matching your project - which is Corona SDK:

PhysicsEditor: set exporter
Set the exporter to Corona SDK

Next drop the sprites onto the left pane of PhysicsEditor to import them

PhysicsEditor: Import Sprites
Import sprites by dropping them onto PhysicsEditor

The Automated Shape-Tracer

Now select the banana image on the left pane and click on the Automated Shape-Tracer button that's the magic wand icon in the toolbar). This opens the Automated Shape-Tracer dialog.

PhysicsEditor: Automated Shape Tracer
Automatically create polygon body definitions with the tracer tool

The most important control in this dialog is the Tolerance setting. It defines how close the tracer tries to match the shape's outline. The closer the outline to the shape is the more vertexes will be added to the shape.

This is an important aspect for your game - too many vertexes might kill the performance because a lot of calculation time is needed. On the other hand a too high value will create a shape which becomes more and more rough.

It's important to find a good balance for this.

physicseditor too detailed 2x

Too detailed
Tolerance = 0.5
Vertexes = 167

physicseditor good trace 2x

Good
Tolerance = 4.0
Vertexes = 21

physicseditor bad fit 2x

Too rough
Tolerance = 20.0
Vertexes = 7

Now press OK and see the final result in the main window.

Collision parameters

Setting Solar2D collision parameters in PhysicsEditor

Let's now set up the collision parameters. These parameters can be applied per fixture - which means that you can compose a shape from different polygons and set parameters for each of them.

First give the fixture a name - this allows you to identify individual parts of a shape later - E.g. the head of a character or other parts. For now type "banana" in the field called Identifier.

Next set the density. The Density multiplied with the area of the shape is the mass of the object. So you device how heavy the object will be here.

Bounce is the elasticity of an object. An object with bounce=0.0 does not bounce, one with 1.0 is repelled completely from other objects. Set it to 0.2 for the banana.

Friction is the value which controls how objects slide on each other. A value of zero would make an object slide better than on ice. Objects slide less with higher values. Set it to 0.5.

IsSensor tells the object to detect but not to participate in collisions. That means that you'll get notified if an object toughes the sensor - but it does not collide with it.

Group sets groups for collision - only objects of the same group number can collide. With this, you can create independent sets of collision objects.

Bit's name, Cat. (Category) and Mask also control the collision behavior of objects. The object's Category value defines what the object is, the object's Mask value tells it what it can collide with. The bit names can be defined like you want - they are just here to give them a better meaning than just assigning numbers.

Set the Category "fruit" for the banana.

Setting a circle shape

The orange might be approximated by using a polygon but this would be a poor result. It is better to use the circle shape for that.

Solar2D comes with one speciality: It does not allow setting the center of a circle shape. So if you want to use a circle you must make sure that the sprite is centered.

Now click on and use the small circular handle to adjust the size to the ball. Center the circle over the ball shape.

PhysicsEditor: Circle shape

Set the values according to the image.

Modifying polygons manually

To avoid objects from dropping from the floor immediately, I would recommend adding some walls to the left and right of the gound sprite.

For this select the ground sprite and run the tracer. After that, double-click near a line to add new vertices or double-click a vertex to remove it.

Adjust the polygon as seen on the next screenshot. Set the parameters according to the parameter panel.

PhysicsEditor: Polygon shape

You can of course create polygon body shapes manually using the tool. This adds a small triangle. Drag it with the mouse use double clicks to add / remove vertices.

Using a multi fixture body

The final shape are the cherries. We are going to separate the leaves and the cherries from the body to be able to handle it differently in the collision detection.

Use the tracer and select the vertices of the cherries - press delete to remove them all at once. Set the identifier to "leaves".

Use the tracer again or create a polygon shape for each of the cherries separately. Name them "left" and "right".

PhysicsEditor: Multi fixture body

Publishing

Finally, press Publish and save the data file as shapedefs.lua.

Also save the complete document in case you want to modify it later.

Let's code

Now it's time to dive into the code.

Let's first init the game scene - hide the status bar and activate physics simulation and add our background image:

-- init display
display.setStatusBar( display.HiddenStatusBar )

-- background image
local background = display.newImage( "sprites/background.png" )
background.x = display.contentCenterX
background.y = display.contentCenterY

-- init physics
local physics = require("physics")
physics.start()

Next let's load the physics data we created before:

-- load the physics data, scale factor is set to 1.0
local physicsData = (require "shapedefs").physicsData(1.0)

You could set different scaling factory for the shapes to adjust to the display resolution. But this might also scale the object's mass resulting in different behavior.

Now add the floor shape:

-- create physical ground shape
local ground = display.newImage("sprites/ground.png")
ground.x = display.contentCenterX;
ground.y = display.contentHeight-ground.height/2
ground.myName = "ground"
physics.addBody( ground, "static", physicsData:get("ground") )

This loads the ground.png, sets its position and adds it as a static, non-moving object, to the physics world. Easy, isn't it?

We also set a property called myName - this allows us to print details about object collisions later.

Now add a function we can call from a timer to create new items:

-- create a random new object
local function newItem()

    -- all items
	local names = {"banana", "crate", "cherries", "orange"};

    -- just pick a random one
	local name = names[math.random(#names)];

	-- set the graphics
	obj = display.newImage("sprites/"..name..".png");

	-- save object's name for collision output
	obj.myName = name

	-- set the shape
	physics.addBody( obj, physicsData:get(name))

	-- random start location
	obj.x = math.random( display.contentWidth/2  ) + display.contentWidth/4
	obj.y = -obj.contentHeight

	-- add collision handler
    obj.collision = onLocalCollision
    obj:addEventListener( "collision", obj )
end

-- call newItem 20 times with a delay of 1s
timer.performWithDelay( 1000, newItem, 20 )

This function chooses from an array of items and selects a random one. Then loads the according sprite and also assigns a name to it.

This time the body is added as dynamic body - simply by omitting the static parameter.

Also choose a random position somewhere above the scene and add a collision handler.

The last line uses the timer to call the newItem() function 20 times with a delay of 1s.

Finally, add the collision handler - it will not do much right now - except for printing the collision objects and parts that collide. Add the collision handler above the newItem() function.

The collision event contains the number of the fixture. Use getFixtureId method to retrieve the identifier we set in PhysicsEditor:

local function onLocalCollision( self, event )

    -- retrieve fixture names from physics data
    local selfFixtureId = physicsData:getFixtureId(self.myName, event.selfElement)
    local otherFixtureId = physicsData:getFixtureId(event.other.myName, event.otherElement)

    -- print collision information
    print(
        self.myName .. ":" .. selfFixtureId ..
        " collision "..event.phase.." with " ..
        event.other.myName .. ":" .. otherFixtureId
        )
end

That's it.

Final project

You can download the source code or clone it from GitHub. Have fun!