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:

For comparison 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 q in range (-radius, radius + 1):
        r1 = max(-radius, -q - radius)
        r2 = min (radius, -q + radius)
        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__)) + '\\']

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:


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

Here is the tile image: Don't forget to rename it to hills.png

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.

  • \$\begingroup\$ 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. \$\endgroup\$ – Hatoru Hansou Jul 3 '18 at 19:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.