-
Notifications
You must be signed in to change notification settings - Fork 2
/
tmx.go
225 lines (197 loc) · 5.26 KB
/
tmx.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// Copyright 2012 Lightpoke. All rights reserved.
// This source code is subject to the terms and
// conditions defined in the "License.txt" file.
// Package tmx implements a Tiled Map XML file loader.
//
// The Tiled Map XML file specification can be found at:
//
// https://github.com/bjorn/tiled/wiki/TMX-Map-Format
//
// This package supports all of the current file specification with the
// exception of embedded image data (I.e. non-external tileset images).
//
package tmx
import (
"encoding/xml"
"fmt"
"image/color"
"strconv"
"strings"
)
// hexColorToRGBA converts hex color strings to color.RGBA
//
// Alpha value in returned color will always be 255
func hexToRGBA(c string) color.RGBA {
// There isin't really a color specification I can find on TMX file format,
// but Tiled exports #RRGGBB hex values, but this also supports #RGB ones
// just in case some abstract tool uses them by coincidence.
// Strip leading # if there is one
if len(c) > 0 && c[0] == '#' {
c = c[1:]
}
// If an invalid length value then simply return
if len(c) != 6 && len(c) != 3 {
return color.RGBA{0, 0, 0, 255}
}
var r, g, b uint8
if len(c) == 6 {
// Parse RRGGBB color
rgb, err := strconv.ParseUint(c, 16, 48)
if err != nil {
return color.RGBA{0, 0, 0, 255}
}
r = uint8(rgb >> 16)
g = uint8(rgb >> 8)
b = uint8(rgb)
} else {
// Parse #RGB values
rgb, err := strconv.ParseUint(c, 16, 24)
if err != nil {
return color.RGBA{0, 0, 0, 255}
}
r = uint8(rgb>>8) & 0xf
g = uint8(rgb>>4) & 0xf
b = uint8(rgb) & 0xf
r |= r << 4
g |= g << 4
b |= b << 4
}
return color.RGBA{r, g, b, 255}
}
type xmlProperty struct {
Name string `xml:"name,attr"`
Value string `xml:"value,attr"`
}
type xmlProperties struct {
Property []xmlProperty `xml:"property"`
}
func (p xmlProperties) toMap() map[string]string {
m := make(map[string]string, len(p.Property))
for _, p := range p.Property {
m[p.Name] = p.Value
}
return m
}
type xmlTileoffset struct {
X int `xml:"x,attr"`
Y int `xml:"y,attr"`
}
type xmlTerrain struct {
Name string `xml:"name,attr"`
Tile int `xml:"id,attr"`
}
type xmlTerraintypes struct {
Terrain []xmlTerrain `xml:"terrain"`
}
type xmlMap struct {
Version string `xml:"version,attr"`
Orientation string `xml:"orientation,attr"`
Width int `xml:"width,attr"`
Height int `xml:"height,attr"`
TileWidth int `xml:"tilewidth,attr"`
TileHeight int `xml:"tileheight,attr"`
BackgroundColor string `xml:"backgroundcolor,attr"`
Properties xmlProperties `xml:"properties"`
Tileset []xmlTileset `xml:"tileset"`
Layer []xmlLayer `xml:"layer"`
Objectgroup []xmlObjectgroup `xml:"objectgroup"`
}
// Parse parses the TMX map file data and returns a *Map.
//
// nil and a error will be returned if there are any problems parsing the data.
func Parse(data []byte) (*Map, error) {
// Unmarshal map data
x := new(xmlMap)
err := xml.Unmarshal(data, &x)
if err != nil {
return nil, err
}
// Parse version string
split := strings.Split(x.Version, ".")
var major, minor int
if len(split) == 2 {
major, err = strconv.Atoi(split[0])
if err != nil {
return nil, err
}
minor, err = strconv.Atoi(split[1])
if err != nil {
return nil, err
}
}
// Find map orientation
var orient Orientation
switch x.Orientation {
case "orthogonal":
orient = Orthogonal
case "isometric":
orient = Isometric
case "staggered":
orient = Staggered
default:
return nil, fmt.Errorf("unknown map orientation.")
}
// Find map properties
props := make(map[string]string, len(x.Properties.Property))
for _, prop := range x.Properties.Property {
props[prop.Name] = prop.Value
}
// Convert the tilesets
tilesets := make([]*Tileset, len(x.Tileset))
for i, tsx := range x.Tileset {
ts := &Tileset{
Name: tsx.Name,
Firstgid: tsx.Firstgid,
Source: tsx.Source,
Width: tsx.TileWidth,
Height: tsx.TileHeight,
Spacing: tsx.Spacing,
Margin: tsx.Margin,
}
// Find tileset offset
ts.OffsetX, ts.OffsetY = tsx.Tileoffset.X, tsx.Tileoffset.Y
// Find tileset properties
ts.Properties = tsx.Properties.toMap()
// Find image properties
ts.Image = &Image{
Source: tsx.Image.Source,
Width: tsx.Image.Width,
Height: tsx.Image.Height,
}
// Find tile definitions
ts.Tiles = tsx.tilesMap()
// Find terrain definitions
ts.Terrain = tsx.terrainTypes()
tilesets[i] = ts
}
// Manage loading layers
layers := make([]*Layer, len(x.Layer))
for i, xl := range x.Layer {
var err error
layers[i], err = xl.toLayer(x.Width, x.Height)
if err != nil {
return nil, err
}
}
// Manager loading object groups.
objectGroups := make([]*ObjectGroup, len(x.Objectgroup))
for i, group := range x.Objectgroup {
objectGroups[i] = group.toObjectGroup()
}
// Create actual map
m := &Map{
VersionMajor: major,
VersionMinor: minor,
Orientation: orient,
Width: x.Width,
Height: x.Height,
TileWidth: x.TileWidth,
TileHeight: x.TileHeight,
BackgroundColor: hexToRGBA(x.BackgroundColor),
Properties: props,
Tilesets: tilesets,
Layers: layers,
ObjectGroups: objectGroups,
}
return m, nil
}