I’m loving learning Lua / Corona SDK but being a 20+ year C/C++ vet I’m really anal about clean, structured, well organised (and readable) code and I’ve noticed a strong tendency for Lua code to start to get really messy once it get’s above a certain size.
To this end I wanted to try and find the best way to implement OOP principals in Lua and have a workaround for the fact that Lua doesn’t natively support classes.
The Lua “bible” Programming in Lua 2nd Ed suggest two ways - the first uses metatables as a way to organise code and have inheritance (but isn’t very strong on encapsulation), and whilst I can see how it works I’m still having problems getting my head round the whole metatable thing.
The other way suggested was to use a single “function” to represent an entire class and encapsulate all of the class fields and methods as local entities to the “function” and then have that “function” return a table which is basically the public interface to the class.
eg.
function newClass( initial values )
local self = { a=0, b=1 } Note you always have to reference self when accessing the table
although you COULD also store many tables or many locals
-- You can also code "constructor" initial setup code here!
local method\_a = function(a) self.b = a
end
local function method\_b(c) return self.a + c Note - you wouldn't have to reference the functions via self as they're
all local - but you would probably need to be aware of forward references
end
-- Here's the magic...
-- or add some more "constructor" code here!
return {
method\_a = method\_a, method\_b = method\_b name
}
end
This would then be accessed like so
local a = newClass( initial values, table or nill etc )
a.method\_a(x)
y = a.method\_b(c)
IMHO this is a much cleaner approach but I’d like to invite anyone with any greater knowledge of Lua to comment and potentially suggest improvements.
Whilst this nicely provides the encapsulation and the “newClass” can be stored in a module and “required” it’s a bit weak when it comes to inheritance - however I don’t see that as a limitation as I’m leaning more to a component based system for my framework rather than a hierarchical one (ie Game objects contain “instances” of the functionality they need rather than inheriting it via a convoluted hierarchy).
To demonstrate this further I’ve knocked up a quick particle system which might be of interest to some.
Put this code in main.lua
display.setStatusBar( display.HiddenStatusBar )
-- The particle system class
-- Contains two classes - the individual particle and the particle system (controller)
local particleSystem = require "particle"
local particleCtrl,gameGroup,background
-- --------------------------
local function touched(event)
if event.phase == "began" then particleCtrl.spawn(50,150,event.x,event.y) end
if event.phase == "moved" then particleCtrl.spawn(2,20,event.x,event.y) end
-- if event.phase == "ended" then particleCtrl.reset() end
end
-- --------------------------
local function loop(event)
particleCtrl.update()
end
-- --------------------------
background = display.newRect(0,0,display.contentWidth,display.contentHeight)
background:setFillColor(64,0,64)
gameGroup = display.newGroup()
-- Create the controller object
particleCtrl = particleSystem.newParticleController(500)
gameGroup:insert(particleCtrl.group())
Runtime:addEventListener( "enterFrame", loop )
Runtime:addEventListener( "touch", touched )
-- End of file...
and this code in particle.lua
module(..., package.seeall)
--[[
OOP in LUA - Game objects - is this the best way to implement them.
--]]
local mRand = math.random
local mCeil = math.ceil
local mSin = math.sin
local mCos = math.cos
local mRad = math.rad
-- --------------------------------------------------------------------------------------------------------
-- --------------------------------------------------------------------------------------------------------
-- The actual particle class
function newParticle(group)
-- -------------------------------
-- Object data
local self = { x=0,y=0,a=0,xinc=0,yinc=0,aStep=0,life=0,image=nil,active=false }
-- Create (Construct) the particle as a small square (but hide it until it's needed)
self.image = display.newRect(0,0,4,4)
self.image:setReferencePoint(display.CenterReferencePoint)
self.image.x = self.x
self.image.y = self.y
self.image.isVisible = false
group:insert(self.image)
-- -------------------------------
-- Object methods
local function init(life, xLoc,yLoc)
-- pick a random colour and direction, reset the position and make active
self.x = xLoc
self.y = yLoc
local r,g,b = mRand(192,255),mRand(192,255),mRand(192,255)
self.a = 1
self.image:setFillColor(r,g,b) -- random colour
self.image.x = self.x
self.image.y = self.y
self.image.isVisible = true
self.image.alpha = self.a
local angle = mRad(mRand(1,36) \* 10) -- pick a random angle of travel
local speed = 2 + (mRand(1,20) \* 0.1) -- and a random speed
self.xinc = speed \* mSin(angle)
self.yinc = speed \* mCos(angle)
self.life = life
self.aStep = 1 / life
self.active = true
end
-- -------------------
local function update()
if self.active == false then return end
self.yinc = self.yinc + 0.05
self.x = self.x + self.xinc
self.y = self.y + self.yinc
self.a = self.life \* self.aStep
self.image.x = self.x
self.image.y = self.y
self.image.alpha = self.a
self.life = self.life - 1
if self.life == 0 then
self.active = false
self.image.isVisible = false
end
end
-- -------------------
local function reset()
self.life = 0
self.active = false
self.image.isVisible = false
end
-- -------------------------------
-- The public interface
return {
init = init,
update = update,
reset = reset,
}
end
-- --------------------------------------------------------------------------------------------------------
-- --------------------------------------------------------------------------------------------------------
-- --------------------------------------------------------------------------------------------------------
-- The particle controller class
function newParticleController(maxParticles)
-- ------------------------------
-- Class storeage space
local group = display.newGroup()
local particles = { }
local numParticles = maxParticles
local last = 1
-- Create the required number of particle objects here
local p
for i=1,numParticles do
p = newParticle(group)
table.insert(particles,p)
end
-- ------------------------------
-- Class method definitions
-- -------------------
-- Trigger a set number of particles to be reset at the specified position
-- start from where we last left off
local function spawn(num, life, xLoc, yLoc)
local current = last
while (num \> 0) do
particles[current].init(life, xLoc,yLoc)
num = num - 1
current = current + 1
if current \> numParticles then current = 1 end
end
last = current
end
-- -------------------
-- Update all of the active particles
local function update()
for i=1,numParticles do
particles[i].update()
end
end
-- -------------------
-- Reset all the particles regardless
local function reset()
for i=1,numParticles do
particles[i].reset()
end
end
-- ------------------------------
-- Return the public interface
return {
group = function() return group end, -- simple "getter"
spawn = spawn,
update = update,
reset = reset,
}
end
-- End of file...
Note on the implementation - rather than dynamically creating particles when the controller “spawns” them - the above code actually creates a “block” of particles at the start and then just recycles them by making each one visible and active as required.
Given my “limited” understanding of Lua - I’m assuming that including some kind of “destructor” function that performs internal cleanup should also be included as part of the interface, and after calling this function setting the “object” variable to nil should free up any claimed memory (or at least inform the GC that it’s available to be freed).
Any comments / feedback is much appreciated (FWIW - I’ll repost this to the code exchange at the point I get my full subscription going).
Jon…
techdojo@gmail.com
www.whitetreegames.com
[import]uid: 7901 topic_id: 9155 reply_id: 309155[/import]