# Batch z-ordering problem in Cocos2d (Python)

I have a weird problem with my hexmap drawing code.

My code uses "squished" hexagons to achieve a pseudo-isometric view. Because of that, the order of drawing them on screen matters a lot.

Thankfully, I'm using cubic coordinates, so I can pass one of them as the z-level of the sprite in question. I've put it in code, and it worked...almost. The problem is that the program sometimes produces anomalous (and ugly) results, in spite of having the exact same input. To me it looks like some of the sprites ignore the z-level i set for them.

Take a look:

Working as intended on the left, and broken on the right.

As of now, I have no idea which factors contribute to this error - three consecutive script launches can have any combination of good and bad results.

Here is a barebones version of my code. I stripped it of all unneccessary parts, and left only the crucial elements. It is based on the Cocos2d(Python) library. The hexmap is stored as a OrderedDict with Hex(q,r,s) tuples as keys. Prior to drawing, all sprites are packed into a batch with appropriate z-level. Pressing any mouse button forces the program to redraw the map (this always removes the problem - until next launch).

import os
import pyglet
import cocos
import collections
import math

Hex = collections.namedtuple("Hex", ["q", "r", "s"])
Point = collections.namedtuple("Point", ["x", "y"])
Orientation = collections.namedtuple("Orientation", ["f0", "f1", "f2", "f3", "b0", "b1", "b2", "b3", "start_angle"])
Layout = collections.namedtuple("Layout", ["orientation", "size", "origin"])

def generate_map(radius): # Here we generate the map
map = collections.OrderedDict()
for r in range (r1,r2 + 1):
tile_type = 0
key = Hex(q,r, -q-r)
map[key] = {"type":tile_type}
return map

def hex_to_pixel(layout, hex): # Map-to-screen coordinate conversion
Matrix = layout.orientation
size = layout.size
origin = layout.origin
x = (Matrix.f0 * hex.q + Matrix.f1 * hex.r) * size.x
y = (Matrix.f2 * hex.q + Matrix.f3 * hex.r) * size.y
return Point(x,y)

def pointy_layout_size(sprite_width, sprite_height): # Some hex math
layout_size_y = sprite_height
layout_size_x = sprite_width/math.sqrt(3)
return Point(layout_size_x, layout_size_y)

sprite_width = 64
sprite_height = 26
layout_size = pointy_layout_size(sprite_width, sprite_height)
layout_pointy = Orientation(math.sqrt(3.0), math.sqrt(3.0) / 2.0, 0.0, 3.0 / 2.0, math.sqrt(3.0) / 3.0, -1.0 / 3.0, 0.0, 2.0 / 3.0, 0.5)
layout = Layout(layout_pointy, layout_size, Point(400,300))

pyglet.resource.path = [os.path.dirname(os.path.realpath(__file__)) + '\\']
pyglet.resource.reindex()

class Map_Layer(cocos.layer.Layer):
is_event_handler = True

def __init__(self):
super(Map_Layer, self).__init__()

self.map_sprites_batch = cocos.batch.BatchNode()
self.map_sprites_batch.position = layout.origin.x, layout.origin.y

def batch_map(self): # Here we pack the sprites into the batch
for hex, properties in map.items():
point = hex_to_pixel (layout, hex)
offset_anchor = sprite_width/2, sprite_height/2
sprite = cocos.sprite.Sprite("hills.png", position = point, anchor = offset_anchor)
self.map_sprites_batch.add(sprite, z = - hex.r)  # This line determines the z-level of every sprite inside the batch

def on_mouse_press(self, x, y, buttons, mod):  # Force redraw on mouse click.
for z, child in self.map_sprites_batch.children:
self.map_sprites_batch.remove(child)
self.batch_map()

cocos.director.director.init(800,600)

map = generate_map(5)  # The argument here determines map radius
layer = Map_Layer()
scene = cocos.scene.Scene(layer)
layer.batch_map()
cocos.director.director.run (scene)


Here is the tile image:

I don't know what causes it - could be my own faulty code, wrong usage of library functions, problems in the library itself - or something else entirely. I will appreciate any help in solving this problem.

• I looked at your code and could not detect the error, but I suggest to use tile y value to calculate z. cocos2d uses positive y up, so you can pass something like z = some_high_value - sprite_y. Where some_high_value is calculated using your map boundaries. For a 15x20 map with a 32 pixels tile, a good value would be 20*32. It should work either if y = 0 means the bottom boundary of the map, or if y = 0 means the center of the map. By temporarily implementing this in your batch_map function you can rule out a bug in the library. – Hatoru Hansou Jul 3 '18 at 19:52