Solution to fix nested groups in G2.0?

As recently established in the forum and confirmed by Corona staff, groups containing other groups with anchorChildren = true produces unpredictable results. The upshot is nested groups and layers with different anchor points no longer work in G2 whereas they did in G1… Is this a bug or is there another name for a feature that used to be possible but no no longer is?

Hopefully this is a temporary situation. But we can’t wait around until it is fixed, so have started writing a function to implement nested groups that can have different anchor points.

The following code uses the example of a few shapes to make a vehicle. It is made up of a group with 3 other groups inserted, each of which can have its own anchor position.

So far, the code is nearly there… It can take an object where ever it is on the screen and put the anchor where you provide to the anchorGroup function with object name and the new anchors x and y e.g.

anchorGroup(box, 1,1)

The trick is not to use anchorChildren = true which is where the crazy factor enters the equation with unpredictable results. Instead, images and groups are sent by their center point to 0,0, anchors added and then sent back to its original position via contentBounds.

Problem is that once groups are inserted into groups, changing the anchor to anything other than 0.5, 0.5 does provide an offset effect which leads to unpredictable effects. G1 also allowed referencePoints to be changed on an object without that object changing position which is possible on the first run of this function on an object but not if you call it again…

This is where some help would be fantastic. Hope it’s all clear and someone is up for the challenge of bringing back some of the awesomeness of setReference…

local gp\_carBody = display.newGroup() local gp\_wheel1 = display.newGroup() local gp\_wheel2 = display.newGroup() local gp\_car = display.newGroup() -- Anchor Group 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 ) local centerX = obj.x local centerY = obj.y -- 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. obj.hasAnchor = true -- CALCULATE the max and min x and y of group's children's bounding area. local bounds = obj.contentBounds centerX = (bounds.xMax - bounds.xMin) / 2 + bounds.xMin centerY = (bounds.yMax - bounds.yMin) / 2 + bounds.yMin print("center x and y = ", centerX, centerY) -- Move the group's children so that their relationship to the center of the group is at 0 local objX, objY local i for i = 1, obj.numChildren do objX, objY = obj[i]:localToContent(0,0) obj[i].x = objX - centerX - offsetX obj[i].y = objY - centerY - offsetY end -- Now the Group contents are properly organised around the anchor at x and y = 0 end -- Set the anchor if passed in or default to center --print("anchors = ", anchorOldX, anchorOldY, anchorNewX, anchorNewY) obj.anchorX, obj.anchorY = anchorNewX, anchorNewY obj.x = centerX + offsetX obj.y = centerY + offsetY print("object x and y = ", obj.x, obj.y) print("object width and height = ", obj.width, obj.height) print("--------") end -- body positioning local body = display.newRect( 1472, 922, 748, 228 ) body:setFillColor( 255/255, 255/255, 255/255 ) -- top positioning local top = display.newRect( 1454, 758, 276, 260 ) top:setFillColor( 255/255, 255/255, 255/255 ) -- tyre1 positioning local tyre1 = display.newCircle( 1265, 1041, 75 ) tyre1:setFillColor( 3/255, 145/255, 222/255 ) -- cap1 positioning local cap1 = display.newCircle( 1265, 1041, 53 ) cap1:setFillColor( 255/255, 255/255, 255/255 ) -- tyre2 positioning local tyre2 = display.newCircle( 1689, 1041, 75 ) tyre2:setFillColor( 3/255, 145/255, 222/255 ) -- cap2 positioning local cap2 = display.newCircle( 1689, 1041, 53 ) cap2:setFillColor( 255/255, 255/255, 255/255 ) -- Organise Groups gp\_carBody:insert(body) gp\_carBody:insert(top) anchorGroup(gp\_carBody) gp\_wheel1:insert(tyre1) gp\_wheel1:insert(cap1) anchorGroup(gp\_wheel1, 0,0) gp\_wheel1.xScale = 0.5 gp\_wheel2:insert(tyre2) gp\_wheel2:insert(cap2) anchorGroup(gp\_wheel2) gp\_wheel2.xScale = 0.5 gp\_car:insert(gp\_carBody) gp\_car:insert(gp\_wheel1) gp\_car:insert(gp\_wheel2) anchorGroup(gp\_car) -- animation transition.to (gp\_car, { time = 4000, x = -200 } ) transition.to (gp\_wheel1, { time = 500, rotation = -360, iterations=8} ) transition.to (gp\_wheel2, { time = 500, rotation = -360, iterations=8} ) transition.to (gp\_carBody, { time = 250, y = -200, iterations=16, transition=easing.continuousLoop } )

Another challenge… the weekend is saved !

B)

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.”

Another challenge… the weekend is saved !

B)