Is there any thing to consider with display groups that have display objects with physic bodies attached to them?

Hi,

Ok, it’s driving me nuts.

I have a display group and I attached a display image and three text objects to it. When I create the display image, I store it’s reference and later on attach physic object to it. I have two groups of this group I just described.

Problem is the physic behavior is acting very weird. I push two different objects into these display groups with drag and each reacts differently based on the object it collided with.

Bug is, one of the display groups is not detected at all and the other one’s only upper half is working!

I suspect the problem is on how I create display groups and attach physic to it because it was fine before me transforming my code into OOP and doing these display groups.

So here are my main.lua and monsterclass which is those display groups:

[lua]-- Enabling debug mode.
–if (system.getInfo(“environment”)==“simulator”) then
– require(“mobdebug”).start()
–end
–[[
getmetatable(display.newRect(0, 0, 0, 0)).__tostring =
function(t) return ("{x=%s, y=%s}"):format(t.x, t.y) end
]]
–local proto, skip, meta = display.newRect(0, 0, 0, 0), {}, {}
–local metafields = “x,y,rotation,alpha,width,height,isVisible,xReference,yReference,xScale,yScale”

–for field in pairs(proto) do
– skip[field] = true
–end

– for field in metafields:gmatch("%w+") do
– meta[field] = true
–end
–getmetatable(proto).__tostring = function(t)
– local obj = {}
– for field in pairs(t) do
– if not skip[field] then
– obj[field] = t[field]
– end
– end

– for field in pairs(meta) do
– obj[field] = t[field]
– end
– return require(“mobdebug”).line(obj, {comment = false})
–end

–local boxClass = require(“box”)
–local newBox = boxClass.make();
–newBox:move();

local gameUI = require(“gameUI”); – We use it for dragging.
local showFPSModule = require(“showFPS”); – Display FPS.
–local widget = require “widget” – Buttons.
local physics = require( “physics” );

local monsterClass = require( “monsterclass” );

physics.start()
physics.setGravity(0, 0)

physics.setDrawMode( “debug” )
–physics.setDrawMode( “hybrid”)
–physics.setDrawMode( “normal”)

local DAY = 86400 – ( 24 * 60 * 60 )
local HOUR = 3600 – ( 60 * 60 )
local MINUTE = 60

math.randomseed(os.time()) – A better seed?

local weNeedToGoToNextWave = false;
local numberOfActiveFoods = 0;
local waveIndex = 1;

– Monsters (regions)
local monster_yellow;
local monster_purple;

– Spawn points.
local spawnPointTop = {x = 210, y = 50};
local spawnPointBottom = {x = 210, y = 200};

–[[
local myButton = widget.newButton{
id = “btn001”,
left = 100,
top = 200,
label = “Widget Button”,
width = 150, height = 28,
cornerRadius = 8,
onEvent = onButtonEvent
}
myButton.x = display.contentWidth * 0.5
myButton.y = display.contentHeight * 0.5
]]
showFPSModule.showFps();
display.setStatusBar(display.HiddenStatusBar)

– listener used by Runtime object. This gets called if no other display object
– intercepts the event.
local function printTouch( event )
print( “event(” … event.phase … “) (”…event.x…","…event.y…")" )
end

local function foodTouchHandler( event )
local t = event.target

– Print info about the event. For actual production code, you should
– not call this function because it wastes CPU resources.
– printTouch(event)

local phase = event.phase
if “began” == phase then
– Make target the top-most object
local parent = t.parent
parent:insert( t )
display.getCurrentStage():setFocus( t )

– Spurious events can be sent to the target, e.g. the user presses
– elsewhere on the screen and then moves the finger over the target.
– To prevent this, we add this flag. Only when it’s true will “move”
– events be sent to the target.
t.isFocus = true

– Store initial position
t.x0 = event.x - t.x
t.y0 = event.y - t.y

t:setLinearVelocity(0, 0)

elseif t.isFocus then
if “moved” == phase then
– Make object move (we subtract t.x0,t.y0 so that moves are
– relative to initial grab point, rather than object “snapping”).
t.x = event.x - t.x0
t.y = event.y - t.y0
elseif “ended” == phase or “cancelled” == phase then
display.getCurrentStage():setFocus( nil )
t.isFocus = false
end
end

– Important to return true. This tells the system that the event
– should not be propagated to listeners of any objects underneath.
return true
end

–[[
1-HowManyBombsToDrop;
2-TimeToWaitBetweenBombSpawns;
3-TimeAmountToRestBeforeStartingThisWave;
4-WaveExpirationTimer;
5-MinimumNumberOfBombsToKeepThisWaveActive;
]]
local waves={
[1] = {1, 0, 1, 5, 1},
[2] = {1, 0.5, 1, 5, 1},
[3] = {1, 0, 0, 5, 1},
[4] = {1, 0, 0, 5, 1},
[5] = {2, 1, 0, 5, 1},
[6] = {2, 0.5, 0, 5, 1},
[7] = {2, 0.5, 0, 5, 1},
[8] = {3, 0.2, 0, 5, 1},
[9] = {5, 1, 0, 5, 1},
[10] = {3, 0.2, 0, 5, 1},
}

local function initializeGame()

local borderCollisionFilter = { categoryBits = 1 }
local borderBodyElement = { friction=0.4, bounce=0.8, filter=borderCollisionFilter }

–borders of screen
local borderTop = display.newRect( 0, 0, display.contentWidth - 1, 1 )
borderTop:setFillColor( 0, 0, 0, 0) – make invisible
physics.addBody( borderTop, “static”, borderBodyElement )

local borderBottom = display.newRect( 0, display.contentHeight - 1, display.contentWidth - 1, 1 )
borderBottom:setFillColor( 0, 0, 0, 0) – make invisible
physics.addBody( borderBottom, “static”, borderBodyElement )

local borderLeft = display.newRect( 0, 1, 1, 480 )
borderLeft:setFillColor( 0, 0, 0, 0) – make invisible
physics.addBody( borderLeft, “static”, borderBodyElement )

local borderRight = display.newRect( display.contentWidth - 1, 1, 1, 480 )
borderRight:setFillColor( 0, 0, 0, 0) – make invisible
physics.addBody( borderRight, “static”, borderBodyElement )

– Background.
local background = display.newImageRect(“background.png”, display.contentWidth, display.contentHeight);
background.x = display.contentWidth / 2
background.y = display.contentHeight / 2

– local monsterPurpleCollisionFilter = { categoryBits = 8, maskBits = 1 } – collides with (1) only
– local monsterPurplePhysicSettings = { density = 0.2, friction = 0, bounce = 0.95, radius= 20.0, filter = monsterPurpleCollisionFilter }

– monster_purple = monsterClass.new(“Purple”, 100, 100);
monster_purple = monsterClass.new(“Purple”, display.contentWidth/2 - 180, display.contentHeight/2);
local squareShape = { -58,-157, 0, -157, 0, 157, -58, 157 } – make mouths smaller than monsters.
physics.addBody( monster_purple.visual, “static”, {shape = squareShape} )
– physics.addBody( monster_purple.visual, “static” )
monster_purple:ResetTimer();

– Yellow monster

– monster_yellow = display.newImageRect(“monster_yellow.png”, 111, 314);
– monster_yellow.x = display.contentWidth/2 + 180
– monster_yellow.y = display.contentHeight/2
– monster_yellow.myType = “Yellow”;
– squareShape = { -40, -157, 58, -157, 58, 157, -40, 157 } – make mouths smaller than monsters.
– physics.addBody( monster_yellow, “static”, { shape = squareShape} )

monster_yellow = monsterClass.new(“Yellow”, display.contentWidth/2 + 180, display.contentHeight/2)
local squareShape_yellow = { -40, -157, 58, -157, 58, 157, -40, 157 } – make mouths smaller than monsters.
physics.addBody( monster_yellow.visual, “static”, { shape = squareShape_yellow} )
monster_yellow:ResetTimer();

end


@param event @class event
@return

local function localDrag( event )

– isTouched is a flag to indicate that player previously touched this food object and if later on this
– object hits with a monster, then that collision is valid. So this prevents foods to get eaten by
– monsters when they move freely and accidentally collide with a monster.
– (We can toggle this to false after some time, if we want)

– event.target.isTouched = true;

local dragget_target = event.target;
dragget_target.isTouched = true;

– foodObject:addEventListener(“touch”, foodOnTouch);
– foodTouchHandler(event)
– gameUI.dragBody( event, {} )
– gameUI.dragBody(event, {maxForce = 100, frequency = 2} ) – slow, elastic dragging
– gameUI.dragBody(event, {maxForce = 100, frequency = 2, center = true } ) – slow, elastic dragging

return gameUI.dragBody(event, {maxForce = 20000, frequency = 10, dampingRatio = 0.9, center = true }) --very tight
– return true – The magic-- line
end

local function onPurpleFoodCollision(self, event)
if ( self.isTouched ) then
if (event.other.myType == self.myType) then
monster_purple.numberOfEatenFoods = monster_purple.numberOfEatenFoods + 1;

numberOfActiveFoods = numberOfActiveFoods - 1;
self:removeSelf();
self = nil;
elseif (event.other.myType == “Yellow”) then
monster_yellow:ResetEatenFoodCount();

numberOfActiveFoods = numberOfActiveFoods - 1;
self:removeSelf();
self = nil;
end
end
end

local function onYellowFoodCollision(self, event)

if ( self.isTouched ) then
if (event.other.myType == self.myType) then
monster_purple.numberOfEatenFoods = monster_purple.numberOfEatenFoods + 1;

numberOfActiveFoods = numberOfActiveFoods - 1;
self:removeSelf();
self = nil;

elseif (event.other.myType == “Purple”) then
monster_yellow:ResetEatenFoodCount();

numberOfActiveFoods = numberOfActiveFoods - 1;
self:removeSelf();
self = nil;
end
end
end


@param x @class number
@param y @class number
@param foodType @class number
@return

local function makeOneFoodObject(x, y, foodType)
@classdef foodobject

– If you don’t pass anything, it will make a random food at center of the screen.

print(“Entering makeOneFoodObject”);

– foodElement = { friction=0.4, bounce=0.2, filter=borderCollisionFilter }

– local purpleFoodCollisionFilter = { categoryBits = 2, maskBits = 3 } – collides with (2 & 1) only
local purpleFoodCollisionFilter = { categoryBits = 2, maskBits = 1 } – collides with (1) only
local purpleFoodPhysicSettings = { density=0.2, friction=0, bounce=0.95, radius=35.0, filter=purpleFoodCollisionFilter }

– local yellowFoodCollisionFilter = { categoryBits = 4, maskBits = 1 } – collides with (4 & 1) only
local yellowFoodCollisionFilter = { categoryBits = 4, maskBits = 1 } – collides with (1) only
local yellowFoodPhysicSettings = { density=0.2, friction=0, bounce=0.95, radius=35.0, filter=yellowFoodCollisionFilter }

if not foodType then
foodType = math.random(1, 2)
end

local foodObject;
if 1 == foodType then
– foodObject = display.newImageRect(“food_purple.png”, 25, 33);
foodObject = display.newImageRect(“food_purple.png”, 42, 54);
foodObject.myType = “Purple”;
foodObject.collision = onPurpleFoodCollision;
foodObject:addEventListener(“collision”, foodObject); – TODO: Why we pass the same object as last parameter.
physics.addBody( foodObject, “dynamic”, purpleFoodPhysicSettings)
elseif 2 == foodType then
– foodObject = display.newImageRect(“food_yellow.png”, 25, 37);
foodObject = display.newImageRect(“food_yellow.png”, 42, 54);
foodObject.myType = “Yellow”;
foodObject.collision = onYellowFoodCollision;
foodObject:addEventListener(“collision”, foodObject);
physics.addBody( foodObject, “dynamic”, yellowFoodCollisionFilter)
end

foodObject:setReferencePoint(display.TopLeftReferencePoint);

– if no position is passed, we put it in center of the screen.
if not x and not y then
foodObject.x = display.contentCenterX;
foodObject.y = display.contentCenterY;
else
foodObject.x = x;
foodObject.y = y;
end

foodObject:setReferencePoint(display.CenterReferencePoint); --we need to revert reference point to center physic will move it by center.
foodObject:addEventListener(“touch”, localDrag);

– Appyling initial speed, if passed.
– foodObject.x = ball.x + vx
– ball.y = ball.y + vy

– Initial speed.

foodObject:setLinearVelocity(math.random(-20, 20) , math.random(-20, 20))

numberOfActiveFoods = numberOfActiveFoods + 1;

end

local function changeWave()
print("Entering changeWave while waveIndex is " … waveIndex);

– Randomly selecting a spawn point.
local currentSpawner;
local whichSpawnerToChoose = math.random(1, 2)
if 1 == whichSpawnerToChoose then
currentSpawner = spawnPointTop;
print(“we chose top spawn point”);
elseif 2 == whichSpawnerToChoose then
currentSpawner = spawnPointBottom;
print(“we chose bottom spawn point”);
end

– Spawning random number of foods.
local numOfFoodsToSpawn = math.random(1, 3)
for foodsToCreate = 1, numOfFoodsToSpawn do
makeOneFoodObject(currentSpawner.x - 100 + foodsToCreate * 50, currentSpawner.y)
end

weNeedToGoToNextWave = false;

end

local function UpdateHUD()
monster_purple.levelTextField:setText("Level: " … tostring(monster_purple.level));
monster_purple.GoalTextField:setText("Goal: " … tostring(monster_purple.numberOfEatenFoods) … “/” … tostring(monster_purple.goal));
monster_purple.mouthTimerTextField:setText("Time: " … tostring(monster_purple.HowMuchTimeIsLeftBeforeClosingMouthInSeconds));

monster_yellow.levelTextField:setText("Level: " … tostring(monster_purple.level));
monster_yellow.GoalTextField.text = "Goal: " … tostring(monster_purple.numberOfEatenFoods) … “/” … tostring(monster_purple.goal);
monster_yellow.mouthTimerTextField.text = "Time: " … tostring(monster_purple.HowMuchTimeIsLeftBeforeClosingMouthInSeconds);
end

timer.performWithDelay( 200, UpdateHUD, 0 )

local function main()
initializeGame();
–makeOneFoodObject(210, 260, 1);
–makeOneFoodObject(280, 260, 2);

end

– Calling entry point (just once).
main();

local function MainLoop()

if 0 == numberOfActiveFoods then
weNeedToGoToNextWave = true;
end

if true == weNeedToGoToNextWave then
changeWave();
end

– monster_purple:timer();

end

Runtime:addEventListener(“enterFrame”, MainLoop);
–timer.performWithDelay( 5000, MainLoop, 0 )[/lua]

[lua]local M = {} – table needed to return the file

local function ResetTimer(self)
self.whenToCloseMouth = os.time() + self.amoutOfTimneToCloseMouthInSeconds;
end

local function timer(self)
self.HowMuchTimeIsLeftBeforeClosingMouthInSeconds = self.whenToCloseMouth - os.time();

if ( self.HowMuchTimeIsLeftBeforeClosingMouthInSeconds <= 0 ) then
print(“Close mouth NAOW!”);
self:ResetTimer();
print( "Next mouth closeing time: " … tostring(self.whenToCloseMouth) )
self:CloseMouth();
end
end
–monsterObject:timer()
–monsterObject.mouthTimerTextField = display.newText("Time: " … tostring(monster_purple.HowMuchTimeIsLeftBeforeClosingMouthInSeconds), 15, 110, native.systemFontBold, 16)

local function CloseMouth(self)
if self.numberOfEatenFoods > self.goal then
print(“Purple monster ate more than goal number of foods, increasing level.”);
self.level = self.level + 1;
elseif self.numberOfEatenFoods < self.goal then
print(“Purple monster ate less than goal number of foods, decreasing level.”);
self.level = self.level - 1;
end

self.numberOfEatenFoods = 0;
self:ResetTimer();
end

local function ResetEatenFoodCount(self)
self.numberOfEatenFoods = 0;
end

– function
local function setText(self, string)
self.text = string or self.text
self:setReferencePoint(display.TopLeftReferencePoint)
self.x = self.originalX or self.x
self.y = self.originalY or self.y
end

function M.new(monsterType, xPos, yPos)
local newMonster = display.newGroup();

– newMonster.myType = monsterType;
newMonster.x = xPos;
newMonster.y = yPos;
newMonster.level = 1;
newMonster.goal = 3;
newMonster.numberOfEatenFoods = 0;
newMonster.amoutOfTimneToCloseMouthInSeconds = 7; – Mouth timer.

local visual;
if “Purple” == monsterType then
– visual = display.newImageRect(“monster_purple.png”, 116, 314);
– newMonster:insert(visual)
– newMonster.visual = visual;

newMonster.visual = display.newImageRect(newMonster, “monster_purple.png”, 116, 314);
elseif “Yellow” == monsterType then
– visual = display.newImageRect(“monster_yellow.png”, 111, 314);
– newMonster:insert(visual)
– newMonster.visual = visual;

newMonster.visual = display.newImageRect(newMonster, “monster_yellow.png”, 116, 314);

end

newMonster.visual.myType = monsterType

– Goal.
newMonster.levelTextField = display.newText(“Level: TEMP” , 15, 50, native.systemFontBold, 16);
local levelTextField = display.newText(“Level: TEMP” , 0, 0, native.systemFontBold, 16);
levelTextField.x = -50
levelTextField.y = -50
levelTextField.originalX = levelTextField.x
levelTextField.originalY = levelTextField.y
levelTextField:setReferencePoint(display.TopLeftReferencePoint)
newMonster:insert(levelTextField)
newMonster.levelTextField = levelTextField;
newMonster.levelTextField.setText = setText;

local GoalTextField = display.newText(“Goal: TEMP” , -50, -80, native.systemFontBold, 16);
GoalTextField.x = -50
GoalTextField.y = -80
GoalTextField.originalX = levelTextField.x
GoalTextField.originalY = levelTextField.y
GoalTextField:setReferencePoint(display.TopLeftReferencePoint)
newMonster:insert(GoalTextField);
newMonster.GoalTextField = GoalTextField;
newMonster.GoalTextField.setText = setText;

local mouthTimerTextField = display.newText(“Time: TEMP” , -50, -110, native.systemFontBold, 16);
mouthTimerTextField.x = -50
mouthTimerTextField.y = -110
mouthTimerTextField.originalX = levelTextField.x
mouthTimerTextField.originalY = levelTextField.y
mouthTimerTextField:setReferencePoint(display.TopLeftReferencePoint)
newMonster:insert(mouthTimerTextField);
newMonster.mouthTimerTextField = mouthTimerTextField;
newMonster.mouthTimerTextField.setText = setText;

– Assigning functions.
newMonster.ResetTimer = ResetTimer;
newMonster.timer = timer;
newMonster.CloseMouth = CloseMouth;
newMonster.ResetEatenFoodCount = ResetEatenFoodCount;

newMonster.visual:toFront();

return newMonster;
end

return M;[/lua] [import]uid: 206803 topic_id: 35380 reply_id: 335380[/import]

Please note the way I attach physic to display groups: There is a visual object inside each group that I pass that to physic.add, as I haven’t seen this anywhere and wrote it as I thought it should work so that may be the problem. [import]uid: 206803 topic_id: 35380 reply_id: 140599[/import]

Solved: Problem was that you should not have physic objects from different display groups that interact with each other.

I think Coronalabs should either fix these or put them somewhere so everyone can read them because it’s intuitive, at least to me, to write code modular and this way and how one should know that box2d has this limitation?

It was frustrating as I already have lots of problems and as I go on, I hit more walls rather than progress but at least this is gone now, even though I’m not happy with the results. [import]uid: 206803 topic_id: 35380 reply_id: 140601[/import]

Please note the way I attach physic to display groups: There is a visual object inside each group that I pass that to physic.add, as I haven’t seen this anywhere and wrote it as I thought it should work so that may be the problem. [import]uid: 206803 topic_id: 35380 reply_id: 140599[/import]

Solved: Problem was that you should not have physic objects from different display groups that interact with each other.

I think Coronalabs should either fix these or put them somewhere so everyone can read them because it’s intuitive, at least to me, to write code modular and this way and how one should know that box2d has this limitation?

It was frustrating as I already have lots of problems and as I go on, I hit more walls rather than progress but at least this is gone now, even though I’m not happy with the results. [import]uid: 206803 topic_id: 35380 reply_id: 140601[/import]

Thanks for posting this info.  Been banging my head most of the day trying to figure out why my detection is not working.  I guess I have to rethink my UI.

This is kind of a bummer, I really like using groups to segment off the UI.

Cheers.

Hi Aidin, @develephant,

Physics objects in different display groups will collide with each other, in fact. However, you will experience issues with improper collisions if you move the origin (x/y position) of these display groups around independently of each other (that includes scaling too). Box2D calculates collisions based on the common 0,0 origin of display groups… we considered a “fix” for this, but it would have likely caused further-reaching issues in other physics APIs, so it was shelved for the time being.

So, the short answer is, you can have objects in different display groups interact with each other. Just don’t move the display groups around independently of each other (you can move them, but move them all in unison if you must).

If your collisions aren’t firing, first check that at least one object in the collision is the “dynamic” type. I state that often, but it doesn’t hurt to say so again. :slight_smile:

Take care,

Brent

Thanks for posting this info.  Been banging my head most of the day trying to figure out why my detection is not working.  I guess I have to rethink my UI.

This is kind of a bummer, I really like using groups to segment off the UI.

Cheers.

Hi Aidin, @develephant,

Physics objects in different display groups will collide with each other, in fact. However, you will experience issues with improper collisions if you move the origin (x/y position) of these display groups around independently of each other (that includes scaling too). Box2D calculates collisions based on the common 0,0 origin of display groups… we considered a “fix” for this, but it would have likely caused further-reaching issues in other physics APIs, so it was shelved for the time being.

So, the short answer is, you can have objects in different display groups interact with each other. Just don’t move the display groups around independently of each other (you can move them, but move them all in unison if you must).

If your collisions aren’t firing, first check that at least one object in the collision is the “dynamic” type. I state that often, but it doesn’t hurt to say so again. :slight_smile:

Take care,

Brent