Solving a Z-Ordering Issue

The square containing our hero sprite covers the rock above…

…A possible solution causes the background to bleed through…

…Ahhh. Fixed!
Cocos2D is quite a complete 2D game engine and it is always growing. However, what do you do when it just can’t do something you are intending? Well, thankfully it is written in a fairly high-level language like Objective C. You can expand the Cocos2D engine as you see fit.
In this article we will be talking about two expansions to the Cocos2D engine that were necessary to get main characters, enemies, and special effects to draw correctly in the game QuexlorLite. These two extensions are called CCZSprite and HudLevelSprite.
CCZSprite: A Cocos2D Extension Included in the iPhone Game Kit
Getting our barbarian (his name is Quexlor) to draw correctly in the multiple layers of ground, rocks, and trees of the QuexlorLite world was a tad tricky. The challenge was that our hero sprite unnaturally appeared on top of trees. Thanks to Ricardo and the Cocos2D community, a solution was developed. For your reference, here’s the 2nd and 3rd pages of the forum topic that led to the eventual solution.
The original challenge was one of hierarchy. All the layers of the map (a subclass of CCTMXTiledMap) were drawn with successive z orders. However, the main character and enemy sprites were drawn as children of one of the layers. No matter what was done to change the z order of the hero, it didn’t matter because the z order was only relative to other children of that particular layer, not all the layers.
The solution started with using a 2D OpenGL projection with a decent depth buffer. This allowed all the nodes of our game to be given a more accurate z order using CCNode‘s vertexZ property. A range of -1000 to 0 was chosen, -1000 being the lowest vertexZ in the stack. Entire background layers, like the ground, were given successive, automatic vertexZ values (-999, -998, etc…). Foreground layers, like the trees, were given more detailed vertexZ values, assigned to individual tiles of the map (trees, in this case) based on the y position in the tile map (-997.4 for a tree to the north, -997.2 for a tree to the south). These were automatically assigned detailed vertexZ values by setting the layer property cc_vertexz to automatic for that particular foreground layer. Finally, all the player, enemy, and item sprites (essentially, any sprite that could possibly move) were assigned dynamic vertexZ values based on their current y position on the map (for example, -997.3 if the hero was standing in the middle of the two previously mentioned trees). This solution finally enabled Quexlor to look right! Trees were drawn on top of him when appropriate.
The last thing that was necessary to cement this vertexZ solution was to make our hero, enemy, and item sprites use a bit of different drawing code. Hence, the creation of CCZSprite, a subclass of CCSprite that simply overrides the draw method with the vertexZ-friendly drawing code. Here’s that draw method:
@implementation CCZSprite
-(void) draw
{
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0.0f);
[super draw];
glDisable(GL_ALPHA_TEST);
}
@end
Pretty simple, eh?
HudLevelSprite: Another Cocos2D Addition
HudLevelSprite in actionBorn out of the same challenge of accurate z ordering, the HudLevelSprite is a solution for drawing sprites that must appear on top of everything else (like fog, auras, etc…). Because of the way we slide the camera around to view the level map from different points, we also needed these HUD sprites to be attached to certain level objects, hence the name “HUD Level Sprite.” (By the way, HUD stands for Heads Up Display.)
A good example of a HudLevelSprite is the lifebar attached to the top of our hero and all the enemies. It is always drawn on top and always follows the sprite that it is attached to.
Coding a HudLevelSprite is as simple as a method to attach to a level-based object, and another method to move when the camera has changed.
// a subclass for sprites attached to level objects
@interface HudLevelSprite : CCSprite
{
CGPoint offset;
LevelObject* attachee;
}
@property CGPoint offset;
-(void) attach:(LevelObject*)newAttachee;
-(void) move:(CGPoint)cameraPos;
@end
@implementation HudLevelSprite
@synthesize offset;
-(void) attach:(LevelObject*)newAttachee
{
// check so that we only add child once
if(attachee != newAttachee)
{
// save attachee
attachee = newAttachee;
// add to hud
[[attachee getHud] addChild:self];
}
}
-(void) move:(CGPoint)cameraPos
{
// self pos = attachee pos - camera pos + offset
CGPoint newPos = ccpSub(attachee.position, cameraPos);
newPos = ccpAdd(newPos, offset);
self.position = newPos;
}
@end
When the camera is moved, the HUD must update all the HudLevelSprite objects. It does this using fast enumeration (the for..in loop) in the moveSprites method. Note the use of the isKindOf method which is a handy extension to Cocoa’s NSObject, part of Extensions.h/.m in the iPhone Game Kit’s source code:
// in our HUD class...
-(void) moveSprites:(CGPoint)cameraPos
{
// move all hud level sprites
for(HudLevelSprite* s in self.children)
if([s isKindOf:@"HudLevelSprite"])
[s move:cameraPos];
}
Keeping track of sprites like this can be handy in other ways. For example, what if you want to make a type of sprite that is “touchable?” You could code something similar to a HudLevelSprite and use a for..in loop like the one above to query your list of sprites when your game receives a touch event. That’s just one idea…
Remember, you can extend Cocos2D and even Cocoa to your liking. Objective C is a really nice language to extend because of its easy to use class categories and lack of multiple inheritance. So, when you find yourself challenged with something that goes beyond the game engine, just create a way! Make it happen.
Enjoy creating.




vertexz will leave a outline for semi-transparent sprite, is that really no method to overcome this? or use z-order ?
You could change the alpha blending factor or change the bottom-most layer of your TMX to change the outline a little. However, that’s not a real answer to your question.
The challenge here is the way Cocos2D does z ordering. It’s all relative to the parent node. This makes it impossible to z-order correctly when you have objects that are children of different layers.
You could get around all this by creating a single layer for all your objects and then implementing your own z-ordering. The trick here is that when an object moves, it needs to be re-ordered, and in Cocos2D this causes the object to be removed and then re-inserted. An expensive operation.
If you find a better solution, please let me know.
Hi,
I need some help…
Is possible to solve this: http://www.cocos2d-iphone.org/forum/topic/35521
What do you think?
Yes, it definitely is possible to solve those z ordering issues. Please read through this article and check out the Cocos2d iPhone forum posts it links to.
hi Nat,
I read and followed your steps at Cocos2d Forum, but the sprite continuos at wrong order… see this image: http://img163.imageshack.us/img163/6356/testnt.jpg
Could you help me?
It looks like the z ordering is working. The challenge now looks like the z order for the walls needs to be a little higher than the ninja. Try making the z order for tiles to be consistent with the bottom of the tile, instead of the middle of the tile.
Hi Nat,
I’m trying to implement your CCZSprite solution — thanks for sharing it, by the way! — but it’s not drawing properly. http://stackoverflow.com/questions/12165481/how-can-i-get-a-child-ccsprite-to-draw-on-top-of-its-parent I’m not sure where I went wrong or what the symptoms indicate. Any ideas?
Wrote you back on Stack Overflow.