pixijs/layers

Children don't undermine their parents

kaansoral opened this issue · 18 comments

So I'm experiencing this problem right now:

screen shot 2018-09-21 at 10 35 03

[1-Major] Basically, no matter what I tried, I couldn't make childrens of a sprite, appear below the sprite (they are on the same layer, aka, PIXI.DisplayGroup) - so basically I want wings to appear behind the body, but just doesn't happen

[2-Minor] I had pixi-layers 0.1.7 - now I got the latest 0.1.7 - it's a lengthier one, has some sortPriority code - I tried setting it to several places but didn't change anything - I think the version should've increased it at one point, to 0.1.8

[3-Major-Opinion-Request] I could solve this problem with tools at hand, make the body a children of the container, rather than the container itself, but looking for the optimal solution, I also apply filters to the body, I want the additional parts like the head etc. to have the filters too, if they were actually one Sprite, this would be the case, but I'm guessing there's no actual solution to this problem now? Example: If I applied an outline filter to sprites, the head and body will be outlined individually now?

I think you use it the wrong way. Please make a Fiddle and i'll fix it.

Actually, never mind, its easier to change "container.renderWebGL" method for your body than to use pixi-layers in this case. Can you do that?

I think you misunderstand, what do you mean by container.renderWebGL? It's already WebGL

For example, the group I was setting all the parents and children to had priority 3 with a special sort function, when I add 2 more groups, 2.99 and 3.01, I can temporarily achieve what I want:

screen shot 2018-09-21 at 11 03 23

But when they are all in the same group, the children of the sprite doesn't appear below the sprite, while their .sort_y attributes are lower

I mean look in the Container source already. Pixi is not a black box.

https://github.com/pixijs/pixi.js/blob/dev/src/core/display/Container.js#L425

override it and move whatever you need wherever you need.

Also, outline will be slow , if you want to use it for more than 10 characters - its better to duplicate each sprite shadow in a different container or level before you draw this thing.

Don't get this the wrong way, If I had the ability to just look and find the all the answers, I would not be asking these questions, I would just write my own library with some more effort, you have to understand that people who use your library, will usually have a lower understanding of the field than you

So let me ask a simpler question:

  1. I have a Sprite (called parent)
  2. I add another Sprite as a child to Sprite (called child)
  3. I set the same parentGroup to both parent and child
  4. If I change the zOrder - can I make child appear above and below parent?

If the answer is yes, I'm doing something wrong, if the answer is no, I'm missing something, and I'm looking for that thing

Yes, you can. Make sure that somewhere there is a Layer object with that group where it all will be rendered inside.

Its not optimal. I advice you to look at how everything works and look for stupid solutions.

// Drag the rabbits to understand what's going on

var app = new PIXI.Application(800, 600, {backgroundColor: 0x1099bb});
document.body.appendChild(app.view);

//META STUFF, groups exist without stage just fine

// z-index = 0, sorting = true;
var greenGroup = new PIXI.display.Group(0, true);
greenGroup.on('sort', function (sprite) {
    //green bunnies go down
    sprite.zOrder = -sprite.y;
});


//specify display list component
app.stage = new PIXI.display.Stage();
app.stage.group.enableSort = true;
//sorry, group cant exist without layer yet :(
app.stage.addChild(new PIXI.display.Layer(greenGroup));


// create a texture from an image path
var texture_green = PIXI.Texture.fromImage('required/assets/bunnies/square_green.png');
var texture_blue = PIXI.Texture.fromImage('required/assets/bunnies/square_blue.png');

// make obsolete containers. Why do we need them?
// Just to show that we can do everything without caring of actual parent container
var bunniesOdd = new PIXI.Container();
app.stage.addChild(bunniesOdd);

var bunny = new PIXI.Sprite(texture_green);
bunny.parentGroup = greenGroup;
var bunny2 = new PIXI.Sprite(texture_blue);
bunny2.parentGroup = greenGroup;
bunny2.x=10;
bunny2.y=-1;

bunny.addChild(bunny2);
//bunniesOdd.addChild(bunny2);
bunniesOdd.addChild(bunny);

Test this here: https://pixijs.io/examples/#/layers/zorder.js

It doesn't work

//bunny.addChild(bunny2);
bunniesOdd.addChild(bunny2);

This works

Btw I dont know what line app.stage.group.enableSort = true; does in that example, I think its redudant.

Oh, right its a known bug. If parent and child belong to the same group, then child is rendered twice - one as a mamber of a group and one as a child of that parent. I dont know how to fix that fast, and its the first time someone actually encountered it :)

Well If you started your own Patreon, I'd join :)

Other than that, my capabilities are beyond it, if it will be a long living bug, I'd probably work around it - for this specific occurrence, I think it'd be better if I turned the Sprite itself into a Container, and manually sort things inside, it's a better solution anyway

But I do come up with a lot of weird in-between stages in development that calls for having the child and parent having the same layer, for example, think of a small ball circling around a player object, someone using pixi-layers could just want to experiment with such a thing, and would stumble onto this bug again

You sometimes just want to add something to a Sprite, and practically make it appear under the Sprite, you know, even if it's a child

Ok, you've got me, I'm gonna fix it right now.

YEAH IT WORKS!

Try to add this thing somewhere on top of your code:

PIXI.Container.prototype.renderWebGL = function (renderer) {
    if (this._activeParentLayer && this._activeParentLayer != renderer._activeLayer) {
        return;
    }
    if (!this.visible) {
        this.displayOrder = 0;
        return;
    }

    this.displayOrder = renderer.incDisplayOrder();

    if (this.worldAlpha <= 0 || !this.renderable) {
        return;
    }
 
    renderer._activeLayer = null; // < -- this is my temporary change
    this.containerRenderWebGL(renderer);
    renderer._activeLayer = this._activeParentLayer; // < -- and this one too
}

Got it , please look again.

Thanks :)

Tested it, for me, it made the child sprites disappear (On the pixi/examples fiddle it works tho)

So my actual use case differs from the fiddle somehow

So let me ask a simpler question:

  1. I have a Sprite (called parent)
  2. I add another Sprite as a child to Sprite (called child)
  3. I set the same parentGroup to both parent and child
  4. If I change the zOrder - can I make child appear above and below parent?

If the answer is yes, I'm doing something wrong, if the answer is no, I'm missing something, and I'm looking for that thing

hum, if i remember on my side , the zOrder not work, but zIndex work

what append if you do ?

            s1.parentGroup = $displayGroup.group[id];
            s1.zIndex = 10;
            s2.parentGroup = $displayGroup.group[id];
            s2.zIndex = 15;

suppose s1 and s2 are containers or sprites inside a stage container, and your stage have PIXI.display.Stage.prototype

@djmisterjon sort direction is different for zIndex and zOrder

Oh, right its a known bug. If parent and child belong to the same group, then child is rendered twice - one as a mamber of a group and one as a child of that parent. I dont know how to fix that fast, and its the first time someone actually encountered it :)

I encountered this but thought it was becasue I was doing something wrong 👍
Is there something I can change in my structure to get it to go away?

Two groups
gameGroup
uiGroup

Two display Layers
World
UI

Stage -> PIXI.display.Stage();
--'User (you)' -> PIXI.Container(); //gameGroup
----UserSpine' -> PIXI.spine.Spine(); //gameGroup
------'various graphics, text, spine, and sprite elements' //gameGroup
--'World' -> PIXI.display.Layer(gameGroup);
-----Users' -> PIXI.Container(); //gameGroup
------'UserSpine' -> PIXI.spine.Spine(); //gameGroup
--------'various graphics, text, spine, and sprite elements' //gameGroup
----'WorldChildren' ->PIXI.Container(); //gameGroup
------'Stage Objects (houses, and other interactive objects') -> PIXI.Sprite() //gameGroup
--------'Spines for some objects' ->PIXI.spine.Spine(); //gameGroup
----'Other elements like tiled backgrounds and grids' -> PIXI.Container(); //gameGroup
--'UI' -> PIXI.display.Layer(uiGroup);

Everything in my world layer is being drawn twice.

EDIT: I'm sorry for anyone that tried to use the first part of what I posted. It had some changes to it based on some things I add to every build of pixi I do. I changed the code to work properly without any kind of extras. Also those changes were not compatible with pixi-lights so I had to go through and undo them myself and remembered this comment!

I should have clarified that the temp solution didnt work for me either. These changes worked for me for now. Not sure if they will work for you for the time being :)

All this does is limit an object to one render per 'frame'. For me it seemed like the 'normal' child drawing was always happening before the sorted array so I set it up to always return on the first attempt and allow any further attempts. It is possible to have things draw more than twice depending on how you set them up. In which case you will have to modify this tmporary solution. Your milage may vary.

Replace this at the top of your dist file

var pixi_display;
(function (pixi_display) {
    Object.assign(PIXI.Container.prototype, {
        renderWebGL: function (renderer) {
            if (this._activeParentLayer && (this.lastRender != renderer.renderTime || this._activeParentLayer != renderer._activeLayer)) {
                this.lastRender = renderer.renderTime;
                return;
            }
            if (!this.visible) {
                this.displayOrder = 0;
                return;
            }
            this.displayOrder = renderer.incDisplayOrder();
            if (this.worldAlpha <= 0 || !this.renderable) {
                return;
            }
            this.containerRenderWebGL(renderer);
        },
        renderCanvas: function (renderer) {
            if (this._activeParentLayer && (this.lastRender != renderer.renderTime || this._activeParentLayer != renderer._activeLayer)) {
                this.lastRender = renderer.renderTime;
                return;
            }
            if (!this.visible) {
                this.displayOrder = 0;
                return;
            }
            this.displayOrder = renderer.incDisplayOrder();
            if (this.worldAlpha <= 0 || !this.renderable) {
                return;
            }
            this.containerRenderCanvas(renderer);
        },
        containerRenderWebGL: PIXI.Container.prototype.renderWebGL,
        containerRenderCanvas: PIXI.Container.prototype.renderCanvas
    });
})(pixi_display || (pixi_display = {}));

Replace this at the bottom of your dist file

var pixi_display;
(function (pixi_display) {
    Object.assign(PIXI.WebGLRenderer.prototype, {
        _lastDisplayOrder: 0,
        _activeLayer: null,
        renderTime : 0,
        incDisplayOrder: function () {
            return ++this._lastDisplayOrder;
        },
        _oldRender: PIXI.WebGLRenderer.prototype.render,
        render: function (displayObject, renderTexture, clear, transform, skipUpdateTransform) {
            this.renderTime = window.performance.now();
            if (!renderTexture) {
                this._lastDisplayOrder = 0;
            }
            this._activeLayer = null;
            if (displayObject.isStage) {
                displayObject.updateStage();
            }
            this._oldRender(displayObject, renderTexture, clear, transform, skipUpdateTransform);
        }
    });
    Object.assign(PIXI.CanvasRenderer.prototype, {
        _lastDisplayOrder: 0,
        _activeLayer: null,
        incDisplayOrder: function () {
            return ++this._lastDisplayOrder;
        },
        _oldRender: PIXI.CanvasRenderer.prototype.render,
        render: function (displayObject, renderTexture, clear, transform, skipUpdateTransform) {
            this.renderTime = window.performance.now();
            if (!renderTexture) {
                this._lastDisplayOrder = 0;
            }
            this._activeLayer = null;
            if (displayObject.isStage) {
                displayObject.updateStage();
            }
            this._oldRender(displayObject, renderTexture, clear, transform, skipUpdateTransform);
        }
    });
})(pixi_display || (pixi_display = {}));