Skip to content

Latest commit

 

History

History
 
 

tiled

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

Maven Central

KTX: Tiled map editor utilities

Tiled API utilities.

Why?

LibGDX brings its own set of Tiled map utilities, including loading and handling of maps exported from the editor. However, the API contains many wrapped non-standard collections, which makes accessing the loaded maps cumbersome. With Kotlin's reified types and extension methods, the Tiled API can be significantly improved.

Guide

MapProperties

In many maps that you create with Tiled you will need to access the properties defined in the editor. They are either defined on map, layer, object, tileset or tile level. The original LibGDX MapProperties class returns untyped Object (or Kotlin's Any!) whenever retrieving a property and is therefore not ideal and unsafe.

To improve this, multiple extension methods were added to TiledMap, MapLayer, MapObject, TiledMapTileSet and TiledMapTile. Also, a new MissingPropertyException to handle missing properties with an explicit exception. The new additions include:

  • property(key: String): T: returns an existing property or throws a MissingPropertyException
  • property(key: String, defaultValue: T): T: returns the value of a property or the default value if the property is missing.
  • propertyOrNull(key: String): T?: same as property(key), but returns null instead of throwing an exception.
  • containsProperty(key: String): Boolean: returns true if and only if the property exists.

MapProperties were also extended to support the contains (in) and set ([] assignment) operators. Unfortunately, since the original class already defines get method, type-safe get ([]) operator was not added.

MapObject

In addition to the property extensions, MapObject automatically comes with a set of standard properties. They allow to retrieve the properties automatically set and initialized by the TmxMapLoader. New extension fields include:

  • id
  • x
  • y
  • width
  • height
  • rotation: this property is only available if you rotate your object in Tiled.
  • type: this property is only available if you enter a text for the Type property in Tiled.

Almost all objects are related to a shape except for TextureMapObject. Sometimes you need access to these shapes like e.g. when creating Box2D bodies out of those objects. For that reason a new extension field was added:

  • shape: returns the Shape2D of a map object. This can either be a Rectangle, Circle, Ellipse, Polyline or Polygon. If there is an object that is not linked to a shape then a MissingShapeException is thrown.

TiledMap

Similar to MapObject, there were several standard properties added to TiledMap as well:

  • width
  • height
  • tileWidth
  • tileHeight
  • backgroundColor: this property is only available if you explicitly select a background color for your map.
  • orientation
  • hexSideLength
  • staggerAxis
  • staggerIndex

Two new extension methods will provide you with the total width and height of your map in pixels:

  • totalWidth()
  • totalHeight()

The problems that we face with properties are also relevant in case of map layers. To improve layer handling API the following extensions were added:

  • contains(layerName: String): works as the in operator.
  • layer(layerName: String): returns the layer or throws a MissingLayerException in case the layer does not exist.

Inlined forEachMapObject extension method allows to iterate over all MapObject instances present on the chosen map layer.

Inlined forEachLayer extension method allows to iterate over all MapLayer instances of a specific type to execute a certain function on them.

MapLayers and MapObjects

isEmpty and isNotEmpty extension method to check if the specific collection is empty or not.

Usage examples

General

Accessing properties of TiledMap, MapLayer, MapObject, TiledMapTileSet and TiledMapTile:

import com.badlogic.gdx.maps.MapLayer
import com.badlogic.gdx.maps.MapObject
import com.badlogic.gdx.maps.tiled.TiledMap
import ktx.tiled.*

val mapObj: MapObject = getMapObject()
val mapLayer: MapLayer = getMapLayer()
val map: TiledMap = getMap()

// Retrieves Float property - throws MissingPropertyException if missing:
val myFloatProp: Float = mapObj.property("myFloatProperty")

// Retrieves String property with a default value:
val myProp: String = mapObj.property("myProperty", defaultValue = "")

// The explicit type can be omitted as it is automatically derived from the type of the default value.
// myProp2 is of type Float
val myProp2 = mapLayer.property("myProperty2", defaultValue = 1f)

// Retrieves Int property or null if the property does not exist.
val myOtherProp: Int? = map.propertyOrNull("myOtherProperty")

// Check if a certain property exists:
if (map.containsProperty("lightColor")) {
    changeAmbientLightColor(map.property<String>("lightColor"))
}

Using MapProperties API:

import com.badlogic.gdx.maps.MapProperties
import ktx.tiled.contains
import ktx.tiled.set

val mapProperties = MapProperties()

// Adds a property to the map:
mapProperties["key"] = "value"

// Checks if a property exists:
if ("key" in mapProperties) {
  // Reads a property:
  val value: String = mapProperties["key"] as String

  // Note: since MapProperties already defines `get` method,
  // a fully typed extension could not have been added.
  // Use property-retrieving extensions of other Tiled classes
  // or handle casting manually as in the example above.
}

MapObject

Retrieving standard properties of a MapObject like id, x or shape:

import com.badlogic.gdx.maps.MapObject
import com.badlogic.gdx.math.Rectangle
import ktx.tiled.*

val mapObj: MapObject = getMapObject()

// Retrieve position of an object:
val x = mapObj.x
val y = mapObj.y

// Retrieves ID of an object:
val id = mapObj.id

// Retrieves shape of an object:
val shape = mapObj.shape // if you only need the Shape2D instance
val rect = mapObj.shape as Rectangle // if you need the rect

TiledMap

Retrieving standard properties of a TiledMap like width or tileheight:

import com.badlogic.gdx.maps.tiled.TiledMap
import ktx.tiled.*

val map: TiledMap = getTiledMap()

// Gets map size in pixels to e.g. lock camera movement within map boundaries:
val totalWidth = map.totalWidth()
val totalHeight = map.totalHeight()

// Retrieve map size information:
val width = map.width
val height = map.height
val tileWidth = map.tileWidth
val tileHeight = map.tileHeight

Working with layers of a TiledMap:

import com.badlogic.gdx.maps.tiled.TiledMap
import ktx.tiled.*

val map: TiledMap = getTiledMap()

// Contains can be used either with the regular method call syntax:
if (map.contains("enemyLayer")) {
    val enemyLayer = map.layer("enemyLayer")
}

// Or with the "in" operator:
if ("collision" in map) {
    val collisionLayer = map.layer("collision")
}

// This will throw a MissingLayerException if the layer does not exist:
val layer = map.layer("myMapLayer")

Iterating over map objects of a specific MapLayer of a map:

import com.badlogic.gdx.maps.tiled.TiledMap
import ktx.tiled.*

val map: TiledMap = getTiledMap()

// Creates collision bodies for every map object of the collision layer:
map.forEachMapObject("collision") { mapObj ->
    createStaticBox2DCollisionBody(mapObj.x, mapObj.y, mapObj.shape)
}

Iterating over a specific type of layers of a map:

import com.badlogic.gdx.maps.tiled.TiledMap
import ktx.tiled.*

val map: TiledMap = getTiledMap()

// Iterate over all object layers and parse them.
// Note that println is only called with layers of the exact MapLayer type.
// For example, TiledMapTileLayer - which is a subclass of MapLayer - does not 
// have this exact class and will not be matched.
map.forEachLayer<MapLayer> { layer ->
  println(layer)
}

Checking if MapLayers and MapObjects collections are empty:

import com.badlogic.gdx.maps.MapObjects
import com.badlogic.gdx.maps.tiled.TiledMap
import ktx.tiled.*

val map: TiledMap = TiledMap()

if (map.layers.isNotEmpty()) {
  map.layers.forEach { layer ->
    if (layer.objects.isEmpty()) {
      // nothing to do if there are no objects
      return@forEach 
    }
    parseObjects(layer.objects)
  }
}

Additional documentation