Z-sorting issue with sprite with custom shader

I’m adding health bars to characters using a custom shader for a sprite, which is aaalmost there:

imagen

But as seen, some of those health bars are hidden behind wall sprites. I’m quite sure I’m messing with z-sorting! All sprites (including walls) are z-sorted dynamically with this pretty basic component:

local layer_z = { 0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9 }

go.property("layer", 1)

function update(self, dt)
   local pos = go.get_position()
   pos.z = layer_z[self.layer] - pos.y * 0.0001
   go.set_position(pos)
end

which works. But as health bars have their own material (based on sprite’s) and their own shader, I’m not sure if I’m breaking that? This is what a mob with a health_bar looks like:

imagen

the vertex program, however, is very basic too:

void main() {
    var_texcoord0 = texcoord0;
    var_position_local = position_local.xy;
    gl_Position = view_proj * vec4(position.xyz, 1.0);
}

I tried sending the mob’s z coordinate to the shader so I could do something like

gl_Position = view_proj * vec4(position.xy, mob_z, 1.0);

but doesn’t seem to fix it. I also tried moving the health bar whenever the mob moves:

local player_pos = go.get_position()
position.z = player_pos.z
go.set_position(position, "#health_bar")

But being that shader involved I fear it makes not much sense. There must be something I’m doing wrong in the vertex program of the shader!

Any clue?

What is the purpose of pos.z = layer_z[self.layer] - pos.y * 0.0001? Why include y-position in the calculation? Is it just to layer enemies / characters that are lower on the Y level under one another?

local layer_z = { 0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9 }

go.property("layer", 1)

function update(self, dt)
   local pos = go.get_position()
   pos.z = layer_z[self.layer] - pos.y * 0.0001 <------ This?
   go.set_position(pos)
end

I created a simple health bar shader that uses position (world position?) to control the display. I plan on adding more layers (colors) to mimic the old Super-Nes strategy RPGs that had to have short health bars. This works with 9-Slice as well.

Find my code here: GitHub - jcbk101/HealthBar: Shader program that creates a simple health bar for Defold

That script mimics y-sorting, yes, because Defold doesn’t do it by default. So there are a number of layers predefined and within those layers sprites are also moved to -z depending on their y position. As they walkaround up and down, they are moved forward and backwardsto keep they y-sorting working.

Your shader looks pretty much like mine, with mine dividing the bar into segments, but it’s basically the same. I think yours wouldn’t y-sort neither! :frowning:

Okay. A suggestion or two.
But first, what is the max height a sprite can be? Can they be on a negative axis? If so, you should apply math.abs(pos.y).

If you want to use Y-position as a scale factor, you must remember that positive Y moves the sprite upward and negative Y moves the player downward. From what I can gather, it seems to me you know this and are calculating based off of this.

When subtracting Y, I assume that the higher player is pushed to the lower level. I think. Lol
If I am correct, then say.

sp1.y = 1000, sp2.y = 1500
sp2 is above sp1 in screen coordinates, so it should be under sp1’s health bar.

local layer_z = { 0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9 }

self.layer = 1

pos.z = layer_z[self.layer] - sp1.y * 0.0001 -> 0.0 - 1000 * 0.0001 = -0.1
pos.z = layer_z[self.layer] - sp2.y * 0.0001 -> 0.0 - 1500 * 0.0001 = -0.15

or 

self.layer = 4

pos.z = layer_z[self.layer] - sp1.y * 0.0001 -> 0.3 - 1000 * 0.0001 = 0.2
pos.z = layer_z[self.layer] - sp2.y * 0.0001 -> 0.3 - 1500 * 0.0001 = 0.15

So to me, it seems that subtracting the Y position scaled places the lower sprite on top of the higher sprite which seems like what you aiming to do.

The only issue I can think of is the possibility of the Z-position being negative or outside of your camera’s near / far Z value. If the sprite cannot move below the wall , then if not doing so already, make the wall tile Z-position lower than the lowest possible layer.

ok, wait, wait, the y-sorting is working everywhere else! : ) That is not the issue, is it? Indeed, sprites are correctly shown in front of sprites if they y is lower, which is how y-sorting works. But here we have to translate y to z and use those base layers to make the trick.

I fear it’s related to the shader, but can’t figure out how.

Okay, if the shader is the issue, I would need to see your shader code so we can evaluate the possibilities.

Ok, sure!!

First, here’s the material:

Here’s the vertex program

attribute mediump vec4 position;
attribute mediump vec2 texcoord0;
attribute mediump vec3 position_local;

varying mediump vec2 var_texcoord0;
varying mediump vec2 var_position_local;

uniform mediump vec4 sprite_z;
uniform mediump mat4 view_proj;

void main() {
    var_texcoord0 = texcoord0;
    var_position_local = position_local.xy;
    gl_Position = view_proj * vec4(position.xyz, 1.0);
}

And while I am not sure it’s needed, as it does not manipulate positions, here’s the fragment program:

 varying mediump vec2 var_texcoord0;
varying mediump vec2 var_position_local;

uniform vec4 health_values;
uniform lowp sampler2D MAIN_TEX;

void main() {
    float SPRITE_WIDTH = 7.0;
   
    float current_health = health_values.x;
    float max_health = health_values.y;
    float normalized_x = (var_position_local.x + SPRITE_WIDTH) / (SPRITE_WIDTH * 2.0);

    float d = fract(normalized_x * max_health);
    d = 1.0 - step(0.6, d);
    if (d > 0)
        if (normalized_x <= current_health / max_health) 
            gl_FragColor = vec4(0, d, 0, 1.);
        else
            gl_FragColor = vec4(0, d*0.2, 0, 1.);
}

I think there’s something I’m not doing in the vertex program. While I tried to set a proper z value, it doesn’t seem to work.

Okay. Weird. I fon’t see anything that stands out. Is your project dmall enough to share or are you too far along to share the code?

Have you tried my shader to see if thevresult is the same?

Looks to me that you are setting the GO position which includes the health bar.

I would say that maybe move only the sprites of the characters with go.set_position("sprite-id") (by putting them into their own go) and have a big enough value for the healthbar by default to always render on top of everything.

That might also decrease the amount of batches needed, as the different materials will be batched together.

@Jay_Cee found the problem, I’ll explain now
@Jerakin thanks, that would solve it, but would defeat the purpose of layers! FoV should also hide health bars

But I found the issue!! It was indeed in the z-sorting code. But not in the code, but in the data! Characters where in the Character layer, walls in the Wall layer, which was ALWAYS on top of character’s layer. If both the character layer and the wall layer are the same, z-sorting works and it looks as expected ':smiley: (that guy on the right side is in the FoV, it’s ok)

imagen

What puzzles me, however is why setting the z value in the vertex program doesn’t seem to change z-sorting at all!

I would expect this line

     gl_Position = view_proj * vec4(position.xyz, 1.0);

if we wrote it like this

gl_Position = view_proj * vec4(position.xy, z, 1.0);

being z whatever we want, to control z-sorting. But it’s totally ignored! I know we can’t change a sprite component position in real time through go.set_position, but I thought the shader would be able to do it!

Thing is

gl_Position = view_proj * vec4(position.x, position.y  - sin(time.x) , position.z, 1.0);

being time.x a value passed to the shader, does indeed move the health_bar sprite up and down! But no matter what value I put in z it’s totally ignored.

So my guess is the z-sorting is done before, somehow, and then it doesn’t really matter what value I change in the shader. Ouch :’(

Anyway, the original problem was solved (it was a matter of setting layer order correctly for characters and walls), this is a different thing which would be nice to understand, but I guess I should start a new thread? : )

Thank you a lot for your answers and your time!

1 Like

Good to see you got it all fixed.

As for changing #sprite position, I don’t believe we can change that, only the GOs position. If we could change the sprite’s pos, that would be awesome as it would allow pivot adjustments.

On another note, I see the characters health bar has a space between the green bars. Why is that?

Also, I think, maybe adjusting Z in the shader breaks batching when drawing. I could definitely be wrong.

Isn’t the purpose of the layers to get your game to render the sprites in the correct order? And isn’t it the correct order that the health bar should be rendered above everything else?

I am not sure how many monsters you will have but mixing shaders like this might impact performance (if it is significant is debatable). As the renderer will have to change context between your shaders. If you only had one they would all be batched in one draw call but now each time a health bar shader is encountered a new batch is made, so in the screen shot above it would probably be ~11 draw calls instead of ~1.

If you are not a fan of doing it that way you can add a new predicate (call it “on top”) and add everything that should be rendered on top of your world into it.

But we can change it through shaders! This does work, moving the health bar up and down:

gl_Position = view_proj * vec4(position.x, position.y  - sin(time.x) , position.z, 1.0);

They have a gap because I wanted healthbars to be discrete. So if you have 3 points of life you see three bars.

imagen

Here the alien has 5 points, the colonists 2 points, dogs only on point. Might be weird that the lower your max health the larger the bar, so I will probably make it the same size no matter your max_health.

Also @Jerakin it’s a turn based game, so I’m not caring much for bathing or optimization. That said, it would be nice to know if changing positions and pixels through shaders does indeed break batching. Afte all, it’s the same material! Just different parameters? :smiley:

1 Like

Ahh, now I understand the spaces. My heath bar can be any length (almost) and any value (Almost). I am adding color layering for high health bosses. So HP = 300.
300-200 is orange, 200-100 is yellow, 100-0 is green

1 Like

Hehe, that depends a lot on the game and the numbers you work with! My characters deal 1, 2, 3 points of damage and have up to 5 health points ^-^

1 Like

Yes, this is true, but I don’t like to do too much in shaders. I prefer to keep them as simple as possible. the GPU makes calls to that data so many times. lol

Well, that’s not an operation a GPU would have problems with :smiley:

In any case I would love to know why moving the x-y position works, but changing the z position does not alter z-sorting in any way. My guess is that sprites are being sorted in a previous step…

I have used the shader in a way that Z was affected, but I believe it breaks atching which may negate thevaffect you were aiming for.

Yes, the GPU is very capable.

1 Like