Jayant in his great in-depth review of my new Corona-based iPad game “Free the Fobbles” asked (http://reviewme.oz-apps.com/2011/03/do-you-wanna-vs-me.html):
> I cannot stress enough and would be curious to
> know how Philipp has done the geese animation,
> are they sprites or scripted objects and the victory
> dance, It would be so good to see that code (if Philipp
> posts it on the forums)
So, here’s some background explanations. The game is free, so if you got a second you should download it to know what this explanation is about (http://versuspad.com).
Now, I created a small framework on top of the Corona framework to help me organize my games. The goal of this mini framework is to:
- allow me to quickly create new games
- allow me to have as little lines of code as possible to allow me to keep a good overview of the full project
- handle mundane stuff like removing sprites etc. without me caring about it for every new game
- still be very flexible for new ideas not available in the old game
Well, those are all also benefits of Corona itself! I love Corona, did I mention?
So this mini framework basically is a class structure based around a sprites object, a sprite being anything that moves on the screen, whether physics-driven or manually steered by code. It’s rather simple and straightforward and as I’m new to Lua, probably violating several good Lua principles 
Here’s what happens:
- The Fobbles are little balls, reacting to physics and gravity. Each Fobble is a Sprite object. The Sprite class however knows nothing specific about what a Fobble is.
- All that is specific to the Fobbles is contained in a sprites creation module, which in turn has a function called createFobbles. This functionality, like any other sprite creation part, has a) a section defining general attributes, and b) an optional handler function.
- Because the physical Fobbles and the dancing Fobbles have basically nothing in common except their appearance, I created two different types for them.
What are types anyway? Well, sprites in this mini framework are split up into:
- types (Fobble, Wood, Ground, Cloud, Bird, etc.)
- subtypes (Bird SwarmLeader, etc.)
- phases (default, dancing, blinking)
- group
Sprites have attributes like:
- id
- energy
- speedLimit
- parentSprite
- phase (another class)
- spriteSheetFrom
- spriteSheetTo
- isAutoPhysical
- alphaChangesWithEnergy
- emphasizeDisappearance
- listenToCollision
- movesWithParent
- energySpeed
- … and anything type-specific is preceded with data.something (like sprite.data.fobbleIsHappy)
Sprites have functions like (paraphrasing here):
- makeSound
- followTarget
- followOtherSprite
- rotateTowardsTargetRotation
- adjustSpeedToTargetSpeed
- setRgb
- setRgbRandom
- setRgbWhite
- handleGenericBehavior (this one is called by default in every frame through the main app handler routine)
- etc.
The app then has selector functions like:
- getSpritesByType(type, optionalSubtype, etc.)
- getSpriteById()
- removeSpritesByType
- removeSpritesByGroup
- etc.
Let’s say I wanted to have a sprite which softly fades in, and there would be a little “boom” explosion when it’s created and enters the scene. Here’s the attributes I would need to set:
self.energy = 10
self.energySpeed = 1
self.alphaChangesWithEnergy = true
self.emphasizeAppearance = true
So basically the general sprite handler functionality will then do the rest, like increasing the energy (up to 100), adjusting the image’s alpha value according to its energy, and so on.
To make a long story short, here is the whole code where the sprites enter the scene, wait and bump around a bit in a funny order, make some sounds and let the grass fly around a bit, and then leave the scene after a while. I removed a small portion of the code that deals with triggering the eye-blinking animation. This function may be a bit odder than other functions in the game because I re-created the Corona auto-physics for this object (as the dance is sort of pseudo-physics: I wanted to get a precise jumping animation)… and because this is a one-time animation without deep relations to the core game logic and functionality, so I didn’t refactor too much:
[code]function self:createCelebratingFobbles(winnerI)
local function handle(self)
local gravity = .4
local groundY = 920
if self.phase.name == ‘active’ then
if not self.phase:isInited() then
self.phase:setNext(‘leave’, 300)
self.speedY = 0
self.speedLimitX = 4
self.targetX = self.x + 665 * self.data.direction
self.targetFuzziness = 3
end
self.speedY = self.speedY + gravity
if math.random(250) <= 1 then self:makeSound(‘fobble-freed’) end
elseif self.phase.name == ‘leave’ then
if not self.phase:isInited() then
self.doDieOutsideField = true
self.targetX = self.x + 900 * self.data.direction
end
self.speedY = self.speedY + gravity
end
if self.y > groundY then
self.y = groundY
self.speedY = self.speedY * -1
app.spritesHandler:createSparks( self.x, self.y, 6, {red = 119, green = 155, blue = 31} )
self:makeSound(‘fobble-bump-soft’)
end
end
local fobblesMax = 8
for i = 1, fobblesMax do
local radius = 20
local imageName = ‘fobble-’ … winnerI
if app.mode == ‘training’ and winnerI == app.enemyPlayer then imageName = imageName … ‘-during-training’ end
local x = app.minX - 550 + (i - 1) * 75
if winnerI == 2 then x = app.maxX + 550 - (i - 1) * 75 end
local y = 780
local spriteSheetFrom = 1
local spriteSheetTo = 2
local self = spriteModule.spriteClass(‘circle’, ‘celebratingFobble’, nil, imageName, false, x, y, radius, nil,
spriteSheetFrom, spriteSheetTo, nil, playerI)
self.parentPlayer = winnerI
self.data.direction = misc.getIf(winnerI == 1, 1, -1)
self.frameNameNumber[‘default’] = 1
self.frameNameNumber[‘blink’] = 2
self.frameName = ‘default’
self.rotation = -33 + math.random(-2, 2)
self.phase:set(‘default’, 2 + (i - 1) * 10, ‘active’)
self.doDieOutsideField = false
appAddSprite(self, handle, moduleGroup)
end
end[/code]
And as far as the swarm of birds go (most of the animating between the cells of the spritesheet is handled by the general sprites handler class elsewhere):
[code]function self:createBirds()
local function handle(self)
self.speedX = self.speedX + misc.randomFloat(-.5, .5)
self.speedY = self.speedY + misc.randomFloat(-.5, .5)
if self.subtype == ‘swarmLeader’ then
if misc.getChance(1) or self.targetX == nil then
local margin = 300
self.targetX = math.random(app.minX - margin, app.maxX + margin)
self.targetY = math.random(750, 850)
end
else
if self.phase.name == ‘followSwarmLeader’ then
if misc.getChance(1) then self.phase.name = ‘default’ end
if self.targetSprite == nil then
self.targetSprite = appGetSpriteByType(‘bird’, ‘swarmLeader’)
end
else
if misc.getChance(1) then self.phase.name = ‘followSwarmLeader’ end
self.targetSprite = nil
end
end
end
appRemoveSpritesByType(‘bird’)
local maxBirds = 8
local swarmX = math.random(app.minX + 50, app.maxX - 50)
local swarmY = math.random(750, 850)
for i = 1, maxBirds do
local x = swarmX + math.random(-100, 100)
local y = swarmY + math.random(-70, 70)
local self = spriteModule.spriteClass(‘rectangle’, ‘bird’, nil, ‘bird’, false, x, y, 11, 14,
1, 6)
self.id = ‘bird’ … i
self.speedLimitX = 2
self.speedLimitY = 1
self.currentFrame = 1
self.doDieOutsideField = false
self.speedStep = .1
if i == 1 then self.subtype = ‘swarmLeader’
else self.phase:set(‘followSwarmLeader’)
end
self.frameNameNumber[‘right1’] = 1
self.frameNameNumber[‘right2’] = 2
self.frameNameNumber[‘right3’] = 3
self.frameNameNumber[‘right4’] = 2
self.frameNameNumber[‘left1’] = 4
self.frameNameNumber[‘left2’] = 5
self.frameNameNumber[‘left3’] = 6
self.frameNameNumber[‘left4’] = 5
self.frameName = self.frameNameNumber[‘left1’]
self.alpha = .18
self.frameSpeed = .2
appAddSprite(self, handle, moduleGroup)
end
end[/code]
The sprite functionality handling of, for instance, the important “skywood” in the game is much shorter… as it doesn’t move, and as I’m using Corona’s physics for it:
[code]function self:createWood(x, y, rotation)
local function handle(self)
if self.actionOld.touched ~= self.action.touched and self.action.touched then
if app.phase.name == ‘playerCanMakeTurn’ then
self:makeSound(‘wood-explodes’)
app.spritesHandler:createSparks(self.x, self.y, nil, nil, true)
app.phase:set(‘playerJustMadeturn’)
self.gone = true
else
app.spritesHandler:createWarningMessage(self.touchedX, self.touchedY, ‘waitUntilHalting’)
end
end
end
local width = 109
local height = 43
local imageName = ‘wood’
local padding = 2
local shape = {
0, 10 + padding,
width, 10 + padding,
width, 32 - padding,
0, 32 - padding
}
local self = spriteModule.spriteClass(‘rectangle’, ‘wood’, nil, imageName, true, x, y, width, height,
nil, nil, shape)
self.bodyType = ‘static’
self.listenToTouch = true
self.rotation = rotation
if misc.getChance(50) then self:toBack() end
appAddSprite(self, handle, moduleGroup)
end[/code]
Hope that was of interest to some! Any questions I’m happy to answer! [import]uid: 10284 topic_id: 7390 reply_id: 307390[/import]