Jump to content

Depth sort + multiple groups


m4uro
 Share

Recommended Posts

I have two groups of sprites, one containing trees, and another one containing characters. I want to sort them with the group.sort() function, but in order to do that I would need to put them in other, bigger group. However, I can't do that with the approach taken in the example of "sub groups", because when I do it that way, the _container has other containers as its children, instead of the actual sprites. What is the best/easiest way to accomplish this, without losing the flexibility of having separate groups for trees and characters?

Link to comment
Share on other sites

  • 1 month later...

That's a very good question and I was looking for an answer to it as well.

I did the very same thing not long ago in Flixel, where I needed to sort objects of different kinds for display (in a display group) and manage their behavior separately (in two logic groups.)
Right now I'm trying to do something similar with Phaser and noticed that adding an object to two groups is not working as I expected. The example does show you how to add the _container to another group but as you mentioned, we actually want the sprites to be part of both groups, not to have groups within groups.

 

Maybe there's a simple solution around this problem that we're not seeing...

 

EDIT: thanks for the answer rich, that makes perfect sens. I managed to reorganize my game logic in an efficient way that doesn't require multiple object lists.

Link to comment
Share on other sites

You can't have a Sprite in more than one Group. Phaser Groups are part of the Display List, and in a display list (as in Flash) everything exists in just one place at once. You'll need to manage your own lists of objects with a custom sort to handle what you need I'm afraid.

Link to comment
Share on other sites

  • 1 month later...

You can properly nest and sort Groups within Groups now. However you still can't mix the z-depth of objects spread across 2 Groups. I.e. the original question was asking how to depth blend a trees Group with a characters Group. You still can't do that without putting both types of object in the same Group, because of the way display lists work.

Link to comment
Share on other sites

I understand now. I also had a look at the source and I see what you mean. This will really be very very handy if it can be done. When I saw what's possible in Phaser with groups, I was amazed and immediately started re-writing my game in Phaser. Not being able to sort multiple groups together kinda makes a lot of the awesome group features redundant in my case.

 

I'd be happy to try and code something into Phaser or Pixi for this, but I don't understand the implications around the changes I have in mind..

 

Here's what I've been thinking: If I add a property to groups that, when set to true for instance, would make the system copy all the child sprites of all the child groups into one single array in the top-level group, sort them in the given order, and only draw sprites from that collection, once every frame. So updates will happen to the individual children as they normally do, but everything is copied to a separate array just for the sake of sorting and drawing at the correct depth.

 

Would this work? I'm not sure how efficient this might be in the end, or if it's just a simple as adding another array, sorting it, and drawing from said array. Should I attempt this? Is there anything in particular that I should be mindful of that might be easily overlooked?

Link to comment
Share on other sites

The issue is that Groups are basically display object containers, and in a display list structure (such as Phaser / Pixi / Flash uses) you can't have an item exist across multiple containers. What you need is a single container for all objects that need depth sorting, but I suspect you want to use the benefit of Groups for pooling items, collision, etc. There's no reason why you couldn't take the Group class and customise it so it doesn't extend a display object container, but instead is just a data bucket of objects - the objects would still need to exist in a single container for display purposes - but with these AbstractGroups you could at least 'talk to' and maintain similar types of object, regardless where they are on the display list.

 

It's definitely not a small change, and would require quite some work, but I'm not sure I see another approach really.

Link to comment
Share on other sites

  • 2 weeks later...

Ok, so I've managed to solve this little problem.

 

Here's a screenshot for proof.. https://twitter.com/Krummelz/status/458307201430347778

 

This currently doesn't work for the canvas renderer, as the code below only illustrates the changes made to Pixi's _renderWebGL function. Fixing this is really very easy.

 

 

Here are the files I've touched and the changes I've made:

 

 

Phaser / src / pixi / display / DisplayObjectContainer.js

In the constructor function, I've added the drawCache array to keep the cached children of Phaser Group objects, as well as the isSingleParentGroup property which, if set, will cause caching to happen.

PIXI.DisplayObjectContainer = function(){    PIXI.DisplayObject.call( this );     /**     * [read-only] The array of children of this container.     *     * @property children     * @type Array<DisplayObject>     * @readOnly     */    this.children = [];     this.drawCache = [];    this.isSingleParentGroup = false;};
Then, I've updated the _renderWebGL function to check the isSingleParentGroup property, and if it's true, will render the sprites from the drawCache instead of the regular children array.

PIXI.DisplayObjectContainer.prototype._renderWebGL = function(renderSession){    if(!this.visible || this.alpha <= 0)return;    if(this._cacheAsBitmap)    {        this._renderCachedSprite(renderSession);        return;    }    var i,j;    if(this._mask || this._filters)    {        if(this._mask)        {            renderSession.spriteBatch.stop();            renderSession.maskManager.pushMask(this.mask, renderSession);            renderSession.spriteBatch.start();        }        if(this._filters)        {            renderSession.spriteBatch.flush();            renderSession.filterManager.pushFilter(this._filterBlock);        }        // for multi-group depth sort        if (this.isSingleParentGroup === true) {            // render the drawCache            for (i = 0, j = this.drawCache.length; i < j; i++) {                this.drawCache[i]._renderWebGL(renderSession);            }        }        else {            // simple render children!            for(i = 0,j = this.children.length; i<j; i++)            {                this.children[i]._renderWebGL(renderSession);            }        }        renderSession.spriteBatch.stop();        if(this._filters)renderSession.filterManager.popFilter();        if(this._mask)renderSession.maskManager.popMask(renderSession);        renderSession.spriteBatch.start();    }    else    {        // for multi-group depth sort        if (this.isSingleParentGroup === true)        {            // render the drawCache            for (i = 0, j = this.drawCache.length; i < j; i++) {                this.drawCache[i]._renderWebGL(renderSession);            }        }        else         {            // simple render children!            for (i = 0, j = this.children.length; i < j; i++) {                this.children[i]._renderWebGL(renderSession);            }        }                }};
Phaser / src / core / Group.js

I've added two functions as below. These will use the Pixi drawCache array as seen above, and add every sprite of every child Group object to it.

 

Phaser.Group.prototype._recursiveCache = function (arrayToSort) {    // recursive function that will stick all nested children sprites in the drawCache so they're on the same level    for (var i = 0; i < arrayToSort.length; i++) {        if (arrayToSort[i].children && arrayToSort[i] instanceof Phaser.Group) {            this._recursiveCache(arrayToSort[i].children);        }        else {            this.drawCache.push(arrayToSort[i]);        }    }};
Phaser.Group.prototype._CacheObjectsForDraw = function () {    //clear the cache    this.drawCache.length = 0;    //cache all children    this._recursiveCache(this.children);};
 

Then I've updated the Group.js sort function as below. This will now check if the isSingleParentGroup property is true, and will then call the _CacheObjectsForDraw function I created, and set the drawCache as the array to sort; or otherwise the regular children array will be sorted as normal.

 

Phaser.Group.prototype.sort = function (index, order) {        if (this.children.length < 2) {        //  Nothing to swap        return;    }    if (typeof index === 'undefined') { index = 'z'; }    if (typeof order === 'undefined') { order = Phaser.Group.SORT_ASCENDING; }    this._sortProperty = index;    // determine the array to sort    var arrToSort;    if (this.isSingleParentGroup === true) {        // cache all children        this._CacheObjectsForDraw();        arrToSort = this.drawCache;    }    else {        arrToSort = this.children;    }    // sort the appropriate array in the appropriate way    if (order === Phaser.Group.SORT_ASCENDING) {        arrToSort.sort(this.ascendingSortHandler.bind(this));    }    else {        arrToSort.sort(this.descendingSortHandler.bind(this));    }    this.updateZ();};
Simple stuff, really :)

 

Here's some sample code from my tower defence game, to show how you would use this:

var game = new Phaser.Game(800, 600, Phaser.AUTO, 'phaser-example', { preload: preload, create: create, update: update });var mainGroup;var enemyGroup;var towerGroup;function preLoad(){  // your preload logic}function create(){  // create your groups  mainGroup = game.add.group();  enemyGroup = game.add.group();  towerGroup = game.add.group();  // nest your enemy and tower groups under the main group  mainGroup.add(enemyGroup);  mainGroup.add(towerGroup);  // this will make the magic happen   mainGroup.isSingleParentGroup = true;  // go ahead and add your sprites to your groups as you normally would  // for example:  // var enemy = game.add.sprite([blah blah]);  // enemyGroup.add(enemy);}function update(){  // your update logic goes here  // as per the examples, we are told to call sort last in the update function..  // this works as you would expect..  mainGroup.sort("y", Phaser.Group.SORT_ASCENDING);}
Link to comment
Share on other sites

  • 2 weeks later...

Is this solution going into the next Phaser release? 

 

Also wanted to add I just tried this out and it works like a champ so far! spent all yesterday trying to get nested groups and depth sorting to work, couldn't figure out what I was doing wrong and finally hit up the forums this morning.

Link to comment
Share on other sites

Ah, just tested it in Safari on both the iPad mini, and the last generation of iPad(not sure what it's called) and depth sorting + nested groups breaks on there. Not sure why?

 

Also tested in IE11: doesn't work

In Firefox it works but is slooow slow.

In Chrome all is good

Link to comment
Share on other sites

The only reason I can think of that it would break is if the iPads and IE use the canvas for rendering instead of WebGL - In the code above, I only changed the internal Pixi "_renderWebGL" function, and not "_renderCanvas". I still have to do the canvas function.

 

I can test IE, but I'm afraid I don't own an iPad to test on.

I'll look at doing the canvas and then a pull request.

Link to comment
Share on other sites

I found an easier work-around, quite by accident and quick theorizing. It does require creating all your objects in one group, but when actually assign the object's data, simply add a custom variable that indicates if it is to be affected by your game functions. It's a slow work-around, but it worked in my code.

function create() {  obj = game.add.group();  obj.add(pc);  obj.add(exit);  pc = game.add.sprite(blah,blah,blah);  pc.objState = false; // identifiable custom variable  exit = game.add.sprite(blah,blah,blah);  exit.objState = true; // identifiable custom variable  for (var i=0;i<x;i++) {    npc = game.add.sprite(blah,blah,blah);    npc.objState = true;  // identifiable custom variable    obj.add(npc);  };  obj.sort();}function update() {    obj.forEach(updateObj, this);  obj.sort('y', Phaser.Group.SORT_ASCENDING);}function updateObj(z) {  if(z.objState) {    // processes here only affect objects with the objState set to true    z.<game object property> <argument>;  };    // processes here affect every object}

I have yet to figure out how to effectively use protoypes, so all my stuff is from a basic programmer's perspective. I'd apreciate any assistance in speeding up this routine, but until I tested it, I didn't actually expect it to work. I expected the interpreter to say ".objState is not a " whatever.

Link to comment
Share on other sites

I was working with dynamic z-indexing (ie. bringing stuff to front and returning back to same position after some action) but I stumbled to an issue with the updateZ() function in the group sorting. Is it required that the Z index is gapless or any other reason why the Z values are being reset after sort? 

 

For  now I was able to fix this with custom sort property to my sprites (called 'zindex') and sort based on that and not to update the actual z value manually, but this has it's problems with Typescript (nothing what makes things stop working, but you have to resort either 'any' type or [] accessor).

 

Problem was a bit similar as this thread was asking. Object of multiple parts and each part could build up from sprites which need to overlap on different z index relative to other parts. Plus I wanted to "lift" one part above others temporarily.

Link to comment
Share on other sites

Small additional question.. anyone tested more with Nokia N9 native browser? Seems things work fairly well, but initial testing shows the z sorting is reversed on the N9. Not sure if webGL is used or is it just canvas mode, but same code works fine on Chrome and iOS. Not going to inspect this more unless it appears again on the devices we're really targetting.

Link to comment
Share on other sites

  • 1 year later...

Many thanks for this example. That's interesting.

 

However I'm not sure if I can use this to solve my problem since I must keep my 2 sprites in different groups (from Phaser point of view) to use collisions.

 

If I understand right, your sprites (MySprite class) have 2 groups : the original one managed by Phaser (which can be used for collisions and stuff), and another one that is used in the updateTransform method. I didn't find any documentation on updateTransform so I'm not sure how this works but anyway.

 

In your example you're putting your sprites in the same group (I mean the "real" phaser group) "spritesGroup", so you can't manage collisions anymore using their groups.

Link to comment
Share on other sites

Hi, you understand it well. The second group is only reference to different parent. When updateTransform on sprite is called, it is given parent and it is transformed as its child (position relative to parent, angle relative to parent, ...). What you do is, that you give it different parent from its actual in scene graph.

 

 I do not have enough experience with Phaser physics engines. But P2 collision groups are not Phaser.Group, but Phaser.Physics.P2.CollisionGroup. So, you can have sprites from different Phaser.Groups in one Phaser.Physics.P2.CollisionGroup.

Link to comment
Share on other sites

  • 4 months later...
On 9/20/2015 at 7:39 AM, Tom Atom said:

Hi, I wrote small tutorial how to simply do it: http://sbcgamesdev.blogspot.cz/2015/09/phaser-tutorial-breaking-z-order-law.html

Trick is to have one group with all items, but transform is done with different parent group.

I had a different scenario where I needed to keep multiple sprites in different groups by have them all affected by one tween.  The above link solved it for me.  Thanks @Tom Atom!

Link to comment
Share on other sites

Hello everyone,

I wrote an ES6 RenderGroup class based on the research by @Krummelz (thanks) that allows sorting of all children recursively, simply create a new RenderGroup as your root group and set it as the parent to each of your groups and it will apply the sort to the group and all of its child groups.

https://gist.github.com/crisu83/f13ec7ad0de9273480de

Hopefully you find it helpful.

Link to comment
Share on other sites

 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...