Pinning objects to physics objects

I want to create a ball class with a number of layers, which can be combined in multiple ways and also act as a physics object.  There will be:-

  1. a base layer (a circle display object) which is plain coloured.  This circle should also act as the physics body.

  2. a pattern layer (a bitmap with transparency) that should simply track the base layer (both position and rotation)

  3. a top layer (another bitmap with transparency) which acts as a “shine” texture for the ball and which should not rotate with the ball

So far, the best I can seem to do to achieve this is to make sure all my instances of the ball are contained in a table and then have an enterFrame listener which loops through the table and runs a class “sync” method that aligns the position and rotation of layer 2 with layer 1, and aligns just the position of layer 3 with layer 1.

I really dislike using this sort of looping method because it makes it more difficult to remove objects from the table when they’re no longer needed (not impossible I know, just inelegant).  I would really like for my class instances to be self-contained.  Is there a more elegant way to do this that I’m missing?

Yes, there are more elegant ways to do this:

  1. Use a shader to achieve the results you want.

OR 

  1. Make the display object responsible for their own tracking.

If you did not want to make shaders or it is too hard (it’s hard for me too, so this is not a dig) you could do this:

-- rudimentary examples for you to expand upon -- local function makeBall( group, x, y ) local obj = display.newCircle( group, x, y, 10 ) -- assuming you are using physcis physics.addBody( obj, "dynamic" ) return obj end local function attachShine( obj ) local shine = display.newImageRect( obj.parent, "shine.png", 20, 20 ) shine.x = obj.x shine.y = obj.y shine.enterFrame = function( self ) if( obj.removeSelf == nil or self.removeSelf == nil) then Runtime:removeEventListener( "enterFrame", self ) return end self.x = obj.x self.y = obj.y return Runtime:addEventListener( "enterFrame", shine ) return shine end local function attachTexture( obj ) -- same idea as shine end local group = display.newGroup() local test = makeBall( group, 100, 100 ) test.myTexture = attachTexture( test ) test.myShine = attachShine( test )

This is safe and self-managing.

Note: Minor nit.  I’m not sure why you’re using the nomenclature ‘class’?  I think its great to approach the design  of your app or game as modular, but I think labeling display objects as classes or as instances of classes may end up muddling things a bit.  Just an observation though (feel free to ignore it).

Very interesting, thank you, will take the time to understand this.  Two points:-

  1. yes, custom shaders definitely beyond me right now!

  2. I use the term class because I’m using class code from Codea (where I learned lua) https://bitbucket.org/TwoLivesLeft/core/wiki/CodeaClasses - so the approach I’ve been using creates a class which includes self.layer1, self.layer2 and self.layer3.  I realise there’s many ways to do OOP but this is the way I’m used to as a relative newbie, and other ways will require a bit more concentration on my part. I think I can adopt your method to my classes, but it might be even better for me to try a new approach!

Thank you very much for the lead. 

Actually I’ve worked out another method which may work better for my purposes.  Since my class creates tables for each instance, if I simply put the following in the initiation code:-

Runtime:addEventListener(“enterFrame”, self)

then it looks at “self” (a table) and treats it as a table listener, and my class can then specify a custom enterFrame method to sync everything.

As a simple proof of principle, the following class code creates a red circle with an off-centre white rectangle that syncs beautifully.  It will need a remove method to get rid of the event listener, but that’s easy enough.

[lua]

local class = require( “scripts.class” )

local physics = require( “physics” )

local M = class()

function M:init( i )

  self.circle = display.newCircle(math.random(100,600), math.random(100, 600), 50)

  self.circle:setFillColor(0.8,0.1,0.1)

  physics.addBody( self.circle )

  self.box = display.newRect(self.circle.x + 30, self.circle.y, 10, 10)

  Runtime:addEventListener(“enterFrame”, self)

end

function M:enterFrame()

  

  self.box.x = self.circle.x + 30

  self.box.y = self.circle.y

  

end

return M

[/lua]

This may be very obvious to you roaming gamer, I’m just posting it in case anyone else has similar issues!

That is a good option too (trades 2 or 3 listeners for 1).  

Be sure to add safety code to verify that the objects you are tracking are still valid or you’ll get bugs when objects are deleted and the ‘enterFrame’ listener on the table tries to modify those objects.

You can verify a display object is still valid as follows:

if( obj.removeSelf == nil ) then print( "This object was removed and only the 'table' stub is left." ) print( "Attempting to modify obj.x, call obj:removeSelf(), ... will now crash." ) end

Yes, there are more elegant ways to do this:

  1. Use a shader to achieve the results you want.

OR 

  1. Make the display object responsible for their own tracking.

If you did not want to make shaders or it is too hard (it’s hard for me too, so this is not a dig) you could do this:

-- rudimentary examples for you to expand upon -- local function makeBall( group, x, y ) local obj = display.newCircle( group, x, y, 10 ) -- assuming you are using physcis physics.addBody( obj, "dynamic" ) return obj end local function attachShine( obj ) local shine = display.newImageRect( obj.parent, "shine.png", 20, 20 ) shine.x = obj.x shine.y = obj.y shine.enterFrame = function( self ) if( obj.removeSelf == nil or self.removeSelf == nil) then Runtime:removeEventListener( "enterFrame", self ) return end self.x = obj.x self.y = obj.y return Runtime:addEventListener( "enterFrame", shine ) return shine end local function attachTexture( obj ) -- same idea as shine end local group = display.newGroup() local test = makeBall( group, 100, 100 ) test.myTexture = attachTexture( test ) test.myShine = attachShine( test )

This is safe and self-managing.

Note: Minor nit.  I’m not sure why you’re using the nomenclature ‘class’?  I think its great to approach the design  of your app or game as modular, but I think labeling display objects as classes or as instances of classes may end up muddling things a bit.  Just an observation though (feel free to ignore it).

Very interesting, thank you, will take the time to understand this.  Two points:-

  1. yes, custom shaders definitely beyond me right now!

  2. I use the term class because I’m using class code from Codea (where I learned lua) https://bitbucket.org/TwoLivesLeft/core/wiki/CodeaClasses - so the approach I’ve been using creates a class which includes self.layer1, self.layer2 and self.layer3.  I realise there’s many ways to do OOP but this is the way I’m used to as a relative newbie, and other ways will require a bit more concentration on my part. I think I can adopt your method to my classes, but it might be even better for me to try a new approach!

Thank you very much for the lead. 

Actually I’ve worked out another method which may work better for my purposes.  Since my class creates tables for each instance, if I simply put the following in the initiation code:-

Runtime:addEventListener(“enterFrame”, self)

then it looks at “self” (a table) and treats it as a table listener, and my class can then specify a custom enterFrame method to sync everything.

As a simple proof of principle, the following class code creates a red circle with an off-centre white rectangle that syncs beautifully.  It will need a remove method to get rid of the event listener, but that’s easy enough.

[lua]

local class = require( “scripts.class” )

local physics = require( “physics” )

local M = class()

function M:init( i )

  self.circle = display.newCircle(math.random(100,600), math.random(100, 600), 50)

  self.circle:setFillColor(0.8,0.1,0.1)

  physics.addBody( self.circle )

  self.box = display.newRect(self.circle.x + 30, self.circle.y, 10, 10)

  Runtime:addEventListener(“enterFrame”, self)

end

function M:enterFrame()

  

  self.box.x = self.circle.x + 30

  self.box.y = self.circle.y

  

end

return M

[/lua]

This may be very obvious to you roaming gamer, I’m just posting it in case anyone else has similar issues!

That is a good option too (trades 2 or 3 listeners for 1).  

Be sure to add safety code to verify that the objects you are tracking are still valid or you’ll get bugs when objects are deleted and the ‘enterFrame’ listener on the table tries to modify those objects.

You can verify a display object is still valid as follows:

if( obj.removeSelf == nil ) then print( "This object was removed and only the 'table' stub is left." ) print( "Attempting to modify obj.x, call obj:removeSelf(), ... will now crash." ) end