Skip to content

Commit

Permalink
More work
Browse files Browse the repository at this point in the history
  • Loading branch information
soywiz committed Jun 11, 2023
1 parent adc1f0e commit 39dd48e
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 70 deletions.
2 changes: 1 addition & 1 deletion korge-ldtk/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/.idea
/.gradle
/build
/build.gradle
/build.gradle
13 changes: 0 additions & 13 deletions korge-ldtk/build.gradle

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package korlibs.korge.ldtk

import korlibs.datastructure.Extra
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.descriptors.*
Expand Down Expand Up @@ -253,7 +254,7 @@ data class Definitions (
* All tilesets
*/
val tilesets: List<TilesetDefinition>
)
) : Extra by Extra.Mixin()

@Serializable
data class EntityDefinition (
Expand Down Expand Up @@ -376,7 +377,7 @@ data class EntityDefinition (
* Pixel width
*/
val width: Int
)
) : Extra by Extra.Mixin()

/**
* This section is mostly only intended for the LDtk editor app itself. You can safely
Expand Down
20 changes: 20 additions & 0 deletions korge-ldtk/src/commonMain/kotlin/korlibs/korge/ldtk/LDTKJsonExt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package korlibs.korge.ldtk

import korlibs.datastructure.Extra
import korlibs.math.geom.Anchor
import korlibs.math.geom.PointInt
import kotlinx.serialization.json.jsonPrimitive

val EntityInstance.gridPos: PointInt get() = PointInt(grid[0], grid[1])
val EntityInstance.pivotAnchor: Anchor get() = Anchor(pivot[0], pivot[1])
val EntityDefinition.fieldDefsByName by Extra.PropertyThis { this.fieldDefs.associateBy { it.identifier } }
val Definitions.entitiesByUid by Extra.PropertyThis { this.entities.associateBy { it.uid } }
operator fun EntityDefinition.get(name: String): FieldDefinition? = fieldDefsByName[name]
fun FieldInstance.definition(ldtk: LDTKJson): FieldDefinition {
return ldtk.defs.levelFields[this.defUid]
}
data class FieldInfo(val def: FieldDefinition, val instance: FieldInstance) {
val identifier = def.identifier

val valueString: String? get() = instance.value?.jsonPrimitive?.content
}
283 changes: 229 additions & 54 deletions korge-ldtk/src/commonMain/kotlin/korlibs/korge/ldtk/view/LDTKView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,78 +10,253 @@ import korlibs.image.color.*
import korlibs.image.format.*
import korlibs.image.tiles.*
import korlibs.io.file.*
import korlibs.korge.view.filter.IdentityFilter
import korlibs.korge.view.filter.filters
import korlibs.math.geom.*

class LDTKView(
val world: LDTKWorld

private fun IStackedIntArray2.getFirst(pos: PointInt): Int = getFirst(pos.x, pos.y)
private fun IStackedIntArray2.getLast(pos: PointInt): Int = getLast(pos.x, pos.y)

class LDTKCollisions(val world: LDTKWorld, val stack: IStackedIntArray2) {
fun tileToPixel(tilePos: PointInt): PointInt = (tilePos.toFloat() * world.ldtk.defaultGridSize).toIntFloor()
fun pixelToTile(pixelPos: PointInt): PointInt = (pixelPos.toFloat() / world.ldtk.defaultGridSize).toIntFloor()

fun getTile(tilePos: PointInt): Int = stack.getLast(tilePos)
fun getPixel(pixelPos: PointInt): Int = getTile(pixelToTile(pixelPos))
fun getPixel(pixelPos: Point): Int = getPixel(pixelPos.toIntFloor())
}

fun LDTKWorld.createCollisionMaps(layerId: String = "Collisions"): LDTKCollisions {
val ldtk = this.ldtk
val world = SparseChunkedStackedIntArray2()
for (level in ldtk.levels) {
//println("## level: ${level.identifier}")
for (layer in (level.layerInstances ?: emptyList()).asReversed()) {
if (layer.identifier != layerId) continue
val intGrid = IntArray2(layer.cWid, layer.cHei, layer.intGridCSV.copyOf(layer.cWid * layer.cHei))
//println("intGrid=$intGrid")
//println(" - layer=${layer.identifier}, level.worldX=${level.worldX}, level.worldY=${level.worldY}")
world.putChunk(
StackedIntArray2(
intGrid,
startX = level.worldX / ldtk.defaultGridSize,
startY = level.worldY / ldtk.defaultGridSize
)
)
}
}
return LDTKCollisions(this, world)
}

class LDTKEntityView(
val entity: EntityInstance,
val llayer: LDTKLayer
) : Container() {

val definition = llayer.world.ldtk.defs.entitiesByUid[entity.defUid]!!
val fields = definition.fieldDefs.zip(entity.fieldInstances).map { FieldInfo(it.first, it.second) }
val fieldsByName = fields.associateBy { it.identifier }

init {
llayer.level.level.fieldInstances
name = entity.identifier
}

val anchor = entity.pivotAnchor
val gridSize = llayer.layer.gridSize.toFloat()
val tile = entity.tile
var view: View = if (tile != null) {
val tileset = llayer.world.tilesetDefsById[tile.tilesetUid]
val utileset = tileset?.unextrudedTileSet
image(tileset!!.unextrudedTileSet!!.base.sliceWithSize(tile.x, tile.y, tile.w, tile.h)).also { it.smoothing = false }
} else {
solidRect(entity.width, entity.height, Colors[entity.smartColor, Colors.FUCHSIA])
}
fun replaceView(view: View) {
this.replaceChild(this.view, view)
this.view = view
//view.anchor(anchor)
}
init {
val ldtk = world.ldtk
val layersDefsById = world.layersDefsById
val tilesetDefsById = world.tilesetDefsById
container {
for (level in ldtk.levels) {
container {
val color = Colors[level.levelBgColor ?: ldtk.bgColor]
solidRect(level.pxWid, level.pxHei, color)
for (layer in (level.layerInstances ?: emptyList()).asReversed()) {
//for (layer in (level.layerInstances ?: emptyList())) {
val layerDef = layersDefsById[layer.layerDefUid] ?: continue
val tilesetExt = tilesetDefsById[layer.tilesetDefUid] ?: continue
val intGrid = IntArray2(layer.cWid, layer.cHei, layer.intGridCSV.copyOf(layer.cWid * layer.cHei))
val tileData = StackedIntArray2(layer.cWid, layer.cHei, -1)
val tileset = tilesetExt.def
val gridSize = tileset.tileGridSize

//val fsprites = FSprites(layer.autoLayerTiles.size)
//val view = fsprites.createView(bitmap).also { it.scale(2) }
//addChild(view)
for (tile in layer.autoLayerTiles) {
val (px, py) = tile.px
val (tileX, tileY) = tile.src
val x = px / gridSize
val y = py / gridSize
val dx = px % gridSize
val dy = py % gridSize
val tx = tileX / gridSize
val ty = tileY / gridSize
val cellsTilesPerRow = tileset.pxWid / gridSize
val tileId = ty * cellsTilesPerRow + tx
val flipX = tile.f.hasBitSet(0)
val flipY = tile.f.hasBitSet(1)
tileData.push(x, y, TileInfo(tileId, flipX = flipX, flipY = flipY, offsetX = dx, offsetY = dy).data)
}
if (tilesetExt.tileset != null) {
tileMap(tileData, tilesetExt.tileset).alpha(layerDef.displayOpacity)
}
//tileset!!.
//println(intGrid)
}
}.xy(level.worldX, level.worldY)
//break // ONLY FIRST LEVEL
//}.filters(IdentityFilter.Nearest).scale(2)
val pos = entity.gridPos.toFloat() * gridSize + anchor.toVector() * gridSize
view
.size(entity.width, entity.height)
(view as? Anchorable)?.anchor(anchor)

this.xy(pos)
this.zIndex(pos.y)
}

}

class LDTKLayerView(
val llayer: LDTKLayer,
var showCollisions: Boolean = false
) : Container() {
val world = llayer.world
val layer = llayer.layer

val layerDef = world.layersDefsById[layer.layerDefUid]
val tilesetExt = world.tilesetDefsById[layer.tilesetDefUid]

fun addTiles() {
val intGrid = IntArray2(layer.cWid, layer.cHei, layer.intGridCSV.copyOf(layer.cWid * layer.cHei))
val tileData = StackedIntArray2(layer.cWid, layer.cHei, -1)
if (tilesetExt != null && layerDef != null) {
val tileset = tilesetExt.def
val gridSize = tileset.tileGridSize

//val fsprites = FSprites(layer.autoLayerTiles.size)
//val view = fsprites.createView(bitmap).also { it.scale(2) }
//addChild(view)

for (tile in layer.autoLayerTiles + layer.gridTiles) {
val (px, py) = tile.px
val (tileX, tileY) = tile.src
val x = px / gridSize
val y = py / gridSize
val dx = px % gridSize
val dy = py % gridSize
val tx = tileX / gridSize
val ty = tileY / gridSize
val cellsTilesPerRow = tileset.pxWid / gridSize
val tileId = ty * cellsTilesPerRow + tx
val flipX = tile.f.hasBitSet(0)
val flipY = tile.f.hasBitSet(1)
tileData.push(x, y, TileInfo(tileId, flipX = flipX, flipY = flipY, offsetX = dx, offsetY = dy).data)
}
//}.xy(300, 300)
if (tilesetExt.tileset != null) {
tileMap(tileData, tilesetExt.tileset!!, smoothing = false)
.alpha(layerDef.displayOpacity)
.also { if (!world.tilesetIsExtruded) it.filters(IdentityFilter.Nearest) }
.also { it.overdrawTiles = 1 }
tileMap(intGrid, world.intsTileSet, smoothing = false)
.visible(showCollisions)
.also { if (!world.tilesetIsExtruded) it.filters(IdentityFilter.Nearest) }
.also { it.overdrawTiles = 1 }
}
//tileset!!.
//println(intGrid)
}
}

init {
name = layer.identifier
//for (layer in (level.layerInstances ?: emptyList())) {
addTiles()
}

val entities = layer.entityInstances.map { LDTKEntityView(it, llayer).addTo(this) }
val entitiesByIID = entities.associateBy { it.entity.iid }
}

class ExtTileset(val def: TilesetDefinition, val tileset: TileSet?)
class LDTKLevelView(
val level: LDTKLevel,
var showCollisions: Boolean = false
) : Container() {
private val ldtk get() = level.ldtk
private val world get() = level.world
private val blevel get() = level.level

private val _layerViews = arrayListOf<View>()
private val _layerViewsByName = linkedHashMapOf<String, View>()

val bgLayer = solidRect(blevel.pxWid, blevel.pxHei, Colors[blevel.levelBgColor ?: ldtk.bgColor]).also {
it.name = "background"
}
val layerViews = level.layers.asReversed().map { layer -> LDTKLayerView(layer, showCollisions).addTo(this) }
val layerViewsByName = layerViews.associateBy { it.layer.identifier }
}

class LDTKWorldView(
val world: LDTKWorld,
var showCollisions: Boolean = false
) : Container() {
init {
for (level in world.levels) {
LDTKLevelView(level, showCollisions)
.addTo(this)
.xy(level.level.worldX, level.level.worldY)
}
}
}

class ExtTileset(val def: TilesetDefinition, val tileset: TileSet?, val unextrudedTileSet: TileSet?)

class LDTKLayer(val level: LDTKLevel, val layer: LayerInstance) {
val world get() = level.world
val entities get() = layer.entityInstances
}

class LDTKLevel(val world: LDTKWorld, val level: Level) {
val ldtk get() = world.ldtk
val layers by lazy { level.layerInstances?.map { LDTKLayer(this, it) } ?: emptyList() }
}

class LDTKWorld(
val ldtk: LDTKJson,
val tilesetDefsById: Map<Int, ExtTileset>
val tilesetDefsById: Map<Int, ExtTileset>,
val tilesetIsExtruded: Boolean
) {
val levels by lazy { ldtk.levels.map { LDTKLevel(this, it) } }
val levelsByName by lazy { levels.associateBy { it.level.identifier } }

val layersDefsById: Map<Int, LayerDefinition> = ldtk.defs.layers.associateBy { it.uid }

//val ldtk = world.ldtk
//val layersDefsById = world.layersDefsById
//val tilesetDefsById = world.tilesetDefsById

val colors = Bitmap32((ldtk.defaultGridSize + 4) * 16, ldtk.defaultGridSize)
val intsTileSet = TileSet(
(0 until 16).map {
TileSetTileInfo(
it,
colors.slice(
RectangleInt(
(ldtk.defaultGridSize + 4) * it,
0,
ldtk.defaultGridSize,
ldtk.defaultGridSize
)
)
)
}
)

init {
// @TODO: Do this for each layer, since we might have several IntGrid layers
for (layer in ldtk.defs.layers) {
for (value in layer.intGridValues) {
colors.fill(Colors[value.color], (ldtk.defaultGridSize + 4) * value.value)
//println("COLOR: ${value.value} : ${value.color}")
}
}
}
}

suspend fun VfsFile.readLDTKWorld(): LDTKWorld {
suspend fun VfsFile.readLDTKWorld(extrude: Boolean = true): LDTKWorld {
val file = this
val json = file.readString()
val ldtk = LDTKJson.load(json)
val tilesetDefsById: Map<Int, ExtTileset> = ldtk.defs.tilesets.associate { def ->
val bitmap = def.relPath?.let { file.parent[it].readBitmap() }
val tileSet = bitmap?.let { TileSet(bitmap.slice(), def.tileGridSize, def.tileGridSize) }
def.uid to ExtTileset(def, tileSet)
val bitmap = def.relPath?.let {
val bmp = file.parent[it].readBitmap()
if (extrude) bmp.toBMP32() else bmp
}
val unextrudedTileSet: TileSet? = bitmap?.let {
TileSet(bitmap.slice(), def.tileGridSize, def.tileGridSize)
}
val tileSet: TileSet? = unextrudedTileSet?.let {
if (extrude) unextrudedTileSet.extrude(border = 2) else unextrudedTileSet
}
def.uid to ExtTileset(def, tileSet, unextrudedTileSet)
}
return LDTKWorld(ldtk, tilesetDefsById)
return LDTKWorld(ldtk, tilesetDefsById, extrude)
}

fun TileSet.extrude(border: Int = 1, mipmaps: Boolean = false): TileSet {
val bitmaps = this.textures.map { (it as BmpSlice).extract().toBMP32() }
return TileSet.fromBitmaps(width, height, bitmaps, border, mipmaps = mipmaps)
}

0 comments on commit 39dd48e

Please sign in to comment.