import tmx
from tmx import Layer, ObjectGroup
from os.path import basename, dirname
from kivent_core.systems.renderers import Renderer, ColorPolyRenderer
from kivent_core.systems.animation import AnimationSystem
from kivy.utils import get_color_from_hex
from math import sin, cos, radians
[docs]def load_map_systems(layer_count, gameworld, renderargs, animargs, polyargs):
'''
Create and initialise the systems required for displaying all layers of the
map. Each layer requires a Renderer for images, PolyRenderer for shapes
and AnimationSystem for animated tiles.
The name format of the Renderer is map_layer%d, PolyRenderer is
map_layer%d_polygons, AnimationSystem is map_layer%d_animator
where %d is the layer number.
Args:
layer_count (unsigned int): Number of layers to init
gameworld (Gameworld): Instance of the gameworld
renderargs (dict): Dict of arguments required to init the Renderer.
This is same as those used in a KV file for adding a system.
animargs (dict): Dict of arguments required to init the AnimationSystem
polyargs (dict): Dict of arguments required to init the PolyRenderer.
Return:
list of str, list of str: A tuple of two lists of system names with fisrt
containing names of Renderers and PolyRenderers and second containing
names of AnimationSystems. The names are in the order in which they are
added to the Gameworld. You can use these lists for init_gameworld
and setup_state.
'''
rendersystems = []
for i in range(layer_count):
rendersystems.extend(['map_layer%d' % i, 'map_layer%d_polygons' % i])
animsystems = ['map_layer%d_animator' % i for i in range(layer_count)]
system_count = gameworld.system_count
for i in range(layer_count):
renderargs['system_id'] = rendersystems[2 * i]
renderargs['system_names'] = [rendersystems[2 * i], 'position']
animargs['system_id'] = animsystems[i]
animargs['system_names'] = [animsystems[i],rendersystems[2 * i]]
polyargs['system_id'] = rendersystems[2 * i + 1]
polyargs['system_names'] = [rendersystems[2 * i + 1],
'position',
'color']
r = Renderer()
a = AnimationSystem()
p = ColorPolyRenderer()
r.gameworld = gameworld
a.gameworld = gameworld
p.gameworld = gameworld
for k in renderargs:
setattr(r,k,renderargs[k])
for k in animargs:
setattr(a,k,animargs[k])
for k in polyargs:
setattr(p,k,polyargs[k])
gameworld.add_widget(r, system_count)
gameworld.add_widget(a, system_count)
gameworld.add_widget(p, system_count)
system_count += 3
gameworld.system_count = system_count
return rendersystems, animsystems
[docs]def init_entities_from_map(tile_map, init_entity):
'''
Initialise entities for every layer of every tile and add them to the
corresponding systems.
Args:
tile_map (TileMap): the tile map from which to load the tiles.
init_entity (function): the gameworld.init_entity function
'''
z_map = tile_map.z_index_map
# Load tile entities
for i in range(tile_map.size[0]):
for j in range(tile_map.size[1]):
# Get Tile object for position (i, j)
tile_layers = tile_map.get_tile(i,j)
# Loop through all LayerTiles
for tile in tile_layers.layers:
renderer_name = 'map_layer%d' % z_map[tile.layer]
animator_name = 'map_layer%d_animator' % z_map[tile.layer]
comp_data = {
'position': tile_map.get_tile_position(i, j),
'tile_map': {'name': tile_map.name, 'pos': (i,j)},
renderer_name: {
'model': tile.model,
'texture': tile.texture
}
}
systems = ['position', 'tile_map', renderer_name]
# If tile is animated add that component
if tile.animation:
comp_data[animator_name] = {
'name': tile.animation,
'loop': True,
}
systems.append(animator_name)
init_entity(comp_data, systems)
# Load object entities
for obj_layer in tile_map.objects:
mh = tile_map.size_on_screen[1]
for obj in obj_layer:
if obj.texture:
# Object is an image
renderer_name = 'map_layer%d' % z_map[obj.layer]
comp_data = {
'position': (obj.position[0], mh - obj.position[1]),
renderer_name: {
'model': obj.model,
'texture': obj.texture,
}
}
systems = ['position', renderer_name]
else:
# Object is a shape
renderer_name = 'map_layer%d_polygons' % z_map[obj.layer]
comp_data = {
'position': (obj.position[0], mh - obj.position[1]),
renderer_name: {
'model_key': obj.model,
},
# Color is taken from vertex, so white here
'color': (255, 255, 255, 255)
}
systems = ['position','color', renderer_name]
init_entity(comp_data, systems)
[docs]def parse_tmx(filename, gameworld):
'''
Uses the tmx library to load the TMX into an object and then calls all the
util functions with the relevant data.
Args:
filename (str): Name of the tmx file.
gameworld (Gameworld): instance of the gameworld.
Return:
str: name of the loaded map which is the filename
'''
texture_manager = gameworld.managers['texture_manager']
model_manager = gameworld.managers['model_manager']
map_manager = gameworld.managers['map_manager']
animation_manager = gameworld.managers['animation_manager']
# Get tilemap object with all the data from tmx
tilemap = tmx.TileMap.load(filename)
# Get the tiles as a 3D list and the z_map, objects as a 2D list and the
# z_map, the set of tile_ids which will be used in the map,
# set of models of the objects in the map.
tiles, tiles_z, objects, objects_z, tile_ids, objmodels = \
_load_tile_map(tilemap.layers, tilemap.width,
_load_tile_properties(tilemap.tilesets))
# Loads the models, textures and animations of the tileset into
# corresponding managers
_load_tilesets(tilemap.tilesets, dirname(filename), tile_ids,
texture_manager.load_atlas,
model_manager.load_textured_rectangle,
animation_manager.load_animation)
# Load the object models with model_manager
_load_obj_models(objmodels, model_manager.load_textured_rectangle,
model_manager.load_model)
# Load the map with map_manager
name ='.'.join(basename(filename).split('.')[:-1])
map_manager.load_map(name, tilemap.width, tilemap.height,
tiles, len(tiles_z),
objects, sum([len(o) for o in objects]),
tilemap.orientation)
# Set the extra map properties for hexagonal/staggered maps
loaded_map = map_manager.maps[name]
loaded_map.tile_size = (tilemap.tilewidth, tilemap.tileheight)
loaded_map.z_index_map = tiles_z + objects_z
if tilemap.staggerindex:
loaded_map.stagger_index = tilemap.staggerindex
if tilemap.staggeraxis:
loaded_map.stagger_axis = tilemap.staggeraxis
if tilemap.hexsidelength:
loaded_map.hex_side_length = tilemap.hexsidelength
return name
def _load_tilesets(tilesets, dirname, tile_ids,
load_atlas, load_model, load_animation):
'''
Tileset of the map contains an atlas of the images used by the tiles. We
need to load all those images as textures and models. If they are animated
tiles we also load an animation for them.
The texture, model names are in the format tile_%d where %d is the tile's
gid. For animation the format is animation_tile_%d.
Args:
tilesets (list): List of TileSet objects. We can create an atlas of
the tile images from TileSet data using the tile_ids.
dirname (str): Directory of the image source of the tilesets
tile_ids (list): List of tile ids which need to be loaded from the
tileset.
load_atlas (function): Takes an atlas dict and loads textures in the
texture_manager
load_model (function): Loads models for all loaded textures in the
model_manager.
load_animation (function): Takes frames of an animated tile and loads
an animation.
'''
atlas_data = {}
model_data = {}
animation_data = {}
for tileset in tilesets:
image = tileset.image
name = image.source
fgid = int(tileset.firstgid)
w, h = int(image.width), int(image.height)
tw, th = int(tileset.tilewidth), int(tileset.tileheight)
m, s = int(tileset.margin), int(tileset.spacing)
rows = (w + s)//(tw + 2*m + s)
cols = (h + s)//(th + 2*m + s)
for tile in tileset.tiles:
if tile.animation:
animation = []
for frame in tile.animation:
animation.append({
'texture': 'tile_%d' % (frame.tileid + fgid),
'model': 'tile_%d' % (frame.tileid + fgid),
'duration': frame.duration
})
tile_ids.add(frame.tileid + fgid)
animation_name = 'animation_tile_%d' % (tile.id + fgid)
animation_data[animation_name] = animation
atlas_data[name] = {}
for tile in range(rows*cols):
if (tile + fgid) in tile_ids:
x, y = tile % rows, cols - 1 - int(tile / rows)
px, py = x * (tw + 2*m + s) + m, y * (th + 2*m + s) + m
atlas_data[name]['tile_%d' % (tile + fgid)] = (px, py, tw, th)
model_data['tile_%d' % (tile + fgid)] = (tw, th)
load_atlas(atlas_data, 'dict', dirname)
for model in model_data:
tw, th = model_data[model]
load_model('vertex_format_4f', tw, th, model, model)
for animation in animation_data:
frames = animation_data[animation]
load_animation(animation, len(frames), frames)
def _load_obj_models(objmodels,
load_img_model, load_poly_model):
'''
Object models are a list of vertices for a shape or a model for a texture.
They are read from the map and loaded into model_manager here.
Args:
objmodels (list): List of dicts containing model data.
load_img_model (function): Load model for an image texture.
load_poly_model (function): Load model from a set of vertices.
'''
for objname in objmodels:
obj = objmodels[objname]
if 'texture' in obj:
load_img_model('vertex_format_4f', obj['width'], obj['height'],
obj['texture'], objname)
elif 'vertices' in obj:
load_poly_model('vertex_format_2f4ub',
obj['vertex_count'],
obj['index_count'],
objname,
vertices=obj['vertices'],
indices=obj['indices'])
def _load_tile_map(layers, width, tile_properties):
'''
Loads data for all the tiles and objects of the tilemap as dicts
which will directly be passed to map_manager. While looping through all
tiles and objects it creates the z_index map, stores a unique set of
tile_ids which will be used so we only load that data and also dicts for
object models.
The dicts for object models require generating the vertices of the polygons
and hence has different math for different types of shapes.
Args:
layers (unsigned int): Number of layers to load
width (unsigned int): Number of columns.
tile_properties (dict): A map for specific tile properties to be loaded.
'''
height = int(len(layers[0].tiles)/width)
tiles = [[[] for j in range(width)] for i in range(height)]
objects = []
objmodels = {}
tile_ids = set()
tile_layer_count = 0
tile_zindex = []
obj_layer_count = 0
obj_zindex = []
for i, layer in enumerate(layers):
layerobjs = []
if type(layer) == Layer:
for n, tile in enumerate(layer.tiles):
if tile.gid > 0:
tile_ids.add(tile.gid)
if tile.gid in tile_properties:
tile = tile_properties[tile.gid]
else:
tile = {'texture': 'tile_%d' % tile.gid,
'model': 'tile_%d' % tile.gid}
tile['layer'] = tile_layer_count
tiles[int(n/width)][n%width].append(tile)
tile_layer_count += 1
tile_zindex.append(i)
elif type(layer) == ObjectGroup:
color = get_color_from_hex(layer.color)
color = (color[0]*255, color[1]*255, color[2]*255, 255)
for n, obj in enumerate(layer.objects):
if obj.gid:
tile_ids.add(obj.gid)
gid, width, height = obj.gid, obj.width, obj.height
obj = {'texture': 'tile_%d' % gid,
'model': 'obj_%d_%d' % (obj_layer_count, n),
'position': (obj.x + width/2, obj.y + height/2)}
objmodel = {'texture': 'tile_%d' % gid,
'width': width,
'height': height}
elif obj.polygon:
x_coords = [v[0] for v in obj.polygon]
y_coords = [v[1] for v in obj.polygon]
w, h = (max(x_coords) - min(x_coords),
max(y_coords) - min(y_coords))
c = obj.x + w/2, obj.y + h/2
vertices = {}
indices = []
for n, v in enumerate(obj.polygon):
vertices[n] = {'pos': (v[0] - w/2, h/2 - v[1]),
'v_color': color}
if n>0 and n<len(obj.polygon)-1:
indices.extend([0,n,n+1])
obj = {'model': 'obj_%d_%d' % (obj_layer_count, n),
'position': c}
objmodel = {'vertices': vertices,
'indices': indices,
'vertex_count': len(vertices),
'index_count': len(indices)}
elif obj.ellipse:
vertices = {}
indices = []
a, b = obj.width/2., obj.height/2.
for t in range(360):
ang = radians(t)
v = (a * cos(ang), b * sin(ang))
vertices[t] = {'pos': v,
'v_color': color}
if t>0 and t<359:
indices.extend([0,t,t+1])
obj = {'model': 'obj_%d_%d' % (obj_layer_count, n),
'position': (obj.x + obj.width/2,
obj.y + obj.height/2),
}
objmodel = {'vertices': vertices,
'indices': indices,
'vertex_count': len(vertices),
'index_count': len(indices)}
else:
w, h = obj.width, obj.height
objmodel = {
'vertices': { 0: {'pos': (-w/2., -h/2.),
'v_color': color},
1: {'pos': (-w/2., h/2.),
'v_color': color},
2: {'pos': (w/2., h/2.),
'v_color': color},
3: {'pos': (w/2., -h/2.),
'v_color': color}},
'indices': [0, 1, 3, 3, 1, 2],
'vertex_count': 4,
'index_count': 6
}
obj = {'model': 'obj_%d_%d' % (obj_layer_count, n),
'position': (obj.x + obj.width/2,
obj.y + obj.height/2)}
name = 'obj_%d_%d' % (obj_layer_count, n)
objmodels[name] = objmodel
layerobjs.append(obj)
objects.append(layerobjs)
obj_layer_count += 1
obj_zindex.append(i)
return tiles, tile_zindex, objects, obj_zindex, tile_ids, objmodels
def _load_tile_properties(tilesets):
tile_properties = {}
for tileset in tilesets:
for tile in tileset.tiles:
if tile.animation:
gid = tile.id + tileset.firstgid
tile_properties[gid] = {
'animation':
'animation_tile_%d' % gid
}
return tile_properties