added parsing of hitboxes

This commit is contained in:
Benjamin Kirkbride
2019-05-06 18:35:45 -04:00
parent d09a89f8b0
commit b6dd3c9510
5 changed files with 160 additions and 85 deletions

View File

@@ -11,30 +11,23 @@ from pathlib import Path
import xml.etree.ElementTree as etree
from typing import * # pylint: disable=W0401
from typing import NamedTuple, Union, Optional, List, Dict
class EncodingError(Exception):
"""
Tmx layer encoding is of an unknown type.
"""
"""Tmx layer encoding is of an unknown type."""
class TileNotFoundError(Exception):
"""
Tile not found in tileset.
"""
"""Tile not found in tileset."""
class ImageNotFoundError(Exception):
"""
Image not found.
"""
"""Image not found."""
class Color(NamedTuple):
"""
Color object.
"""Color object.
Attributes:
:red (int): Red, between 1 and 255.
@@ -49,17 +42,27 @@ class Color(NamedTuple):
class OrderedPair(NamedTuple):
"""
OrderedPair NamedTuple.
"""OrderedPair NamedTuple.
Attributes:
:x (Union[int, float]): X coordinate.
:y (Union[int, float]): Y coordinate.
x (Union[int, float]): X coordinate.
y (Union[int, float]): Y coordinate.
"""
x: Union[int, float]
y: Union[int, float]
class Size(NamedTuple):
"""Size NamedTuple.
Attributes:
width (Union[int, float]): The width of the object.
size (Union[int, float]): The height of the object.
"""
width: Union[int, float]
height: Union[int, float]
class Template:
"""
FIXME TODO
@@ -102,7 +105,7 @@ class Image(NamedTuple):
:height (Optional[str]): The image height in pixels (optional).
"""
source: str
size: OrderedPair
size: Size
trans: Optional[Color]
@@ -222,7 +225,7 @@ Either a 2 dimensional array of integers representing the global tile IDs
@dataclasses.dataclass
class _LayerBase:
size: OrderedPair
size: Size
data: LayerData
@@ -234,7 +237,7 @@ class Layer(LayerType, _LayerBase):
See: https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#layer
Attributes:
:size (OrderedPair): The width of the layer in tiles. Always the same
:size (Size): The width of the layer in tiles. Always the same
as the map width for not infitite maps.
:data (LayerData): Either an 2 dimensional array of integers
representing the global tile IDs for the map layer, or a list of
@@ -250,7 +253,7 @@ class _ObjectBase:
@dataclasses.dataclass
class _ObjectDefaults:
size: OrderedPair = OrderedPair(0, 0)
size: Size = Size(0, 0)
rotation: int = 0
opacity: int = 0xFF
@@ -266,15 +269,15 @@ class Object(_ObjectDefaults, _ObjectBase):
"""
ObjectGroup Object.
See: \
https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#object
See:
https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#object
Args:
:id (int): Unique ID of the object. Each object that is placed on a
map gets a unique id. Even if an object was deleted, no object
gets the same ID.
:location (OrderedPair): The location of the object in pixels.
:size (OrderedPair): The width of the object in pixels
:size (Size): The width of the object in pixels
(default: (0, 0)).
:rotation (int): The rotation of the object in degrees clockwise
(default: 0).
@@ -355,8 +358,8 @@ class PolylineObject(Object, _PointsObjectBase):
"""
Polyline defined by a set of connections between points.
See: \
https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#polyline
See:
https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#polyline
Attributes:
:points (List[Tuple[int, int]]): List of coordinates relative to \
@@ -444,10 +447,12 @@ https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#objectgroup
"""
@dataclasses.dataclass
class _LayerGroupBase(_LayerTypeBase):
layers: Optional[List[LayerType]]
@dataclasses.dataclass
class LayerGroup(LayerType):
"""
Layer Group.
@@ -464,6 +469,12 @@ class LayerGroup(LayerType):
"""
@dataclasses.dataclass
class Hitbox:
"""Group of hitboxes for
"""
@dataclasses.dataclass
class Tile:
"""
@@ -482,7 +493,7 @@ class Tile:
terrain: Optional[TileTerrain]
animation: Optional[List[Frame]]
image: Optional[Image]
hit_box: Optional[List[Object]]
hitboxes: Optional[List[Object]]
@dataclasses.dataclass
@@ -492,7 +503,7 @@ class TileSet:
Args:
:name (str): The name of this tileset.
:max_tile_size (OrderedPair): The maximum size of a tile in this
:max_tile_size (Size): The maximum size of a tile in this
tile set in pixels.
:spacing (int): The spacing in pixels between the tiles in this
tileset (applies to the tileset image).
@@ -516,7 +527,7 @@ class TileSet:
:tiles (Optional[Dict[int, Tile]]): Dict of Tile objects by Tile.id.
"""
name: str
max_tile_size: OrderedPair
max_tile_size: Size
spacing: Optional[int]
margin: Optional[int]
tile_count: Optional[int]
@@ -529,6 +540,9 @@ class TileSet:
tiles: Optional[Dict[int, Tile]]
TileSetDict = Dict[int, TileSet]
@dataclasses.dataclass
class TileMap:
"""
@@ -548,8 +562,8 @@ class TileMap:
rendered. Valid values are right-down, right-up, left-down and
left-up. In all cases, the map is drawn row-by-row. (only
supported for orthogonal maps at the moment)
:map_size (OrderedPair): The map width in tiles.
:tile_size (OrderedPair): The width of a tile.
:map_size (Size): The map width in tiles.
:tile_size (Size): The width of a tile.
:infinite (bool): If the map is infinite or not.
:hexsidelength (int): Only for hexagonal maps. Determines the width or
height (depending on the staggered axis) of the tiles edge, in
@@ -563,8 +577,8 @@ class TileMap:
:nextlayerid (int): Stores the next available ID for new layers.
:nextobjectid (int): Stores the next available ID for new objects.
:tile_sets (dict[str, TileSet]): Dict of tile sets used
in this map. Key is the source for external tile sets or the name
for embedded ones. The value is a TileSet object.
in this map. Key is the first GID for the tile set. The value
is a TileSet object.
:layers List[LayerType]: List of layer objects by draw order.
"""
parent_dir: Path
@@ -573,13 +587,13 @@ class TileMap:
tiled_version: str
orientation: str
render_order: str
map_size: OrderedPair
tile_size: OrderedPair
map_size: Size
tile_size: Size
infinite: bool
next_layer_id: int
next_object_id: int
tile_sets: Dict[int, TileSet]
tile_sets: TileSetDict
layers: List[LayerType]
hex_side_length: Optional[int] = None
@@ -607,5 +621,3 @@ class TileMap:
[22:23] == markb1 [~mbiggers@45.36.35.206] has quit [Ping timeout: 245 seconds]
[22:23] <__m4ch1n3__> !py3 max(i for i in [1, 10, 100] if i < 242)
'''
#buffer

View File

@@ -1,17 +1,19 @@
import functools
import re
import base64
import gzip
import re
import zlib
from pathlib import Path
from typing import *
from typing import Dict, List, Optional, Union
import pytiled_parser.objects as objects
import pytiled_parser.utilities as utilities
import xml.etree.ElementTree as etree
def _decode_base64_data(data_text, compression, layer_width):
tile_grid: List[List[int]] = [[]]
@@ -47,8 +49,7 @@ def _decode_base64_data(data_text, compression, layer_width):
def _decode_csv_layer(data_text):
"""
Decodes csv encoded layer data.
"""Decodes csv encoded layer data.
Credit:
"""
@@ -69,8 +70,7 @@ def _decode_csv_layer(data_text):
def _decode_data(element: etree.Element, layer_width: int, encoding: str,
compression: Optional[str]) -> List[List[int]]:
"""
Decodes data or chunk data.
"""Decodes data or chunk data.
Args:
:element (Element): Element to have text decoded.
@@ -105,8 +105,7 @@ def _decode_data(element: etree.Element, layer_width: int, encoding: str,
def _parse_data(element: etree.Element,
layer_width: int) -> objects.LayerData:
"""
Parses layer data.
"""Parses layer data.
Will parse CSV, base64, gzip-base64, or zlip-base64 encoded data.
@@ -143,9 +142,7 @@ def _parse_data(element: etree.Element,
def _parse_layer(element: etree.Element,
layer_type: objects.LayerType) -> objects.Layer:
"""
Parse layer element given.
"""
"""Parse layer element given."""
width = int(element.attrib['width'])
height = int(element.attrib['height'])
size = objects.OrderedPair(width, height)
@@ -159,9 +156,7 @@ def _parse_layer(element: etree.Element,
def _parse_layer_type(layer_element: etree.Element) -> objects.LayerType:
"""
Parse layer type element given.
"""
"""Parse layer type element given."""
id = int(layer_element.attrib['id'])
name = layer_element.attrib['name']
@@ -198,13 +193,10 @@ def _parse_layer_type(layer_element: etree.Element) -> objects.LayerType:
# return _parse_layer_group(layer_element, layer_type_object)
def _parse_object_group(element: etree.Element,
layer_type: objects.LayerType) -> objects.ObjectGroup:
def _parse_objects(objects: List[etree.Element]) -> List[objects.Object]:
"""
Parse object group element given.
"""
object_elements = element.findall('./object')
tile_objects: List[objects.Object] = []
tiled_objects: List[objects.Object] = []
for object_element in object_elements:
id = int(object_element.attrib['id'])
@@ -246,9 +238,21 @@ def _parse_object_group(element: etree.Element,
if properties_element is not None:
object.properties = _parse_properties_element(properties_element)
tile_objects.append(object)
tiled_objects.append(object)
object_group = objects.ObjectGroup(tile_objects, **layer_type.__dict__)
return tiled_objects
def _parse_object_group(element: etree.Element,
layer_type: objects.LayerType) -> objects.ObjectGroup:
"""Parse the objectgroup element given.
Args:
layer_type (objects.LayerType):
"""
tiled_objects = _parse_objects(element.findall('./object'))
object_group = objects.ObjectGroup(tiled_objects, **layer_type.__dict__)
try:
color = utilities.parse_color(element.attrib['color'])
except KeyError:
@@ -265,8 +269,7 @@ def _parse_object_group(element: etree.Element,
@functools.lru_cache()
def _parse_external_tile_set(parent_dir: Path, tile_set_element: etree.Element
) -> objects.TileSet:
"""
Parses an external tile set.
"""Parses an external tile set.
Caches the results to speed up subsequent instances.
"""
@@ -276,17 +279,21 @@ def _parse_external_tile_set(parent_dir: Path, tile_set_element: etree.Element
return _parse_tile_set(tile_set_tree)
def _parse_hitboxes(element: etree.Element) -> List[objects.Object]:
return _parse_objects(element.findall('./object'))
def _parse_tiles(tile_element_list: List[etree.Element]
) -> Dict[int, objects.Tile]:
tiles: Dict[int, objects.Tile] = {}
for tile_element in tile_element_list:
# id is not optional
#id is not optional
id = int(tile_element.attrib['id'])
# optional attributes
type = None
#optional attributes
tile_type = None
try:
type = tile_element.attrib['type']
tile_type = tile_element.attrib['type']
except KeyError:
pass
@@ -296,15 +303,15 @@ def _parse_tiles(tile_element_list: List[etree.Element]
except KeyError:
pass
else:
# an attempt to explain how terrains are handled is below.
# 'terrain' attribute is a comma seperated list of 4 values,
# each is either an integer or blank
#below is an attempt to explain how terrains are handled.
#'terrain' attribute is a comma seperated list of 4 values,
#each is either an integer or blank
# convert to list of values
#convert to list of values
terrain_list_attrib = re.split(',', tile_terrain_attrib)
# terrain_list is list of indexes of Tileset.terrain_types
#terrain_list is list of indexes of Tileset.terrain_types
terrain_list: List[Optional[int]] = []
# each index in terrain_list_attrib reffers to a corner
#each index in terrain_list_attrib reffers to a corner
for corner in terrain_list_attrib:
if corner == '':
terrain_list.append(None)
@@ -312,7 +319,7 @@ def _parse_tiles(tile_element_list: List[etree.Element]
terrain_list.append(int(corner))
tile_terrain = objects.TileTerrain(*terrain_list)
# tile element optional sub-elements
#tile element optional sub-elements
animation: Optional[List[objects.Frame]] = None
tile_animation_element = tile_element.find('./animation')
if tile_animation_element:
@@ -332,21 +339,19 @@ def _parse_tiles(tile_element_list: List[etree.Element]
if tile_image_element is not None:
tile_image = _parse_image_element(tile_image_element)
object_group = None
tile_object_group_element = tile_element.find('./objectgroup')
if tile_object_group_element:
### FIXME: why did they do this :(
pass
hitboxes = None
tile_hitboxes_element = tile_element.find('./objectgroup')
if tile_hitboxes_element is not None:
hitboxes = _parse_hitboxes(tile_hitboxes_element)
tiles[id] = objects.Tile(id, type, tile_terrain, animation,
tile_image, object_group)
tiles[id] = objects.Tile(id, tile_type, tile_terrain, animation,
tile_image, hitboxes)
return tiles
def _parse_image_element(image_element: etree.Element) -> objects.Image:
"""
Parse image element given.
"""Parse image element given.
Returns:
:Color: Color in Arcade's preffered format.
@@ -361,15 +366,14 @@ def _parse_image_element(image_element: etree.Element) -> objects.Image:
width = int(image_element.attrib['width'])
height = int(image_element.attrib['height'])
size = objects.OrderedPair(width, height)
size = objects.Size(width, height)
return objects.Image(source, size, trans)
def _parse_properties_element(properties_element: etree.Element
) -> objects.Properties:
"""
Adds Tiled property to Properties dict.
"""Adds Tiled property to Properties dict.
Args:
:name (str): Name of property.

View File

@@ -9,7 +9,7 @@ def parse_color(color: str) -> objects.Color:
:Color: Color object in the format that Arcade understands.
"""
# strip initial '#' character
if not len(color) % 2 == 0: # pylint: disable=C2001
if not len(color) % 2 == 0:
color = color[1:]
if len(color) == 6:
@@ -25,3 +25,21 @@ def parse_color(color: str) -> objects.Color:
blue = int(color[6:8], 16)
return objects.Color(red, green, blue, alpha)
def get_tile_by_gid(tile_sets: objects.TileSetDict, gid: int) -> objects.Tile:
"""Gets Tile from a global tile ID.
Args:
tile_sets (objects.TileSetDict): TileSetDict from TileMap.
gid (int): Global tile ID of the tile to be returned.
Returns:
objects.Tile: The Tile object reffered to by the global tile ID.
"""
for tileset_key, tileset in tile_sets.items():
for tile_key, tile in tileset.tiles.items():
tile_gid = tile.id + tileset_key
if tile_gid == gid:
return tile
return None

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.2" tiledversion="1.2.3" name="tile_set_image" tilewidth="32" tileheight="32" spacing="1" margin="1" tilecount="48" columns="8">
<image source="images/tmw_desert_spacing.png" width="265" height="199"/>
<tile id="9">
<objectgroup draworder="index">
<object id="2" name="wall" type="rectangle type" x="1" y="1" width="32" height="32" rotation="1"/>
</objectgroup>
</tile>
<tile id="19">
<objectgroup draworder="index">
<object id="1" name="wall corner" type="polygon type" x="32" y="1" rotation="1">
<polygon points="0,0 -32,0 -32,32 -16,32.1818 -15.8182,16.9091 0.181818,17.0909"/>
</object>
</objectgroup>
</tile>
<tile id="20">
<objectgroup draworder="index">
<object id="1" name="polyline" type="polyline type" x="1.45455" y="1.45455" rotation="1">
<polyline points="0,0 25.0909,21.2727 9.63636,28.3636"/>
</object>
</objectgroup>
</tile>
<tile id="31">
<objectgroup draworder="index">
<object id="1" name="rock 1" type="elipse type" x="5.09091" y="2.54545" width="19.6364" height="19.2727" rotation="1">
<ellipse/>
</object>
<object id="2" name="rock 2" type="elipse type" x="16.1818" y="22" width="8.54545" height="8.36364" rotation="-1">
<ellipse/>
</object>
</objectgroup>
</tile>
<tile id="45">
<objectgroup draworder="index">
<object id="1" name="sign" type="point type" x="14.7273" y="26.3636">
<point/>
</object>
</objectgroup>
</tile>
</tileset>

View File

@@ -15,7 +15,8 @@ def test_map_simple():
"""
TMX with a very simple spritesheet tile set and some properties.
"""
map = pytiled_parser.parse_tile_map(Path("../test_data/test_map_simple.tmx"))
map = pytiled_parser.parse_tile_map(
Path("../test_data/test_map_simple.tmx"))
# map
# unsure how to get paths to compare propperly