Skip to content

Commit 4379baa

Browse files
committed
Add Ldtk model
See: https://ldtk.io/docs/
1 parent c22df06 commit 4379baa

File tree

3 files changed

+4922
-0
lines changed

3 files changed

+4922
-0
lines changed
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
package com.github.minigdx.tiny.resources.ldtk
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
import kotlinx.serialization.json.Json
6+
7+
/**
8+
* Unique Instance Identifier (IID)
9+
*
10+
* @see: https://ldtk.io/docs/game-dev/json-overview/unique-identifiers/
11+
*/
12+
typealias StrIID = String
13+
14+
/**
15+
* Coordinate with grid unit.
16+
*/
17+
typealias GridInt = Int
18+
19+
/**
20+
* Coordinate with pixel unit.
21+
*/
22+
typealias PixelInt = Int
23+
24+
/**
25+
* [x,y] format
26+
*/
27+
typealias PixelCoord = List<PixelInt>
28+
29+
/**
30+
* Grid-based coordinates [x,y] format
31+
*/
32+
typealias GridCoord = List<GridInt>
33+
34+
/**
35+
* Tile ID, used in tileset.
36+
*/
37+
typealias TileID = Int
38+
39+
/**
40+
* Describes the layout of levels within a game world.
41+
*
42+
* <p>There are three primary layout types:</p>
43+
*
44+
* <ul>
45+
* <li><b>Linear Horizontal or Linear Vertical:</b>
46+
* The order of the levels in the {@code levels} array directly corresponds
47+
* to their arrangement in the world. For horizontal layouts, levels are
48+
* placed sequentially along the X-axis. For vertical layouts, they are
49+
* placed sequentially along the Y-axis.</li>
50+
*
51+
* <li><b>Free:</b>
52+
* Each level is positioned in the world using its {@code worldX} and
53+
* {@code worldY} coordinates, allowing for arbitrary placement.</li>
54+
*
55+
* <li><b>GridVania:</b>
56+
* The {@code worldGridWidth} and {@code worldGridHeight} values defined in
57+
* the root of the JSON structure specify the world grid dimensions. Each
58+
* level's {@code worldX} and {@code worldY} coordinates are snapped to this grid,
59+
* ensuring alignment with the grid structure.</li>
60+
* </ul>
61+
*
62+
* See: https://ldtk.io/docs/game-dev/json-overview/world-layout/
63+
*/
64+
enum class WorldLayout {
65+
LinearHorizontal,
66+
LinearVertical,
67+
Free,
68+
GridVania,
69+
}
70+
71+
@Serializable
72+
data class Ldtk(
73+
val worldLayout: WorldLayout,
74+
val levels: List<Level>,
75+
) {
76+
companion object {
77+
fun read(content: String): Ldtk {
78+
val json = Json {
79+
allowStructuredMapKeys = true
80+
ignoreUnknownKeys = true
81+
classDiscriminator = "__type"
82+
}
83+
return json.decodeFromString(Ldtk.serializer(), content)
84+
}
85+
}
86+
}
87+
88+
@Serializable
89+
data class Level(
90+
val identifier: String,
91+
val iid: StrIID,
92+
val worldX: Int,
93+
val worldY: Int,
94+
val layerInstances: List<Layer>,
95+
)
96+
97+
@Serializable
98+
sealed interface Layer {
99+
100+
val __identifier: String
101+
val __cWid: GridInt
102+
val __cHei: GridInt
103+
val __gridSize: GridInt
104+
105+
// optional offset that could happen when resizing levels
106+
val pxOffsetX: Int
107+
108+
// optional offset that could happen when resizing levels
109+
val pxOffsetY: Int
110+
111+
val seed: Long
112+
113+
@SerialName("IntGrid")
114+
@Serializable
115+
data class IntGrid(
116+
override val __identifier: String,
117+
override val __cWid: GridInt,
118+
override val __cHei: GridInt,
119+
override val __gridSize: GridInt,
120+
override val pxOffsetX: Int,
121+
override val pxOffsetY: Int,
122+
override val seed: Long,
123+
/**
124+
* A list of all values in the IntGrid layer, stored in CSV format (Comma Separated Values).
125+
* Order is from left to right, and top to bottom (ie. first row from left to right, followed by second row, etc).
126+
* 0 means "empty cell" and IntGrid values start at 1.
127+
* The array size is __cWid x __cHei cells.
128+
*/
129+
val intGridCsv: List<Int>,
130+
) : Layer
131+
132+
@SerialName("AutoLayer")
133+
@Serializable
134+
data class AutoLayer(
135+
override val __identifier: String,
136+
override val __cWid: GridInt,
137+
override val __cHei: GridInt,
138+
override val __gridSize: GridInt,
139+
override val pxOffsetX: Int,
140+
override val pxOffsetY: Int,
141+
override val seed: Long,
142+
val autoLayer: List<Tile>,
143+
) : Layer
144+
145+
@SerialName("Tiles")
146+
@Serializable
147+
data class TilesLayer(
148+
override val __identifier: String,
149+
override val __cWid: GridInt,
150+
override val __cHei: GridInt,
151+
override val __gridSize: GridInt,
152+
override val pxOffsetX: Int,
153+
override val pxOffsetY: Int,
154+
override val seed: Long,
155+
val gridTiles: List<Tile>,
156+
) : Layer
157+
158+
@SerialName("Entities")
159+
@Serializable
160+
data class EntitiesLayer(
161+
override val __identifier: String,
162+
override val __cWid: GridInt,
163+
override val __cHei: GridInt,
164+
override val __gridSize: GridInt,
165+
override val pxOffsetX: Int,
166+
override val pxOffsetY: Int,
167+
override val seed: Long,
168+
val entityInstances: List<Entity>,
169+
) : Layer
170+
}
171+
172+
/**
173+
* @See: https://ldtk.io/json/#ldtk-Tile
174+
*/
175+
@Serializable
176+
data class Tile(
177+
/**
178+
* Alpha/opacity of the tile (0-1, defaults to 1)
179+
*/
180+
val a: Float,
181+
/**
182+
* "Flip bits", a 2-bits integer to represent the mirror transformations of the tile.
183+
* - Bit 0 = X flip
184+
* - Bit 1 = Y flip
185+
* Examples: f=0 (no flip), f=1 (X flip only), f=2 (Y flip only), f=3 (both flips)
186+
*/
187+
val f: Int,
188+
189+
/**
190+
* Pixel coordinates of the tile in the layer ([x,y] format). Don't forget optional layer offsets, if they exist!
191+
*/
192+
val px: PixelCoord,
193+
194+
/**
195+
* Pixel coordinates of the tile in the tileset ([x,y] format)
196+
*/
197+
val src: PixelCoord,
198+
199+
/**
200+
* The Tile ID in the corresponding tileset.
201+
*/
202+
val t: TileID,
203+
)
204+
205+
@Serializable
206+
data class Entity(
207+
/**
208+
* Grid-based coordinates ([x,y] format)
209+
*/
210+
val __grid: GridCoord,
211+
val __identifier: String,
212+
val __pivot: List<Float>,
213+
/**
214+
* X world coordinate in pixels. Only available in GridVania or Free world layouts.
215+
*/
216+
val __worldX: PixelInt? = null,
217+
/**
218+
* Y world coordinate in pixels Only available in GridVania or Free world layouts.
219+
*/
220+
val __worldY: PixelInt? = null,
221+
/**
222+
* An array of all custom fields and their values.
223+
*/
224+
val fieldInstances: List<CustomField>,
225+
/**
226+
* Entity height in pixels. For non-resizable entities, it will be the same as Entity definition.
227+
*/
228+
val height: PixelInt,
229+
/**
230+
* Entity width in pixels. For non-resizable entities, it will be the same as Entity definition.
231+
*/
232+
val width: PixelInt,
233+
val iid: StrIID,
234+
/**
235+
* Pixel coordinates ([x,y] format) in current level coordinate space. Don't forget optional layer offsets, if they exist!
236+
*/
237+
val px: PixelCoord,
238+
)
239+
240+
@Serializable
241+
data class CustomField(
242+
val __identifier: String,
243+
/**
244+
* Type of the field, such as Int, Float, String, Enum(my_enum_name), Bool, etc.
245+
* NOTE: if you enable the advanced option Use Multilines type, you will have "Multilines" instead of "String" when relevant.
246+
*/
247+
val __type: String,
248+
)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.github.minigdx.tiny.resources.ldtk
2+
3+
import kotlin.test.Test
4+
5+
class LdtkTest {
6+
7+
@Test
8+
fun loadLdtkFile() {
9+
val content = LdtkTest::class.java.classLoader.getResource("reflections.ldtk")!!.readText()
10+
println(Ldtk.read(content))
11+
}
12+
}

0 commit comments

Comments
 (0)