Solution to fix nested groups in G2.0?

fwiw: i sort of do the opposite, and always use anchorChildren. the “trick” for me is to always include a big invisible “bounds” rectangle, so my group will have a known size. then anchorChildren will work in a reasonably sensible way (so long as you never exceed those bounds).

the “crazy” seems a byproduct of “groups have no inherent size, but take on the size of their contents” logic, so if anchorChildren is true then when the group repositions its children it also changes its own “size”, so with a new size that affects the x/y derived from its anchor, so if x/y changed then move children, then affects size, so then move children, then affects size, ad inf… which is just silly.

you can see this behavior just *inserting* into a non-bounded group as it shifts and shimmies all over the place after each addition, growing its content bounds and repositioning itself to its anchor, so good luck “guessing” where to insert the next object! :smiley:

i banged this out to try to roughly mimic your car using this approach. you probably don’t need ALL those group wrappers, but the fn was there so i used it. really the only thing of note is “bounds”:

-- wrap object in group util fn local function newWrapper(obj,x,y) local grp = display.newGroup() grp.anchorChildren = true if (obj) then grp:insert(obj) end if (x and y) then grp.x,grp.y=x,y end return grp end -- create all parts at origin local bounds = display.newRect(0, 0, 300, 300); bounds:setFillColor(1,1,1,0.1) -- typically bounds.isVisible=false local body = display.newRect(0, 0, 200, 50); body:setFillColor(0.5,0.5,0.8) local cab = display.newRect(0,0,50,50); cab:setFillColor(0.6,0.6,0.9) local tiref = display.newCircle(0,0,30); tiref:setFillColor(0.2,0.2,0.2) local wheelf = display.newCircle(0,0,20); wheelf:setFillColor(0.4,0.7,0.7) local tireb = display.newCircle(0,0,30); tireb:setFillColor(0.2,0.2,0.2) local wheelb = display.newCircle(0,0,20); wheelb:setFillColor(0.4,0.7,0.7) -- wrap into groups and position internally local truck = newWrapper(bounds) local chassis = newWrapper() chassis:insert( newWrapper(body) ) chassis:insert( newWrapper(cab, -25, -50) ) truck:insert( chassis ) local wtiref = newWrapper() wtiref:insert( newWrapper(tiref) ) wtiref:insert( newWrapper(wheelf) ) wtiref.x, wtiref.y = -75, 50 wtiref.xScale = 0.5 truck:insert( wtiref ); local wtireb = newWrapper() wtireb:insert( newWrapper(tireb) ) wtireb:insert( newWrapper(wheelb) ) wtireb.x, wtireb.y = 75, 50 wtireb.xScale = 0.5 truck:insert( wtireb ); -- now position overall truck.x, truck.y = display.contentCenterX, display.contentCenterY -- give a fixed indication of center -- (with animation off, the back corner of cab-at-chassis should occur here) local center = display.newCircle(display.contentCenterX, display.contentCenterY, 5); center:setFillColor(1,0,0,0.5) if (true) then -- animation?? transition.to (wtireb, { time = 500, rotation = -360, iterations=8} ) transition.to (wtiref, { time = 500, rotation = -360, iterations=8} ) transition.to (chassis, { time = 250, y = -50, iterations=16, transition=easing.continuousLoop } ) transition.to (truck, { time = 4000, x = 200 } ) end

Huh! Neat trick!

Good find Dave, you’ve zeroed in to the exact issue with nested groups. Do you think it’s worth submitting this as a bug, or are we doomed to forever adding this kind of work around everytime a group is created that needs nested animation?

Back to your solution Dave. Very cool trick where you put things into a temporary group to slam a layers origin into 0,0 with anchorChildren = true. You then nudge objects around in relation according to offsets you’ve worked out. These kind of little manual equations are another example of disruptions to work flow. In fact this is a big issue with us as our scenes are laid out in photshop with a script that outputs their positions which need to be stated when they are first created in newImageRect.

So the big issue is how you maintain the relationship of objects to each other in a group when you bring them through your wrapper function. So when you create the chassis group for instance you base the “body” to define the main origin of the group chassis and then adjust the position of “top” to fit with that. But how would you take a group of objects with their relation to each other sorted with their x and y already set and then use your wrapper to slam each in the temp group - how do you automate their positioning after the wrapper process to return them back to their original positioning in relation to each other - without manually passing in x and y into the wrapper?

Other than that, if you change the anchor point, then the object still does shift to match so some kind of offset calculation needs to about
every time you set the anchor if it’s not 0.5, 0.5  This can be easily achieved with an offset function.

I think (?) they intend for it to work this way. I personally can’t imagine the use case that makes this way seem “better”, but there must be one – it made it out of beta afterall, so has become “entrenched”, and seems unlikely they’d change it (or risk breaking those who DO know the use case).

I think you’re asking if I know a way to “wrap existing content in place, thence anchored”, right? If so, short answer: no. It’s the “opposite” of what I’ve found works for me, but it would seem your method was headed that way? Perhaps some type of flow like:

create an unanchored group
add pre-positioned children
groups bounds fit children, but x/y still meaningless 0,0

have a “becomeAnchored(grp)” -type function that:

takes that populated unanchored group
calc its bounds center xy
for each child, stores its delta from center xy, puts at 0,0
grp.anchorChildren=true, grp xy now meaningful 0,0
and bounds should (? - test!) match zero’d children
for each child, put back at dx/dy
(groups bound would change, tho, so still need that big “bounds” rectangle to prevent)
put group xy at original bounds center

and your code already appears to do 90% of that, except for the anchoring (without which I think it won’t really act as what you want it to, right?). now everything ?*should*? be relatively “where it was”, but anchored to the group. Note that I haven’t tested any of that tho, just rambling ideas off top of my head, fwiw.

Yeah use case is very hard to determine…

Ok, so the bounds solution works well. It is a work around solution but it does work so that’s a relief…

I’ve adapted a function which also works to adjust an object passed in with anchor points which when used with bounds also now works to set anchors and offset group content so it doesn’t jump around.

local function anchorGroup (obj, anchorX, anchorY) local obj = obj local anchorNewX = anchorX or 0.5 local anchorNewY = anchorY or 0.5 local anchorOldX = obj.anchorX or 0.5 local anchorOldY = obj.anchorY or 0.5 local offsetX = obj.width \* ( anchorNewX - anchorOldX ) local offsetY = obj.height \* ( anchorNewY - anchorOldY ) if obj.numChildren ~= nil or obj.anchorChildren ~= true then -- NOT an image, IS a non anchored group -- CALCULATE the Center of the group. local bounds = obj.contentBounds local centerX = (bounds.xMax - bounds.xMin) / 2 + bounds.xMin local centerY = (bounds.yMax - bounds.yMin) / 2 + bounds.yMin -- Set group to this position obj.x, obj.y = centerX, centerY -- Set .anchorChildren = true to slam group origin into center obj.anchorChildren = true end -- Set the anchor if passed in or default to center obj.anchorX, obj.anchorY = anchorNewX, anchorNewY obj.x = obj.x + offsetX obj.y = obj.y + offsetY end

The only problem now when putting groups into groups is that contents x/y relate to their position in their immediate parent without consideration for how they are offset by the parent’s parent’s etc… So if you try to transition an object in a group that is in a group, you need to multiply the x and y value by 2. You also need to do the same for scale. So transitioning a group within a group to twice the xScale isn’t 2 - it’s 4… Groups suuuure have changed in G2.0. Shockers.

…if you try to transition an object in a group that is in a group, you need to multiply the x and y value by 2. You also need to do the same for scale. So transitioning a group within a group to twice the xScale isn’t 2 - it’s 4…

Really?!

Now that must be a bug. I can’t think of any use-case where *that* makes any sense…

Here is something I reported, maybe you have covered it.  In this example the object is first transitioned to a Y coordinate, then a second transition on the group ignores the Y position and the object is jerked back up to original position.

[lua]local group          = display.newGroup()

group.anchorChildren = true – Commenting this out and nothing will jump around

group.x              = 150

group.y              = 200

local box = display.newRect(group, 0, 0, 90, 54)

transition.to(box, {time = 1000, y = box.height})

–box.y = box.height – Comment this in + transition above out, nothing will jump around 

– This transition will affect the y position of the red box, but should not

transition.to(group, {delay = 1200, time = 1000, x = 50})

[/lua]

It was deemed not a bug and Coroan said ‘Setting “anchorChildren = true” on groups is not a good idea unless you have a specific need for using anchor points on groups.’

I gave up understanding anchored groups after that lol.

Ingemar - Yeah seems like it.

Calling someone from Corona Staff. Did you know if you put a group B within group A and want to xScale or yScale to twice its size you have to use 4 not 2…?

jonjonsson - Definitely another one of graphic 2.0’s crazy clown fun time quirks. If you swap them over and transition the group first, it does work if you then transition the object. It must be that the group doesn’t know itself until you use it somehow? Then when it wakes up it assumes the child’s initial position rather than taking into account where it currently is on screen… awesome…

It was deemed not a bug and Coroan said ‘Setting “anchorChildren = true” on groups is not a good idea unless you have a specific need for using anchor points on groups.’

In that case I’m happy that I spent the weekend modifying my frameworks to get rid of “anchorChildren=true” on all groups.

The reason I used it in the first place was that my groups are not always symmetrical. If the shared anchor among the children isn’t in the center of the group it’s nearly impossible to find a way to position it where you want.

I struggled for a while, but I managed to find a way to reposition the children within the group so that the internal anchor effectively becomes an anchor point that can be positioned where you want it.

I’ve modified my alignment functions to also take care of groups now. It’s still a bit early to tell, but so far I haven’t seen any adverse effects when updating my existing projects. 

It’s probably not the answer to everybody’s problems, but below is an excerpt from my utilities module.

This mini version only has one public function: 

align = function(object, alignTo, x, y) 

It aligns an object / group according to the anchor point specified as a string.

“TL” - Top Left

“TC” - Top Center

“TR” - Top Right

“CL” - Center Left

“C” - Center

“CR” - Center Right

“BL” - Bottom Left

“BC” - Bottom Center

“BR” - Bottom Right

-- utilities.lua local M = {}; -- ------------------------------- -- text/group alignment functions -- ------------------------------- local setGroupAnchor = function(object) local anchorCoordinates = function(object) local xc = (object.width \* object.anchorX) - object.x; local yc = (object.height \* object.anchorY) - object.y; if (xc \< 0 ) then xc = 0; end if (yc \< 0) then yc = 0; end return xc, yc; end local internalAnchor = {x=nil, y=nil}; -- find group;s anchor point for i = 1, object.numChildren do local xc, yc = anchorCoordinates(object[i]); if (not internalAnchor.x) or (xc \> internalAnchor.x) then internalAnchor.x = xc; end if (not internalAnchor.y) or (yc \> internalAnchor.y) then internalAnchor.y = yc; end end object.internalAnchor = internalAnchor; end local alignGroup = function(object, alignTo, x, y) setGroupAnchor(object); local alignPos = alignTo or "C"; local xPos = x or 0; local yPos = y or 0; local gWidth = object.width; local gHeight = object.height; local xOffset = 0; local yOffset = 0; if (alignPos == "TL") then xOffset = object.internalAnchor.x; yOffset = object.internalAnchor.y; elseif (alignPos == "TC") then xOffset = object.internalAnchor.x - (gWidth / 2); yOffset = object.internalAnchor.y; elseif (alignPos == "TR") then xOffset = object.internalAnchor.x - gWidth; yOffset = object.internalAnchor.y; elseif (alignPos == "CL") then xOffset = object.internalAnchor.x; yOffset = object.internalAnchor.y - (gHeight / 2); elseif (alignPos == "CR") then xOffset = object.internalAnchor.x - gWidth; yOffset = object.internalAnchor.y - (gHeight / 2); elseif (alignPos == "BL") then xOffset = object.internalAnchor.x; yOffset = object.internalAnchor.y - gHeight; elseif (alignPos == "BC") then xOffset = object.internalAnchor.x - (gWidth / 2); yOffset = object.internalAnchor.y - gHeight; elseif (alignPos == "BR") then xOffset = object.internalAnchor.x - gWidth; yOffset = object.internalAnchor.y - gHeight; else -- default "C" xOffset = object.internalAnchor.x - (gWidth / 2); yOffset = object.internalAnchor.y - (gHeight / 2); end for i = 1, object.numChildren do object[i].x = object[i].x + xOffset; object[i].y = object[i].y + yOffset; end object.x = math.round(xPos); object.y = math.round(yPos); return object; end M.align = function(object, alignTo, x, y) if (object.anchorChildren ~= nil) and (not object.anchorChildren) then return alignGroup(object, alignTo, x, y); end local alignPos = alignTo or "C"; local xPos = x or 0; local yPos = y or 0; if (alignPos == "TL") then object.anchorX = 0; object.anchorY = 0; elseif (alignPos == "TC") then object.anchorX = 0.5; object.anchorY = 0; elseif (alignPos == "TR") then object.anchorX = 1; object.anchorY = 0; elseif (alignPos == "CL") then object.anchorX = 0; object.anchorY = 0.5; elseif (alignPos == "CR") then object.anchorX = 1; object.anchorY = 0.5; elseif (alignPos == "BL") then object.anchorX = 0; object.anchorY = 1; elseif (alignPos == "BC") then object.anchorX = 0.5; object.anchorY = 1; elseif (alignPos == "BR") then object.anchorX = 1; object.anchorY = 1; else -- default "C" object.anchorX = 0.5; object.anchorY = 0.5; end object.x = math.round(xPos); object.y = math.round(yPos); return object; end return M;

A small sample project with 2 animated groups (with animated children) within a container group:

I tried scaling one of the internal groups in a transition (xScale=2, yScale=2), and it seems to work as expected, but my sample might not be complex enough to show the problem…

Right, ok. I’ll break things down and see where this size thing is occuring…

Glad you’ve got a solution that works for you. Especially good to bring back the setReference names. Can’t see much use for anything other than those 9 spots. If you want to move the anchor with reference points, I always used a small non visible rect and moved that around until the group was the right shape for the anchor to fall in the right spot. Hell of a lot easier than the kind of extreme work arounds we’re now faced with.

How does your solution work with animating, nested groups?

I modified my post above to include a sample project (ZIP).

If you run it you’ll see 2 animated groups (with animated children) within a container group.

Cool. Nice work Ingemar, will be good to have a look :slight_smile:

Oh and tracked down the source of the problem with scale and position is the use of transition=easing.continuousLoop in transition.to. this happens with groups or images outside of groups.

Oh and tracked down the source of the problem with scale and position is the use of transition=easing.continuousLoop in transition.to. this happens with groups or images outside of groups.

confirmed.

Guess I’m getting old - I’ve heard that sort of thing too many times for it to be funny any more.

Imagine if that sort of statement were from an automobile manufacturer:
'Our brakes have unexpected behavior, so using them is not a good idea… unless you REALLY need them."

Or if from a fire extinguisher manufacturer:
‘The trigger on our fire extinguishers is faulty, so we recommend that you only use it if you REALLY need it.’

Isn’t the flaw in that logic evident?

In my opinion, this is an “engineering cop out” - to admit that you have “weird” behavior, and suggest that it only be used ‘when needed’ (as if devs go around all day wasting their time implementing stuff they DON’T need!). But, of course, if you actually DO need it that’s when you’re most likely to notice the weirdness!!

BTW, here’s a scene from a new screenplay I’m working on, for your amusement:

Engineering: it’s weird, don’t use it, unless you need to.
Dev: i need to use it, but it’s weird, fix it
Engineering: not our fault, we told you not to use it, won’t fix
Dev: but you said i could use it if i ‘needed it’!
Engineering: no, we said only if you ‘specifically’ needed it
Dev: what’s the difference?
Engineering: we have internally redefined the word ‘specifically’ to cover only those uses that DON’T expose known flaws in our software - those are the ‘specific’ needs that are allowed.
Dev: so we are only to use that portion of your software that works correctly?
Engineering: yes, now you’ve got it
Dev: what about the other portion, the portion that doesn’t work correctly?
Engineering: you don’t ‘specifically need’ that portion, by definition, so stop complaining about it
Dev: ahhh, brilliant!

( :smiley: don’t take me too seriously!)

I don’t want accused of taking that quote out of context. Its from here with an explanation http://forums.coronalabs.com/topic/41599-fixed-transitions-jerky/?p=218568

dave - it does sound like the engineers are calling the shots with regard to use case. I’m sure Steve Jobs would argue for engineering to follow use case not the other way around, I guess Corona lacks a visionary leader in that respect.

jonjonsson - also to add to your point from Tom over at http://forums.coronalabs.com/topic/42781-questions-about-anchorchildren/

nested groups with “anchorChildren = true” was never intended to be supported and is not tested. The property was added to support containers, which are a form of groups. Graphics 2.0 groups are not 100% compatible with Graphics 1.0 groups

So you can hear the debates over at Corona engineering when they programmed anchors in G2. Someone must have pointed out that they didn’t work in groups so a hurried solution was found in the form of anchorChildren which wasn’t tested and isn’t universally supported by all the team members. Clearly some of those team members just don’t think developers need to use anchors in nested groups. This just seems really short sighted to dictate only specific uses of Corona.

Corona engineers, if you are struggling to imagine why anyone would want to set anchors in nested groups, just think of setting up a character for animation where the parts of the body are parented together in the form of a hierarchy. That’s exactly what we’ve been doing in Corona G1 with fantastic results. Now in G2, this is nolonger possible. To that you are effectively saying “that’s fine by us, you shouldn’t be using Corona to do character animation, you can no longer do that - go away.”

i am trying to get things running with anchorChidren, but it seems to be very buggy to me. has anyone come up with a neat solution to this problem yet?

We have a library module “anchorGroups”. It takes care of all the repositioning that happens when you create groups automatically. You can also use it to change the position of the anchor group on the fly without the group jumping around visibly.

The main problem with anchorChildren is when items within the group are moving in their own orbit and increasing the bounds of the group. What is required then is to create a null shape that is as big as the group ever gets when the item inside adjusts its position.

Anyway, this works great. You require it as anchorGroups:

local anchor = require(“anchorGroups”)

And apply it with this to a group you have created, lets say groupName:

anchor.new(groupName, 1, 1) – Bottom right.

--====================================================================-- -- PROCESS GROUPS --====================================================================-- local M = {} -- Anchor Group M.new = function (obj, anchorX, anchorY) local obj = obj obj.name = obj.name or "nameless" --print("Group obj = ", obj.name) local anchorX, anchorY = anchorX, anchorY local anchorNewX = anchorX or 0.5 local anchorNewY = anchorY or 0.5 local anchorOldX = obj.anchorX or 0.5 local anchorOldY = obj.anchorY or 0.5 local offsetX = obj.width \* ( anchorNewX - anchorOldX ) local offsetY = obj.height \* ( anchorNewY - anchorOldY ) -- Determine whether obj is a single image (numChildren = nil) or a group that has already been prepped for anchor (hasAnchor) if obj.numChildren ~= nil then -- NOT an image, is a group if obj.width \>0 and obj.height \>0 then -- isn't an empty group if not obj.anchorChildren then -- IS a non anchored group -- CALCULATE the Center of the group, add to key obj.xOrig, obj.yOrig local bounds = obj.contentBounds obj.xOrig = (bounds.xMax - bounds.xMin) / 2 + bounds.xMin obj.yOrig = (bounds.yMax - bounds.yMin) / 2 + bounds.yMin --print(obj.name.." xOrig, yOrig = ", obj.xOrig, obj.yOrig) --print(obj.name.." xMin, xMax = ", bounds.xMin, bounds.xMax) --print(obj.name.." Width, height = ", bounds.xMax-bounds.xMin, bounds.yMax-bounds.yMin) -- Set group to this position obj.x, obj.y = obj.xOrig, obj.yOrig -- Set .anchorChildren = true to slam group origin into center obj.anchorChildren = true end end end -- Set the anchor if passed in or default to center if obj.width \>0 and obj.height \>0 then -- isn't an empty group obj.x = obj.x + offsetX obj.y = obj.y + offsetY obj.anchorX, obj.anchorY = anchorNewX, anchorNewY --print("imgMode = ", imgMode) --print(obj.name, " anchors = ", anchorNewX, anchorNewY) else --print (obj.name, " = an empty group") end end return M

hi kilopop

thanks for the class. i gave it a try yesterday but it did not really help to solve my problems. but it was a quick run, so i hope to be wrong :).

right now i am using a group for the character, and inside i change sprite animations. to make it work i had to use display.newContainer instead of display.newGroup. positioning inside the group is fine now, but the parent group itself gets missplaced during gameplay (and corrected withing a few ms). an anchorX and anchorY is set on the parent group, which stays the same for all the level (so not changing any anchors etc on the parent group). pretty confusing.

let me try your class again!