Over the past few LÖVE projects I’ve constantly rewritten the way objects were declared. Each time I started a new project I found my previous method either unsuitable, or just plain ugly.
My first method:
function MakeBox(x,y,s,img,mass,r,g,b,a,sw,sh) -- Spawns a box: Start X, Start Y, Scale of img, image, Mass, Colour red, Colour green, Colour blue, Colour alpha, Width, Height
--Base
boxcount = boxcount + 1
boxrealcount = boxrealcount + 1
_G["bodybox"..boxcount] = love.physics.newBody( world1, x, y, mass, mass ) --Box
_G["scalebox"..boxcount] = s
_G["spawnmass"..boxcount] = mass
--Colours
_G["redbox"..boxcount] = r
_G["greenbox"..boxcount] = g
_G["bluebox"..boxcount] = b
_G["alphabox"..boxcount] = a
--Image
_G["imagebox"..boxcount] = img
_G["shapebox"..boxcount] = love.physics.newRectangleShape( _G["bodybox"..boxcount], 0, 0, sw , sh)
_G["shapebox"..boxcount]:setData( _G["shapebox"..boxcount] )
return boxcount
end
A new object would be assigned just a number (Lets say 1). So to manipulate the object I would have to use “bodybox1” and “shapebox1”. I used it as a quick fix and as my code grew so did the problems.
If I wanted to create a box using this method, I would have to do this:
MakeBox( wX/2 , wY/2 , 1 , imgpork , 100 , 90 , 255 , 215 , 197, 70, 120)
Now while this looks simple enough, it is very confusing to understand. What do the values represent? How I can tell what’s scale and what’s alpha? What if I want to add air resistance as a value? I’d often put a mass value in the wrong place, and to cater for an additional value like air resistance I’d have to edit all calls of MakeBox. This obviously wasn’t going to work.
The Solution:
One of the main limitations of the Lua language is the fact that it’s not, by default, object orientated. A quick look at the LÖVE wiki and I found exactly what I was looking for. The MiddleClass library. The MiddleClass library makes it easy to create classes, which are perfect for an entity system.
First, I created a “Body” base class in a lua file of it’s own.
function Body:initialize()
end
function Body:SetPos( x , y )
if self.body == nil then
self.body:setPosition( x , y )
else
self.spawnpos = x , y
end
end
function Body:SetScale( scale )
self.scale = scale
end
function Body:SetWorld( worldnum )
self.world = Worlds[worldnum]
end
function Body:SetMass( mass )
self.mass = mass
if self.body ~= nil then
self.body:setMass( nil , nil , self.mass , nil )
end
end
function Body:SetRotInertia( rotinertia )
self.rotinertia = rotinertia
if self.body ~= nil then
self.body:setMass( nil , nil , nil , self.rotinertia )
end
end
function Body:SetSprite( sprite )
self.sprite = sprite
end
function Body:Spawn()
self.body = love.physics.newBody( self.world , self.spawnpos , self.mass , self.rotinertia )
end
This base class provides loads of useful functions, like SetMass() and SetPos(), but on it’s own the “Body” class creates just a blank body with some functions, there’s no shapes for collisions or for sprites to draw on. This is where subclasses come in.
Using a subclass of “Body” I can create any shape and define additional useful functions specific to that subclass. Here’s a look in my “Physics_Rectangle.lua” subclass:
function Physics_Rectangle:initialize()
super.initialize( self )
end
function Physics_Rectangle:SetHeight( height )
self.height = height
end
function Physics_Rectangle:SetWidth( width )
self.width = width
end
function Physics_Rectangle:Spawn()
--Create Body
super:Spawn( self )
--Define Defaults
if self.width == nil then self.width = ( self.sprite:getWidth() * self.scale ) end
if self.height == nil then self.height = ( self.sprite:getHeight() * self.scale ) end
self.shape = love.physics.newRectangleShape( self.body, 0, 0, self.width , self.height )
end
Here I’m creating a body from Body.lua, inheriting basic functions and then defining two more useful functions, SetWidth() and SetHeight().
This is great because it means you can create a new subclass for anything, a subclass for a new shape, a subclass for a layout of shapes and even a subclass for the physics of a character. It’s easy.
If I wanted to create a physics box using this method it’s much much more customizable and legible:
phybox = CreateEntity( Physics_Rectangle )
phybox:SetPos(10,20)
phybox:SetWidth(5)
phybox:SetHeight(5)
phybox:SetSprite(imgbox)
phybox:Spawn()
These are the necessary functions needed to spawn a box, but you still have all the possible functions like SetRotInertia() and SetWorld() which you can either call before or after spawn. You can use any new subclass you create as an entity type.
Every created entity is added to an entity table, so you can easily get all entities or just search for specific entity types. The new entity system is massively customizable and easy to read. Job done.
- BAZ