From a54c89491a10b5ddda1e9c82c9e1d6e5eed9c815 Mon Sep 17 00:00:00 2001 From: Quillraven Date: Sat, 21 Dec 2019 10:39:10 +0100 Subject: [PATCH 01/32] #233 tile map extensions for libktx --- settings.gradle | 1 + tiled/README.md | 127 ++++++++++++++++++ tiled/build.gradle | 3 + tiled/gradle.properties | 2 + tiled/src/main/kotlin/ktx/tiled/mapLayer.kt | 28 ++++ tiled/src/main/kotlin/ktx/tiled/mapObject.kt | 73 ++++++++++ tiled/src/main/kotlin/ktx/tiled/tiledMap.kt | 99 ++++++++++++++ tiled/src/test/kotlin/ktx/tiled/mapLayer.kt | 33 +++++ tiled/src/test/kotlin/ktx/tiled/mapObjects.kt | 75 +++++++++++ tiled/src/test/kotlin/ktx/tiled/tiledMap.kt | 84 ++++++++++++ 10 files changed, 525 insertions(+) create mode 100644 tiled/README.md create mode 100644 tiled/build.gradle create mode 100644 tiled/gradle.properties create mode 100644 tiled/src/main/kotlin/ktx/tiled/mapLayer.kt create mode 100644 tiled/src/main/kotlin/ktx/tiled/mapObject.kt create mode 100644 tiled/src/main/kotlin/ktx/tiled/tiledMap.kt create mode 100644 tiled/src/test/kotlin/ktx/tiled/mapLayer.kt create mode 100644 tiled/src/test/kotlin/ktx/tiled/mapObjects.kt create mode 100644 tiled/src/test/kotlin/ktx/tiled/tiledMap.kt diff --git a/settings.gradle b/settings.gradle index 8cb59456..18081e60 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,6 +16,7 @@ include( 'math', 'scene2d', 'style', + 'tiled', 'vis', 'vis-style' ) diff --git a/tiled/README.md b/tiled/README.md new file mode 100644 index 00000000..ba70f52c --- /dev/null +++ b/tiled/README.md @@ -0,0 +1,127 @@ +# KTX : TiledMap utilities + +TiledMap utilities for LibGDX applications written with Kotlin. + +### Why? + +As usual with plain libgdx Java the code compared to Kotlin becomes boilerplate and is not +very easy to read especially for users who are not familiar with your code. +With the TiledMap functionality it becomes worse since there are a lot of wrapped collections +and iterators and accessing properties or iterating over objects in the map becomes very cumbersome. + +Luckily for us with Kotlin we can write extension functions and properties to make our life +a little bit easier and most notably make our code more readable. + +### Guide + +#### Miscellaneous utilities + +#### `MapObject` + +In most maps that you create with Tiled you will create objects in it. There are a few +things that are common for any map object like `id`, `shape`, `x-` and `y` coordinates. +Properties in libgdx are stored in a wrapper class called `MapProperties` which is nothing less +than a `ObjectMap`. + +For easier access of those properties, including the standard properties mentioned above, you can use +following new extensions: +- `property(key, defaultValue)` +- `propertyOrNull(key)` +- `containsProperty(key)` +- `x`, `y`, `id` and `shape` + +#### `MapLayer` + +The same thing regarding properties holds true for `MapLayer`. Therefore, following three extensions +will help you out: +- `property(key, defaultValue)` +- `propertyOrNull(key)` +- `containsProperty(key)` + +#### `TiledMap` + +What is true for `MapObject` and `MapLayer` is of course also true for `TiledMap`. +Standard properties for a map are `width`, `height`, `tilewidth` and `tileheight`. They +are part of any map and are useful to e.g. lock the camera movement to the map boundaries. + +As usual with Kotlin we are trying to avoid **null** and for that reason a new layer extension +was added to retrieve a non-null `MapLayer` which is just an empty default layer without any properties +or objects. Its name is **ktx-default-map-layer**. + +Often you need to iterate over all objects of a layer to do something with them like +creating collision bodies for box2d or other things. To ease that use case a new +extension was added called `forEachMapObject` which takes a lambda that is executed for each +object of the given layer. Together with the `layer` extension mentioned above it becomes easy to write +such loops. + +To summarize it, following extensions are available: +- `property(key, defaultValue)` +- `propertyOrNull(key)` +- `containsProperty(key)` +- `width`, `height`, `tileWidth`, `tileHeight` +- `totalWidth()` and `totalHeight()` +- `layer(layerName)` +- `forEachMapObject(layerName, action)` + +### Usage examples + +`MapObject`: + +```kotlin +// assume mapObj refers to a MapObject instance + +// before +val myProp = mapObj.properties.get("myProperty", "", String::class.java) +val myOtherProp = mapObj.properties.get("myOtherProperty", Int::class.java) +val x = mapObj.properties.get("x", 0f, Float::class.java) +val y = mapObj.properties.get("y", 0f, Float::class.java) +val shape = (mapObj as RectangleMapObject).rectangle + +// using libktx +val myProp = mapObj.property("myProperty", "") +val myOtherProp: Int? = mapObj.propertyOrNull("myOtherProperty") +val x = mapObj.x +val y = mapObj.y +val shape = mapObj.shape // if you only need the Shape2D instance +val rect = mapObj.shape as Rectangle // if you need the rect +``` + +`TiledMap`: + +```kotlin +// assume map refers to a TiledMap instance + +// before +val myProp = map.properties.get("myProp", String::class.java) +val myProp2 = map.properties.get("myProp2", true, Boolean::class.java) +val width = map.properties.get("width", Float::class.java) +val totalWidth = map.properties.get("width", Float::class.java) * map.properties.get("tilewidth", Float::class.java) + +map.layers.get("myLayer")?.objects?.forEach { + val x = it.properties.get("x", Float::class.java) + val y = it.properties.get("y", Float::class.java) + // create an object at x/y position +} + +val myLayerProp = map.layers.get("myLayer")?.properties?.get("myLayerProp", Int::class.java) + + + +// using libktx +val myProp:String? = map.propertyOrNull("myProp") +val myProp2 = map.property("myProp2", true) +val width = map.width +val totalWidth = map.totalWidth() + +map.forEachMapObject("myLayer") { + val x = it.x + val y = it.y + // create an object at x/y position +} + +val myLayerProp: Int? = map.layer("myLayer").propertyOrNull("myLayerProp") +``` + +#### Additional documentation + +- [LibGDX Tile maps official wiki.](https://github.com/libgdx/libgdx/wiki/Tile-maps) diff --git a/tiled/build.gradle b/tiled/build.gradle new file mode 100644 index 00000000..6a513cc6 --- /dev/null +++ b/tiled/build.gradle @@ -0,0 +1,3 @@ +dependencies { + provided "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" +} diff --git a/tiled/gradle.properties b/tiled/gradle.properties new file mode 100644 index 00000000..1d4431eb --- /dev/null +++ b/tiled/gradle.properties @@ -0,0 +1,2 @@ +projectName=ktx-tiled +projectDesc=TiledMap utilities for LibGDX applications written with Kotlin. diff --git a/tiled/src/main/kotlin/ktx/tiled/mapLayer.kt b/tiled/src/main/kotlin/ktx/tiled/mapLayer.kt new file mode 100644 index 00000000..bb138dd7 --- /dev/null +++ b/tiled/src/main/kotlin/ktx/tiled/mapLayer.kt @@ -0,0 +1,28 @@ +package ktx.tiled + +import com.badlogic.gdx.maps.MapLayer +import com.badlogic.gdx.maps.MapProperties + +/** + * Extension method to directly access the [MapProperties] of a [MapLayer]. The type is automatically + * derived from the type of the given default value. If the property is not defined the defaultValue will be returned. + * @param key property name + * @param defaultValue default value in case the property is missing + * @return value of the property or defaultValue if property is missing + */ +inline fun MapLayer.property(key: String, defaultValue: T): T = this.properties[key, defaultValue, T::class.java] + +/** + * Extension method to directly access the [MapProperties] of a [MapLayer]. If the property + * is not defined then this method returns null. + * @param key property name + * @return value of the property or null if the property is missing + */ +inline fun MapLayer.propertyOrNull(key: String): T? = this.properties[key, T::class.java] + +/** + * Extension method to directly access the [MapProperties] of a [MapLayer] and its [containsKey][MapProperties.containsKey] method + * @param key property name + * @return true if the property exists. Otherwise false + */ +fun MapLayer.containsProperty(key: String) = properties.containsKey(key) \ No newline at end of file diff --git a/tiled/src/main/kotlin/ktx/tiled/mapObject.kt b/tiled/src/main/kotlin/ktx/tiled/mapObject.kt new file mode 100644 index 00000000..4acce8b0 --- /dev/null +++ b/tiled/src/main/kotlin/ktx/tiled/mapObject.kt @@ -0,0 +1,73 @@ +package ktx.tiled + +import com.badlogic.gdx.maps.MapObject +import com.badlogic.gdx.maps.MapProperties +import com.badlogic.gdx.maps.objects.* +import com.badlogic.gdx.math.* + +/** + * Extension method to directly access the [MapProperties] of a [MapObject]. The type is automatically + * derived from the type of the given default value. If the property is not defined the defaultValue will be returned. + * @param key property name + * @param defaultValue default value in case the property is missing + * @return value of the property or defaultValue if property is missing + */ +inline fun MapObject.property(key: String, defaultValue: T): T = this.properties[key, defaultValue, T::class.java] + +/** + * Extension method to directly access the [MapProperties] of a [MapObject]. If the property + * is not defined then this method returns null. + * @param key property name + * @return value of the property or null if the property is missing + */ +inline fun MapObject.propertyOrNull(key: String): T? = this.properties[key, T::class.java] + +/** + * Extension method to directly access the [MapProperties] of a [MapObject] and its [containsKey][MapProperties.containsKey] method + * @param key property name + * @return true if the property exists. Otherwise false + */ +fun MapObject.containsProperty(key: String) = properties.containsKey(key) + +/** + * Extension property to retrieve the x-coordinate of the [MapObject]. If the object does not have + * a x-coordinate then 0 is returned + */ +val MapObject.x: Float + get() = property("x", 0f) + +/** + * Extension property to retrieve the y-coordinate of the [MapObject]. If the object does not have + * a y-coordinate then 0 is returned + */ +val MapObject.y: Float + get() = property("y", 0f) + +/** + * Extension property to retrieve the unique ID of the [MapObject]. If the object does not have an + * id then -1 is returned + */ +val MapObject.id: Int + get() = property("id", -1) + +/** + * Extension method to retrieve the [Shape2D] instance of a [MapObject]. + * Depending on the type of the object a different shape will be returned: + * + * - [CircleMapObject] -> [Circle] + * - [EllipseMapObject] -> [Ellipse] + * - [PolylineMapObject] -> [Polyline] + * - [PolygonMapObject] -> [Polygon] + * - [RectangleMapObject] -> [Rectangle] + * + * Note that [TextureMapObject] is not supported by this method as it has no related shape. + */ +val MapObject.shape: Shape2D + get() = when (this) { + is CircleMapObject -> this.circle + is EllipseMapObject -> this.ellipse + is PolylineMapObject -> this.polyline + is PolygonMapObject -> this.polygon + is RectangleMapObject -> this.rectangle + else -> throw UnsupportedOperationException("Shape extension function is not supported for MapObject of type ${this::class.java}") + } diff --git a/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt b/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt new file mode 100644 index 00000000..b473a13c --- /dev/null +++ b/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt @@ -0,0 +1,99 @@ +package ktx.tiled + +import com.badlogic.gdx.maps.MapLayer +import com.badlogic.gdx.maps.MapObject +import com.badlogic.gdx.maps.MapProperties +import com.badlogic.gdx.maps.tiled.TiledMap + +private val DEFAULT_MAP_LAYER = MapLayer().apply { name = "ktx-default-map-layer" } + +/** + * Extension method to directly access the [MapProperties] of a [TiledMap]. The type is automatically + * derived from the type of the given default value. If the property is not defined the defaultValue will be returned. + * @param key property name + * @param defaultValue default value in case the property is missing + * @return value of the property or defaultValue if property is missing + */ +inline fun TiledMap.property(key: String, defaultValue: T): T = this.properties[key, defaultValue, T::class.java] + +/** + * Extension method to directly access the [MapProperties] of a [TiledMap]. If the property + * is not defined then this method returns null. + * @param key property name + * @return value of the property or null if the property is missing + */ +inline fun TiledMap.propertyOrNull(key: String): T? = this.properties[key, T::class.java] + +/** + * Extension method to directly access the [MapProperties] of a [TiledMap] and its [containsKey][MapProperties.containsKey] method + * @param key property name + * @return true if the property exists. Otherwise false + */ +fun TiledMap.containsProperty(key: String) = properties.containsKey(key) + +/** + * Extension property to retrieve the width of the [TiledMap]. If the map does not have + * a **width** property then 0 is returned + */ +val TiledMap.width: Int + get() = property("width", 0) + +/** + * Extension property to retrieve the height of the [TiledMap]. If the map does not have + * a **height** property then 0 is returned + */ +val TiledMap.height: Int + get() = property("height", 0) + +/** + * Extension property to retrieve the tile width of each tile of the [TiledMap]. If the map does not have + * a **tilewidth** property then 0 is returned + */ +val TiledMap.tileWidth: Int + get() = property("tilewidth", 0) + +/** + * Extension property to retrieve the tile height of each tile of the [TiledMap]. If the map does not have + * a **tileheight** property then 0 is returned + */ +val TiledMap.tileHeight: Int + get() = property("tileheight", 0) + +/** + * Extension method to retrieve the total width of the [TiledMap]. It is the result of the + * width multiplied by the tile width of the map. + * + * @see [width] + * @see [tileWidth] + * + * @return total width in pixels + */ +fun TiledMap.totalWidth() = width * tileWidth + +/** + * Extension method to retrieve the total height of the [TiledMap]. It is the result of the + * height multiplied by the tile height of the map. + * + * @see [height] + * @see [tileHeight] + * + * @return total height in pixels + */ +fun TiledMap.totalHeight() = height * tileHeight + +/** + * Extension method to retrieve a non-null [MapLayer] of the [TiledMap]. In case the layer + * cannot be found, a default empty layer is returned with name **ktx-default-map-layer**. + * @param layerName name of [MapLayer] + * @return [MapLayer] of given name or an empty default map layer, if the specified layer does not exist + */ +fun TiledMap.layer(layerName: String) = layers[layerName] ?: DEFAULT_MAP_LAYER + +/** + * Extension method to easily execute an action per [MapObject] of a given [MapLayer]. + * @param layerName name of [MapLayer] + * @param action action to execute per [MapObject] of the [MapLayer] + */ +fun TiledMap.forEachMapObject(layerName: String, action: (MapObject) -> Unit) { + layer(layerName).objects.forEach { action(it) } +} \ No newline at end of file diff --git a/tiled/src/test/kotlin/ktx/tiled/mapLayer.kt b/tiled/src/test/kotlin/ktx/tiled/mapLayer.kt new file mode 100644 index 00000000..1caa9f22 --- /dev/null +++ b/tiled/src/test/kotlin/ktx/tiled/mapLayer.kt @@ -0,0 +1,33 @@ +package ktx.tiled + +import com.badlogic.gdx.maps.MapLayer +import org.junit.Assert +import org.junit.Test + +class MapLayerTest { + private val mapLayer = MapLayer().apply { + properties.put("active", true) + properties.put("customProperty", 123) + } + + @Test + fun `retrieve properties from MapLayer with default value`() { + Assert.assertEquals(true, mapLayer.property("active", false)) + Assert.assertEquals(123, mapLayer.property("customProperty", 0)) + Assert.assertEquals(-1f, mapLayer.property("x", -1f)) + } + + @Test + fun `retrieve properties from MapLayer without default value`() { + Assert.assertNull(mapLayer.propertyOrNull("x")) + val customProperty: Int? = mapLayer.propertyOrNull("customProperty") + Assert.assertNotNull(customProperty) + Assert.assertEquals(123, customProperty) + } + + @Test + fun `check if property from MapLayer exists`() { + Assert.assertTrue(mapLayer.containsProperty("active")) + Assert.assertFalse(mapLayer.containsProperty("x")) + } +} \ No newline at end of file diff --git a/tiled/src/test/kotlin/ktx/tiled/mapObjects.kt b/tiled/src/test/kotlin/ktx/tiled/mapObjects.kt new file mode 100644 index 00000000..c1c94e2e --- /dev/null +++ b/tiled/src/test/kotlin/ktx/tiled/mapObjects.kt @@ -0,0 +1,75 @@ +package ktx.tiled + +import com.badlogic.gdx.maps.MapObject +import com.badlogic.gdx.maps.objects.* +import com.badlogic.gdx.math.Circle +import com.badlogic.gdx.math.Ellipse +import com.badlogic.gdx.math.Rectangle +import org.junit.Assert.* +import org.junit.Test + +class MapObjectTest { + private val mapObject = MapObject().apply { + properties.also { + it.put("id", 13) + it.put("x", 1) + it.put("width", 1f) + it.put("name", "Property") + it.put("active", true) + } + } + + private val polylineVertices = floatArrayOf(0f, 0f, 1f, 1f) + private val polygonVertices = floatArrayOf(0f, 0f, 1f, 1f, 2f, 0f) + + private val circleObject = CircleMapObject() + private val ellipseObject = EllipseMapObject() + private val polylineObject = PolylineMapObject(polylineVertices) + private val polygonObject = PolygonMapObject(polygonVertices) + private val rectObject = RectangleMapObject() + private val textureObject = TextureMapObject() + + @Test + fun `retrieve properties from MapObject with default value`() { + assertEquals(1, mapObject.property("x", 0)) + assertEquals(0, mapObject.property("y", 0)) + assertEquals(1f, mapObject.property("width", 0f)) + assertEquals("Property", mapObject.property("name", "")) + assertEquals(true, mapObject.property("active", false)) + } + + @Test + fun `retrieve properties from MapObject without default value`() { + assertNull(mapObject.propertyOrNull("y")) + val x: Int? = mapObject.propertyOrNull("x") + assertNotNull(x) + assertEquals(1, x) + } + + @Test + fun `check if property from MapObject exists`() { + assertTrue(mapObject.containsProperty("x")) + assertFalse(mapObject.containsProperty("y")) + } + + @Test + fun `retrieve standard properties of MapObject`() { + assertEquals(1f, mapObject.x) + assertEquals(0f, mapObject.y) + assertEquals(13, mapObject.id) + } + + @Test + fun `retrieve shape from MapObject`() { + assertEquals(Circle(0f, 0f, 1f), circleObject.shape) + assertEquals(Ellipse(0f, 0f, 1f, 1f), ellipseObject.shape) + assertEquals(polylineObject.polyline, polylineObject.shape) + assertEquals(polygonObject.polygon, polygonObject.shape) + assertEquals(Rectangle(0f, 0f, 1f, 1f), rectObject.shape) + } + + @Test(expected = UnsupportedOperationException::class) + fun `retrieve shape from unsupported MapObject`() { + textureObject.shape + } +} \ No newline at end of file diff --git a/tiled/src/test/kotlin/ktx/tiled/tiledMap.kt b/tiled/src/test/kotlin/ktx/tiled/tiledMap.kt new file mode 100644 index 00000000..8b60f88c --- /dev/null +++ b/tiled/src/test/kotlin/ktx/tiled/tiledMap.kt @@ -0,0 +1,84 @@ +package ktx.tiled + +import com.badlogic.gdx.maps.MapLayer +import com.badlogic.gdx.maps.MapObject +import com.badlogic.gdx.maps.tiled.TiledMap +import org.junit.Assert.* +import org.junit.Test + +class TiledMapTest { + private val tiledMap = TiledMap().apply { + properties.put("width", 16) + properties.put("height", 8) + properties.put("tilewidth", 32) + properties.put("tileheight", 32) + layers.add(MapLayer().apply { + name = "layer-1" + objects.add(MapObject()) + objects.add(MapObject()) + objects.add(MapObject()) + }) + layers.add(MapLayer().apply { name = "layer-2" }) + } + + @Test + fun `retrieve properties from TiledMap with default value`() { + assertEquals(16, tiledMap.property("width", 0)) + assertEquals(-1, tiledMap.property("x", -1)) + } + + @Test + fun `retrieve properties from TiledMap without default value`() { + assertNull(tiledMap.propertyOrNull("x")) + val width: Int? = tiledMap.propertyOrNull("width") + assertNotNull(width) + assertEquals(16, width) + } + + @Test + fun `check if property from TiledMap exists`() { + assertTrue(tiledMap.containsProperty("width")) + assertFalse(tiledMap.containsProperty("x")) + } + + @Test + fun `retrieve standard properties of TiledMap`() { + assertEquals(16, tiledMap.width) + assertEquals(8, tiledMap.height) + assertEquals(32, tiledMap.tileWidth) + assertEquals(32, tiledMap.tileHeight) + assertEquals(16 * 32, tiledMap.totalWidth()) + assertEquals(8 * 32, tiledMap.totalHeight()) + } + + @Test + fun `retrieve layers by name`() { + assertEquals("layer-2", tiledMap.layer("layer-2").name) + assertEquals("ktx-default-map-layer", tiledMap.layer("layer-3").name) + } + + @Test + fun `execute action per object of a layer`() { + // check that there are objects in layer -1 + assertEquals(3, tiledMap.layer("layer-1").objects.count) + // verify that they are all visible and set them to not visible + var counter = 0 + tiledMap.forEachMapObject("layer-1") { + assertTrue(it.isVisible) + it.isVisible = false + counter++ + } + // verify again that they are now all invisible and revert them back to being visible + assertEquals(3, counter) + tiledMap.forEachMapObject("layer-1") { + assertFalse(it.isVisible) + it.isVisible = true + } + + // also, test empty default layer which should do nothing + assertEquals(0, tiledMap.layer("non-existing").objects.count) + counter = 0 + tiledMap.forEachMapObject("non-existing") { ++counter } + assertEquals(0, counter) + } +} \ No newline at end of file From 5a83af1d9a9ab6163ea0c2aea29b1719889eef36 Mon Sep 17 00:00:00 2001 From: Quillraven Date: Sun, 22 Dec 2019 10:08:51 +0100 Subject: [PATCH 02/32] #233 refinement - added missing new lines at EOF - changed test file names to ...Test - updated README.md (updated guide and examples) --- tiled/README.md | 131 +++++++++++++----- tiled/src/main/kotlin/ktx/tiled/mapLayer.kt | 2 +- tiled/src/main/kotlin/ktx/tiled/tiledMap.kt | 2 +- .../tiled/{mapLayer.kt => mapLayerTest.kt} | 2 +- .../{mapObjects.kt => mapObjectsTest.kt} | 2 +- .../tiled/{tiledMap.kt => tiledMapTest.kt} | 2 +- 6 files changed, 98 insertions(+), 43 deletions(-) rename tiled/src/test/kotlin/ktx/tiled/{mapLayer.kt => mapLayerTest.kt} (99%) rename tiled/src/test/kotlin/ktx/tiled/{mapObjects.kt => mapObjectsTest.kt} (99%) rename tiled/src/test/kotlin/ktx/tiled/{tiledMap.kt => tiledMapTest.kt} (99%) diff --git a/tiled/README.md b/tiled/README.md index ba70f52c..1eed7530 100644 --- a/tiled/README.md +++ b/tiled/README.md @@ -12,6 +12,13 @@ and iterators and accessing properties or iterating over objects in the map beco Luckily for us with Kotlin we can write extension functions and properties to make our life a little bit easier and most notably make our code more readable. +From a code point of view it makes it easier to read as most of the time we can skip the +wrapper class and directly access things like e.g. we can directly get the `layer` from a `TiledMap` +without mentioning the `layers` collection wrapper. + +Also, due to Kotlin's `reified` possibility we can retrieve properties without passing the `Class` +parameter and therefore also have this information during runtime. + ### Guide #### Miscellaneous utilities @@ -65,61 +72,109 @@ To summarize it, following extensions are available: ### Usage examples -`MapObject`: +#### General + +Properties functionality is the same for `MapObject`, `MapLayer` and `TiledMap`: + +```kotlin +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() + +// retrieve String property via defaultValue +val myProp: String = mapObj.property("myProperty", "") + +// 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", 1f) + +// retrieve Int property without defaultValue +val myOtherProp: Int? = map.propertyOrNull("myOtherProperty") + +// check if a certain property exists +if (map.containsProperty("lightColor")) { + // change box2d light ambient color +} +``` + +#### `MapObject` + +Retrieving standard properties of a `MapObject` like `id`, `x` or `shape`: ```kotlin -// assume mapObj refers to a MapObject instance - -// before -val myProp = mapObj.properties.get("myProperty", "", String::class.java) -val myOtherProp = mapObj.properties.get("myOtherProperty", Int::class.java) -val x = mapObj.properties.get("x", 0f, Float::class.java) -val y = mapObj.properties.get("y", 0f, Float::class.java) -val shape = (mapObj as RectangleMapObject).rectangle - -// using libktx -val myProp = mapObj.property("myProperty", "") -val myOtherProp: Int? = mapObj.propertyOrNull("myOtherProperty") +import com.badlogic.gdx.maps.MapObject +import com.badlogic.gdx.math.Rectangle +import ktx.tiled.* + + +val mapObj: MapObject = getMapObject() + +// retrieve position of object val x = mapObj.x val y = mapObj.y + +// retrieve id of object +val id = mapObj.id + +// retrieve shape val shape = mapObj.shape // if you only need the Shape2D instance val rect = mapObj.shape as Rectangle // if you need the rect ``` -`TiledMap`: +#### `TiledMap` + +Retrieving standard properties of a `TiledMap` like `width` or `tileheight`: ```kotlin -// assume map refers to a TiledMap instance - -// before -val myProp = map.properties.get("myProp", String::class.java) -val myProp2 = map.properties.get("myProp2", true, Boolean::class.java) -val width = map.properties.get("width", Float::class.java) -val totalWidth = map.properties.get("width", Float::class.java) * map.properties.get("tilewidth", Float::class.java) - -map.layers.get("myLayer")?.objects?.forEach { - val x = it.properties.get("x", Float::class.java) - val y = it.properties.get("y", Float::class.java) - // create an object at x/y position -} +import com.badlogic.gdx.maps.tiled.TiledMap +import ktx.tiled.* -val myLayerProp = map.layers.get("myLayer")?.properties?.get("myLayerProp", Int::class.java) +val map: TiledMap = getTiledMap() +// get map size in pixels to e.g. lock camera movement within map boundaries +val totalWidth = map.totalWidth() +val totalHeight = map.totalHeight() -// using libktx -val myProp:String? = map.propertyOrNull("myProp") -val myProp2 = map.property("myProp2", true) +// retrieve map size information val width = map.width -val totalWidth = map.totalWidth() +val height = map.height +val tileWidth = map.tileWidth +val tileHeight = map.tileHeight +``` -map.forEachMapObject("myLayer") { - val x = it.x - val y = it.y - // create an object at x/y position -} +Retrieving a non-null layer of a `TiledMap`: + +```kotlin +import com.badlogic.gdx.maps.tiled.TiledMap +import ktx.tiled.* -val myLayerProp: Int? = map.layer("myLayer").propertyOrNull("myLayerProp") + +val map: TiledMap = getTiledMap() + +// get collision layer - if it is missing an empty default layer is returned +val collisionLayer = map.layer("collision") +``` + +Iterating over map objects of a specific `MapLayer` of a map: + +```kotlin +import com.badlogic.gdx.maps.tiled.TiledMap +import ktx.tiled.* + + +val map: TiledMap = getTiledMap() + +// create collision bodies for every map object of the collision layer +map.forEachMapObject("collision") { mapObj -> + createStaticBox2DCollisionBody(mapObj.x, mapObj.y, mapObj.shape) +} ``` #### Additional documentation diff --git a/tiled/src/main/kotlin/ktx/tiled/mapLayer.kt b/tiled/src/main/kotlin/ktx/tiled/mapLayer.kt index bb138dd7..8c9d61d3 100644 --- a/tiled/src/main/kotlin/ktx/tiled/mapLayer.kt +++ b/tiled/src/main/kotlin/ktx/tiled/mapLayer.kt @@ -25,4 +25,4 @@ inline fun MapLayer.propertyOrNull(key: String): T? = this.propertie * @param key property name * @return true if the property exists. Otherwise false */ -fun MapLayer.containsProperty(key: String) = properties.containsKey(key) \ No newline at end of file +fun MapLayer.containsProperty(key: String) = properties.containsKey(key) diff --git a/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt b/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt index b473a13c..4a3886ec 100644 --- a/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt +++ b/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt @@ -96,4 +96,4 @@ fun TiledMap.layer(layerName: String) = layers[layerName] ?: DEFAULT_MAP_LAYER */ fun TiledMap.forEachMapObject(layerName: String, action: (MapObject) -> Unit) { layer(layerName).objects.forEach { action(it) } -} \ No newline at end of file +} diff --git a/tiled/src/test/kotlin/ktx/tiled/mapLayer.kt b/tiled/src/test/kotlin/ktx/tiled/mapLayerTest.kt similarity index 99% rename from tiled/src/test/kotlin/ktx/tiled/mapLayer.kt rename to tiled/src/test/kotlin/ktx/tiled/mapLayerTest.kt index 1caa9f22..861880e9 100644 --- a/tiled/src/test/kotlin/ktx/tiled/mapLayer.kt +++ b/tiled/src/test/kotlin/ktx/tiled/mapLayerTest.kt @@ -30,4 +30,4 @@ class MapLayerTest { Assert.assertTrue(mapLayer.containsProperty("active")) Assert.assertFalse(mapLayer.containsProperty("x")) } -} \ No newline at end of file +} diff --git a/tiled/src/test/kotlin/ktx/tiled/mapObjects.kt b/tiled/src/test/kotlin/ktx/tiled/mapObjectsTest.kt similarity index 99% rename from tiled/src/test/kotlin/ktx/tiled/mapObjects.kt rename to tiled/src/test/kotlin/ktx/tiled/mapObjectsTest.kt index c1c94e2e..436c18e9 100644 --- a/tiled/src/test/kotlin/ktx/tiled/mapObjects.kt +++ b/tiled/src/test/kotlin/ktx/tiled/mapObjectsTest.kt @@ -72,4 +72,4 @@ class MapObjectTest { fun `retrieve shape from unsupported MapObject`() { textureObject.shape } -} \ No newline at end of file +} diff --git a/tiled/src/test/kotlin/ktx/tiled/tiledMap.kt b/tiled/src/test/kotlin/ktx/tiled/tiledMapTest.kt similarity index 99% rename from tiled/src/test/kotlin/ktx/tiled/tiledMap.kt rename to tiled/src/test/kotlin/ktx/tiled/tiledMapTest.kt index 8b60f88c..cc13f5a3 100644 --- a/tiled/src/test/kotlin/ktx/tiled/tiledMap.kt +++ b/tiled/src/test/kotlin/ktx/tiled/tiledMapTest.kt @@ -81,4 +81,4 @@ class TiledMapTest { tiledMap.forEachMapObject("non-existing") { ++counter } assertEquals(0, counter) } -} \ No newline at end of file +} From e18a82e70b437ddba7cb2fe656df19beb757117b Mon Sep 17 00:00:00 2001 From: MJ Date: Sun, 22 Dec 2019 13:46:10 +0100 Subject: [PATCH 03/32] Renamed serializers test file for consistency. #217 --- .../kotlin/ktx/json/{jsonSerializerTest.kt => serializersTest.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename json/src/test/kotlin/ktx/json/{jsonSerializerTest.kt => serializersTest.kt} (100%) diff --git a/json/src/test/kotlin/ktx/json/jsonSerializerTest.kt b/json/src/test/kotlin/ktx/json/serializersTest.kt similarity index 100% rename from json/src/test/kotlin/ktx/json/jsonSerializerTest.kt rename to json/src/test/kotlin/ktx/json/serializersTest.kt From 779e411b1389502c0a18d9bb36cc69d185bd148e Mon Sep 17 00:00:00 2001 From: MJ Date: Sun, 22 Dec 2019 13:47:02 +0100 Subject: [PATCH 04/32] Prepared for the next release. #231 --- CHANGELOG.md | 2 ++ version.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e52c0d9..7c0249e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +#### 1.9.10-SNAPSHOT + #### 1.9.10-b3 - **[UPDATE]** Updated to Kotlin 1.3.61. diff --git a/version.txt b/version.txt index e2b6eabf..e7b756bc 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.9.10-b3 +1.9.10-SNAPSHOT From 90c6081b611f06d6f9f9cf10043790e6b4c2d446 Mon Sep 17 00:00:00 2001 From: dakeese Date: Sun, 22 Dec 2019 09:14:33 -0500 Subject: [PATCH 05/32] removeAll and retainAll return Boolean --- .../src/main/kotlin/ktx/collections/arrays.kt | 16 +++++++--- .../test/kotlin/ktx/collections/arraysTest.kt | 30 +++++++++++++------ 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/collections/src/main/kotlin/ktx/collections/arrays.kt b/collections/src/main/kotlin/ktx/collections/arrays.kt index 2e4d5eae..8818fb6f 100644 --- a/collections/src/main/kotlin/ktx/collections/arrays.kt +++ b/collections/src/main/kotlin/ktx/collections/arrays.kt @@ -237,7 +237,7 @@ inline fun > GdxArray.sortByDescending(crossin * Removes elements from the array that satisfy the [predicate]. * @param pool Removed items are freed to this pool. */ -inline fun GdxArray.removeAll(pool: Pool?, predicate: (Type) -> Boolean) { +fun GdxArray.removeAll(pool: Pool? = null, predicate: (Type) -> Boolean): Boolean { var currentWriteIndex = 0 for (i in 0 until size) { val value = items[i] @@ -250,14 +250,18 @@ inline fun GdxArray.removeAll(pool: Pool?, predicate: (Type) pool?.free(value) } } - truncate(currentWriteIndex) + if (currentWriteIndex < size) { + truncate(currentWriteIndex) + return true + } + return false } /** * Removes elements from the array that do not satisfy the [predicate]. * @param pool Removed items are freed to this optional pool. */ -inline fun GdxArray.retainAll(pool: Pool? = null, predicate: (Type) -> Boolean) { +fun GdxArray.retainAll(pool: Pool? = null, predicate: (Type) -> Boolean): Boolean { var currentWriteIndex = 0 for (i in 0 until size) { val value = items[i] @@ -270,7 +274,11 @@ inline fun GdxArray.retainAll(pool: Pool? = null, predicate: pool?.free(value) } } - truncate(currentWriteIndex) + if (currentWriteIndex < size) { + truncate(currentWriteIndex) + return true + } + return false } /** diff --git a/collections/src/test/kotlin/ktx/collections/arraysTest.kt b/collections/src/test/kotlin/ktx/collections/arraysTest.kt index 708d578e..56624c25 100644 --- a/collections/src/test/kotlin/ktx/collections/arraysTest.kt +++ b/collections/src/test/kotlin/ktx/collections/arraysTest.kt @@ -2,7 +2,6 @@ package ktx.collections import com.badlogic.gdx.math.Vector2 import com.badlogic.gdx.utils.Pool -import com.badlogic.gdx.utils.Pools import org.junit.Assert.* import org.junit.Test import java.util.LinkedList @@ -370,16 +369,20 @@ class ArraysTest { @Test fun `should remove elements from existing GdxArray`() { val array = GdxArray.with(1, 2, 3, 4, 5) - array.removeAll { it > 10 } + val noneRemovedResult = array.removeAll { it > 10 } + assert(!noneRemovedResult) assertEquals(GdxArray.with(1, 2, 3, 4, 5), array) - array.removeAll { it % 2 == 0 } + val evensRemovedResult = array.removeAll { it % 2 == 0 } + assert(evensRemovedResult) assertEquals(GdxArray.with(1, 3, 5), array) - array.removeAll { it is Number } + val allRemovedResult = array.removeAll { it is Number } + assert(allRemovedResult) assertEquals(GdxArray(), array) - array.removeAll { it > 0 } + val emptyRemoveResult = array.removeAll { it > 0 } + assert(!emptyRemoveResult) assertEquals(GdxArray(), array) } @@ -396,16 +399,20 @@ class ArraysTest { @Test fun `should retain elements from existing GdxArray`() { val array = GdxArray.with(1, 2, 3, 4, 5) - array.retainAll { it < 6 } + val allRetainedResult = array.retainAll { it < 6 } + assert(!allRetainedResult) assertEquals(GdxArray.with(1, 2, 3, 4, 5), array) - array.retainAll { it % 2 == 1 } + val oddsRetainedResult = array.retainAll { it % 2 == 1 } + assert(oddsRetainedResult) assertEquals(GdxArray.with(1, 3, 5), array) - array.retainAll { it < 0 } + val noneRetainedResult = array.retainAll { it < 0 } + assert(noneRetainedResult) assertEquals(GdxArray(), array) - array.retainAll { it > 0 } + val emptyRetainResult = array.retainAll { it > 0 } + assert(!emptyRetainResult) assertEquals(GdxArray(), array) } @@ -419,6 +426,11 @@ class ArraysTest { assertEquals(pool.peak, 2) } + @Test + fun `should transfer elements`() { + + } + @Test fun `should map elements into a new GdxArray`() { val array = GdxArray.with(1, 2, 3) From 8104f267255bf7d8ed9df1049e027c1394a33bb1 Mon Sep 17 00:00:00 2001 From: MJ Date: Sun, 22 Dec 2019 15:43:01 +0100 Subject: [PATCH 06/32] Updated documentation of new GdxArray utilities. #237 --- CHANGELOG.md | 3 +++ collections/src/main/kotlin/ktx/collections/arrays.kt | 2 ++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c0249e6..5dbf99f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ #### 1.9.10-SNAPSHOT +- **[CHANGE]** `Array.removeAll` and `retainAll` now return a boolean if any elements were removed. +- **[CHANGE]** `Array.transfer` is now less strict about typing. + #### 1.9.10-b3 - **[UPDATE]** Updated to Kotlin 1.3.61. diff --git a/collections/src/main/kotlin/ktx/collections/arrays.kt b/collections/src/main/kotlin/ktx/collections/arrays.kt index a8a534aa..28fc6780 100644 --- a/collections/src/main/kotlin/ktx/collections/arrays.kt +++ b/collections/src/main/kotlin/ktx/collections/arrays.kt @@ -236,6 +236,7 @@ inline fun > GdxArray.sortByDescending(crossin /** * Removes elements from the array that satisfy the [predicate]. * @param pool Removed items are freed to this pool. + * @return true if the array was modified, false otherwise. */ inline fun GdxArray.removeAll(pool: Pool? = null, predicate: (Type) -> Boolean): Boolean { var currentWriteIndex = 0 @@ -260,6 +261,7 @@ inline fun GdxArray.removeAll(pool: Pool? = null, predicate: /** * Removes elements from the array that do not satisfy the [predicate]. * @param pool Removed items are freed to this optional pool. + * @return true if the array was modified, false otherwise. */ inline fun GdxArray.retainAll(pool: Pool? = null, predicate: (Type) -> Boolean): Boolean { var currentWriteIndex = 0 From 908c83804e983292ab6483569286353e8dd52c1d Mon Sep 17 00:00:00 2001 From: Quillraven Date: Sun, 22 Dec 2019 15:58:34 +0100 Subject: [PATCH 07/32] #233 removed default layer and added additional standard properties - removed default layer from TiledMap extensions - added width and height standard properties for MapObject - updated README.md - changed ProjectDesc to Tiled utilities instead of TiledMap utilities - updated unit tests accordingly --- tiled/README.md | 25 +++---------------- tiled/gradle.properties | 2 +- tiled/src/main/kotlin/ktx/tiled/mapObject.kt | 14 +++++++++++ tiled/src/main/kotlin/ktx/tiled/tiledMap.kt | 14 +++-------- .../src/test/kotlin/ktx/tiled/tiledMapTest.kt | 9 +------ 5 files changed, 22 insertions(+), 42 deletions(-) diff --git a/tiled/README.md b/tiled/README.md index 1eed7530..df7f3840 100644 --- a/tiled/README.md +++ b/tiled/README.md @@ -1,6 +1,6 @@ # KTX : TiledMap utilities -TiledMap utilities for LibGDX applications written with Kotlin. +Tiled utilities for LibGDX applications written with Kotlin. ### Why? @@ -35,7 +35,7 @@ following new extensions: - `property(key, defaultValue)` - `propertyOrNull(key)` - `containsProperty(key)` -- `x`, `y`, `id` and `shape` +- `x`, `y`, `width`, `height`, `id` and `shape` #### `MapLayer` @@ -51,15 +51,10 @@ What is true for `MapObject` and `MapLayer` is of course also true for `TiledMap Standard properties for a map are `width`, `height`, `tilewidth` and `tileheight`. They are part of any map and are useful to e.g. lock the camera movement to the map boundaries. -As usual with Kotlin we are trying to avoid **null** and for that reason a new layer extension -was added to retrieve a non-null `MapLayer` which is just an empty default layer without any properties -or objects. Its name is **ktx-default-map-layer**. - Often you need to iterate over all objects of a layer to do something with them like creating collision bodies for box2d or other things. To ease that use case a new extension was added called `forEachMapObject` which takes a lambda that is executed for each -object of the given layer. Together with the `layer` extension mentioned above it becomes easy to write -such loops. +object of the given layer. To summarize it, following extensions are available: - `property(key, defaultValue)` @@ -67,7 +62,6 @@ To summarize it, following extensions are available: - `containsProperty(key)` - `width`, `height`, `tileWidth`, `tileHeight` - `totalWidth()` and `totalHeight()` -- `layer(layerName)` - `forEachMapObject(layerName, action)` ### Usage examples @@ -149,19 +143,6 @@ val tileWidth = map.tileWidth val tileHeight = map.tileHeight ``` -Retrieving a non-null layer of a `TiledMap`: - -```kotlin -import com.badlogic.gdx.maps.tiled.TiledMap -import ktx.tiled.* - - -val map: TiledMap = getTiledMap() - -// get collision layer - if it is missing an empty default layer is returned -val collisionLayer = map.layer("collision") -``` - Iterating over map objects of a specific `MapLayer` of a map: ```kotlin diff --git a/tiled/gradle.properties b/tiled/gradle.properties index 1d4431eb..6c1451c3 100644 --- a/tiled/gradle.properties +++ b/tiled/gradle.properties @@ -1,2 +1,2 @@ projectName=ktx-tiled -projectDesc=TiledMap utilities for LibGDX applications written with Kotlin. +projectDesc=Tiled utilities for LibGDX applications written with Kotlin. diff --git a/tiled/src/main/kotlin/ktx/tiled/mapObject.kt b/tiled/src/main/kotlin/ktx/tiled/mapObject.kt index 4acce8b0..84c08ef8 100644 --- a/tiled/src/main/kotlin/ktx/tiled/mapObject.kt +++ b/tiled/src/main/kotlin/ktx/tiled/mapObject.kt @@ -43,6 +43,20 @@ val MapObject.x: Float val MapObject.y: Float get() = property("y", 0f) +/** + * Extension property to retrieve the width of the [MapObject]. If the object does not have + * a width then 0 is returned + */ +val MapObject.width: Float + get() = property("width", 0f) + +/** + * Extension property to retrieve the height of the [MapObject]. If the object does not have + * a height then 0 is returned + */ +val MapObject.height: Float + get() = property("height", 0f) + /** * Extension property to retrieve the unique ID of the [MapObject]. If the object does not have an * id then -1 is returned diff --git a/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt b/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt index 4a3886ec..c0c8d8e6 100644 --- a/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt +++ b/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt @@ -5,8 +5,6 @@ import com.badlogic.gdx.maps.MapObject import com.badlogic.gdx.maps.MapProperties import com.badlogic.gdx.maps.tiled.TiledMap -private val DEFAULT_MAP_LAYER = MapLayer().apply { name = "ktx-default-map-layer" } - /** * Extension method to directly access the [MapProperties] of a [TiledMap]. The type is automatically * derived from the type of the given default value. If the property is not defined the defaultValue will be returned. @@ -81,19 +79,13 @@ fun TiledMap.totalWidth() = width * tileWidth */ fun TiledMap.totalHeight() = height * tileHeight -/** - * Extension method to retrieve a non-null [MapLayer] of the [TiledMap]. In case the layer - * cannot be found, a default empty layer is returned with name **ktx-default-map-layer**. - * @param layerName name of [MapLayer] - * @return [MapLayer] of given name or an empty default map layer, if the specified layer does not exist - */ -fun TiledMap.layer(layerName: String) = layers[layerName] ?: DEFAULT_MAP_LAYER - /** * Extension method to easily execute an action per [MapObject] of a given [MapLayer]. + * If the layer does not exist then nothing is happening. + * * @param layerName name of [MapLayer] * @param action action to execute per [MapObject] of the [MapLayer] */ fun TiledMap.forEachMapObject(layerName: String, action: (MapObject) -> Unit) { - layer(layerName).objects.forEach { action(it) } + layers[layerName]?.objects?.forEach { action(it) } } diff --git a/tiled/src/test/kotlin/ktx/tiled/tiledMapTest.kt b/tiled/src/test/kotlin/ktx/tiled/tiledMapTest.kt index cc13f5a3..251e024f 100644 --- a/tiled/src/test/kotlin/ktx/tiled/tiledMapTest.kt +++ b/tiled/src/test/kotlin/ktx/tiled/tiledMapTest.kt @@ -51,16 +51,10 @@ class TiledMapTest { assertEquals(8 * 32, tiledMap.totalHeight()) } - @Test - fun `retrieve layers by name`() { - assertEquals("layer-2", tiledMap.layer("layer-2").name) - assertEquals("ktx-default-map-layer", tiledMap.layer("layer-3").name) - } - @Test fun `execute action per object of a layer`() { // check that there are objects in layer -1 - assertEquals(3, tiledMap.layer("layer-1").objects.count) + assertEquals(3, tiledMap.layers["layer-1"].objects.count) // verify that they are all visible and set them to not visible var counter = 0 tiledMap.forEachMapObject("layer-1") { @@ -76,7 +70,6 @@ class TiledMapTest { } // also, test empty default layer which should do nothing - assertEquals(0, tiledMap.layer("non-existing").objects.count) counter = 0 tiledMap.forEachMapObject("non-existing") { ++counter } assertEquals(0, counter) From 16a5c702b9a23ed018d24c66b679324adc05e6f5 Mon Sep 17 00:00:00 2001 From: Quillraven Date: Mon, 23 Dec 2019 09:59:32 +0100 Subject: [PATCH 08/32] #233 added remaining standard properties, contains and exceptions - added three exceptions for missing property, layer and shape - added property extension that throws an exception in case the property does not exist - added rotation and type standard properties to MapObject - added backgroundcolor, orientation, hexsidelength, staggeraxis and staggerindex standard properties to TiledMap - added contains(layerName) extension to TiledMap - added layer(layerName) to TiledMap. This time throwing an exception instead of returning a default layer - fixed mapObjectTest filename typo - updated tests accordingly --- tiled/README.md | 2 +- tiled/src/main/kotlin/ktx/tiled/exception.kt | 23 +++++ tiled/src/main/kotlin/ktx/tiled/mapLayer.kt | 10 +++ tiled/src/main/kotlin/ktx/tiled/mapObject.kt | 60 +++++++++---- tiled/src/main/kotlin/ktx/tiled/tiledMap.kt | 85 ++++++++++++++++--- .../src/test/kotlin/ktx/tiled/mapLayerTest.kt | 5 ++ .../{mapObjectsTest.kt => mapObjectTest.kt} | 18 +++- .../src/test/kotlin/ktx/tiled/tiledMapTest.kt | 33 +++++++ 8 files changed, 202 insertions(+), 34 deletions(-) create mode 100644 tiled/src/main/kotlin/ktx/tiled/exception.kt rename tiled/src/test/kotlin/ktx/tiled/{mapObjectsTest.kt => mapObjectTest.kt} (78%) diff --git a/tiled/README.md b/tiled/README.md index df7f3840..4f54d8e8 100644 --- a/tiled/README.md +++ b/tiled/README.md @@ -93,7 +93,7 @@ val myOtherProp: Int? = map.propertyOrNull("myOtherProperty") // check if a certain property exists if (map.containsProperty("lightColor")) { - // change box2d light ambient color + changeAmbientLightColor(map.property("lightColor")) } ``` diff --git a/tiled/src/main/kotlin/ktx/tiled/exception.kt b/tiled/src/main/kotlin/ktx/tiled/exception.kt new file mode 100644 index 00000000..75ccc7fa --- /dev/null +++ b/tiled/src/main/kotlin/ktx/tiled/exception.kt @@ -0,0 +1,23 @@ +package ktx.tiled + +import com.badlogic.gdx.maps.MapLayer +import com.badlogic.gdx.maps.MapObject +import com.badlogic.gdx.maps.MapProperties +import com.badlogic.gdx.maps.objects.TextureMapObject +import com.badlogic.gdx.maps.tiled.TiledMap +import com.badlogic.gdx.utils.GdxRuntimeException + +/** + * [GdxRuntimeException] that is thrown when trying to access a non-existing property of a [MapProperties] instance + */ +class MissingPropertyException(message: String) : GdxRuntimeException(message) + +/** + * [GdxRuntimeException] that is thrown when trying to access a non-existing [MapLayer] of a [TiledMap] instance + */ +class MissingLayerException(message: String) : GdxRuntimeException(message) + +/** + * [GdxRuntimeException] that is thrown when trying to access a shape of a [MapObject] that do not have any shape like [TextureMapObject] + */ +class MissingShapeException(message: String) : GdxRuntimeException(message) \ No newline at end of file diff --git a/tiled/src/main/kotlin/ktx/tiled/mapLayer.kt b/tiled/src/main/kotlin/ktx/tiled/mapLayer.kt index 8c9d61d3..9f0d8493 100644 --- a/tiled/src/main/kotlin/ktx/tiled/mapLayer.kt +++ b/tiled/src/main/kotlin/ktx/tiled/mapLayer.kt @@ -3,6 +3,16 @@ package ktx.tiled import com.badlogic.gdx.maps.MapLayer import com.badlogic.gdx.maps.MapProperties +/** + * Extension method to directly access the [MapProperties] of a [MapLayer]. If the property + * is not defined then this method throws a [MissingPropertyException]. + * @param key property name + * @return value of the property + * @throws MissingPropertyException If the property is not defined + */ +inline fun MapLayer.property(key: String): T = this.properties[key, T::class.java] + ?: throw MissingPropertyException("Property $key does not exist for layer ${this.name}") + /** * Extension method to directly access the [MapProperties] of a [MapLayer]. The type is automatically * derived from the type of the given default value. If the property is not defined the defaultValue will be returned. diff --git a/tiled/src/main/kotlin/ktx/tiled/mapObject.kt b/tiled/src/main/kotlin/ktx/tiled/mapObject.kt index 84c08ef8..a7f23e55 100644 --- a/tiled/src/main/kotlin/ktx/tiled/mapObject.kt +++ b/tiled/src/main/kotlin/ktx/tiled/mapObject.kt @@ -5,6 +5,17 @@ import com.badlogic.gdx.maps.MapProperties import com.badlogic.gdx.maps.objects.* import com.badlogic.gdx.math.* + +/** + * Extension method to directly access the [MapProperties] of a [MapObject]. If the property + * is not defined then this method throws a [MissingPropertyException]. + * @param key property name + * @return value of the property + * @throws MissingPropertyException If the property is not defined + */ +inline fun MapObject.property(key: String): T = this.properties[key, T::class.java] + ?: throw MissingPropertyException("Property $key does not exist for object ${this.name}") + /** * Extension method to directly access the [MapProperties] of a [MapObject]. The type is automatically * derived from the type of the given default value. If the property is not defined the defaultValue will be returned. @@ -30,39 +41,53 @@ inline fun MapObject.propertyOrNull(key: String): T? = this.properti fun MapObject.containsProperty(key: String) = properties.containsKey(key) /** - * Extension property to retrieve the x-coordinate of the [MapObject]. If the object does not have - * a x-coordinate then 0 is returned + * Extension property to retrieve the x-coordinate of the [MapObject] + * @throws MissingPropertyException if property x does not exist */ val MapObject.x: Float - get() = property("x", 0f) + get() = property("x") /** - * Extension property to retrieve the y-coordinate of the [MapObject]. If the object does not have - * a y-coordinate then 0 is returned + * Extension property to retrieve the y-coordinate of the [MapObject] + * @throws MissingPropertyException if property y does not exist */ val MapObject.y: Float - get() = property("y", 0f) + get() = property("y") /** - * Extension property to retrieve the width of the [MapObject]. If the object does not have - * a width then 0 is returned + * Extension property to retrieve the width of the [MapObject] + * @throws MissingPropertyException if property width does not exist */ val MapObject.width: Float - get() = property("width", 0f) + get() = property("width") /** - * Extension property to retrieve the height of the [MapObject]. If the object does not have - * a height then 0 is returned + * Extension property to retrieve the height of the [MapObject] + * @throws MissingPropertyException if property height does not exist */ val MapObject.height: Float - get() = property("height", 0f) + get() = property("height") /** - * Extension property to retrieve the unique ID of the [MapObject]. If the object does not have an - * id then -1 is returned + * Extension property to retrieve the unique ID of the [MapObject] + * @throws MissingPropertyException if property id does not exist */ val MapObject.id: Int - get() = property("id", -1) + get() = property("id") + +/** + * Extension property to retrieve the rotation of the [MapObject] + * @throws MissingPropertyException if property rotation does not exist + */ +val MapObject.rotation: Float + get() = property("rotation") + +/** + * Extension property to retrieve the type of the [MapObject] + * @throws MissingPropertyException if property type does not exist + */ +val MapObject.type: String + get() = property("type") /** * Extension method to retrieve the [Shape2D] instance of a [MapObject]. @@ -74,7 +99,8 @@ val MapObject.id: Int * - [PolygonMapObject] -> [Polygon] * - [RectangleMapObject] -> [Rectangle] * - * Note that [TextureMapObject] is not supported by this method as it has no related shape. + * Note that objects that do not have any shape like [TextureMapObject] will throw a [MissingShapeException] + * @throws MissingShapeException If the object does not have any shape */ val MapObject.shape: Shape2D get() = when (this) { @@ -83,5 +109,5 @@ val MapObject.shape: Shape2D is PolylineMapObject -> this.polyline is PolygonMapObject -> this.polygon is RectangleMapObject -> this.rectangle - else -> throw UnsupportedOperationException("Shape extension function is not supported for MapObject of type ${this::class.java}") + else -> throw MissingShapeException("MapObject of type ${this::class.java} does not have a shape") } diff --git a/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt b/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt index c0c8d8e6..6973201c 100644 --- a/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt +++ b/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt @@ -5,6 +5,16 @@ import com.badlogic.gdx.maps.MapObject import com.badlogic.gdx.maps.MapProperties import com.badlogic.gdx.maps.tiled.TiledMap +/** + * Extension method to directly access the [MapProperties] of a [TiledMap]. If the property + * is not defined then this method throws a [MissingPropertyException]. + * @param key property name + * @return value of the property + * @throws MissingPropertyException If the property is not defined + */ +inline fun TiledMap.property(key: String): T = this.properties[key, T::class.java] + ?: throw MissingPropertyException("Property $key does not exist for map") + /** * Extension method to directly access the [MapProperties] of a [TiledMap]. The type is automatically * derived from the type of the given default value. If the property is not defined the defaultValue will be returned. @@ -30,32 +40,67 @@ inline fun TiledMap.propertyOrNull(key: String): T? = this.propertie fun TiledMap.containsProperty(key: String) = properties.containsKey(key) /** - * Extension property to retrieve the width of the [TiledMap]. If the map does not have - * a **width** property then 0 is returned + * Extension property to retrieve the width of the [TiledMap] + * @throws MissingPropertyException if property width does not exist */ val TiledMap.width: Int - get() = property("width", 0) + get() = property("width") /** - * Extension property to retrieve the height of the [TiledMap]. If the map does not have - * a **height** property then 0 is returned + * Extension property to retrieve the height of the [TiledMap] + * @throws MissingPropertyException if property height does not exist */ val TiledMap.height: Int - get() = property("height", 0) + get() = property("height") /** - * Extension property to retrieve the tile width of each tile of the [TiledMap]. If the map does not have - * a **tilewidth** property then 0 is returned + * Extension property to retrieve the tile width of each tile of the [TiledMap] + * @throws MissingPropertyException if property tilewidth does not exist */ val TiledMap.tileWidth: Int - get() = property("tilewidth", 0) + get() = property("tilewidth") /** - * Extension property to retrieve the tile height of each tile of the [TiledMap]. If the map does not have - * a **tileheight** property then 0 is returned + * Extension property to retrieve the tile height of each tile of the [TiledMap] + * @throws MissingPropertyException if property tileheight does not exist */ val TiledMap.tileHeight: Int - get() = property("tileheight", 0) + get() = property("tileheight") + +/** + * Extension property to retrieve the background color of the [TiledMap] + * @throws MissingPropertyException if property backgroundcolor does not exist + */ +val TiledMap.backgroundColor: String + get() = property("backgroundcolor") + +/** + * Extension property to retrieve the orientation of the [TiledMap] + * @throws MissingPropertyException if property orientation does not exist + */ +val TiledMap.orientation: String + get() = property("orientation") + +/** + * Extension property to retrieve the hex side length of a hexagonal [TiledMap] + * @throws MissingPropertyException if property hexsidelength does not exist + */ +val TiledMap.hexSideLength: Int + get() = property("hexsidelength") + +/** + * Extension property to retrieve the stagger axis of the [TiledMap] + * @throws MissingPropertyException if property staggeraxis does not exist + */ +val TiledMap.staggerAxis: String + get() = property("staggeraxis") + +/** + * Extension property to retrieve the stagger index of the [TiledMap] + * @throws MissingPropertyException if property staggerindex does not exist + */ +val TiledMap.staggerIndex: String + get() = property("staggerindex") /** * Extension method to retrieve the total width of the [TiledMap]. It is the result of the @@ -79,6 +124,22 @@ fun TiledMap.totalWidth() = width * tileWidth */ fun TiledMap.totalHeight() = height * tileHeight +/** + * Extension operator to check if a certain [MapLayer] is part of the [TiledMap] + * @param layerName name of [MapLayer] + * @return true if and only if the layer does exist + */ +operator fun TiledMap.contains(layerName: String) = layers[layerName] != null + +/** + * Extension method to retrieve a [MapLayer] of a [TiledMap]. If the layer does + * not exist then this method is throwing a [MissingLayerException] + * @param layerName name of [MapLayer] + * @throws MissingLayerException If the layer does not exist + */ +fun TiledMap.layer(layerName: String) = layers[layerName] + ?: throw MissingLayerException("Layer $layerName does not exist for map") + /** * Extension method to easily execute an action per [MapObject] of a given [MapLayer]. * If the layer does not exist then nothing is happening. diff --git a/tiled/src/test/kotlin/ktx/tiled/mapLayerTest.kt b/tiled/src/test/kotlin/ktx/tiled/mapLayerTest.kt index 861880e9..34d9db25 100644 --- a/tiled/src/test/kotlin/ktx/tiled/mapLayerTest.kt +++ b/tiled/src/test/kotlin/ktx/tiled/mapLayerTest.kt @@ -30,4 +30,9 @@ class MapLayerTest { Assert.assertTrue(mapLayer.containsProperty("active")) Assert.assertFalse(mapLayer.containsProperty("x")) } + + @Test(expected = MissingPropertyException::class) + fun `retrieve non-existing property from MapLayer using exception`() { + mapLayer.property("non-existing") + } } diff --git a/tiled/src/test/kotlin/ktx/tiled/mapObjectsTest.kt b/tiled/src/test/kotlin/ktx/tiled/mapObjectTest.kt similarity index 78% rename from tiled/src/test/kotlin/ktx/tiled/mapObjectsTest.kt rename to tiled/src/test/kotlin/ktx/tiled/mapObjectTest.kt index 436c18e9..30510111 100644 --- a/tiled/src/test/kotlin/ktx/tiled/mapObjectsTest.kt +++ b/tiled/src/test/kotlin/ktx/tiled/mapObjectTest.kt @@ -13,6 +13,9 @@ class MapObjectTest { properties.also { it.put("id", 13) it.put("x", 1) + it.put("y", 0f) + it.put("rotation", -2.33f) + it.put("type", "SomeType") it.put("width", 1f) it.put("name", "Property") it.put("active", true) @@ -32,7 +35,7 @@ class MapObjectTest { @Test fun `retrieve properties from MapObject with default value`() { assertEquals(1, mapObject.property("x", 0)) - assertEquals(0, mapObject.property("y", 0)) + assertEquals(0, mapObject.property("non-existing", 0)) assertEquals(1f, mapObject.property("width", 0f)) assertEquals("Property", mapObject.property("name", "")) assertEquals(true, mapObject.property("active", false)) @@ -40,7 +43,7 @@ class MapObjectTest { @Test fun `retrieve properties from MapObject without default value`() { - assertNull(mapObject.propertyOrNull("y")) + assertNull(mapObject.propertyOrNull("non-existing")) val x: Int? = mapObject.propertyOrNull("x") assertNotNull(x) assertEquals(1, x) @@ -49,7 +52,7 @@ class MapObjectTest { @Test fun `check if property from MapObject exists`() { assertTrue(mapObject.containsProperty("x")) - assertFalse(mapObject.containsProperty("y")) + assertFalse(mapObject.containsProperty("non-existing")) } @Test @@ -57,6 +60,8 @@ class MapObjectTest { assertEquals(1f, mapObject.x) assertEquals(0f, mapObject.y) assertEquals(13, mapObject.id) + assertEquals(-2.33f, mapObject.rotation) + assertEquals("SomeType", mapObject.type) } @Test @@ -68,8 +73,13 @@ class MapObjectTest { assertEquals(Rectangle(0f, 0f, 1f, 1f), rectObject.shape) } - @Test(expected = UnsupportedOperationException::class) + @Test(expected = MissingShapeException::class) fun `retrieve shape from unsupported MapObject`() { textureObject.shape } + + @Test(expected = MissingPropertyException::class) + fun `retrieve non-existing property from MapObject using exception`() { + mapObject.property("non-existing") + } } diff --git a/tiled/src/test/kotlin/ktx/tiled/tiledMapTest.kt b/tiled/src/test/kotlin/ktx/tiled/tiledMapTest.kt index 251e024f..9f77571c 100644 --- a/tiled/src/test/kotlin/ktx/tiled/tiledMapTest.kt +++ b/tiled/src/test/kotlin/ktx/tiled/tiledMapTest.kt @@ -12,6 +12,13 @@ class TiledMapTest { properties.put("height", 8) properties.put("tilewidth", 32) properties.put("tileheight", 32) + + properties.put("backgroundcolor", "#ffffff") + properties.put("orientation", "orthogonal") + properties.put("hexsidelength", 0) + properties.put("staggeraxis", "Y") + properties.put("staggerindex", "Odd") + layers.add(MapLayer().apply { name = "layer-1" objects.add(MapObject()) @@ -49,6 +56,32 @@ class TiledMapTest { assertEquals(32, tiledMap.tileHeight) assertEquals(16 * 32, tiledMap.totalWidth()) assertEquals(8 * 32, tiledMap.totalHeight()) + assertEquals("#ffffff", tiledMap.backgroundColor) + assertEquals("orthogonal", tiledMap.orientation) + assertEquals(0, tiledMap.hexSideLength) + assertEquals("Y", tiledMap.staggerAxis) + assertEquals("Odd", tiledMap.staggerIndex) + } + + @Test(expected = MissingPropertyException::class) + fun `retrieve non-existing property from TiledMap using exception`() { + tiledMap.property("non-existing") + } + + @Test + fun `retrieve existing layer from TiledMap`() { + assertEquals("layer-1", tiledMap.layer("layer-1").name) + } + + @Test(expected = MissingLayerException::class) + fun `retrieve non-existing layer from TiledMap using exception`() { + tiledMap.layer("non-existing") + } + + @Test + fun `check if layer exists in TiledMap`() { + assertTrue(tiledMap.contains("layer-1")) + assertFalse("non-existing" in tiledMap) } @Test From 5217f7c7ef2794524203f97989484900e002a8ea Mon Sep 17 00:00:00 2001 From: Quillraven Date: Mon, 23 Dec 2019 15:04:23 +0100 Subject: [PATCH 09/32] #233 added Tile and TileSet property extensions and updated ReadMe - added property extensions to TiledMapTile - added property extensions to TiledMapTileSet - updated README.md - added tests for tile and tileset --- tiled/README.md | 119 ++++++++++++------ tiled/src/main/kotlin/ktx/tiled/exception.kt | 2 +- .../src/main/kotlin/ktx/tiled/tiledMapTile.kt | 39 ++++++ .../main/kotlin/ktx/tiled/tiledMapTileSet.kt | 38 ++++++ .../kotlin/ktx/tiled/tiledMapTileSetTest.kt | 38 ++++++ .../test/kotlin/ktx/tiled/tiledMapTileTest.kt | 47 +++++++ 6 files changed, 246 insertions(+), 37 deletions(-) create mode 100644 tiled/src/main/kotlin/ktx/tiled/tiledMapTile.kt create mode 100644 tiled/src/main/kotlin/ktx/tiled/tiledMapTileSet.kt create mode 100644 tiled/src/test/kotlin/ktx/tiled/tiledMapTileSetTest.kt create mode 100644 tiled/src/test/kotlin/ktx/tiled/tiledMapTileTest.kt diff --git a/tiled/README.md b/tiled/README.md index 4f54d8e8..afd1f1fc 100644 --- a/tiled/README.md +++ b/tiled/README.md @@ -10,65 +10,85 @@ With the TiledMap functionality it becomes worse since there are a lot of wrappe and iterators and accessing properties or iterating over objects in the map becomes very cumbersome. Luckily for us with Kotlin we can write extension functions and properties to make our life -a little bit easier and most notably make our code more readable. +a little bit easier and most notably make our code more readable and safe. -From a code point of view it makes it easier to read as most of the time we can skip the -wrapper class and directly access things like e.g. we can directly get the `layer` from a `TiledMap` -without mentioning the `layers` collection wrapper. - -Also, due to Kotlin's `reified` possibility we can retrieve properties without passing the `Class` -parameter and therefore also have this information during runtime. +Due to Kotlin's `reified` possibility we can retrieve properties without passing the `Class` +parameter and therefore also have this information during runtime. Also, most of the wrapper classes return `platform types (Any!)` +as the original Java code is null-unsafe. One of the goals of these extensions is also +to improve that situation and make it explicit so that a method either returns `null` or +throws an `exception`. ### Guide #### Miscellaneous utilities -#### `MapObject` +#### `MapProperties` -In most maps that you create with Tiled you will create objects in it. There are a few -things that are common for any map object like `id`, `shape`, `x-` and `y` coordinates. -Properties in libgdx are stored in a wrapper class called `MapProperties` which is nothing less -than a `ObjectMap`. +In most maps that you create with Tiled you will need access to the properties defined in the editor. +They are either defined on map, layer, object, tileset or tile level. The original libgdx +`MapProperties` class returns `Any!` whenever retrieving a property and is therefore not ideal and unsafe. -For easier access of those properties, including the standard properties mentioned above, you can use -following new extensions: -- `property(key, defaultValue)` -- `propertyOrNull(key)` -- `containsProperty(key)` -- `x`, `y`, `width`, `height`, `id` and `shape` +For that reason four extension methods got added to `TiledMap`, `MapLayer`, `MapObject`, `TiledMapTileSet` +and `TiledMapTile`. Also, a new `MissingPropertyException` was added for one of these extensions which allows us +to get informed when trying to access a missing mandatory property: +- `property(key)`: returns an existing property or throws a `MissingPropertyException` +- `property(key, defaultValue`: returns the value of a property or the default value if the property is missing +- `propertyOrNull(key)`: same as `property(key)` but returns `null` instead of throwing an exception +- `containsProperty(key)`: returns `true` if and only if the property exists -#### `MapLayer` +#### `MapObject` -The same thing regarding properties holds true for `MapLayer`. Therefore, following three extensions -will help you out: -- `property(key, defaultValue)` -- `propertyOrNull(key)` -- `containsProperty(key)` +In addition to the property extensions, `MapObject` automatically comes with a set of standard properties. +They get automatically set and initialized by the `TmxMapLoader`. Extension properties got added +to ease the access: +- `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 property got 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` -What is true for `MapObject` and `MapLayer` is of course also true for `TiledMap`. -Standard properties for a map are `width`, `height`, `tilewidth` and `tileheight`. They -are part of any map and are useful to e.g. lock the camera movement to the map boundaries. +Similar to `MapObject` there are several standard properties for `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 extensions will provide you with the total width and height of your map in pixels: +- `totalWidth()` +- `totalHeight()` + +The problems that we face with properties is also true for map layers. To improve the +situation here as well following extensions got added: +- `contains(layerName)` +- `layer(layerName)`: returns the layer or throws a `MissingLayerException` in case the layer does not exist Often you need to iterate over all objects of a layer to do something with them like creating collision bodies for box2d or other things. To ease that use case a new extension was added called `forEachMapObject` which takes a lambda that is executed for each -object of the given layer. - -To summarize it, following extensions are available: -- `property(key, defaultValue)` -- `propertyOrNull(key)` -- `containsProperty(key)` -- `width`, `height`, `tileWidth`, `tileHeight` -- `totalWidth()` and `totalHeight()` +object of the given layer: - `forEachMapObject(layerName, action)` ### Usage examples #### General -Properties functionality is the same for `MapObject`, `MapLayer` and `TiledMap`: +Properties functionality is the same for `TiledMap`, `MapLayer`, `MapObject`, `TiledMapTileSet` and `TiledMapTile`: ```kotlin import com.badlogic.gdx.maps.MapLayer @@ -81,6 +101,9 @@ val mapObj: MapObject = getMapObject() val mapLayer: MapLayer = getMapLayer() val map: TiledMap = getMap() +// retrieve Float property - throws MissingPropertyException if missing +val myFloatProp: Float = mapObj.property("myFloatProperty") + // retrieve String property via defaultValue val myProp: String = mapObj.property("myProperty", "") @@ -143,6 +166,29 @@ val tileWidth = map.tileWidth val tileHeight = map.tileHeight ``` +Check and retrieve a layer of a `TiledMap`: + +```kotlin +import com.badlogic.gdx.maps.tiled.TiledMap +import ktx.tiled.* + + +val map: TiledMap = getTiledMap() + +// contains can either be used with the normal syntax +if(map.contains("enemyLayer")) { + val enemyLayer = map.layer("enemyLayer") +} + +// or with the "in" syntax +if("collision" in map) { + val collisionLayer = map.layer("collision") +} + +// the next line 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: ```kotlin @@ -161,3 +207,4 @@ map.forEachMapObject("collision") { mapObj -> #### Additional documentation - [LibGDX Tile maps official wiki.](https://github.com/libgdx/libgdx/wiki/Tile-maps) +- [Tiled editor official documentation](https://doc.mapeditor.org/en/stable/) diff --git a/tiled/src/main/kotlin/ktx/tiled/exception.kt b/tiled/src/main/kotlin/ktx/tiled/exception.kt index 75ccc7fa..5b24a49f 100644 --- a/tiled/src/main/kotlin/ktx/tiled/exception.kt +++ b/tiled/src/main/kotlin/ktx/tiled/exception.kt @@ -20,4 +20,4 @@ class MissingLayerException(message: String) : GdxRuntimeException(message) /** * [GdxRuntimeException] that is thrown when trying to access a shape of a [MapObject] that do not have any shape like [TextureMapObject] */ -class MissingShapeException(message: String) : GdxRuntimeException(message) \ No newline at end of file +class MissingShapeException(message: String) : GdxRuntimeException(message) diff --git a/tiled/src/main/kotlin/ktx/tiled/tiledMapTile.kt b/tiled/src/main/kotlin/ktx/tiled/tiledMapTile.kt new file mode 100644 index 00000000..afe6dede --- /dev/null +++ b/tiled/src/main/kotlin/ktx/tiled/tiledMapTile.kt @@ -0,0 +1,39 @@ +package ktx.tiled + +import com.badlogic.gdx.maps.MapProperties +import com.badlogic.gdx.maps.tiled.TiledMapTile + + +/** + * Extension method to directly access the [MapProperties] of a [TiledMapTile]. If the property + * is not defined then this method throws a [MissingPropertyException]. + * @param key property name + * @return value of the property + * @throws MissingPropertyException If the property is not defined + */ +inline fun TiledMapTile.property(key: String): T = this.properties[key, T::class.java] + ?: throw MissingPropertyException("Property $key does not exist for tile ${this.id}") + +/** + * Extension method to directly access the [MapProperties] of a [TiledMapTile]. The type is automatically + * derived from the type of the given default value. If the property is not defined the defaultValue will be returned. + * @param key property name + * @param defaultValue default value in case the property is missing + * @return value of the property or defaultValue if property is missing + */ +inline fun TiledMapTile.property(key: String, defaultValue: T): T = this.properties[key, defaultValue, T::class.java] + +/** + * Extension method to directly access the [MapProperties] of a [TiledMapTile]. If the property + * is not defined then this method returns null. + * @param key property name + * @return value of the property or null if the property is missing + */ +inline fun TiledMapTile.propertyOrNull(key: String): T? = this.properties[key, T::class.java] + +/** + * Extension method to directly access the [MapProperties] of a [TiledMapTile] and its [containsKey][MapProperties.containsKey] method + * @param key property name + * @return true if the property exists. Otherwise false + */ +fun TiledMapTile.containsProperty(key: String) = properties.containsKey(key) diff --git a/tiled/src/main/kotlin/ktx/tiled/tiledMapTileSet.kt b/tiled/src/main/kotlin/ktx/tiled/tiledMapTileSet.kt new file mode 100644 index 00000000..41860126 --- /dev/null +++ b/tiled/src/main/kotlin/ktx/tiled/tiledMapTileSet.kt @@ -0,0 +1,38 @@ +package ktx.tiled + +import com.badlogic.gdx.maps.MapProperties +import com.badlogic.gdx.maps.tiled.TiledMapTileSet + +/** + * Extension method to directly access the [MapProperties] of a [TiledMapTileSet]. If the property + * is not defined then this method throws a [MissingPropertyException]. + * @param key property name + * @return value of the property + * @throws MissingPropertyException If the property is not defined + */ +inline fun TiledMapTileSet.property(key: String): T = this.properties[key, T::class.java] + ?: throw MissingPropertyException("Property $key does not exist for tileset ${this.name}") + +/** + * Extension method to directly access the [MapProperties] of a [TiledMapTileSet]. The type is automatically + * derived from the type of the given default value. If the property is not defined the defaultValue will be returned. + * @param key property name + * @param defaultValue default value in case the property is missing + * @return value of the property or defaultValue if property is missing + */ +inline fun TiledMapTileSet.property(key: String, defaultValue: T): T = this.properties[key, defaultValue, T::class.java] + +/** + * Extension method to directly access the [MapProperties] of a [TiledMapTileSet]. If the property + * is not defined then this method returns null. + * @param key property name + * @return value of the property or null if the property is missing + */ +inline fun TiledMapTileSet.propertyOrNull(key: String): T? = this.properties[key, T::class.java] + +/** + * Extension method to directly access the [MapProperties] of a [TiledMapTileSet] and its [containsKey][MapProperties.containsKey] method + * @param key property name + * @return true if the property exists. Otherwise false + */ +fun TiledMapTileSet.containsProperty(key: String) = properties.containsKey(key) diff --git a/tiled/src/test/kotlin/ktx/tiled/tiledMapTileSetTest.kt b/tiled/src/test/kotlin/ktx/tiled/tiledMapTileSetTest.kt new file mode 100644 index 00000000..19c8fb54 --- /dev/null +++ b/tiled/src/test/kotlin/ktx/tiled/tiledMapTileSetTest.kt @@ -0,0 +1,38 @@ +package ktx.tiled + +import com.badlogic.gdx.maps.tiled.TiledMapTileSet +import org.junit.Assert +import org.junit.Test + +class TiledMapTileSetTest { + private val tileset = TiledMapTileSet().apply { + properties.put("tilesetProp1", true) + properties.put("tilesetProp2", 123) + } + + @Test + fun `retrieve properties from TileSet with default value`() { + Assert.assertEquals(true, tileset.property("tilesetProp1", false)) + Assert.assertEquals(123, tileset.property("tilesetProp2", 0)) + Assert.assertEquals(-1f, tileset.property("non-existing", -1f)) + } + + @Test + fun `retrieve properties from TileSet without default value`() { + Assert.assertNull(tileset.propertyOrNull("non-existing")) + val customProperty: Int? = tileset.propertyOrNull("tilesetProp2") + Assert.assertNotNull(customProperty) + Assert.assertEquals(123, customProperty) + } + + @Test + fun `check if property from TileSet exists`() { + Assert.assertTrue(tileset.containsProperty("tilesetProp1")) + Assert.assertFalse(tileset.containsProperty("non-existing")) + } + + @Test(expected = MissingPropertyException::class) + fun `retrieve non-existing property from TileSet using exception`() { + tileset.property("non-existing") + } +} diff --git a/tiled/src/test/kotlin/ktx/tiled/tiledMapTileTest.kt b/tiled/src/test/kotlin/ktx/tiled/tiledMapTileTest.kt new file mode 100644 index 00000000..b0c5a27c --- /dev/null +++ b/tiled/src/test/kotlin/ktx/tiled/tiledMapTileTest.kt @@ -0,0 +1,47 @@ +package ktx.tiled + +import com.badlogic.gdx.graphics.g2d.TextureRegion +import com.badlogic.gdx.maps.tiled.tiles.AnimatedTiledMapTile +import com.badlogic.gdx.maps.tiled.tiles.StaticTiledMapTile +import com.badlogic.gdx.utils.Array +import org.junit.Assert +import org.junit.Test + +class TiledMapTileTest { + private val staticTile = StaticTiledMapTile(TextureRegion()).apply { + properties.put("staticProp1", true) + properties.put("staticProp2", 123) + } + private val animatedTile = AnimatedTiledMapTile(0f, Array()).apply { + properties.put("aniProp1", 1f) + properties.put("aniProp2", "SomeText") + } + + @Test + fun `retrieve properties from Tile with default value`() { + Assert.assertEquals(true, staticTile.property("staticProp1", false)) + Assert.assertEquals(123, staticTile.property("staticProp2", 0)) + Assert.assertEquals(-1f, animatedTile.property("non-existing", -1f)) + } + + @Test + fun `retrieve properties from Tile without default value`() { + Assert.assertNull(animatedTile.propertyOrNull("non-existing")) + val customProperty: Int? = staticTile.propertyOrNull("staticProp2") + Assert.assertNotNull(customProperty) + Assert.assertEquals(123, customProperty) + } + + @Test + fun `check if property from Tile exists`() { + Assert.assertTrue(staticTile.containsProperty("staticProp1")) + Assert.assertFalse(staticTile.containsProperty("non-existing")) + Assert.assertTrue(animatedTile.containsProperty("aniProp2")) + Assert.assertFalse(animatedTile.containsProperty("non-existing")) + } + + @Test(expected = MissingPropertyException::class) + fun `retrieve non-existing property from Tile using exception`() { + animatedTile.property("non-existing") + } +} From 47e553afb5fd5d4d2878d0cb0978f0afac670d7f Mon Sep 17 00:00:00 2001 From: MJ Date: Mon, 23 Dec 2019 19:04:43 +0100 Subject: [PATCH 10/32] Removed freetype-async from Gradle settings. #182 --- settings.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/settings.gradle b/settings.gradle index 18081e60..bf79c012 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,7 +9,6 @@ include( 'json', 'graphics', 'freetype', -// 'freetype-async', 'i18n', 'inject', 'log', @@ -18,5 +17,5 @@ include( 'style', 'tiled', 'vis', - 'vis-style' + 'vis-style', ) From 5350328b8c917a611e4e4e8bbb39bd63b3a6318d Mon Sep 17 00:00:00 2001 From: MJ Date: Mon, 23 Dec 2019 19:05:33 +0100 Subject: [PATCH 11/32] Updated ktx-tiled contributor. #233 --- .github/CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CONTRIBUTORS.md b/.github/CONTRIBUTORS.md index 5641e261..7634ab87 100644 --- a/.github/CONTRIBUTORS.md +++ b/.github/CONTRIBUTORS.md @@ -42,6 +42,7 @@ Project contributors listed chronologically. * Prepared several KTX template projects and showcases. * Improved [app](../app) utilities. * [@Quillraven](https://github.com/Quillraven) + * Author of the [Tiled](../tiled) module. * Wrote a complete [KTX tutorial](https://github.com/Quillraven/SimpleKtxGame/wiki) based on the original LibGDX introduction. * [@FocusPo1nt](https://github.com/FocusPo1nt) * Added utilities to [style module](../style). From 500da529a3476c8c691339dd161917fbc488502f Mon Sep 17 00:00:00 2001 From: MJ Date: Mon, 23 Dec 2019 19:09:14 +0100 Subject: [PATCH 12/32] Added ktx-tiled to main README. #233 --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5e4e6ea9..9c1a7934 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ Module | Dependency name | Description [math](math) | `ktx-math` | Operator functions for LibGDX math API and general math utilities. [scene2d](scene2d) | `ktx-scene2d` | Type-safe Kotlin builders for [`Scene2D`](https://github.com/libgdx/libgdx/wiki/Scene2d) GUI. [style](style) | `ktx-style` | Type-safe Kotlin builders for `Scene2D` widget styles extending `Skin` API. +[tiled](tiled) | `ktx-tiled` | Utilities for [Tiled](https://www.mapeditor.org/) maps. [vis](vis) | `ktx-vis` | Type-safe Kotlin builders for [`VisUI`](https://github.com/kotcrab/vis-ui/). An _alternative_ to the [scene2d](scene2d) module. [vis-style](vis-style) | `ktx-vis-style` | Type-safe Kotlin builders for `VisUI` widget styles. An _extension_ of [style](style) module. @@ -79,9 +80,9 @@ dependencies { } ``` -Note that defining `ktxVersion` is not necessary, as versions can be defined directly in the `dependencies` section. -However, extracting the dependencies versions is a good practice, especially if they can be reused throughout the -build files. This will speed up updating of your project if you include multiple KTX modules. +Note that defining `ktxVersion` as a property is not necessary, as versions can be set directly in the `dependencies` +section. However, extracting the dependencies versions is a good practice, especially if they can be reused throughout +the build files. This will speed up updating of your project if you include multiple KTX modules. **KTX** modules should generally be added to the dependencies of the shared `core` module of your LibGDX application. @@ -147,7 +148,7 @@ directly. ### Links -[KTX wiki](https://github.com/libktx/ktx/wiki) lists some useful resources that can help you get started. +[**KTX** wiki](https://github.com/libktx/ktx/wiki) lists some useful resources that can help you get started. Note that most official guides and examples in this repository assume that the reader is at least a bit familiar with the LibGDX API. If you are just getting to know the framework, it might be helpful to go through From 9598007de4917974cc3351c4de8377b1b647ed0b Mon Sep 17 00:00:00 2001 From: MJ Date: Mon, 23 Dec 2019 19:11:37 +0100 Subject: [PATCH 13/32] Code formatting. #233 --- tiled/src/main/kotlin/ktx/tiled/mapLayer.kt | 2 +- tiled/src/main/kotlin/ktx/tiled/mapObject.kt | 33 ++-- tiled/src/main/kotlin/ktx/tiled/tiledMap.kt | 24 +-- .../src/main/kotlin/ktx/tiled/tiledMapTile.kt | 3 +- .../main/kotlin/ktx/tiled/tiledMapTileSet.kt | 2 +- .../src/test/kotlin/ktx/tiled/mapLayerTest.kt | 52 +++--- .../test/kotlin/ktx/tiled/mapObjectTest.kt | 126 ++++++------- .../src/test/kotlin/ktx/tiled/tiledMapTest.kt | 176 +++++++++--------- .../kotlin/ktx/tiled/tiledMapTileSetTest.kt | 52 +++--- .../test/kotlin/ktx/tiled/tiledMapTileTest.kt | 64 +++---- 10 files changed, 266 insertions(+), 268 deletions(-) diff --git a/tiled/src/main/kotlin/ktx/tiled/mapLayer.kt b/tiled/src/main/kotlin/ktx/tiled/mapLayer.kt index 9f0d8493..c1bbf6bb 100644 --- a/tiled/src/main/kotlin/ktx/tiled/mapLayer.kt +++ b/tiled/src/main/kotlin/ktx/tiled/mapLayer.kt @@ -11,7 +11,7 @@ import com.badlogic.gdx.maps.MapProperties * @throws MissingPropertyException If the property is not defined */ inline fun MapLayer.property(key: String): T = this.properties[key, T::class.java] - ?: throw MissingPropertyException("Property $key does not exist for layer ${this.name}") + ?: throw MissingPropertyException("Property $key does not exist for layer ${this.name}") /** * Extension method to directly access the [MapProperties] of a [MapLayer]. The type is automatically diff --git a/tiled/src/main/kotlin/ktx/tiled/mapObject.kt b/tiled/src/main/kotlin/ktx/tiled/mapObject.kt index a7f23e55..22629136 100644 --- a/tiled/src/main/kotlin/ktx/tiled/mapObject.kt +++ b/tiled/src/main/kotlin/ktx/tiled/mapObject.kt @@ -5,7 +5,6 @@ import com.badlogic.gdx.maps.MapProperties import com.badlogic.gdx.maps.objects.* import com.badlogic.gdx.math.* - /** * Extension method to directly access the [MapProperties] of a [MapObject]. If the property * is not defined then this method throws a [MissingPropertyException]. @@ -14,7 +13,7 @@ import com.badlogic.gdx.math.* * @throws MissingPropertyException If the property is not defined */ inline fun MapObject.property(key: String): T = this.properties[key, T::class.java] - ?: throw MissingPropertyException("Property $key does not exist for object ${this.name}") + ?: throw MissingPropertyException("Property $key does not exist for object ${this.name}") /** * Extension method to directly access the [MapProperties] of a [MapObject]. The type is automatically @@ -45,49 +44,49 @@ fun MapObject.containsProperty(key: String) = properties.containsKey(key) * @throws MissingPropertyException if property x does not exist */ val MapObject.x: Float - get() = property("x") + get() = property("x") /** * Extension property to retrieve the y-coordinate of the [MapObject] * @throws MissingPropertyException if property y does not exist */ val MapObject.y: Float - get() = property("y") + get() = property("y") /** * Extension property to retrieve the width of the [MapObject] * @throws MissingPropertyException if property width does not exist */ val MapObject.width: Float - get() = property("width") + get() = property("width") /** * Extension property to retrieve the height of the [MapObject] * @throws MissingPropertyException if property height does not exist */ val MapObject.height: Float - get() = property("height") + get() = property("height") /** * Extension property to retrieve the unique ID of the [MapObject] * @throws MissingPropertyException if property id does not exist */ val MapObject.id: Int - get() = property("id") + get() = property("id") /** * Extension property to retrieve the rotation of the [MapObject] * @throws MissingPropertyException if property rotation does not exist */ val MapObject.rotation: Float - get() = property("rotation") + get() = property("rotation") /** * Extension property to retrieve the type of the [MapObject] * @throws MissingPropertyException if property type does not exist */ val MapObject.type: String - get() = property("type") + get() = property("type") /** * Extension method to retrieve the [Shape2D] instance of a [MapObject]. @@ -103,11 +102,11 @@ val MapObject.type: String * @throws MissingShapeException If the object does not have any shape */ val MapObject.shape: Shape2D - get() = when (this) { - is CircleMapObject -> this.circle - is EllipseMapObject -> this.ellipse - is PolylineMapObject -> this.polyline - is PolygonMapObject -> this.polygon - is RectangleMapObject -> this.rectangle - else -> throw MissingShapeException("MapObject of type ${this::class.java} does not have a shape") - } + get() = when (this) { + is CircleMapObject -> this.circle + is EllipseMapObject -> this.ellipse + is PolylineMapObject -> this.polyline + is PolygonMapObject -> this.polygon + is RectangleMapObject -> this.rectangle + else -> throw MissingShapeException("MapObject of type ${this::class.java} does not have a shape") + } diff --git a/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt b/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt index 6973201c..6346d7ab 100644 --- a/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt +++ b/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt @@ -13,7 +13,7 @@ import com.badlogic.gdx.maps.tiled.TiledMap * @throws MissingPropertyException If the property is not defined */ inline fun TiledMap.property(key: String): T = this.properties[key, T::class.java] - ?: throw MissingPropertyException("Property $key does not exist for map") + ?: throw MissingPropertyException("Property $key does not exist for map") /** * Extension method to directly access the [MapProperties] of a [TiledMap]. The type is automatically @@ -44,63 +44,63 @@ fun TiledMap.containsProperty(key: String) = properties.containsKey(key) * @throws MissingPropertyException if property width does not exist */ val TiledMap.width: Int - get() = property("width") + get() = property("width") /** * Extension property to retrieve the height of the [TiledMap] * @throws MissingPropertyException if property height does not exist */ val TiledMap.height: Int - get() = property("height") + get() = property("height") /** * Extension property to retrieve the tile width of each tile of the [TiledMap] * @throws MissingPropertyException if property tilewidth does not exist */ val TiledMap.tileWidth: Int - get() = property("tilewidth") + get() = property("tilewidth") /** * Extension property to retrieve the tile height of each tile of the [TiledMap] * @throws MissingPropertyException if property tileheight does not exist */ val TiledMap.tileHeight: Int - get() = property("tileheight") + get() = property("tileheight") /** * Extension property to retrieve the background color of the [TiledMap] * @throws MissingPropertyException if property backgroundcolor does not exist */ val TiledMap.backgroundColor: String - get() = property("backgroundcolor") + get() = property("backgroundcolor") /** * Extension property to retrieve the orientation of the [TiledMap] * @throws MissingPropertyException if property orientation does not exist */ val TiledMap.orientation: String - get() = property("orientation") + get() = property("orientation") /** * Extension property to retrieve the hex side length of a hexagonal [TiledMap] * @throws MissingPropertyException if property hexsidelength does not exist */ val TiledMap.hexSideLength: Int - get() = property("hexsidelength") + get() = property("hexsidelength") /** * Extension property to retrieve the stagger axis of the [TiledMap] * @throws MissingPropertyException if property staggeraxis does not exist */ val TiledMap.staggerAxis: String - get() = property("staggeraxis") + get() = property("staggeraxis") /** * Extension property to retrieve the stagger index of the [TiledMap] * @throws MissingPropertyException if property staggerindex does not exist */ val TiledMap.staggerIndex: String - get() = property("staggerindex") + get() = property("staggerindex") /** * Extension method to retrieve the total width of the [TiledMap]. It is the result of the @@ -138,7 +138,7 @@ operator fun TiledMap.contains(layerName: String) = layers[layerName] != null * @throws MissingLayerException If the layer does not exist */ fun TiledMap.layer(layerName: String) = layers[layerName] - ?: throw MissingLayerException("Layer $layerName does not exist for map") + ?: throw MissingLayerException("Layer $layerName does not exist for map") /** * Extension method to easily execute an action per [MapObject] of a given [MapLayer]. @@ -148,5 +148,5 @@ fun TiledMap.layer(layerName: String) = layers[layerName] * @param action action to execute per [MapObject] of the [MapLayer] */ fun TiledMap.forEachMapObject(layerName: String, action: (MapObject) -> Unit) { - layers[layerName]?.objects?.forEach { action(it) } + layers[layerName]?.objects?.forEach { action(it) } } diff --git a/tiled/src/main/kotlin/ktx/tiled/tiledMapTile.kt b/tiled/src/main/kotlin/ktx/tiled/tiledMapTile.kt index afe6dede..30a816c2 100644 --- a/tiled/src/main/kotlin/ktx/tiled/tiledMapTile.kt +++ b/tiled/src/main/kotlin/ktx/tiled/tiledMapTile.kt @@ -3,7 +3,6 @@ package ktx.tiled import com.badlogic.gdx.maps.MapProperties import com.badlogic.gdx.maps.tiled.TiledMapTile - /** * Extension method to directly access the [MapProperties] of a [TiledMapTile]. If the property * is not defined then this method throws a [MissingPropertyException]. @@ -12,7 +11,7 @@ import com.badlogic.gdx.maps.tiled.TiledMapTile * @throws MissingPropertyException If the property is not defined */ inline fun TiledMapTile.property(key: String): T = this.properties[key, T::class.java] - ?: throw MissingPropertyException("Property $key does not exist for tile ${this.id}") + ?: throw MissingPropertyException("Property $key does not exist for tile ${this.id}") /** * Extension method to directly access the [MapProperties] of a [TiledMapTile]. The type is automatically diff --git a/tiled/src/main/kotlin/ktx/tiled/tiledMapTileSet.kt b/tiled/src/main/kotlin/ktx/tiled/tiledMapTileSet.kt index 41860126..88f1c898 100644 --- a/tiled/src/main/kotlin/ktx/tiled/tiledMapTileSet.kt +++ b/tiled/src/main/kotlin/ktx/tiled/tiledMapTileSet.kt @@ -11,7 +11,7 @@ import com.badlogic.gdx.maps.tiled.TiledMapTileSet * @throws MissingPropertyException If the property is not defined */ inline fun TiledMapTileSet.property(key: String): T = this.properties[key, T::class.java] - ?: throw MissingPropertyException("Property $key does not exist for tileset ${this.name}") + ?: throw MissingPropertyException("Property $key does not exist for tileset ${this.name}") /** * Extension method to directly access the [MapProperties] of a [TiledMapTileSet]. The type is automatically diff --git a/tiled/src/test/kotlin/ktx/tiled/mapLayerTest.kt b/tiled/src/test/kotlin/ktx/tiled/mapLayerTest.kt index 34d9db25..2cd228a7 100644 --- a/tiled/src/test/kotlin/ktx/tiled/mapLayerTest.kt +++ b/tiled/src/test/kotlin/ktx/tiled/mapLayerTest.kt @@ -5,34 +5,34 @@ import org.junit.Assert import org.junit.Test class MapLayerTest { - private val mapLayer = MapLayer().apply { - properties.put("active", true) - properties.put("customProperty", 123) - } + private val mapLayer = MapLayer().apply { + properties.put("active", true) + properties.put("customProperty", 123) + } - @Test - fun `retrieve properties from MapLayer with default value`() { - Assert.assertEquals(true, mapLayer.property("active", false)) - Assert.assertEquals(123, mapLayer.property("customProperty", 0)) - Assert.assertEquals(-1f, mapLayer.property("x", -1f)) - } + @Test + fun `retrieve properties from MapLayer with default value`() { + Assert.assertEquals(true, mapLayer.property("active", false)) + Assert.assertEquals(123, mapLayer.property("customProperty", 0)) + Assert.assertEquals(-1f, mapLayer.property("x", -1f)) + } - @Test - fun `retrieve properties from MapLayer without default value`() { - Assert.assertNull(mapLayer.propertyOrNull("x")) - val customProperty: Int? = mapLayer.propertyOrNull("customProperty") - Assert.assertNotNull(customProperty) - Assert.assertEquals(123, customProperty) - } + @Test + fun `retrieve properties from MapLayer without default value`() { + Assert.assertNull(mapLayer.propertyOrNull("x")) + val customProperty: Int? = mapLayer.propertyOrNull("customProperty") + Assert.assertNotNull(customProperty) + Assert.assertEquals(123, customProperty) + } - @Test - fun `check if property from MapLayer exists`() { - Assert.assertTrue(mapLayer.containsProperty("active")) - Assert.assertFalse(mapLayer.containsProperty("x")) - } + @Test + fun `check if property from MapLayer exists`() { + Assert.assertTrue(mapLayer.containsProperty("active")) + Assert.assertFalse(mapLayer.containsProperty("x")) + } - @Test(expected = MissingPropertyException::class) - fun `retrieve non-existing property from MapLayer using exception`() { - mapLayer.property("non-existing") - } + @Test(expected = MissingPropertyException::class) + fun `retrieve non-existing property from MapLayer using exception`() { + mapLayer.property("non-existing") + } } diff --git a/tiled/src/test/kotlin/ktx/tiled/mapObjectTest.kt b/tiled/src/test/kotlin/ktx/tiled/mapObjectTest.kt index 30510111..f19c3c1b 100644 --- a/tiled/src/test/kotlin/ktx/tiled/mapObjectTest.kt +++ b/tiled/src/test/kotlin/ktx/tiled/mapObjectTest.kt @@ -9,77 +9,77 @@ import org.junit.Assert.* import org.junit.Test class MapObjectTest { - private val mapObject = MapObject().apply { - properties.also { - it.put("id", 13) - it.put("x", 1) - it.put("y", 0f) - it.put("rotation", -2.33f) - it.put("type", "SomeType") - it.put("width", 1f) - it.put("name", "Property") - it.put("active", true) - } + private val mapObject = MapObject().apply { + properties.also { + it.put("id", 13) + it.put("x", 1) + it.put("y", 0f) + it.put("rotation", -2.33f) + it.put("type", "SomeType") + it.put("width", 1f) + it.put("name", "Property") + it.put("active", true) } + } - private val polylineVertices = floatArrayOf(0f, 0f, 1f, 1f) - private val polygonVertices = floatArrayOf(0f, 0f, 1f, 1f, 2f, 0f) + private val polylineVertices = floatArrayOf(0f, 0f, 1f, 1f) + private val polygonVertices = floatArrayOf(0f, 0f, 1f, 1f, 2f, 0f) - private val circleObject = CircleMapObject() - private val ellipseObject = EllipseMapObject() - private val polylineObject = PolylineMapObject(polylineVertices) - private val polygonObject = PolygonMapObject(polygonVertices) - private val rectObject = RectangleMapObject() - private val textureObject = TextureMapObject() + private val circleObject = CircleMapObject() + private val ellipseObject = EllipseMapObject() + private val polylineObject = PolylineMapObject(polylineVertices) + private val polygonObject = PolygonMapObject(polygonVertices) + private val rectObject = RectangleMapObject() + private val textureObject = TextureMapObject() - @Test - fun `retrieve properties from MapObject with default value`() { - assertEquals(1, mapObject.property("x", 0)) - assertEquals(0, mapObject.property("non-existing", 0)) - assertEquals(1f, mapObject.property("width", 0f)) - assertEquals("Property", mapObject.property("name", "")) - assertEquals(true, mapObject.property("active", false)) - } + @Test + fun `retrieve properties from MapObject with default value`() { + assertEquals(1, mapObject.property("x", 0)) + assertEquals(0, mapObject.property("non-existing", 0)) + assertEquals(1f, mapObject.property("width", 0f)) + assertEquals("Property", mapObject.property("name", "")) + assertEquals(true, mapObject.property("active", false)) + } - @Test - fun `retrieve properties from MapObject without default value`() { - assertNull(mapObject.propertyOrNull("non-existing")) - val x: Int? = mapObject.propertyOrNull("x") - assertNotNull(x) - assertEquals(1, x) - } + @Test + fun `retrieve properties from MapObject without default value`() { + assertNull(mapObject.propertyOrNull("non-existing")) + val x: Int? = mapObject.propertyOrNull("x") + assertNotNull(x) + assertEquals(1, x) + } - @Test - fun `check if property from MapObject exists`() { - assertTrue(mapObject.containsProperty("x")) - assertFalse(mapObject.containsProperty("non-existing")) - } + @Test + fun `check if property from MapObject exists`() { + assertTrue(mapObject.containsProperty("x")) + assertFalse(mapObject.containsProperty("non-existing")) + } - @Test - fun `retrieve standard properties of MapObject`() { - assertEquals(1f, mapObject.x) - assertEquals(0f, mapObject.y) - assertEquals(13, mapObject.id) - assertEquals(-2.33f, mapObject.rotation) - assertEquals("SomeType", mapObject.type) - } + @Test + fun `retrieve standard properties of MapObject`() { + assertEquals(1f, mapObject.x) + assertEquals(0f, mapObject.y) + assertEquals(13, mapObject.id) + assertEquals(-2.33f, mapObject.rotation) + assertEquals("SomeType", mapObject.type) + } - @Test - fun `retrieve shape from MapObject`() { - assertEquals(Circle(0f, 0f, 1f), circleObject.shape) - assertEquals(Ellipse(0f, 0f, 1f, 1f), ellipseObject.shape) - assertEquals(polylineObject.polyline, polylineObject.shape) - assertEquals(polygonObject.polygon, polygonObject.shape) - assertEquals(Rectangle(0f, 0f, 1f, 1f), rectObject.shape) - } + @Test + fun `retrieve shape from MapObject`() { + assertEquals(Circle(0f, 0f, 1f), circleObject.shape) + assertEquals(Ellipse(0f, 0f, 1f, 1f), ellipseObject.shape) + assertEquals(polylineObject.polyline, polylineObject.shape) + assertEquals(polygonObject.polygon, polygonObject.shape) + assertEquals(Rectangle(0f, 0f, 1f, 1f), rectObject.shape) + } - @Test(expected = MissingShapeException::class) - fun `retrieve shape from unsupported MapObject`() { - textureObject.shape - } + @Test(expected = MissingShapeException::class) + fun `retrieve shape from unsupported MapObject`() { + textureObject.shape + } - @Test(expected = MissingPropertyException::class) - fun `retrieve non-existing property from MapObject using exception`() { - mapObject.property("non-existing") - } + @Test(expected = MissingPropertyException::class) + fun `retrieve non-existing property from MapObject using exception`() { + mapObject.property("non-existing") + } } diff --git a/tiled/src/test/kotlin/ktx/tiled/tiledMapTest.kt b/tiled/src/test/kotlin/ktx/tiled/tiledMapTest.kt index 9f77571c..1cc10c4a 100644 --- a/tiled/src/test/kotlin/ktx/tiled/tiledMapTest.kt +++ b/tiled/src/test/kotlin/ktx/tiled/tiledMapTest.kt @@ -7,104 +7,104 @@ import org.junit.Assert.* import org.junit.Test class TiledMapTest { - private val tiledMap = TiledMap().apply { - properties.put("width", 16) - properties.put("height", 8) - properties.put("tilewidth", 32) - properties.put("tileheight", 32) + private val tiledMap = TiledMap().apply { + properties.put("width", 16) + properties.put("height", 8) + properties.put("tilewidth", 32) + properties.put("tileheight", 32) - properties.put("backgroundcolor", "#ffffff") - properties.put("orientation", "orthogonal") - properties.put("hexsidelength", 0) - properties.put("staggeraxis", "Y") - properties.put("staggerindex", "Odd") + properties.put("backgroundcolor", "#ffffff") + properties.put("orientation", "orthogonal") + properties.put("hexsidelength", 0) + properties.put("staggeraxis", "Y") + properties.put("staggerindex", "Odd") - layers.add(MapLayer().apply { - name = "layer-1" - objects.add(MapObject()) - objects.add(MapObject()) - objects.add(MapObject()) - }) - layers.add(MapLayer().apply { name = "layer-2" }) - } - - @Test - fun `retrieve properties from TiledMap with default value`() { - assertEquals(16, tiledMap.property("width", 0)) - assertEquals(-1, tiledMap.property("x", -1)) - } + layers.add(MapLayer().apply { + name = "layer-1" + objects.add(MapObject()) + objects.add(MapObject()) + objects.add(MapObject()) + }) + layers.add(MapLayer().apply { name = "layer-2" }) + } - @Test - fun `retrieve properties from TiledMap without default value`() { - assertNull(tiledMap.propertyOrNull("x")) - val width: Int? = tiledMap.propertyOrNull("width") - assertNotNull(width) - assertEquals(16, width) - } + @Test + fun `retrieve properties from TiledMap with default value`() { + assertEquals(16, tiledMap.property("width", 0)) + assertEquals(-1, tiledMap.property("x", -1)) + } - @Test - fun `check if property from TiledMap exists`() { - assertTrue(tiledMap.containsProperty("width")) - assertFalse(tiledMap.containsProperty("x")) - } + @Test + fun `retrieve properties from TiledMap without default value`() { + assertNull(tiledMap.propertyOrNull("x")) + val width: Int? = tiledMap.propertyOrNull("width") + assertNotNull(width) + assertEquals(16, width) + } - @Test - fun `retrieve standard properties of TiledMap`() { - assertEquals(16, tiledMap.width) - assertEquals(8, tiledMap.height) - assertEquals(32, tiledMap.tileWidth) - assertEquals(32, tiledMap.tileHeight) - assertEquals(16 * 32, tiledMap.totalWidth()) - assertEquals(8 * 32, tiledMap.totalHeight()) - assertEquals("#ffffff", tiledMap.backgroundColor) - assertEquals("orthogonal", tiledMap.orientation) - assertEquals(0, tiledMap.hexSideLength) - assertEquals("Y", tiledMap.staggerAxis) - assertEquals("Odd", tiledMap.staggerIndex) - } + @Test + fun `check if property from TiledMap exists`() { + assertTrue(tiledMap.containsProperty("width")) + assertFalse(tiledMap.containsProperty("x")) + } - @Test(expected = MissingPropertyException::class) - fun `retrieve non-existing property from TiledMap using exception`() { - tiledMap.property("non-existing") - } + @Test + fun `retrieve standard properties of TiledMap`() { + assertEquals(16, tiledMap.width) + assertEquals(8, tiledMap.height) + assertEquals(32, tiledMap.tileWidth) + assertEquals(32, tiledMap.tileHeight) + assertEquals(16 * 32, tiledMap.totalWidth()) + assertEquals(8 * 32, tiledMap.totalHeight()) + assertEquals("#ffffff", tiledMap.backgroundColor) + assertEquals("orthogonal", tiledMap.orientation) + assertEquals(0, tiledMap.hexSideLength) + assertEquals("Y", tiledMap.staggerAxis) + assertEquals("Odd", tiledMap.staggerIndex) + } - @Test - fun `retrieve existing layer from TiledMap`() { - assertEquals("layer-1", tiledMap.layer("layer-1").name) - } + @Test(expected = MissingPropertyException::class) + fun `retrieve non-existing property from TiledMap using exception`() { + tiledMap.property("non-existing") + } - @Test(expected = MissingLayerException::class) - fun `retrieve non-existing layer from TiledMap using exception`() { - tiledMap.layer("non-existing") - } + @Test + fun `retrieve existing layer from TiledMap`() { + assertEquals("layer-1", tiledMap.layer("layer-1").name) + } - @Test - fun `check if layer exists in TiledMap`() { - assertTrue(tiledMap.contains("layer-1")) - assertFalse("non-existing" in tiledMap) - } + @Test(expected = MissingLayerException::class) + fun `retrieve non-existing layer from TiledMap using exception`() { + tiledMap.layer("non-existing") + } - @Test - fun `execute action per object of a layer`() { - // check that there are objects in layer -1 - assertEquals(3, tiledMap.layers["layer-1"].objects.count) - // verify that they are all visible and set them to not visible - var counter = 0 - tiledMap.forEachMapObject("layer-1") { - assertTrue(it.isVisible) - it.isVisible = false - counter++ - } - // verify again that they are now all invisible and revert them back to being visible - assertEquals(3, counter) - tiledMap.forEachMapObject("layer-1") { - assertFalse(it.isVisible) - it.isVisible = true - } + @Test + fun `check if layer exists in TiledMap`() { + assertTrue(tiledMap.contains("layer-1")) + assertFalse("non-existing" in tiledMap) + } - // also, test empty default layer which should do nothing - counter = 0 - tiledMap.forEachMapObject("non-existing") { ++counter } - assertEquals(0, counter) + @Test + fun `execute action per object of a layer`() { + // check that there are objects in layer -1 + assertEquals(3, tiledMap.layers["layer-1"].objects.count) + // verify that they are all visible and set them to not visible + var counter = 0 + tiledMap.forEachMapObject("layer-1") { + assertTrue(it.isVisible) + it.isVisible = false + counter++ + } + // verify again that they are now all invisible and revert them back to being visible + assertEquals(3, counter) + tiledMap.forEachMapObject("layer-1") { + assertFalse(it.isVisible) + it.isVisible = true } + + // also, test empty default layer which should do nothing + counter = 0 + tiledMap.forEachMapObject("non-existing") { ++counter } + assertEquals(0, counter) + } } diff --git a/tiled/src/test/kotlin/ktx/tiled/tiledMapTileSetTest.kt b/tiled/src/test/kotlin/ktx/tiled/tiledMapTileSetTest.kt index 19c8fb54..333d2117 100644 --- a/tiled/src/test/kotlin/ktx/tiled/tiledMapTileSetTest.kt +++ b/tiled/src/test/kotlin/ktx/tiled/tiledMapTileSetTest.kt @@ -5,34 +5,34 @@ import org.junit.Assert import org.junit.Test class TiledMapTileSetTest { - private val tileset = TiledMapTileSet().apply { - properties.put("tilesetProp1", true) - properties.put("tilesetProp2", 123) - } + private val tileset = TiledMapTileSet().apply { + properties.put("tilesetProp1", true) + properties.put("tilesetProp2", 123) + } - @Test - fun `retrieve properties from TileSet with default value`() { - Assert.assertEquals(true, tileset.property("tilesetProp1", false)) - Assert.assertEquals(123, tileset.property("tilesetProp2", 0)) - Assert.assertEquals(-1f, tileset.property("non-existing", -1f)) - } + @Test + fun `retrieve properties from TileSet with default value`() { + Assert.assertEquals(true, tileset.property("tilesetProp1", false)) + Assert.assertEquals(123, tileset.property("tilesetProp2", 0)) + Assert.assertEquals(-1f, tileset.property("non-existing", -1f)) + } - @Test - fun `retrieve properties from TileSet without default value`() { - Assert.assertNull(tileset.propertyOrNull("non-existing")) - val customProperty: Int? = tileset.propertyOrNull("tilesetProp2") - Assert.assertNotNull(customProperty) - Assert.assertEquals(123, customProperty) - } + @Test + fun `retrieve properties from TileSet without default value`() { + Assert.assertNull(tileset.propertyOrNull("non-existing")) + val customProperty: Int? = tileset.propertyOrNull("tilesetProp2") + Assert.assertNotNull(customProperty) + Assert.assertEquals(123, customProperty) + } - @Test - fun `check if property from TileSet exists`() { - Assert.assertTrue(tileset.containsProperty("tilesetProp1")) - Assert.assertFalse(tileset.containsProperty("non-existing")) - } + @Test + fun `check if property from TileSet exists`() { + Assert.assertTrue(tileset.containsProperty("tilesetProp1")) + Assert.assertFalse(tileset.containsProperty("non-existing")) + } - @Test(expected = MissingPropertyException::class) - fun `retrieve non-existing property from TileSet using exception`() { - tileset.property("non-existing") - } + @Test(expected = MissingPropertyException::class) + fun `retrieve non-existing property from TileSet using exception`() { + tileset.property("non-existing") + } } diff --git a/tiled/src/test/kotlin/ktx/tiled/tiledMapTileTest.kt b/tiled/src/test/kotlin/ktx/tiled/tiledMapTileTest.kt index b0c5a27c..6a6f74f1 100644 --- a/tiled/src/test/kotlin/ktx/tiled/tiledMapTileTest.kt +++ b/tiled/src/test/kotlin/ktx/tiled/tiledMapTileTest.kt @@ -8,40 +8,40 @@ import org.junit.Assert import org.junit.Test class TiledMapTileTest { - private val staticTile = StaticTiledMapTile(TextureRegion()).apply { - properties.put("staticProp1", true) - properties.put("staticProp2", 123) - } - private val animatedTile = AnimatedTiledMapTile(0f, Array()).apply { - properties.put("aniProp1", 1f) - properties.put("aniProp2", "SomeText") - } + private val staticTile = StaticTiledMapTile(TextureRegion()).apply { + properties.put("staticProp1", true) + properties.put("staticProp2", 123) + } + private val animatedTile = AnimatedTiledMapTile(0f, Array()).apply { + properties.put("aniProp1", 1f) + properties.put("aniProp2", "SomeText") + } - @Test - fun `retrieve properties from Tile with default value`() { - Assert.assertEquals(true, staticTile.property("staticProp1", false)) - Assert.assertEquals(123, staticTile.property("staticProp2", 0)) - Assert.assertEquals(-1f, animatedTile.property("non-existing", -1f)) - } + @Test + fun `retrieve properties from Tile with default value`() { + Assert.assertEquals(true, staticTile.property("staticProp1", false)) + Assert.assertEquals(123, staticTile.property("staticProp2", 0)) + Assert.assertEquals(-1f, animatedTile.property("non-existing", -1f)) + } - @Test - fun `retrieve properties from Tile without default value`() { - Assert.assertNull(animatedTile.propertyOrNull("non-existing")) - val customProperty: Int? = staticTile.propertyOrNull("staticProp2") - Assert.assertNotNull(customProperty) - Assert.assertEquals(123, customProperty) - } + @Test + fun `retrieve properties from Tile without default value`() { + Assert.assertNull(animatedTile.propertyOrNull("non-existing")) + val customProperty: Int? = staticTile.propertyOrNull("staticProp2") + Assert.assertNotNull(customProperty) + Assert.assertEquals(123, customProperty) + } - @Test - fun `check if property from Tile exists`() { - Assert.assertTrue(staticTile.containsProperty("staticProp1")) - Assert.assertFalse(staticTile.containsProperty("non-existing")) - Assert.assertTrue(animatedTile.containsProperty("aniProp2")) - Assert.assertFalse(animatedTile.containsProperty("non-existing")) - } + @Test + fun `check if property from Tile exists`() { + Assert.assertTrue(staticTile.containsProperty("staticProp1")) + Assert.assertFalse(staticTile.containsProperty("non-existing")) + Assert.assertTrue(animatedTile.containsProperty("aniProp2")) + Assert.assertFalse(animatedTile.containsProperty("non-existing")) + } - @Test(expected = MissingPropertyException::class) - fun `retrieve non-existing property from Tile using exception`() { - animatedTile.property("non-existing") - } + @Test(expected = MissingPropertyException::class) + fun `retrieve non-existing property from Tile using exception`() { + animatedTile.property("non-existing") + } } From 457575d0c81d27854b21ca3a77508e56902b5e6e Mon Sep 17 00:00:00 2001 From: MJ Date: Mon, 23 Dec 2019 19:16:07 +0100 Subject: [PATCH 14/32] Added common type for Tiled exceptions. #233 --- tiled/src/main/kotlin/ktx/tiled/exception.kt | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tiled/src/main/kotlin/ktx/tiled/exception.kt b/tiled/src/main/kotlin/ktx/tiled/exception.kt index 5b24a49f..ac1f7fdb 100644 --- a/tiled/src/main/kotlin/ktx/tiled/exception.kt +++ b/tiled/src/main/kotlin/ktx/tiled/exception.kt @@ -8,16 +8,24 @@ import com.badlogic.gdx.maps.tiled.TiledMap import com.badlogic.gdx.utils.GdxRuntimeException /** - * [GdxRuntimeException] that is thrown when trying to access a non-existing property of a [MapProperties] instance + * Common type of exceptions thrown by the Tiled API extensions. */ -class MissingPropertyException(message: String) : GdxRuntimeException(message) +open class TiledException(message: String, cause: Throwable? = null): GdxRuntimeException(message, cause) /** - * [GdxRuntimeException] that is thrown when trying to access a non-existing [MapLayer] of a [TiledMap] instance + * [GdxRuntimeException] that is thrown when trying to access a non-existing property + * of a [MapProperties] instance. */ -class MissingLayerException(message: String) : GdxRuntimeException(message) +class MissingPropertyException(message: String, cause: Throwable? = null) : TiledException(message, cause) /** - * [GdxRuntimeException] that is thrown when trying to access a shape of a [MapObject] that do not have any shape like [TextureMapObject] + * [GdxRuntimeException] that is thrown when trying to access a non-existing [MapLayer] + * of a [TiledMap] instance. */ -class MissingShapeException(message: String) : GdxRuntimeException(message) +class MissingLayerException(message: String, cause: Throwable? = null) : TiledException(message, cause) + +/** + * [GdxRuntimeException] that is thrown when trying to access a shape of a [MapObject] + * that do not have any shape such as the [TextureMapObject]. + */ +class MissingShapeException(message: String, cause: Throwable? = null) : TiledException(message, cause) From f3b10f73e8cee611897590146aee0722644d0574 Mon Sep 17 00:00:00 2001 From: MJ Date: Mon, 23 Dec 2019 19:18:05 +0100 Subject: [PATCH 15/32] Renamed ktx-tiled files for consistency. #233 --- tiled/src/main/kotlin/ktx/tiled/{exception.kt => exceptions.kt} | 0 tiled/src/main/kotlin/ktx/tiled/{mapLayer.kt => mapLayers.kt} | 0 tiled/src/main/kotlin/ktx/tiled/{mapObject.kt => mapObjects.kt} | 0 .../kotlin/ktx/tiled/{tiledMapTileSet.kt => tiledMapTileSets.kt} | 0 .../main/kotlin/ktx/tiled/{tiledMapTile.kt => tiledMapTiles.kt} | 0 tiled/src/main/kotlin/ktx/tiled/{tiledMap.kt => tiledMaps.kt} | 0 .../test/kotlin/ktx/tiled/{mapLayerTest.kt => mapLayersTest.kt} | 0 .../test/kotlin/ktx/tiled/{mapObjectTest.kt => mapObjectsTest.kt} | 0 .../ktx/tiled/{tiledMapTileSetTest.kt => tiledMapTileSetsTest.kt} | 0 .../ktx/tiled/{tiledMapTileTest.kt => tiledMapTilesTest.kt} | 0 .../test/kotlin/ktx/tiled/{tiledMapTest.kt => tiledMapsTest.kt} | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename tiled/src/main/kotlin/ktx/tiled/{exception.kt => exceptions.kt} (100%) rename tiled/src/main/kotlin/ktx/tiled/{mapLayer.kt => mapLayers.kt} (100%) rename tiled/src/main/kotlin/ktx/tiled/{mapObject.kt => mapObjects.kt} (100%) rename tiled/src/main/kotlin/ktx/tiled/{tiledMapTileSet.kt => tiledMapTileSets.kt} (100%) rename tiled/src/main/kotlin/ktx/tiled/{tiledMapTile.kt => tiledMapTiles.kt} (100%) rename tiled/src/main/kotlin/ktx/tiled/{tiledMap.kt => tiledMaps.kt} (100%) rename tiled/src/test/kotlin/ktx/tiled/{mapLayerTest.kt => mapLayersTest.kt} (100%) rename tiled/src/test/kotlin/ktx/tiled/{mapObjectTest.kt => mapObjectsTest.kt} (100%) rename tiled/src/test/kotlin/ktx/tiled/{tiledMapTileSetTest.kt => tiledMapTileSetsTest.kt} (100%) rename tiled/src/test/kotlin/ktx/tiled/{tiledMapTileTest.kt => tiledMapTilesTest.kt} (100%) rename tiled/src/test/kotlin/ktx/tiled/{tiledMapTest.kt => tiledMapsTest.kt} (100%) diff --git a/tiled/src/main/kotlin/ktx/tiled/exception.kt b/tiled/src/main/kotlin/ktx/tiled/exceptions.kt similarity index 100% rename from tiled/src/main/kotlin/ktx/tiled/exception.kt rename to tiled/src/main/kotlin/ktx/tiled/exceptions.kt diff --git a/tiled/src/main/kotlin/ktx/tiled/mapLayer.kt b/tiled/src/main/kotlin/ktx/tiled/mapLayers.kt similarity index 100% rename from tiled/src/main/kotlin/ktx/tiled/mapLayer.kt rename to tiled/src/main/kotlin/ktx/tiled/mapLayers.kt diff --git a/tiled/src/main/kotlin/ktx/tiled/mapObject.kt b/tiled/src/main/kotlin/ktx/tiled/mapObjects.kt similarity index 100% rename from tiled/src/main/kotlin/ktx/tiled/mapObject.kt rename to tiled/src/main/kotlin/ktx/tiled/mapObjects.kt diff --git a/tiled/src/main/kotlin/ktx/tiled/tiledMapTileSet.kt b/tiled/src/main/kotlin/ktx/tiled/tiledMapTileSets.kt similarity index 100% rename from tiled/src/main/kotlin/ktx/tiled/tiledMapTileSet.kt rename to tiled/src/main/kotlin/ktx/tiled/tiledMapTileSets.kt diff --git a/tiled/src/main/kotlin/ktx/tiled/tiledMapTile.kt b/tiled/src/main/kotlin/ktx/tiled/tiledMapTiles.kt similarity index 100% rename from tiled/src/main/kotlin/ktx/tiled/tiledMapTile.kt rename to tiled/src/main/kotlin/ktx/tiled/tiledMapTiles.kt diff --git a/tiled/src/main/kotlin/ktx/tiled/tiledMap.kt b/tiled/src/main/kotlin/ktx/tiled/tiledMaps.kt similarity index 100% rename from tiled/src/main/kotlin/ktx/tiled/tiledMap.kt rename to tiled/src/main/kotlin/ktx/tiled/tiledMaps.kt diff --git a/tiled/src/test/kotlin/ktx/tiled/mapLayerTest.kt b/tiled/src/test/kotlin/ktx/tiled/mapLayersTest.kt similarity index 100% rename from tiled/src/test/kotlin/ktx/tiled/mapLayerTest.kt rename to tiled/src/test/kotlin/ktx/tiled/mapLayersTest.kt diff --git a/tiled/src/test/kotlin/ktx/tiled/mapObjectTest.kt b/tiled/src/test/kotlin/ktx/tiled/mapObjectsTest.kt similarity index 100% rename from tiled/src/test/kotlin/ktx/tiled/mapObjectTest.kt rename to tiled/src/test/kotlin/ktx/tiled/mapObjectsTest.kt diff --git a/tiled/src/test/kotlin/ktx/tiled/tiledMapTileSetTest.kt b/tiled/src/test/kotlin/ktx/tiled/tiledMapTileSetsTest.kt similarity index 100% rename from tiled/src/test/kotlin/ktx/tiled/tiledMapTileSetTest.kt rename to tiled/src/test/kotlin/ktx/tiled/tiledMapTileSetsTest.kt diff --git a/tiled/src/test/kotlin/ktx/tiled/tiledMapTileTest.kt b/tiled/src/test/kotlin/ktx/tiled/tiledMapTilesTest.kt similarity index 100% rename from tiled/src/test/kotlin/ktx/tiled/tiledMapTileTest.kt rename to tiled/src/test/kotlin/ktx/tiled/tiledMapTilesTest.kt diff --git a/tiled/src/test/kotlin/ktx/tiled/tiledMapTest.kt b/tiled/src/test/kotlin/ktx/tiled/tiledMapsTest.kt similarity index 100% rename from tiled/src/test/kotlin/ktx/tiled/tiledMapTest.kt rename to tiled/src/test/kotlin/ktx/tiled/tiledMapsTest.kt From 3c11d2f194979bea787f58f1a51ecc60cbf147e7 Mon Sep 17 00:00:00 2001 From: MJ Date: Mon, 23 Dec 2019 20:18:43 +0100 Subject: [PATCH 16/32] Added ktx-tiled to change log. #233 --- CHANGELOG.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dbf99f3..148d5731 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,15 @@ #### 1.9.10-SNAPSHOT -- **[CHANGE]** `Array.removeAll` and `retainAll` now return a boolean if any elements were removed. -- **[CHANGE]** `Array.transfer` is now less strict about typing. +- **[CHANGE]** (`ktx-collections`) `Array.removeAll` and `retainAll` now return a boolean if any elements were removed. +- **[CHANGE]** (`ktx-collections`) `Array.transfer` is now less strict about typing. +- **[FEATURE]** (`ktx-tiled`) Added a new KTX module: Tiled API extensions. + - Added `contains` (`in`) and `set` (`[]`) operators support to `MapProperties`. + - Added extension methods that simplify properties extraction from `MapLayer`, `MapObject`, `TiledMap`, `TiledMapTile` and `TiledMapTileSet`: + - `property` + - `propertyOrNull` + - `containsProperty` + - Added `shape` extension field to `MapObject`. + - Added extension fields that ease extraction of basic properties from `TiledMap` and `MapObject`. #### 1.9.10-b3 From a7a12347f9f25367bad874f3f8225942352209a1 Mon Sep 17 00:00:00 2001 From: MJ Date: Mon, 23 Dec 2019 20:19:08 +0100 Subject: [PATCH 17/32] Added operators support to MapProperties. #233 --- .../main/kotlin/ktx/tiled/mapProperties.kt | 19 ++++++++ .../kotlin/ktx/tiled/mapPropertiesTest.kt | 45 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 tiled/src/main/kotlin/ktx/tiled/mapProperties.kt create mode 100644 tiled/src/test/kotlin/ktx/tiled/mapPropertiesTest.kt diff --git a/tiled/src/main/kotlin/ktx/tiled/mapProperties.kt b/tiled/src/main/kotlin/ktx/tiled/mapProperties.kt new file mode 100644 index 00000000..bddc3167 --- /dev/null +++ b/tiled/src/main/kotlin/ktx/tiled/mapProperties.kt @@ -0,0 +1,19 @@ +package ktx.tiled + +import com.badlogic.gdx.maps.MapProperties + +/** + * Allows to check if a [MapProperties] instance contains a property mapped to [key] + * with Kotlin `in` operator. + * @param key name of the property. + * @return true if the property with the given [key] can be found in this [MapProperties]. False otherwise. + */ +operator fun MapProperties.contains(key: String): Boolean = containsKey(key) + +/** + * Allows to add a property mapped to [key] with given [value] using Kotlin + * square braces assignment operator. + * @param key name of the property. + * @param value value of the property. + */ +operator fun MapProperties.set(key: String, value: Any) = put(key, value) diff --git a/tiled/src/test/kotlin/ktx/tiled/mapPropertiesTest.kt b/tiled/src/test/kotlin/ktx/tiled/mapPropertiesTest.kt new file mode 100644 index 00000000..7f3b9e18 --- /dev/null +++ b/tiled/src/test/kotlin/ktx/tiled/mapPropertiesTest.kt @@ -0,0 +1,45 @@ +package ktx.tiled + +import com.badlogic.gdx.maps.MapProperties +import org.junit.Assert.* +import org.junit.Test + +class MapPropertiesTest { + @Test + fun `should check if a property exists`() { + val mapProperties = MapProperties() + mapProperties.put("key", "value") + + val result = "key" in mapProperties + + assertTrue(result) + } + + @Test + fun `should check if a property exists given missing key`() { + val mapProperties = MapProperties() + + val result = "key" in mapProperties + + assertFalse(result) + } + + @Test + fun `should add property`() { + val mapProperties = MapProperties() + + mapProperties["key"] = "value" + + assertEquals("value", mapProperties["key"]) + } + + @Test + fun `should override property`() { + val mapProperties = MapProperties() + mapProperties.put("key", "old") + + mapProperties["key"] = "new" + + assertEquals("new", mapProperties["key"]) + } +} From 96402cf190713e6338b2c2288976a49ae90d4abd Mon Sep 17 00:00:00 2001 From: MJ Date: Mon, 23 Dec 2019 20:20:46 +0100 Subject: [PATCH 18/32] Refactored ktx-tiled extensions. #233 --- tiled/src/main/kotlin/ktx/tiled/exceptions.kt | 2 +- tiled/src/main/kotlin/ktx/tiled/mapLayers.kt | 34 +++---- tiled/src/main/kotlin/ktx/tiled/mapObjects.kt | 74 +++++++-------- .../main/kotlin/ktx/tiled/tiledMapTileSets.kt | 34 +++---- .../main/kotlin/ktx/tiled/tiledMapTiles.kt | 34 +++---- tiled/src/main/kotlin/ktx/tiled/tiledMaps.kt | 92 ++++++++++--------- .../test/kotlin/ktx/tiled/mapLayersTest.kt | 38 ++++---- .../test/kotlin/ktx/tiled/mapObjectsTest.kt | 88 +++++++++++------- .../kotlin/ktx/tiled/tiledMapTileSetsTest.kt | 38 ++++---- .../kotlin/ktx/tiled/tiledMapTilesTest.kt | 63 ++++++++----- .../test/kotlin/ktx/tiled/tiledMapsTest.kt | 91 ++++++++++-------- 11 files changed, 333 insertions(+), 255 deletions(-) diff --git a/tiled/src/main/kotlin/ktx/tiled/exceptions.kt b/tiled/src/main/kotlin/ktx/tiled/exceptions.kt index ac1f7fdb..304e61a9 100644 --- a/tiled/src/main/kotlin/ktx/tiled/exceptions.kt +++ b/tiled/src/main/kotlin/ktx/tiled/exceptions.kt @@ -10,7 +10,7 @@ import com.badlogic.gdx.utils.GdxRuntimeException /** * Common type of exceptions thrown by the Tiled API extensions. */ -open class TiledException(message: String, cause: Throwable? = null): GdxRuntimeException(message, cause) +open class TiledException(message: String, cause: Throwable? = null) : GdxRuntimeException(message, cause) /** * [GdxRuntimeException] that is thrown when trying to access a non-existing property diff --git a/tiled/src/main/kotlin/ktx/tiled/mapLayers.kt b/tiled/src/main/kotlin/ktx/tiled/mapLayers.kt index c1bbf6bb..6f6252db 100644 --- a/tiled/src/main/kotlin/ktx/tiled/mapLayers.kt +++ b/tiled/src/main/kotlin/ktx/tiled/mapLayers.kt @@ -6,33 +6,35 @@ import com.badlogic.gdx.maps.MapProperties /** * Extension method to directly access the [MapProperties] of a [MapLayer]. If the property * is not defined then this method throws a [MissingPropertyException]. - * @param key property name - * @return value of the property - * @throws MissingPropertyException If the property is not defined + * @param key property name. + * @return value of the property. + * @throws MissingPropertyException If the property is not defined. */ -inline fun MapLayer.property(key: String): T = this.properties[key, T::class.java] - ?: throw MissingPropertyException("Property $key does not exist for layer ${this.name}") +inline fun MapLayer.property(key: String): T = properties[key, T::class.java] + ?: throw MissingPropertyException("Property $key does not exist for layer $name") /** * Extension method to directly access the [MapProperties] of a [MapLayer]. The type is automatically - * derived from the type of the given default value. If the property is not defined the defaultValue will be returned. - * @param key property name - * @param defaultValue default value in case the property is missing - * @return value of the property or defaultValue if property is missing + * derived from the type of the given default value. If the property is not defined the [defaultValue] + * will be returned. + * @param key property name. + * @param defaultValue default value in case the property is missing. + * @return value of the property or defaultValue if property is missing. */ -inline fun MapLayer.property(key: String, defaultValue: T): T = this.properties[key, defaultValue, T::class.java] +inline fun MapLayer.property(key: String, defaultValue: T): T = properties[key, defaultValue, T::class.java] /** * Extension method to directly access the [MapProperties] of a [MapLayer]. If the property * is not defined then this method returns null. - * @param key property name - * @return value of the property or null if the property is missing + * @param key property name. + * @return value of the property or null if the property is missing. */ -inline fun MapLayer.propertyOrNull(key: String): T? = this.properties[key, T::class.java] +inline fun MapLayer.propertyOrNull(key: String): T? = properties[key, T::class.java] /** - * Extension method to directly access the [MapProperties] of a [MapLayer] and its [containsKey][MapProperties.containsKey] method - * @param key property name - * @return true if the property exists. Otherwise false + * Extension method to directly access the [MapProperties] of a [MapLayer] and its + * [containsKey][MapProperties.containsKey] method. + * @param key property name. + * @return true if the property exists. Otherwise false. */ fun MapLayer.containsProperty(key: String) = properties.containsKey(key) diff --git a/tiled/src/main/kotlin/ktx/tiled/mapObjects.kt b/tiled/src/main/kotlin/ktx/tiled/mapObjects.kt index 22629136..8ea079d7 100644 --- a/tiled/src/main/kotlin/ktx/tiled/mapObjects.kt +++ b/tiled/src/main/kotlin/ktx/tiled/mapObjects.kt @@ -8,82 +8,84 @@ import com.badlogic.gdx.math.* /** * Extension method to directly access the [MapProperties] of a [MapObject]. If the property * is not defined then this method throws a [MissingPropertyException]. - * @param key property name - * @return value of the property - * @throws MissingPropertyException If the property is not defined + * @param key property name. + * @return value of the property. + * @throws MissingPropertyException If the property is not defined. */ -inline fun MapObject.property(key: String): T = this.properties[key, T::class.java] - ?: throw MissingPropertyException("Property $key does not exist for object ${this.name}") +inline fun MapObject.property(key: String): T = properties[key, T::class.java] + ?: throw MissingPropertyException("Property $key does not exist for object $name") /** * Extension method to directly access the [MapProperties] of a [MapObject]. The type is automatically - * derived from the type of the given default value. If the property is not defined the defaultValue will be returned. - * @param key property name - * @param defaultValue default value in case the property is missing - * @return value of the property or defaultValue if property is missing + * derived from the type of the given default value. If the property is not defined the [defaultValue] + * will be returned. + * @param key property name. + * @param defaultValue default value in case the property is missing. + * @return value of the property or defaultValue if property is missing. */ -inline fun MapObject.property(key: String, defaultValue: T): T = this.properties[key, defaultValue, T::class.java] +inline fun MapObject.property(key: String, defaultValue: T): T = properties[key, defaultValue, T::class.java] /** * Extension method to directly access the [MapProperties] of a [MapObject]. If the property * is not defined then this method returns null. - * @param key property name - * @return value of the property or null if the property is missing + * @param key property name. + * @return value of the property or null if the property is missing. */ -inline fun MapObject.propertyOrNull(key: String): T? = this.properties[key, T::class.java] +inline fun MapObject.propertyOrNull(key: String): T? = properties[key, T::class.java] /** - * Extension method to directly access the [MapProperties] of a [MapObject] and its [containsKey][MapProperties.containsKey] method - * @param key property name - * @return true if the property exists. Otherwise false + * Extension method to directly access the [MapProperties] of a [MapObject] and its + * [containsKey][MapProperties.containsKey] method. + * @param key property name. + * @return true if the property exists. Otherwise false. */ fun MapObject.containsProperty(key: String) = properties.containsKey(key) /** - * Extension property to retrieve the x-coordinate of the [MapObject] - * @throws MissingPropertyException if property x does not exist + * Extension property to retrieve the x-coordinate of the [MapObject]. + * @throws MissingPropertyException if property x does not exist. */ val MapObject.x: Float get() = property("x") /** - * Extension property to retrieve the y-coordinate of the [MapObject] - * @throws MissingPropertyException if property y does not exist + * Extension property to retrieve the y-coordinate of the [MapObject]. + * @throws MissingPropertyException if property y does not exist. */ val MapObject.y: Float get() = property("y") /** - * Extension property to retrieve the width of the [MapObject] - * @throws MissingPropertyException if property width does not exist + * Extension property to retrieve the width of the [MapObject]. + * @throws MissingPropertyException if property width does not exist. */ val MapObject.width: Float get() = property("width") /** - * Extension property to retrieve the height of the [MapObject] - * @throws MissingPropertyException if property height does not exist + * Extension property to retrieve the height of the [MapObject]. + * @throws MissingPropertyException if property height does not exist. */ val MapObject.height: Float get() = property("height") /** - * Extension property to retrieve the unique ID of the [MapObject] - * @throws MissingPropertyException if property id does not exist + * Extension property to retrieve the unique ID of the [MapObject]. + * @throws MissingPropertyException if property id does not exist. */ val MapObject.id: Int get() = property("id") /** - * Extension property to retrieve the rotation of the [MapObject] - * @throws MissingPropertyException if property rotation does not exist + * Extension property to retrieve the rotation of the [MapObject]. + * @throws MissingPropertyException if property rotation does not exist. */ val MapObject.rotation: Float get() = property("rotation") /** - * Extension property to retrieve the type of the [MapObject] - * @throws MissingPropertyException if property type does not exist + * Extension property to retrieve the type of the [MapObject]. + * @throws MissingPropertyException if property type does not exist. */ val MapObject.type: String get() = property("type") @@ -103,10 +105,10 @@ val MapObject.type: String */ val MapObject.shape: Shape2D get() = when (this) { - is CircleMapObject -> this.circle - is EllipseMapObject -> this.ellipse - is PolylineMapObject -> this.polyline - is PolygonMapObject -> this.polygon - is RectangleMapObject -> this.rectangle - else -> throw MissingShapeException("MapObject of type ${this::class.java} does not have a shape") + is CircleMapObject -> circle + is EllipseMapObject -> ellipse + is PolylineMapObject -> polyline + is PolygonMapObject -> polygon + is RectangleMapObject -> rectangle + else -> throw MissingShapeException("MapObject of type ${this::class.java} does not have a shape.") } diff --git a/tiled/src/main/kotlin/ktx/tiled/tiledMapTileSets.kt b/tiled/src/main/kotlin/ktx/tiled/tiledMapTileSets.kt index 88f1c898..57489066 100644 --- a/tiled/src/main/kotlin/ktx/tiled/tiledMapTileSets.kt +++ b/tiled/src/main/kotlin/ktx/tiled/tiledMapTileSets.kt @@ -6,33 +6,35 @@ import com.badlogic.gdx.maps.tiled.TiledMapTileSet /** * Extension method to directly access the [MapProperties] of a [TiledMapTileSet]. If the property * is not defined then this method throws a [MissingPropertyException]. - * @param key property name - * @return value of the property - * @throws MissingPropertyException If the property is not defined + * @param key property name. + * @return value of the property. + * @throws MissingPropertyException If the property is not defined. */ -inline fun TiledMapTileSet.property(key: String): T = this.properties[key, T::class.java] - ?: throw MissingPropertyException("Property $key does not exist for tileset ${this.name}") +inline fun TiledMapTileSet.property(key: String): T = properties[key, T::class.java] + ?: throw MissingPropertyException("Property $key does not exist for tile set $name") /** * Extension method to directly access the [MapProperties] of a [TiledMapTileSet]. The type is automatically - * derived from the type of the given default value. If the property is not defined the defaultValue will be returned. - * @param key property name - * @param defaultValue default value in case the property is missing - * @return value of the property or defaultValue if property is missing + * derived from the type of the given default value. If the property is not defined the [defaultValue] + * will be returned. + * @param key property name. + * @param defaultValue default value in case the property is missing. + * @return value of the property or defaultValue if property is missing. */ -inline fun TiledMapTileSet.property(key: String, defaultValue: T): T = this.properties[key, defaultValue, T::class.java] +inline fun TiledMapTileSet.property(key: String, defaultValue: T): T = properties[key, defaultValue, T::class.java] /** * Extension method to directly access the [MapProperties] of a [TiledMapTileSet]. If the property * is not defined then this method returns null. - * @param key property name - * @return value of the property or null if the property is missing + * @param key property name. + * @return value of the property or null if the property is missing. */ -inline fun TiledMapTileSet.propertyOrNull(key: String): T? = this.properties[key, T::class.java] +inline fun TiledMapTileSet.propertyOrNull(key: String): T? = properties[key, T::class.java] /** - * Extension method to directly access the [MapProperties] of a [TiledMapTileSet] and its [containsKey][MapProperties.containsKey] method - * @param key property name - * @return true if the property exists. Otherwise false + * Extension method to directly access the [MapProperties] of a [TiledMapTileSet] and its + * [containsKey][MapProperties.containsKey] method. + * @param key property name. + * @return true if the property exists. Otherwise false. */ fun TiledMapTileSet.containsProperty(key: String) = properties.containsKey(key) diff --git a/tiled/src/main/kotlin/ktx/tiled/tiledMapTiles.kt b/tiled/src/main/kotlin/ktx/tiled/tiledMapTiles.kt index 30a816c2..267c4ea3 100644 --- a/tiled/src/main/kotlin/ktx/tiled/tiledMapTiles.kt +++ b/tiled/src/main/kotlin/ktx/tiled/tiledMapTiles.kt @@ -6,33 +6,35 @@ import com.badlogic.gdx.maps.tiled.TiledMapTile /** * Extension method to directly access the [MapProperties] of a [TiledMapTile]. If the property * is not defined then this method throws a [MissingPropertyException]. - * @param key property name - * @return value of the property - * @throws MissingPropertyException If the property is not defined + * @param key property name. + * @return value of the property. + * @throws MissingPropertyException If the property is not defined. */ -inline fun TiledMapTile.property(key: String): T = this.properties[key, T::class.java] - ?: throw MissingPropertyException("Property $key does not exist for tile ${this.id}") +inline fun TiledMapTile.property(key: String): T = properties[key, T::class.java] + ?: throw MissingPropertyException("Property $key does not exist for tile $id") /** * Extension method to directly access the [MapProperties] of a [TiledMapTile]. The type is automatically - * derived from the type of the given default value. If the property is not defined the defaultValue will be returned. - * @param key property name - * @param defaultValue default value in case the property is missing - * @return value of the property or defaultValue if property is missing + * derived from the type of the given default value. If the property is not defined the [defaultValue] + * will be returned. + * @param key property name. + * @param defaultValue default value in case the property is missing. + * @return value of the property or defaultValue if property is missing. */ -inline fun TiledMapTile.property(key: String, defaultValue: T): T = this.properties[key, defaultValue, T::class.java] +inline fun TiledMapTile.property(key: String, defaultValue: T): T = properties[key, defaultValue, T::class.java] /** * Extension method to directly access the [MapProperties] of a [TiledMapTile]. If the property * is not defined then this method returns null. - * @param key property name - * @return value of the property or null if the property is missing + * @param key property name. + * @return value of the property or null if the property is missing. */ -inline fun TiledMapTile.propertyOrNull(key: String): T? = this.properties[key, T::class.java] +inline fun TiledMapTile.propertyOrNull(key: String): T? = properties[key, T::class.java] /** - * Extension method to directly access the [MapProperties] of a [TiledMapTile] and its [containsKey][MapProperties.containsKey] method - * @param key property name - * @return true if the property exists. Otherwise false + * Extension method to directly access the [MapProperties] of a [TiledMapTile] and its + * [containsKey][MapProperties.containsKey] method. + * @param key property name. + * @return true if the property exists. Otherwise false. */ fun TiledMapTile.containsProperty(key: String) = properties.containsKey(key) diff --git a/tiled/src/main/kotlin/ktx/tiled/tiledMaps.kt b/tiled/src/main/kotlin/ktx/tiled/tiledMaps.kt index 6346d7ab..dbcf5eb5 100644 --- a/tiled/src/main/kotlin/ktx/tiled/tiledMaps.kt +++ b/tiled/src/main/kotlin/ktx/tiled/tiledMaps.kt @@ -8,96 +8,98 @@ import com.badlogic.gdx.maps.tiled.TiledMap /** * Extension method to directly access the [MapProperties] of a [TiledMap]. If the property * is not defined then this method throws a [MissingPropertyException]. - * @param key property name - * @return value of the property - * @throws MissingPropertyException If the property is not defined + * @param key property name. + * @return value of the property. + * @throws MissingPropertyException If the property is not defined. */ -inline fun TiledMap.property(key: String): T = this.properties[key, T::class.java] - ?: throw MissingPropertyException("Property $key does not exist for map") +inline fun TiledMap.property(key: String): T = properties[key, T::class.java] + ?: throw MissingPropertyException("Property $key does not exist.") /** * Extension method to directly access the [MapProperties] of a [TiledMap]. The type is automatically - * derived from the type of the given default value. If the property is not defined the defaultValue will be returned. - * @param key property name - * @param defaultValue default value in case the property is missing - * @return value of the property or defaultValue if property is missing + * derived from the type of the given default value. If the property is not defined the [defaultValue] + * will be returned. + * @param key property name. + * @param defaultValue default value in case the property is missing. + * @return value of the property or defaultValue if property is missing. */ -inline fun TiledMap.property(key: String, defaultValue: T): T = this.properties[key, defaultValue, T::class.java] +inline fun TiledMap.property(key: String, defaultValue: T): T = properties[key, defaultValue, T::class.java] /** * Extension method to directly access the [MapProperties] of a [TiledMap]. If the property * is not defined then this method returns null. - * @param key property name - * @return value of the property or null if the property is missing + * @param key property name. + * @return value of the property or null if the property is missing. */ -inline fun TiledMap.propertyOrNull(key: String): T? = this.properties[key, T::class.java] +inline fun TiledMap.propertyOrNull(key: String): T? = properties[key, T::class.java] /** - * Extension method to directly access the [MapProperties] of a [TiledMap] and its [containsKey][MapProperties.containsKey] method - * @param key property name - * @return true if the property exists. Otherwise false + * Extension method to directly access the [MapProperties] of a [TiledMap] and its + * [containsKey][MapProperties.containsKey] method. + * @param key property name. + * @return true if the property exists. Otherwise false. */ fun TiledMap.containsProperty(key: String) = properties.containsKey(key) /** - * Extension property to retrieve the width of the [TiledMap] - * @throws MissingPropertyException if property width does not exist + * Extension property to retrieve the width of the [TiledMap]. + * @throws MissingPropertyException if property width does not exist. */ val TiledMap.width: Int get() = property("width") /** - * Extension property to retrieve the height of the [TiledMap] - * @throws MissingPropertyException if property height does not exist + * Extension property to retrieve the height of the [TiledMap]. + * @throws MissingPropertyException if property height does not exist. */ val TiledMap.height: Int get() = property("height") /** - * Extension property to retrieve the tile width of each tile of the [TiledMap] - * @throws MissingPropertyException if property tilewidth does not exist + * Extension property to retrieve the tile width of each tile of the [TiledMap]. + * @throws MissingPropertyException if property tilewidth does not exist. */ val TiledMap.tileWidth: Int get() = property("tilewidth") /** - * Extension property to retrieve the tile height of each tile of the [TiledMap] - * @throws MissingPropertyException if property tileheight does not exist + * Extension property to retrieve the tile height of each tile of the [TiledMap]. + * @throws MissingPropertyException if property tileheight does not exist. */ val TiledMap.tileHeight: Int get() = property("tileheight") /** - * Extension property to retrieve the background color of the [TiledMap] - * @throws MissingPropertyException if property backgroundcolor does not exist + * Extension property to retrieve the background color of the [TiledMap]. + * @throws MissingPropertyException if property backgroundcolor does not exist. */ val TiledMap.backgroundColor: String get() = property("backgroundcolor") /** - * Extension property to retrieve the orientation of the [TiledMap] - * @throws MissingPropertyException if property orientation does not exist + * Extension property to retrieve the orientation of the [TiledMap]. + * @throws MissingPropertyException if property orientation does not exist. */ val TiledMap.orientation: String get() = property("orientation") /** - * Extension property to retrieve the hex side length of a hexagonal [TiledMap] - * @throws MissingPropertyException if property hexsidelength does not exist + * Extension property to retrieve the hex side length of a hexagonal [TiledMap]. + * @throws MissingPropertyException if property hexsidelength does not exist. */ val TiledMap.hexSideLength: Int get() = property("hexsidelength") /** - * Extension property to retrieve the stagger axis of the [TiledMap] - * @throws MissingPropertyException if property staggeraxis does not exist + * Extension property to retrieve the stagger axis of the [TiledMap]. + * @throws MissingPropertyException if property staggeraxis does not exist. */ val TiledMap.staggerAxis: String get() = property("staggeraxis") /** - * Extension property to retrieve the stagger index of the [TiledMap] - * @throws MissingPropertyException if property staggerindex does not exist + * Extension property to retrieve the stagger index of the [TiledMap]. + * @throws MissingPropertyException if property staggerindex does not exist. */ val TiledMap.staggerIndex: String get() = property("staggerindex") @@ -106,10 +108,10 @@ val TiledMap.staggerIndex: String * Extension method to retrieve the total width of the [TiledMap]. It is the result of the * width multiplied by the tile width of the map. * - * @see [width] - * @see [tileWidth] + * @see [TiledMap.width] + * @see [TiledMap.tileWidth] * - * @return total width in pixels + * @return total width in pixels. */ fun TiledMap.totalWidth() = width * tileWidth @@ -117,17 +119,17 @@ fun TiledMap.totalWidth() = width * tileWidth * Extension method to retrieve the total height of the [TiledMap]. It is the result of the * height multiplied by the tile height of the map. * - * @see [height] - * @see [tileHeight] + * @see [TiledMap.height] + * @see [TiledMap.tileHeight] * - * @return total height in pixels + * @return total height in pixels. */ fun TiledMap.totalHeight() = height * tileHeight /** * Extension operator to check if a certain [MapLayer] is part of the [TiledMap] - * @param layerName name of [MapLayer] - * @return true if and only if the layer does exist + * @param layerName name of [MapLayer]. + * @return true if and only if the layer does exist. */ operator fun TiledMap.contains(layerName: String) = layers[layerName] != null @@ -147,6 +149,8 @@ fun TiledMap.layer(layerName: String) = layers[layerName] * @param layerName name of [MapLayer] * @param action action to execute per [MapObject] of the [MapLayer] */ -fun TiledMap.forEachMapObject(layerName: String, action: (MapObject) -> Unit) { - layers[layerName]?.objects?.forEach { action(it) } +inline fun TiledMap.forEachMapObject(layerName: String, action: (MapObject) -> Unit) { + layers[layerName]?.objects?.forEach { + action(it) + } } diff --git a/tiled/src/test/kotlin/ktx/tiled/mapLayersTest.kt b/tiled/src/test/kotlin/ktx/tiled/mapLayersTest.kt index 2cd228a7..66f61d59 100644 --- a/tiled/src/test/kotlin/ktx/tiled/mapLayersTest.kt +++ b/tiled/src/test/kotlin/ktx/tiled/mapLayersTest.kt @@ -1,38 +1,44 @@ package ktx.tiled import com.badlogic.gdx.maps.MapLayer -import org.junit.Assert +import org.junit.Assert.* import org.junit.Test class MapLayerTest { private val mapLayer = MapLayer().apply { - properties.put("active", true) - properties.put("customProperty", 123) + properties.apply { + put("active", true) + put("customProperty", 123) + } } @Test - fun `retrieve properties from MapLayer with default value`() { - Assert.assertEquals(true, mapLayer.property("active", false)) - Assert.assertEquals(123, mapLayer.property("customProperty", 0)) - Assert.assertEquals(-1f, mapLayer.property("x", -1f)) + fun `should retrieve properties from MapLayer`() { + assertEquals(true, mapLayer.property("active")) + assertEquals(123, mapLayer.property("customProperty")) } @Test - fun `retrieve properties from MapLayer without default value`() { - Assert.assertNull(mapLayer.propertyOrNull("x")) - val customProperty: Int? = mapLayer.propertyOrNull("customProperty") - Assert.assertNotNull(customProperty) - Assert.assertEquals(123, customProperty) + fun `should retrieve properties from MapLayer with default value`() { + assertEquals(true, mapLayer.property("active", false)) + assertEquals(123, mapLayer.property("customProperty", 0)) + assertEquals(-1f, mapLayer.property("x", -1f)) } @Test - fun `check if property from MapLayer exists`() { - Assert.assertTrue(mapLayer.containsProperty("active")) - Assert.assertFalse(mapLayer.containsProperty("x")) + fun `should retrieve properties from MapLayer without default value`() { + assertNull(mapLayer.propertyOrNull("x")) + assertEquals(123, mapLayer.propertyOrNull("customProperty")) + } + + @Test + fun `should check if property from MapLayer exists`() { + assertTrue(mapLayer.containsProperty("active")) + assertFalse(mapLayer.containsProperty("x")) } @Test(expected = MissingPropertyException::class) - fun `retrieve non-existing property from MapLayer using exception`() { + fun `should not retrieve non-existing property from MapLayer`() { mapLayer.property("non-existing") } } diff --git a/tiled/src/test/kotlin/ktx/tiled/mapObjectsTest.kt b/tiled/src/test/kotlin/ktx/tiled/mapObjectsTest.kt index f19c3c1b..e41cef61 100644 --- a/tiled/src/test/kotlin/ktx/tiled/mapObjectsTest.kt +++ b/tiled/src/test/kotlin/ktx/tiled/mapObjectsTest.kt @@ -10,30 +10,28 @@ import org.junit.Test class MapObjectTest { private val mapObject = MapObject().apply { - properties.also { - it.put("id", 13) - it.put("x", 1) - it.put("y", 0f) - it.put("rotation", -2.33f) - it.put("type", "SomeType") - it.put("width", 1f) - it.put("name", "Property") - it.put("active", true) + properties.apply { + put("id", 13) + put("x", 1) + put("y", 0f) + put("rotation", -2.33f) + put("type", "SomeType") + put("width", 1f) + put("name", "Property") + put("active", true) } } - private val polylineVertices = floatArrayOf(0f, 0f, 1f, 1f) - private val polygonVertices = floatArrayOf(0f, 0f, 1f, 1f, 2f, 0f) - - private val circleObject = CircleMapObject() - private val ellipseObject = EllipseMapObject() - private val polylineObject = PolylineMapObject(polylineVertices) - private val polygonObject = PolygonMapObject(polygonVertices) - private val rectObject = RectangleMapObject() - private val textureObject = TextureMapObject() + @Test + fun `should retrieve properties from MapObject`() { + assertEquals(1, mapObject.property("x")) + assertEquals(1f, mapObject.property("width")) + assertEquals("Property", mapObject.property("name")) + assertEquals(true, mapObject.property("active")) + } @Test - fun `retrieve properties from MapObject with default value`() { + fun `should retrieve properties from MapObject with default value`() { assertEquals(1, mapObject.property("x", 0)) assertEquals(0, mapObject.property("non-existing", 0)) assertEquals(1f, mapObject.property("width", 0f)) @@ -42,21 +40,19 @@ class MapObjectTest { } @Test - fun `retrieve properties from MapObject without default value`() { + fun `should retrieve properties from MapObject without default value`() { assertNull(mapObject.propertyOrNull("non-existing")) - val x: Int? = mapObject.propertyOrNull("x") - assertNotNull(x) - assertEquals(1, x) + assertEquals(1, mapObject.propertyOrNull("x")) } @Test - fun `check if property from MapObject exists`() { + fun `should check if property from MapObject exists`() { assertTrue(mapObject.containsProperty("x")) assertFalse(mapObject.containsProperty("non-existing")) } @Test - fun `retrieve standard properties of MapObject`() { + fun `should retrieve standard properties of MapObject`() { assertEquals(1f, mapObject.x) assertEquals(0f, mapObject.y) assertEquals(13, mapObject.id) @@ -64,22 +60,52 @@ class MapObjectTest { assertEquals("SomeType", mapObject.type) } + @Test(expected = MissingPropertyException::class) + fun `should not retrieve non-existing property from MapObject`() { + mapObject.property("non-existing") + } + @Test - fun `retrieve shape from MapObject`() { + fun `should retrieve shape from MapObject with Circle type`() { + val circleObject = CircleMapObject() + assertEquals(Circle(0f, 0f, 1f), circleObject.shape) + } + + @Test + fun `should retrieve shape from MapObject with Ellipse type`() { + val ellipseObject = EllipseMapObject() + assertEquals(Ellipse(0f, 0f, 1f, 1f), ellipseObject.shape) + } + + @Test + fun `should retrieve shape from MapObject with Polyline type`() { + val polylineVertices = floatArrayOf(0f, 0f, 1f, 1f) + val polylineObject = PolylineMapObject(polylineVertices) + assertEquals(polylineObject.polyline, polylineObject.shape) + } + + @Test + fun `should retrieve shape from MapObject with Polygon type`() { + val polygonVertices = floatArrayOf(0f, 0f, 1f, 1f, 2f, 0f) + val polygonObject = PolygonMapObject(polygonVertices) + assertEquals(polygonObject.polygon, polygonObject.shape) + } + + @Test + fun `should retrieve shape from MapObject with Rectangle type`() { + val rectObject = RectangleMapObject() + assertEquals(Rectangle(0f, 0f, 1f, 1f), rectObject.shape) } @Test(expected = MissingShapeException::class) fun `retrieve shape from unsupported MapObject`() { - textureObject.shape - } + val textureObject = TextureMapObject() - @Test(expected = MissingPropertyException::class) - fun `retrieve non-existing property from MapObject using exception`() { - mapObject.property("non-existing") + textureObject.shape } } diff --git a/tiled/src/test/kotlin/ktx/tiled/tiledMapTileSetsTest.kt b/tiled/src/test/kotlin/ktx/tiled/tiledMapTileSetsTest.kt index 333d2117..a6b12413 100644 --- a/tiled/src/test/kotlin/ktx/tiled/tiledMapTileSetsTest.kt +++ b/tiled/src/test/kotlin/ktx/tiled/tiledMapTileSetsTest.kt @@ -1,38 +1,44 @@ package ktx.tiled import com.badlogic.gdx.maps.tiled.TiledMapTileSet -import org.junit.Assert +import org.junit.Assert.* import org.junit.Test class TiledMapTileSetTest { private val tileset = TiledMapTileSet().apply { - properties.put("tilesetProp1", true) - properties.put("tilesetProp2", 123) + properties.apply { + put("tilesetProp1", true) + put("tilesetProp2", 123) + } } @Test - fun `retrieve properties from TileSet with default value`() { - Assert.assertEquals(true, tileset.property("tilesetProp1", false)) - Assert.assertEquals(123, tileset.property("tilesetProp2", 0)) - Assert.assertEquals(-1f, tileset.property("non-existing", -1f)) + fun `should retrieve properties from TileSet`() { + assertEquals(true, tileset.property("tilesetProp1")) + assertEquals(123, tileset.property("tilesetProp2")) } @Test - fun `retrieve properties from TileSet without default value`() { - Assert.assertNull(tileset.propertyOrNull("non-existing")) - val customProperty: Int? = tileset.propertyOrNull("tilesetProp2") - Assert.assertNotNull(customProperty) - Assert.assertEquals(123, customProperty) + fun `should retrieve properties from TileSet with default value`() { + assertEquals(true, tileset.property("tilesetProp1", false)) + assertEquals(123, tileset.property("tilesetProp2", 0)) + assertEquals(-1f, tileset.property("non-existing", -1f)) } @Test - fun `check if property from TileSet exists`() { - Assert.assertTrue(tileset.containsProperty("tilesetProp1")) - Assert.assertFalse(tileset.containsProperty("non-existing")) + fun `should retrieve properties from TileSet without default value`() { + assertNull(tileset.propertyOrNull("non-existing")) + assertEquals(123, tileset.propertyOrNull("tilesetProp2")) + } + + @Test + fun `should check if property from TileSet exists`() { + assertTrue(tileset.containsProperty("tilesetProp1")) + assertFalse(tileset.containsProperty("non-existing")) } @Test(expected = MissingPropertyException::class) - fun `retrieve non-existing property from TileSet using exception`() { + fun `should not retrieve non-existing property from TileSet`() { tileset.property("non-existing") } } diff --git a/tiled/src/test/kotlin/ktx/tiled/tiledMapTilesTest.kt b/tiled/src/test/kotlin/ktx/tiled/tiledMapTilesTest.kt index 6a6f74f1..0516e5c4 100644 --- a/tiled/src/test/kotlin/ktx/tiled/tiledMapTilesTest.kt +++ b/tiled/src/test/kotlin/ktx/tiled/tiledMapTilesTest.kt @@ -1,47 +1,62 @@ package ktx.tiled import com.badlogic.gdx.graphics.g2d.TextureRegion +import com.badlogic.gdx.maps.tiled.TiledMapTile import com.badlogic.gdx.maps.tiled.tiles.AnimatedTiledMapTile import com.badlogic.gdx.maps.tiled.tiles.StaticTiledMapTile import com.badlogic.gdx.utils.Array -import org.junit.Assert +import org.junit.Assert.* import org.junit.Test -class TiledMapTileTest { - private val staticTile = StaticTiledMapTile(TextureRegion()).apply { - properties.put("staticProp1", true) - properties.put("staticProp2", 123) - } - private val animatedTile = AnimatedTiledMapTile(0f, Array()).apply { - properties.put("aniProp1", 1f) - properties.put("aniProp2", "SomeText") +abstract class TiledMapTileTest { + abstract val tile: T + + @Test + fun `should retrieve properties from tile`() { + assertEquals(true, tile.property("prop1")) + assertEquals("text", tile.property("prop2")) } @Test - fun `retrieve properties from Tile with default value`() { - Assert.assertEquals(true, staticTile.property("staticProp1", false)) - Assert.assertEquals(123, staticTile.property("staticProp2", 0)) - Assert.assertEquals(-1f, animatedTile.property("non-existing", -1f)) + fun `should retrieve properties from tile with default value`() { + assertEquals(true, tile.property("prop1", false)) + assertEquals("text", tile.property("prop2", "")) + assertEquals(-1f, tile.property("non-existing", -1f)) } @Test - fun `retrieve properties from Tile without default value`() { - Assert.assertNull(animatedTile.propertyOrNull("non-existing")) - val customProperty: Int? = staticTile.propertyOrNull("staticProp2") - Assert.assertNotNull(customProperty) - Assert.assertEquals(123, customProperty) + fun `should retrieve properties from tile without default value`() { + assertNull(tile.propertyOrNull("non-existing")) + assertEquals("text", tile.propertyOrNull("prop2")) } @Test - fun `check if property from Tile exists`() { - Assert.assertTrue(staticTile.containsProperty("staticProp1")) - Assert.assertFalse(staticTile.containsProperty("non-existing")) - Assert.assertTrue(animatedTile.containsProperty("aniProp2")) - Assert.assertFalse(animatedTile.containsProperty("non-existing")) + fun `should check if property from tile exists`() { + assertTrue(tile.containsProperty("prop1")) + assertTrue(tile.containsProperty("prop2")) + assertFalse(tile.containsProperty("non-existing")) } @Test(expected = MissingPropertyException::class) fun `retrieve non-existing property from Tile using exception`() { - animatedTile.property("non-existing") + tile.property("non-existing") + } +} + +class StaticTiledMapTileTest : TiledMapTileTest() { + override val tile = StaticTiledMapTile(TextureRegion()).apply { + properties.apply { + put("prop1", true) + put("prop2", "text") + } + } +} + +class AnimatedTiledMapTileTest : TiledMapTileTest() { + override val tile = AnimatedTiledMapTile(1f, Array()).apply { + properties.apply { + put("prop1", true) + put("prop2", "text") + } } } diff --git a/tiled/src/test/kotlin/ktx/tiled/tiledMapsTest.kt b/tiled/src/test/kotlin/ktx/tiled/tiledMapsTest.kt index 1cc10c4a..eb7937b8 100644 --- a/tiled/src/test/kotlin/ktx/tiled/tiledMapsTest.kt +++ b/tiled/src/test/kotlin/ktx/tiled/tiledMapsTest.kt @@ -8,48 +8,55 @@ import org.junit.Test class TiledMapTest { private val tiledMap = TiledMap().apply { - properties.put("width", 16) - properties.put("height", 8) - properties.put("tilewidth", 32) - properties.put("tileheight", 32) - - properties.put("backgroundcolor", "#ffffff") - properties.put("orientation", "orthogonal") - properties.put("hexsidelength", 0) - properties.put("staggeraxis", "Y") - properties.put("staggerindex", "Odd") - + properties.apply { + put("width", 16) + put("height", 8) + put("tilewidth", 32) + put("tileheight", 32) + put("backgroundcolor", "#ffffff") + put("orientation", "orthogonal") + put("hexsidelength", 0) + put("staggeraxis", "Y") + put("staggerindex", "Odd") + } layers.add(MapLayer().apply { name = "layer-1" - objects.add(MapObject()) - objects.add(MapObject()) - objects.add(MapObject()) + objects.apply { + add(MapObject()) + add(MapObject()) + add(MapObject()) + } + }) + layers.add(MapLayer().apply { + name = "layer-2" }) - layers.add(MapLayer().apply { name = "layer-2" }) } @Test - fun `retrieve properties from TiledMap with default value`() { + fun `should retrieve properties from TiledMap`() { + assertEquals(16, tiledMap.property("width")) + } + + @Test + fun `should retrieve properties from TiledMap with default value`() { assertEquals(16, tiledMap.property("width", 0)) assertEquals(-1, tiledMap.property("x", -1)) } @Test - fun `retrieve properties from TiledMap without default value`() { + fun `should retrieve properties from TiledMap without default value`() { assertNull(tiledMap.propertyOrNull("x")) - val width: Int? = tiledMap.propertyOrNull("width") - assertNotNull(width) - assertEquals(16, width) + assertEquals(16, tiledMap.propertyOrNull("width")) } @Test - fun `check if property from TiledMap exists`() { + fun `should check if property from TiledMap exists`() { assertTrue(tiledMap.containsProperty("width")) assertFalse(tiledMap.containsProperty("x")) } @Test - fun `retrieve standard properties of TiledMap`() { + fun `should retrieve standard properties of TiledMap`() { assertEquals(16, tiledMap.width) assertEquals(8, tiledMap.height) assertEquals(32, tiledMap.tileWidth) @@ -64,47 +71,53 @@ class TiledMapTest { } @Test(expected = MissingPropertyException::class) - fun `retrieve non-existing property from TiledMap using exception`() { + fun `should not retrieve non-existing property from TiledMap`() { tiledMap.property("non-existing") } @Test - fun `retrieve existing layer from TiledMap`() { + fun `should retrieve existing layer from TiledMap`() { assertEquals("layer-1", tiledMap.layer("layer-1").name) } @Test(expected = MissingLayerException::class) - fun `retrieve non-existing layer from TiledMap using exception`() { + fun `should not retrieve non-existing layer from TiledMap`() { tiledMap.layer("non-existing") } @Test - fun `check if layer exists in TiledMap`() { + fun `should check if layer exists in TiledMap`() { assertTrue(tiledMap.contains("layer-1")) + assertFalse(tiledMap.contains("non-existing")) + + assertTrue("layer-1" in tiledMap) assertFalse("non-existing" in tiledMap) } @Test - fun `execute action per object of a layer`() { - // check that there are objects in layer -1 - assertEquals(3, tiledMap.layers["layer-1"].objects.count) - // verify that they are all visible and set them to not visible + fun `should execute action per object of a layer`() { var counter = 0 + tiledMap.forEachMapObject("layer-1") { - assertTrue(it.isVisible) it.isVisible = false counter++ } - // verify again that they are now all invisible and revert them back to being visible + assertEquals(3, counter) - tiledMap.forEachMapObject("layer-1") { - assertFalse(it.isVisible) - it.isVisible = true + assertTrue(tiledMap.layers["layer-1"].objects.all { !it.isVisible }) + } + + @Test + fun `should not execute any action for empty layer`() { + tiledMap.forEachMapObject("layer-2") { + fail() } + } - // also, test empty default layer which should do nothing - counter = 0 - tiledMap.forEachMapObject("non-existing") { ++counter } - assertEquals(0, counter) + @Test + fun `should not execute any action for non-existing layer`() { + tiledMap.forEachMapObject("non-existing") { + fail() + } } } From 97c9846f295551b2ef0ad1577c4998bebbbb266b Mon Sep 17 00:00:00 2001 From: MJ Date: Mon, 23 Dec 2019 20:42:15 +0100 Subject: [PATCH 19/32] Updated ktx-tiled documentation. #233 --- tiled/README.md | 143 ++++++++++++++++++++++++++---------------------- 1 file changed, 77 insertions(+), 66 deletions(-) diff --git a/tiled/README.md b/tiled/README.md index afd1f1fc..39e85d12 100644 --- a/tiled/README.md +++ b/tiled/README.md @@ -1,94 +1,84 @@ # KTX : TiledMap utilities -Tiled utilities for LibGDX applications written with Kotlin. +[Tiled](https://www.mapeditor.org/) API utilities. ### Why? -As usual with plain libgdx Java the code compared to Kotlin becomes boilerplate and is not -very easy to read especially for users who are not familiar with your code. -With the TiledMap functionality it becomes worse since there are a lot of wrapped collections -and iterators and accessing properties or iterating over objects in the map becomes very cumbersome. - -Luckily for us with Kotlin we can write extension functions and properties to make our life -a little bit easier and most notably make our code more readable and safe. - -Due to Kotlin's `reified` possibility we can retrieve properties without passing the `Class` -parameter and therefore also have this information during runtime. Also, most of the wrapper classes return `platform types (Any!)` -as the original Java code is null-unsafe. One of the goals of these extensions is also -to improve that situation and make it explicit so that a method either returns `null` or -throws an `exception`. +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 -#### Miscellaneous utilities - #### `MapProperties` -In most maps that you create with Tiled you will need access to the properties defined in the editor. -They are either defined on map, layer, object, tileset or tile level. The original libgdx -`MapProperties` class returns `Any!` whenever retrieving a property and is therefore not ideal and unsafe. +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. -For that reason four extension methods got added to `TiledMap`, `MapLayer`, `MapObject`, `TiledMapTileSet` -and `TiledMapTile`. Also, a new `MissingPropertyException` was added for one of these extensions which allows us -to get informed when trying to access a missing mandatory property: -- `property(key)`: returns an existing property or throws a `MissingPropertyException` -- `property(key, defaultValue`: returns the value of a property or the default value if the property is missing -- `propertyOrNull(key)`: same as `property(key)` but returns `null` instead of throwing an exception -- `containsProperty(key)`: returns `true` if and only if the property exists +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 get automatically set and initialized by the `TmxMapLoader`. Extension properties got added -to ease the access: +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 +- `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 property got added: +access to these shapes like e.g. when creating [Box2D](../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 +`MissingShapeException` is thrown. #### `TiledMap` -Similar to `MapObject` there are several standard properties for `TiledMap` as well: +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 +- `backgroundColor`: this property is only available if you explicitly select a background color for your map. - `orientation` - `hexSideLength` - `staggerAxis` - `staggerIndex` -Two new extensions will provide you with the total width and height of your map in pixels: +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 is also true for map layers. To improve the -situation here as well following extensions got added: -- `contains(layerName)` -- `layer(layerName)`: returns the layer or throws a `MissingLayerException` in case the layer does not exist +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. -Often you need to iterate over all objects of a layer to do something with them like -creating collision bodies for box2d or other things. To ease that use case a new -extension was added called `forEachMapObject` which takes a lambda that is executed for each -object of the given layer: -- `forEachMapObject(layerName, action)` +Inlined `forEachMapObject` extension methods allows to iterate over all `MapObject` instances present on the chosen +map layer. ### Usage examples #### General -Properties functionality is the same for `TiledMap`, `MapLayer`, `MapObject`, `TiledMapTileSet` and `TiledMapTile`: +Accessing properties of `TiledMap`, `MapLayer`, `MapObject`, `TiledMapTileSet` and `TiledMapTile`: ```kotlin import com.badlogic.gdx.maps.MapLayer @@ -96,30 +86,53 @@ 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() -// retrieve Float property - throws MissingPropertyException if missing +// Retrieves Float property - throws MissingPropertyException if missing: val myFloatProp: Float = mapObj.property("myFloatProperty") -// retrieve String property via defaultValue -val myProp: String = mapObj.property("myProperty", "") +// 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 +// 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", 1f) +val myProp2 = mapLayer.property("myProperty2", defaultValue=1f) -// retrieve Int property without defaultValue +// Retrieves Int property or null if the property does not exist. val myOtherProp: Int? = map.propertyOrNull("myOtherProperty") -// check if a certain property exists +// Check if a certain property exists: if (map.containsProperty("lightColor")) { changeAmbientLightColor(map.property("lightColor")) } ``` +Using `MapProperties` API: + +```kotlin +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`: @@ -129,17 +142,16 @@ import com.badlogic.gdx.maps.MapObject import com.badlogic.gdx.math.Rectangle import ktx.tiled.* - val mapObj: MapObject = getMapObject() -// retrieve position of object +// Retrieve position of an object: val x = mapObj.x val y = mapObj.y -// retrieve id of object +// Retrieves ID of an object: val id = mapObj.id -// retrieve shape +// 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 ``` @@ -152,27 +164,25 @@ Retrieving standard properties of a `TiledMap` like `width` or `tileheight`: import com.badlogic.gdx.maps.tiled.TiledMap import ktx.tiled.* - val map: TiledMap = getTiledMap() -// get map size in pixels to e.g. lock camera movement within map boundaries +// 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 +// Retrieve map size information: val width = map.width val height = map.height val tileWidth = map.tileWidth val tileHeight = map.tileHeight ``` -Check and retrieve a layer of a `TiledMap`: +Working with layers of a `TiledMap`: ```kotlin import com.badlogic.gdx.maps.tiled.TiledMap import ktx.tiled.* - val map: TiledMap = getTiledMap() // contains can either be used with the normal syntax @@ -198,7 +208,7 @@ import ktx.tiled.* val map: TiledMap = getTiledMap() -// create collision bodies for every map object of the collision layer +// Creates collision bodies for every map object of the collision layer: map.forEachMapObject("collision") { mapObj -> createStaticBox2DCollisionBody(mapObj.x, mapObj.y, mapObj.shape) } @@ -206,5 +216,6 @@ map.forEachMapObject("collision") { mapObj -> #### Additional documentation -- [LibGDX Tile maps official wiki.](https://github.com/libgdx/libgdx/wiki/Tile-maps) -- [Tiled editor official documentation](https://doc.mapeditor.org/en/stable/) +- [Official Tiled website.](https://www.mapeditor.org/) +- [LibGDX wiki article on tile maps.](https://github.com/libgdx/libgdx/wiki/Tile-maps) +- [Official Tiled documentation.](https://doc.mapeditor.org/en/stable/) From 8201d69d51fa8cf89f7101aa1c30a06a9ccf1b53 Mon Sep 17 00:00:00 2001 From: MJ Date: Mon, 23 Dec 2019 20:42:43 +0100 Subject: [PATCH 20/32] Selected standard properties are now nullable. #233 --- tiled/src/main/kotlin/ktx/tiled/mapObjects.kt | 14 ++++++-------- tiled/src/main/kotlin/ktx/tiled/tiledMaps.kt | 7 +++---- tiled/src/test/kotlin/ktx/tiled/mapObjectsTest.kt | 11 +++++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tiled/src/main/kotlin/ktx/tiled/mapObjects.kt b/tiled/src/main/kotlin/ktx/tiled/mapObjects.kt index 8ea079d7..6c07ddf9 100644 --- a/tiled/src/main/kotlin/ktx/tiled/mapObjects.kt +++ b/tiled/src/main/kotlin/ktx/tiled/mapObjects.kt @@ -77,18 +77,16 @@ val MapObject.id: Int get() = property("id") /** - * Extension property to retrieve the rotation of the [MapObject]. - * @throws MissingPropertyException if property rotation does not exist. + * Extension property to retrieve the rotation of the [MapObject]. Null if the property is unset. */ -val MapObject.rotation: Float - get() = property("rotation") +val MapObject.rotation: Float? + get() = propertyOrNull("rotation") /** - * Extension property to retrieve the type of the [MapObject]. - * @throws MissingPropertyException if property type does not exist. + * Extension property to retrieve the type of the [MapObject]. Null if the property is unset. */ -val MapObject.type: String - get() = property("type") +val MapObject.type: String? + get() = propertyOrNull("type") /** * Extension method to retrieve the [Shape2D] instance of a [MapObject]. diff --git a/tiled/src/main/kotlin/ktx/tiled/tiledMaps.kt b/tiled/src/main/kotlin/ktx/tiled/tiledMaps.kt index dbcf5eb5..15014f56 100644 --- a/tiled/src/main/kotlin/ktx/tiled/tiledMaps.kt +++ b/tiled/src/main/kotlin/ktx/tiled/tiledMaps.kt @@ -70,11 +70,10 @@ val TiledMap.tileHeight: Int get() = property("tileheight") /** - * Extension property to retrieve the background color of the [TiledMap]. - * @throws MissingPropertyException if property backgroundcolor does not exist. + * Extension property to retrieve the background color of the [TiledMap]. Null if property was not set. */ -val TiledMap.backgroundColor: String - get() = property("backgroundcolor") +val TiledMap.backgroundColor: String? + get() = propertyOrNull("backgroundcolor") /** * Extension property to retrieve the orientation of the [TiledMap]. diff --git a/tiled/src/test/kotlin/ktx/tiled/mapObjectsTest.kt b/tiled/src/test/kotlin/ktx/tiled/mapObjectsTest.kt index e41cef61..e4bd6fc2 100644 --- a/tiled/src/test/kotlin/ktx/tiled/mapObjectsTest.kt +++ b/tiled/src/test/kotlin/ktx/tiled/mapObjectsTest.kt @@ -16,7 +16,8 @@ class MapObjectTest { put("y", 0f) put("rotation", -2.33f) put("type", "SomeType") - put("width", 1f) + put("width", 20f) + put("height", 30f) put("name", "Property") put("active", true) } @@ -25,7 +26,7 @@ class MapObjectTest { @Test fun `should retrieve properties from MapObject`() { assertEquals(1, mapObject.property("x")) - assertEquals(1f, mapObject.property("width")) + assertEquals(20f, mapObject.property("width")) assertEquals("Property", mapObject.property("name")) assertEquals(true, mapObject.property("active")) } @@ -34,7 +35,7 @@ class MapObjectTest { fun `should retrieve properties from MapObject with default value`() { assertEquals(1, mapObject.property("x", 0)) assertEquals(0, mapObject.property("non-existing", 0)) - assertEquals(1f, mapObject.property("width", 0f)) + assertEquals(20f, mapObject.property("width", 0f)) assertEquals("Property", mapObject.property("name", "")) assertEquals(true, mapObject.property("active", false)) } @@ -53,9 +54,11 @@ class MapObjectTest { @Test fun `should retrieve standard properties of MapObject`() { + assertEquals(13, mapObject.id) assertEquals(1f, mapObject.x) assertEquals(0f, mapObject.y) - assertEquals(13, mapObject.id) + assertEquals(20f, mapObject.width) + assertEquals(30f, mapObject.height) assertEquals(-2.33f, mapObject.rotation) assertEquals("SomeType", mapObject.type) } From 97f66b958168db657dfe1f0b6d8806cc3512d983 Mon Sep 17 00:00:00 2001 From: Quillraven Date: Sun, 19 Jan 2020 17:37:28 +0100 Subject: [PATCH 21/32] (#238) adds touch event extensions for actors --- actors/README.md | 30 ++++++- actors/src/main/kotlin/ktx/actors/events.kt | 83 +++++++++++++++++++ .../src/test/kotlin/ktx/actors/eventsTest.kt | 59 +++++++++++++ 3 files changed, 171 insertions(+), 1 deletion(-) diff --git a/actors/README.md b/actors/README.md index a69846be..24f9846f 100644 --- a/actors/README.md +++ b/actors/README.md @@ -27,6 +27,7 @@ a `Group` with `actor in group` syntax. - Lambda-compatible `Actor.onChange` method was added. Allows to listen to `ChangeEvents`. - Lambda-compatible `Actor.onClick` method was added. Attaches `ClickListeners`. +- Lambda-compatible `Actor.onTouchDown` and `Actor.onTouchUp` methods were added. Attaches `ClickListeners`. - Lambda-compatible `Actor.onKey` method was added. Allows to listen to `InputEvents` with `keyTyped` type. - Lambda-compatible `Actor.onKeyDown` and `Actor.onKeyUp` methods were added. They allow to listen to `InputEvents` with `keyDown` and `keyUp` type, consuming key code of the pressed or released key (see LibGDX `Keys` class). @@ -34,7 +35,7 @@ with `keyDown` and `keyUp` type, consuming key code of the pressed or released k - Lambda-compatible `Actor.onKeyboardFocus` method was added. Allows to listen to `FocusEvents` with `keyboard` type. - `KtxInputListener` is an open class that extends `InputListener` with no-op default implementations and type improvements (nullability data). -- `onChangeEvent`, `onClickEvent`, `onKeyEvent`, `onKeyDownEvent`, `onKeyUpEvent`, `onScrollFocusEvent` and +- `onChangeEvent`, `onClickEvent`, `onTouchEvent`, `onKeyEvent`, `onKeyDownEvent`, `onKeyUpEvent`, `onScrollFocusEvent` and `onKeyboardFocusEvent` `Actor` extension methods were added. They consume the relevant `Event` instances as lambda parameters. Both listener factory variants are inlined, but the ones ending with *Event* provide more lambda parameters and allow to inspect the original `Event` instance that triggered the listener. Regular listener factory methods should @@ -131,6 +132,33 @@ label.onClickEvent { inputEvent, actor, x, y -> // If you need access to the local actor click coordinates, use this expanded method variant. println("$actor clicked by $inputEvent at ($x, $y)!") } + +button.onTouchDown { + println("Button down!") + true +} + +button.onTouchUp { + println("Button up!") +} + +button.onTouchEvent( + // If you need access to the original InputEvent, use this expanded method variant. + downListener = { inputEvent, actor -> println("$actor down by $inputEvent!"); true }, + upListener = { inputEvent, actor -> println("$actor up by $inputEvent!") } +) + +button.onTouchEvent( + // If you need access to the local actor coordinates, use this expanded method variant. + downListener = { inputEvent, actor, x, y -> println("$actor down by $inputEvent! at ($x, $y)"); true }, + upListener = { inputEvent, actor, x, y -> println("$actor up by $inputEvent! at ($x, $y)")} +) + +button.onTouchEvent( + // If you need access to the pointer and mouse button, use this expanded method variant. + downListener = { inputEvent, actor, x, y, pointer, mouseButton -> println("$actor down by $inputEvent! at ($x, $y) with pointer $pointer and mouseButton $mouseButton"); true }, + upListener = { inputEvent, actor, x, y, pointer, mouseButton -> println("$actor up by $inputEvent! at ($x, $y) with pointer $pointer and mouseButton $mouseButton")} +) ``` Adding an `EventListener` which consumes typed characters: diff --git a/actors/src/main/kotlin/ktx/actors/events.kt b/actors/src/main/kotlin/ktx/actors/events.kt index efd28017..2450e0b8 100644 --- a/actors/src/main/kotlin/ktx/actors/events.kt +++ b/actors/src/main/kotlin/ktx/actors/events.kt @@ -80,6 +80,89 @@ inline fun Widget.onClickEvent( return clickListener } +/** + * Attaches a [ClickListener] to this actor. + * @param listener invoked each time this actor is touched. + * @return [ClickListener] instance. + */ +inline fun Actor.onTouchDown(crossinline listener: () -> Boolean): ClickListener { + val clickListener = object: ClickListener() { + override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = listener() + } + this.addListener(clickListener) + return clickListener +} + +/** + * Attaches a [ClickListener] to this actor. + * @param listener invoked each time the touch of the actor is released. + * @return [ClickListener] instance. + */ +inline fun Actor.onTouchUp(crossinline listener: () -> Unit): ClickListener { + val clickListener = object: ClickListener() { + override fun touchUp(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = listener() + } + this.addListener(clickListener) + return clickListener +} + +/** + * Attaches a [ClickListener] to this actor. + * @param downListener invoked each time this actor is touched. Consumes the triggered [InputEvent] and the [Actor] that + * the listener was originally attached to. Refer to [ClickListener.touchDown] for parameter details. + * * @param upListener invoked each time the touch of the actor is released. Consumes the triggered [InputEvent] and the [Actor] that + * the listener was originally attached to. Refer to [ClickListener.touchUp] for parameter details. + * @return [ClickListener] instance. + */ +inline fun Widget.onTouchEvent( + crossinline downListener: (event: InputEvent, actor: Widget) -> Boolean, + crossinline upListener: (event: InputEvent, actor: Widget) -> Unit): ClickListener { + val clickListener = object : ClickListener() { + override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean = downListener(event, this@onTouchEvent) + override fun touchUp(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = upListener(event, this@onTouchEvent) + } + this.addListener(clickListener) + return clickListener +} + +/** + * Attaches a [ClickListener] to this actor. + * @param downListener invoked each time this actor is touched. Consumes the triggered [InputEvent] and the [Actor] that + * the listener was originally attached to. Refer to [ClickListener.touchDown] for parameter details. + * * @param upListener invoked each time the touch of the actor is released. Consumes the triggered [InputEvent] and the [Actor] that + * the listener was originally attached to. Refer to [ClickListener.touchUp] for parameter details. + * @return [ClickListener] instance. + */ +inline fun Widget.onTouchEvent( + crossinline downListener: (event: InputEvent, actor: Widget, x: Float, y: Float) -> Boolean, + crossinline upListener: (event: InputEvent, actor: Widget, x: Float, y: Float) -> Unit): ClickListener { + val clickListener = object : ClickListener() { + override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean = downListener(event, this@onTouchEvent, x, y) + override fun touchUp(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = upListener(event, this@onTouchEvent, x, y) + } + this.addListener(clickListener) + return clickListener +} + +/** + * Attaches a [ClickListener] to this actor. + * @param downListener invoked each time this actor is touched. Consumes the triggered [InputEvent] and the [Actor] that + * the listener was originally attached to. Refer to [ClickListener.touchDown] for parameter details. + * * @param upListener invoked each time the touch of the actor is released. Consumes the triggered [InputEvent] and the [Actor] that + * the listener was originally attached to. Refer to [ClickListener.touchUp] for parameter details. + * @return [ClickListener] instance. + */ +inline fun Widget.onTouchEvent( + crossinline downListener: (event: InputEvent, actor: Widget, x: Float, y: Float, pointer: Int, button: Int) -> Boolean, + crossinline upListener: (event: InputEvent, actor: Widget, x: Float, y: Float, pointer: Int, button: Int) -> Unit): ClickListener { + val clickListener = object : ClickListener() { + override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean = downListener(event, this@onTouchEvent, x, y, pointer, button) + override fun touchUp(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = upListener(event, this@onTouchEvent, x, y, pointer, button) + } + this.addListener(clickListener) + return clickListener +} + /** * Attaches an [EventListener] optimized to listen for key type events fired for this actor. * @param catchEvent if true, the event will not be passed further after it is handled by this listener. Defaults to false. diff --git a/actors/src/test/kotlin/ktx/actors/eventsTest.kt b/actors/src/test/kotlin/ktx/actors/eventsTest.kt index 398fab63..c5b0adca 100644 --- a/actors/src/test/kotlin/ktx/actors/eventsTest.kt +++ b/actors/src/test/kotlin/ktx/actors/eventsTest.kt @@ -72,6 +72,65 @@ class EventsTest { assertTrue(listener in actor.listeners) } + @Test + fun `should attach ClickListener for touchDown`() { + val actor = Actor() + + val listener = actor.onTouchDown { true } + + assertNotNull(listener) + assertTrue(listener in actor.listeners) + } + + @Test + fun `should attach ClickListener for touchUp`() { + val actor = Actor() + + val listener = actor.onTouchUp { } + + assertNotNull(listener) + assertTrue(listener in actor.listeners) + } + + @Test + fun `should attach ClickListener consuming InputEvent for touch events`() { + val actor = Actor() + + val listener = actor.onTouchEvent( + downListener = { event, widget -> true }, + upListener = { event, widget -> } + ) + + assertNotNull(listener) + assertTrue(listener in actor.listeners) + } + + @Test + fun `should attach ClickListener consuming InputEvent for touch events with local coordinates`() { + val actor = Actor() + + val listener = actor.onTouchEvent( + downListener = { event, widget, x, y -> true }, + upListener = { event, widget, x, y -> } + ) + + assertNotNull(listener) + assertTrue(listener in actor.listeners) + } + + @Test + fun `should attach ClickListener consuming InputEvent for touch events with local coordinates, pointer and button`() { + val actor = Actor() + + val listener = actor.onTouchEvent( + downListener = { event, widget, x, y, pointer, button -> true }, + upListener = { event, widget, x, y, pointer, button -> } + ) + + assertNotNull(listener) + assertTrue(listener in actor.listeners) + } + @Test fun `should attach key listener`() { val actor = Actor() From dca34dfaaaa67ba13102dca915759acc8762d30b Mon Sep 17 00:00:00 2001 From: Quillraven Date: Sun, 19 Jan 2020 20:40:46 +0100 Subject: [PATCH 22/32] (#238) fixes formatting issues and updates CHANGELOG.md --- CHANGELOG.md | 1 + actors/src/main/kotlin/ktx/actors/events.kt | 40 +++++++++++++-------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 148d5731..3db99116 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - `containsProperty` - Added `shape` extension field to `MapObject`. - Added extension fields that ease extraction of basic properties from `TiledMap` and `MapObject`. + - **[FEATURE]** (`ktx-actors`) Added extensions for `touch` events like `touchDown` and `touchUp` to be able to react on actor presses and releases #### 1.9.10-b3 diff --git a/actors/src/main/kotlin/ktx/actors/events.kt b/actors/src/main/kotlin/ktx/actors/events.kt index 2450e0b8..77018831 100644 --- a/actors/src/main/kotlin/ktx/actors/events.kt +++ b/actors/src/main/kotlin/ktx/actors/events.kt @@ -86,7 +86,7 @@ inline fun Widget.onClickEvent( * @return [ClickListener] instance. */ inline fun Actor.onTouchDown(crossinline listener: () -> Boolean): ClickListener { - val clickListener = object: ClickListener() { + val clickListener = object : ClickListener() { override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = listener() } this.addListener(clickListener) @@ -99,7 +99,7 @@ inline fun Actor.onTouchDown(crossinline listener: () -> Boolean): ClickListener * @return [ClickListener] instance. */ inline fun Actor.onTouchUp(crossinline listener: () -> Unit): ClickListener { - val clickListener = object: ClickListener() { + val clickListener = object : ClickListener() { override fun touchUp(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = listener() } this.addListener(clickListener) @@ -110,16 +110,20 @@ inline fun Actor.onTouchUp(crossinline listener: () -> Unit): ClickListener { * Attaches a [ClickListener] to this actor. * @param downListener invoked each time this actor is touched. Consumes the triggered [InputEvent] and the [Actor] that * the listener was originally attached to. Refer to [ClickListener.touchDown] for parameter details. - * * @param upListener invoked each time the touch of the actor is released. Consumes the triggered [InputEvent] and the [Actor] that + * @param upListener invoked each time the touch of the actor is released. Consumes the triggered [InputEvent] and the [Actor] that * the listener was originally attached to. Refer to [ClickListener.touchUp] for parameter details. * @return [ClickListener] instance. */ inline fun Widget.onTouchEvent( crossinline downListener: (event: InputEvent, actor: Widget) -> Boolean, - crossinline upListener: (event: InputEvent, actor: Widget) -> Unit): ClickListener { + crossinline upListener: (event: InputEvent, actor: Widget) -> Unit +): ClickListener { val clickListener = object : ClickListener() { - override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean = downListener(event, this@onTouchEvent) - override fun touchUp(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = upListener(event, this@onTouchEvent) + override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean = + downListener(event, this@onTouchEvent) + + override fun touchUp(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = + upListener(event, this@onTouchEvent) } this.addListener(clickListener) return clickListener @@ -129,16 +133,20 @@ inline fun Widget.onTouchEvent( * Attaches a [ClickListener] to this actor. * @param downListener invoked each time this actor is touched. Consumes the triggered [InputEvent] and the [Actor] that * the listener was originally attached to. Refer to [ClickListener.touchDown] for parameter details. - * * @param upListener invoked each time the touch of the actor is released. Consumes the triggered [InputEvent] and the [Actor] that + * @param upListener invoked each time the touch of the actor is released. Consumes the triggered [InputEvent] and the [Actor] that * the listener was originally attached to. Refer to [ClickListener.touchUp] for parameter details. * @return [ClickListener] instance. */ inline fun Widget.onTouchEvent( crossinline downListener: (event: InputEvent, actor: Widget, x: Float, y: Float) -> Boolean, - crossinline upListener: (event: InputEvent, actor: Widget, x: Float, y: Float) -> Unit): ClickListener { + crossinline upListener: (event: InputEvent, actor: Widget, x: Float, y: Float) -> Unit +): ClickListener { val clickListener = object : ClickListener() { - override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean = downListener(event, this@onTouchEvent, x, y) - override fun touchUp(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = upListener(event, this@onTouchEvent, x, y) + override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean = + downListener(event, this@onTouchEvent, x, y) + + override fun touchUp(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = + upListener(event, this@onTouchEvent, x, y) } this.addListener(clickListener) return clickListener @@ -148,16 +156,20 @@ inline fun Widget.onTouchEvent( * Attaches a [ClickListener] to this actor. * @param downListener invoked each time this actor is touched. Consumes the triggered [InputEvent] and the [Actor] that * the listener was originally attached to. Refer to [ClickListener.touchDown] for parameter details. - * * @param upListener invoked each time the touch of the actor is released. Consumes the triggered [InputEvent] and the [Actor] that + * @param upListener invoked each time the touch of the actor is released. Consumes the triggered [InputEvent] and the [Actor] that * the listener was originally attached to. Refer to [ClickListener.touchUp] for parameter details. * @return [ClickListener] instance. */ inline fun Widget.onTouchEvent( crossinline downListener: (event: InputEvent, actor: Widget, x: Float, y: Float, pointer: Int, button: Int) -> Boolean, - crossinline upListener: (event: InputEvent, actor: Widget, x: Float, y: Float, pointer: Int, button: Int) -> Unit): ClickListener { + crossinline upListener: (event: InputEvent, actor: Widget, x: Float, y: Float, pointer: Int, button: Int) -> Unit +): ClickListener { val clickListener = object : ClickListener() { - override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean = downListener(event, this@onTouchEvent, x, y, pointer, button) - override fun touchUp(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = upListener(event, this@onTouchEvent, x, y, pointer, button) + override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean = + downListener(event, this@onTouchEvent, x, y, pointer, button) + + override fun touchUp(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = + upListener(event, this@onTouchEvent, x, y, pointer, button) } this.addListener(clickListener) return clickListener From a3559cf4e2b84cc3ca1923aa7811a9598f5bb47a Mon Sep 17 00:00:00 2001 From: Quillraven Date: Mon, 20 Jan 2020 06:27:05 +0100 Subject: [PATCH 23/32] (#238) adds single lambda extensions for touch events and changes the default return type for touchDown to true --- actors/README.md | 16 +++- actors/src/main/kotlin/ktx/actors/events.kt | 94 +++++++++++++++++-- .../src/test/kotlin/ktx/actors/eventsTest.kt | 86 ++++++++++++++++- 3 files changed, 179 insertions(+), 17 deletions(-) diff --git a/actors/README.md b/actors/README.md index 24f9846f..7fdaaae7 100644 --- a/actors/README.md +++ b/actors/README.md @@ -144,21 +144,27 @@ button.onTouchUp { button.onTouchEvent( // If you need access to the original InputEvent, use this expanded method variant. - downListener = { inputEvent, actor -> println("$actor down by $inputEvent!"); true }, + downListener = { inputEvent, actor -> println("$actor down by $inputEvent!") }, upListener = { inputEvent, actor -> println("$actor up by $inputEvent!") } ) +// or with a single lambda. In this case you can use InputEvent.Type to distinguish between touchDown and touchUp +button.onTouchEvent( { inputEvent, actor -> println("$actor ${inputEvent.type} by $inputEvent!") }) button.onTouchEvent( // If you need access to the local actor coordinates, use this expanded method variant. - downListener = { inputEvent, actor, x, y -> println("$actor down by $inputEvent! at ($x, $y)"); true }, - upListener = { inputEvent, actor, x, y -> println("$actor up by $inputEvent! at ($x, $y)")} + downListener = { inputEvent, actor, x, y -> println("$actor down by $inputEvent at ($x, $y)!") }, + upListener = { inputEvent, actor, x, y -> println("$actor up by $inputEvent at ($x, $y)!")} ) +// or again as single lambda +button.onTouchEvent( { inputEvent, actor -> println("$actor ${inputEvent.type} by $inputEvent at ($x, $y)!") }) button.onTouchEvent( // If you need access to the pointer and mouse button, use this expanded method variant. - downListener = { inputEvent, actor, x, y, pointer, mouseButton -> println("$actor down by $inputEvent! at ($x, $y) with pointer $pointer and mouseButton $mouseButton"); true }, - upListener = { inputEvent, actor, x, y, pointer, mouseButton -> println("$actor up by $inputEvent! at ($x, $y) with pointer $pointer and mouseButton $mouseButton")} + downListener = { inputEvent, actor, x, y, pointer, mouseButton -> println("$actor down by $inputEvent at ($x, $y) with pointer $pointer and mouseButton $mouseButton!") }, + upListener = { inputEvent, actor, x, y, pointer, mouseButton -> println("$actor up by $inputEvent at ($x, $y) with pointer $pointer and mouseButton $mouseButton!")} ) +// or again as single lambda +button.onTouchEvent( { inputEvent, actor -> println("$actor ${inputEvent.type} by $inputEvent at ($x, $y) with pointer $pointer and mouseButton $mouseButton!") }) ``` Adding an `EventListener` which consumes typed characters: diff --git a/actors/src/main/kotlin/ktx/actors/events.kt b/actors/src/main/kotlin/ktx/actors/events.kt index 77018831..d76ef12e 100644 --- a/actors/src/main/kotlin/ktx/actors/events.kt +++ b/actors/src/main/kotlin/ktx/actors/events.kt @@ -85,9 +85,12 @@ inline fun Widget.onClickEvent( * @param listener invoked each time this actor is touched. * @return [ClickListener] instance. */ -inline fun Actor.onTouchDown(crossinline listener: () -> Boolean): ClickListener { +inline fun Actor.onTouchDown(crossinline listener: () -> Unit): ClickListener { val clickListener = object : ClickListener() { - override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = listener() + override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean { + listener() + return true + } } this.addListener(clickListener) return clickListener @@ -115,12 +118,14 @@ inline fun Actor.onTouchUp(crossinline listener: () -> Unit): ClickListener { * @return [ClickListener] instance. */ inline fun Widget.onTouchEvent( - crossinline downListener: (event: InputEvent, actor: Widget) -> Boolean, + crossinline downListener: (event: InputEvent, actor: Widget) -> Unit, crossinline upListener: (event: InputEvent, actor: Widget) -> Unit ): ClickListener { val clickListener = object : ClickListener() { - override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean = + override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean { downListener(event, this@onTouchEvent) + return true + } override fun touchUp(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = upListener(event, this@onTouchEvent) @@ -129,6 +134,29 @@ inline fun Widget.onTouchEvent( return clickListener } +/** + * Attaches a [ClickListener] to this actor. Retrieve the [InputEvent.type] to distinguish between [touchDown][InputEvent.Type.touchDown] + * and [touchUp][InputEvent.Type.touchUp] events. + * @param listener invoked each time this actor is touched or the touch is released. Consumes the triggered [InputEvent] and the [Actor] that + * the listener was originally attached to. Refer to [ClickListener.touchDown] and [ClickListener.touchUp] for parameter details. + * @return [ClickListener] instance. + */ +inline fun Widget.onTouchEvent( + crossinline listener: (event: InputEvent, actor: Widget) -> Unit +): ClickListener { + val clickListener = object : ClickListener() { + override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean { + listener(event, this@onTouchEvent) + return true + } + + override fun touchUp(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = + listener(event, this@onTouchEvent) + } + this.addListener(clickListener) + return clickListener +} + /** * Attaches a [ClickListener] to this actor. * @param downListener invoked each time this actor is touched. Consumes the triggered [InputEvent] and the [Actor] that @@ -138,12 +166,14 @@ inline fun Widget.onTouchEvent( * @return [ClickListener] instance. */ inline fun Widget.onTouchEvent( - crossinline downListener: (event: InputEvent, actor: Widget, x: Float, y: Float) -> Boolean, + crossinline downListener: (event: InputEvent, actor: Widget, x: Float, y: Float) -> Unit, crossinline upListener: (event: InputEvent, actor: Widget, x: Float, y: Float) -> Unit ): ClickListener { val clickListener = object : ClickListener() { - override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean = + override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean { downListener(event, this@onTouchEvent, x, y) + return true + } override fun touchUp(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = upListener(event, this@onTouchEvent, x, y) @@ -152,6 +182,29 @@ inline fun Widget.onTouchEvent( return clickListener } +/** + * Attaches a [ClickListener] to this actor. Retrieve the [InputEvent.type] to distinguish between [touchDown][InputEvent.Type.touchDown] + * and [touchUp][InputEvent.Type.touchUp] events. + * @param listener invoked each time this actor is touched or the touch is released. Consumes the triggered [InputEvent] and the [Actor] that + * the listener was originally attached to. Refer to [ClickListener.touchDown] and [ClickListener.touchUp] for parameter details. + * @return [ClickListener] instance. + */ +inline fun Widget.onTouchEvent( + crossinline listener: (event: InputEvent, actor: Widget, x: Float, y: Float) -> Unit +): ClickListener { + val clickListener = object : ClickListener() { + override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean { + listener(event, this@onTouchEvent, x, y) + return true + } + + override fun touchUp(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = + listener(event, this@onTouchEvent, x, y) + } + this.addListener(clickListener) + return clickListener +} + /** * Attaches a [ClickListener] to this actor. * @param downListener invoked each time this actor is touched. Consumes the triggered [InputEvent] and the [Actor] that @@ -161,12 +214,14 @@ inline fun Widget.onTouchEvent( * @return [ClickListener] instance. */ inline fun Widget.onTouchEvent( - crossinline downListener: (event: InputEvent, actor: Widget, x: Float, y: Float, pointer: Int, button: Int) -> Boolean, + crossinline downListener: (event: InputEvent, actor: Widget, x: Float, y: Float, pointer: Int, button: Int) -> Unit, crossinline upListener: (event: InputEvent, actor: Widget, x: Float, y: Float, pointer: Int, button: Int) -> Unit ): ClickListener { val clickListener = object : ClickListener() { - override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean = + override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean { downListener(event, this@onTouchEvent, x, y, pointer, button) + return true + } override fun touchUp(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = upListener(event, this@onTouchEvent, x, y, pointer, button) @@ -175,6 +230,29 @@ inline fun Widget.onTouchEvent( return clickListener } +/** + * Attaches a [ClickListener] to this actor. Retrieve the [InputEvent.type] to distinguish between [touchDown][InputEvent.Type.touchDown] + * and [touchUp][InputEvent.Type.touchUp] events. + * @param listener invoked each time this actor is touched or the touch is released. Consumes the triggered [InputEvent] and the [Actor] that + * the listener was originally attached to. Refer to [ClickListener.touchDown] and [ClickListener.touchUp] for parameter details. + * @return [ClickListener] instance. + */ +inline fun Widget.onTouchEvent( + crossinline listener: (event: InputEvent, actor: Widget, x: Float, y: Float, pointer: Int, button: Int) -> Unit +): ClickListener { + val clickListener = object : ClickListener() { + override fun touchDown(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int): Boolean { + listener(event, this@onTouchEvent, x, y, pointer, button) + return true + } + + override fun touchUp(event: InputEvent, x: Float, y: Float, pointer: Int, button: Int) = + listener(event, this@onTouchEvent, x, y, pointer, button) + } + this.addListener(clickListener) + return clickListener +} + /** * Attaches an [EventListener] optimized to listen for key type events fired for this actor. * @param catchEvent if true, the event will not be passed further after it is handled by this listener. Defaults to false. diff --git a/actors/src/test/kotlin/ktx/actors/eventsTest.kt b/actors/src/test/kotlin/ktx/actors/eventsTest.kt index c5b0adca..d3f8f090 100644 --- a/actors/src/test/kotlin/ktx/actors/eventsTest.kt +++ b/actors/src/test/kotlin/ktx/actors/eventsTest.kt @@ -76,7 +76,7 @@ class EventsTest { fun `should attach ClickListener for touchDown`() { val actor = Actor() - val listener = actor.onTouchDown { true } + val listener = actor.onTouchDown { } assertNotNull(listener) assertTrue(listener in actor.listeners) @@ -97,7 +97,7 @@ class EventsTest { val actor = Actor() val listener = actor.onTouchEvent( - downListener = { event, widget -> true }, + downListener = { event, widget -> }, upListener = { event, widget -> } ) @@ -110,7 +110,7 @@ class EventsTest { val actor = Actor() val listener = actor.onTouchEvent( - downListener = { event, widget, x, y -> true }, + downListener = { event, widget, x, y -> }, upListener = { event, widget, x, y -> } ) @@ -123,7 +123,7 @@ class EventsTest { val actor = Actor() val listener = actor.onTouchEvent( - downListener = { event, widget, x, y, pointer, button -> true }, + downListener = { event, widget, x, y, pointer, button -> }, upListener = { event, widget, x, y, pointer, button -> } ) @@ -131,6 +131,84 @@ class EventsTest { assertTrue(listener in actor.listeners) } + @Test + fun `should attach ClickListener and trigger touchDown event`() { + val actor = Actor() + var result = false + + val listener = actor.onTouchEvent { event, widget -> result = event.type == touchDown } + listener.touchDown(InputEvent().apply { type = touchDown }, 0f, 0f, 0, 0) + + assertNotNull(listener) + assertTrue(listener in actor.listeners) + assertTrue(result) + } + + @Test + fun `should attach ClickListener and trigger touchUp event`() { + val actor = Actor() + var result = false + + val listener = actor.onTouchEvent { event, widget -> result = event.type == touchUp } + listener.touchDown(InputEvent().apply { type = touchUp }, 0f, 0f, 0, 0) + + assertNotNull(listener) + assertTrue(listener in actor.listeners) + assertTrue(result) + } + + @Test + fun `should attach ClickListener and trigger touchDown event with local coordinates, pointer and button`() { + val actor = Actor() + var result = false + + val listener = actor.onTouchEvent { event, widget, x, y, pointer, button -> result = event.type == touchDown } + listener.touchDown(InputEvent().apply { type = touchDown }, 0f, 0f, 0, 0) + + assertNotNull(listener) + assertTrue(listener in actor.listeners) + assertTrue(result) + } + + @Test + fun `should attach ClickListener and trigger touchUp event with local coordinates, pointer and button`() { + val actor = Actor() + var result = false + + val listener = actor.onTouchEvent { event, widget, x, y, pointer, button -> result = event.type == touchUp } + listener.touchDown(InputEvent().apply { type = touchUp }, 0f, 0f, 0, 0) + + assertNotNull(listener) + assertTrue(listener in actor.listeners) + assertTrue(result) + } + + @Test + fun `should attach ClickListener and trigger touchDown event with local coordinates`() { + val actor = Actor() + var result = false + + val listener = actor.onTouchEvent { event, widget, x, y -> result = event.type == touchDown } + listener.touchDown(InputEvent().apply { type = touchDown }, 0f, 0f, 0, 0) + + assertNotNull(listener) + assertTrue(listener in actor.listeners) + assertTrue(result) + } + + @Test + fun `should attach ClickListener and trigger touchUp event with local coordinates`() { + val actor = Actor() + var result = false + + val listener = actor.onTouchEvent { event, widget, x, y -> result = event.type == touchUp } + listener.touchDown(InputEvent().apply { type = touchUp }, 0f, 0f, 0, 0) + + assertNotNull(listener) + assertTrue(listener in actor.listeners) + assertTrue(result) + } + @Test fun `should attach key listener`() { val actor = Actor() From 660e4bdb17c4e04864d358cb851204e0e687f8f5 Mon Sep 17 00:00:00 2001 From: MJ Date: Mon, 20 Jan 2020 21:17:40 +0100 Subject: [PATCH 24/32] Updated new touch event listeners documentation. #238 --- .github/CONTRIBUTORS.md | 1 + CHANGELOG.md | 2 +- actors/README.md | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CONTRIBUTORS.md b/.github/CONTRIBUTORS.md index 7634ab87..e0b2c394 100644 --- a/.github/CONTRIBUTORS.md +++ b/.github/CONTRIBUTORS.md @@ -43,6 +43,7 @@ Project contributors listed chronologically. * Improved [app](../app) utilities. * [@Quillraven](https://github.com/Quillraven) * Author of the [Tiled](../tiled) module. + * Contributed [actors](../actors) utilities. * Wrote a complete [KTX tutorial](https://github.com/Quillraven/SimpleKtxGame/wiki) based on the original LibGDX introduction. * [@FocusPo1nt](https://github.com/FocusPo1nt) * Added utilities to [style module](../style). diff --git a/CHANGELOG.md b/CHANGELOG.md index 3db99116..57067ad1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - **[CHANGE]** (`ktx-collections`) `Array.removeAll` and `retainAll` now return a boolean if any elements were removed. - **[CHANGE]** (`ktx-collections`) `Array.transfer` is now less strict about typing. +- **[FEATURE]** (`ktx-actors`) Added `onTouchDown`, `onTouchUp` and `onTouchEvent` extension methods that allow to attach `ClickListener` instances to actors. - **[FEATURE]** (`ktx-tiled`) Added a new KTX module: Tiled API extensions. - Added `contains` (`in`) and `set` (`[]`) operators support to `MapProperties`. - Added extension methods that simplify properties extraction from `MapLayer`, `MapObject`, `TiledMap`, `TiledMapTile` and `TiledMapTileSet`: @@ -10,7 +11,6 @@ - `containsProperty` - Added `shape` extension field to `MapObject`. - Added extension fields that ease extraction of basic properties from `TiledMap` and `MapObject`. - - **[FEATURE]** (`ktx-actors`) Added extensions for `touch` events like `touchDown` and `touchUp` to be able to react on actor presses and releases #### 1.9.10-b3 diff --git a/actors/README.md b/actors/README.md index 7fdaaae7..2b3893d3 100644 --- a/actors/README.md +++ b/actors/README.md @@ -135,7 +135,6 @@ label.onClickEvent { inputEvent, actor, x, y -> button.onTouchDown { println("Button down!") - true } button.onTouchUp { From f9856d49f96b42f808082857660d4e4dedece03f Mon Sep 17 00:00:00 2001 From: dakeese Date: Wed, 22 Jan 2020 22:53:47 -0500 Subject: [PATCH 25/32] Add ranges utitlities to math --- math/README.md | 55 ++++- math/src/main/kotlin/ktx/math/ranges.kt | 130 +++++++++++ math/src/test/kotlin/ktx/math/rangesTest.kt | 244 ++++++++++++++++++++ 3 files changed, 428 insertions(+), 1 deletion(-) create mode 100644 math/src/main/kotlin/ktx/math/ranges.kt create mode 100644 math/src/test/kotlin/ktx/math/rangesTest.kt diff --git a/math/README.md b/math/README.md index 79f3bf62..bebd29b1 100644 --- a/math/README.md +++ b/math/README.md @@ -1,6 +1,6 @@ # KTX: math utilities -Math extensions and operator overloads for LibGDX math API. +Math extensions and operator overloads for LibGDX math API and Kotlin ranges. ### Why? @@ -9,6 +9,9 @@ the possibility to use a much more readable and natural syntax with its operator LibGDX API does not match Kotlin naming conventions (necessary for operators to work), which means extension functions are necessary to make it work like that. +Kotlin also provides convenient syntax for ranges, which can be used for clearly describing criteria for selecting random +numbers. + ### Guide #### `Vector2` @@ -166,6 +169,56 @@ new instances of matrices. - `Matrix4` instances can be multiplied with a `Vector3` using `*` operator. - `Matrix4` instances can be destructed into sixteen float variables (each representing one of its cells) thanks to the `component1()` - `component16()` operator functions. + +#### Ranges + +- The `amid` infix function for Int and Float allows easy creation of a range by using a center and a tolerance. Such a +definition is a convenient way to think about a range from which random values will be selected. +- The four arithmetic operators are available for easily shifting or scaling ranges. This allows intuitive modification +of ranges in code, which can be useful for code clarity when defining a range for random number selection, or for +rapidly iterating a design. +- `IntRange.random(random: java.util.Random)` allows using a Java Random to select a number from the range, and is +provided in case there is a need to use the `MathUtils.random` instance or an instance of Gdx's fast RandomXS128. +- `ClosedRange.random()` allows a evenly distributed random number to be selected from a range (but treating +the `endInclusive` as exclusive for simplicity). +- `ClosedRange.randomGaussian()` selectes a normally distributed value to be selected from the range, scaled so the +range is six standard deviations wide. +- `ClosedRange.randomTriangular()` allow easy selection of a triangularly distributed number from the range. A +a `normalizedMode` can be passed for asymmetrical distributions. + +##### Usage example + +Suppose there is a class that has a random behavior. Its can be constructed by passing several ranges to its constructor. +```kotlin +class CreatureSpawner(val spawnIntervalRange: ClosedRange) { + //... + fun update(dt: Float){ + untilNext -= dt + while (untilNext <= 0){ + untilNext += spawnIntervalRange.random() + spawnSomething() + } + } +} +``` + +In a parent class, there are many of these instances set up. The ranges can be described intuitively: +```kotlin +val spawners = listOf( + //... + CreatureSpawner(0.5f amid 0.2f), + //... +) +``` + +And as the design is iterated, the range can be adjusted quickly and intuitively by applying arithmetic operations: +```kotlin +val spawners = listOf( + //... + CreatureSpawner((0.5f amid 0.2f) * 1.2f + 0.1f), + //... +) +``` ### Alternatives diff --git a/math/src/main/kotlin/ktx/math/ranges.kt b/math/src/main/kotlin/ktx/math/ranges.kt new file mode 100644 index 00000000..3d7b8081 --- /dev/null +++ b/math/src/main/kotlin/ktx/math/ranges.kt @@ -0,0 +1,130 @@ +package ktx.math + +import com.badlogic.gdx.math.MathUtils + +/** + * Creates a range defined with the given inclusive [tolerance] above and below this center value. + */ +infix fun Int.amid(tolerance: Int) = (this - tolerance)..(this + tolerance) + +/** + * Returns a random element from this range using the specified source of randomness. + * + * This overload allows passing a [java.util.Random] instance so, for instance, [MathUtils.random] may be used. Results + * are undefined for an empty range, and there is no error checking. + */ +fun IntRange.random(random: java.util.Random) = random.nextInt(1 + last - first) + first + +/** + * Creates a range by scaling this range's [start][ClosedRange.start] and [endInclusive][ClosedRange.endInclusive] by + * the [multiplier]. + */ +operator fun IntRange.times(multiplier: Int) = (first * multiplier)..(last * multiplier) + +/** + * Creates a range by scaling the [range]'s [start][ClosedRange.start] and [endInclusive][ClosedRange.endInclusive] by + * this multiplier. + */ +operator fun Int.times(range: IntRange) = (this * range.first)..(this * range.last) + +/** + * Creates a range by scaling this range's [start][ClosedRange.start] and [endInclusive][ClosedRange.endInclusive] by + * the [divisor]. + */ +operator fun IntRange.div(divisor: Int) = (first / divisor)..(last / divisor) + +/** + * Creates a range by shifting this range's [start][ClosedRange.start] and [endInclusive][ClosedRange.endInclusive] by + * the [addend]. + */ +operator fun IntRange.plus(addend: Int) = (first + addend)..(last + addend) + +/** + * Creates a range by shifting this range's [start][ClosedRange.start] and [endInclusive][ClosedRange.endInclusive] by + * the [subtrahend]. + */ +operator fun IntRange.minus(subtrahend: Int) = (start - subtrahend)..(endInclusive - subtrahend) + +/** + * Creates a range defined with the given [tolerance] above and below this center value. + */ +infix fun Float.amid(tolerance: Float) = (this - tolerance)..(this + tolerance) + +/** + * Creates a range by scaling this range's [start][ClosedRange.start] and [endInclusive][ClosedRange.endInclusive] by + * the [multiplier]. + */ +operator fun ClosedRange.times(multiplier: Float) = (start * multiplier)..(endInclusive * multiplier) + +/** + * Creates a range by scaling the [range]'s [start][ClosedRange.start] and [endInclusive][ClosedRange.endInclusive] by + * this multiplier. + */ +operator fun Float.times(range: ClosedRange) = (this * range.start)..(this * range.endInclusive) + +/** + * Creates a range by scaling this range's [start][ClosedRange.start] and [endInclusive][ClosedRange.endInclusive] by + * the [denominator]. + */ +operator fun ClosedRange.div(denominator: Float) = (start / denominator)..(endInclusive / denominator) + +/** + * Creates a range by shifting this range's [start][ClosedRange.start] and [endInclusive][ClosedRange.endInclusive] by + * the [addend]. + */ +operator fun ClosedRange.plus(addend: Float) = (start + addend)..(endInclusive + addend) + +/** + * Creates a range by shifting this range's [start][ClosedRange.start] and [endInclusive][ClosedRange.endInclusive] by + * the [subtrahend]. + */ +operator fun ClosedRange.minus(subtrahend: Float) = (start - subtrahend)..(endInclusive - subtrahend) + +/** + * Returns a pseudo-random, uniformly distributed [Float] value from [MathUtils.random]'s sequence, bounded by + * this range. + * + * Results are undefined for an empty range, and there is no error checking. Note that + * [endInclusive][ClosedRange.endInclusive] is treated as exclusive as it is not practical to keep it inclusive. + */ +fun ClosedRange.random() = MathUtils.random.nextFloat() * (endInclusive - start) + start + +/** + * Returns a pseudo-random, standard Gaussian distributed [Float] value from [MathUtils.random]'s sequence. The + * distribution is centered to this range's center and is scaled so this range is six standard deviations wide. + * + * Results are undefined for an empty range, and there is no error checking. + * + * @param clamped If true (the default), values outside the range are clamped to the range. + */ +fun ClosedRange.randomGaussian(clamped: Boolean = true) = + ((MathUtils.random.nextGaussian() / 6.0 + 0.5).toFloat() * (endInclusive - start) + start).let { + if (clamped) + it.coerceIn(this) + else + it + } + +/** + * Returns a triangularly distributed random number in this range, with the *mode* centered in this range, giving a + * symmetric distribution. + * + * This function uses [MathUtils.randomTriangular]. Note that [endInclusive][ClosedRange.endInclusive] is treated as + * exclusive as it is not practical to keep it inclusive. Results are undefined for an empty range, and there is no + * error checking. + */ +fun ClosedRange.randomTriangular() = MathUtils.randomTriangular(start, endInclusive) + +/** + * Returns a triangularly distributed random number in this range, where values around the *mode* are more likely. + * [normalizedMode] must be a value in the range 0.0..1.0 and represents the fractional position of the mode across the + * range. + * + * This function uses `MathUtils.randomTriangular(min, max, mode)`. Note that [endInclusive][ClosedRange.endInclusive] + * is treated as exclusive as it is not practical to keep it inclusive. Results are undefined for an empty range, and + * there is no error checking. + */ +fun ClosedRange.randomTriangular(normalizedMode: Float) = + MathUtils.randomTriangular(start, endInclusive, + normalizedMode * (endInclusive - start) + start + ) \ No newline at end of file diff --git a/math/src/test/kotlin/ktx/math/rangesTest.kt b/math/src/test/kotlin/ktx/math/rangesTest.kt new file mode 100644 index 00000000..2f6c23f2 --- /dev/null +++ b/math/src/test/kotlin/ktx/math/rangesTest.kt @@ -0,0 +1,244 @@ +package ktx.math + +import com.badlogic.gdx.math.MathUtils +import com.badlogic.gdx.math.RandomXS128 +import org.junit.Assert.* +import org.junit.Test +import kotlin.math.abs + +/** + * Tests [ClosedRange]-related utilities. + */ +class RangesTest { + + @Test + fun `should produce expected int range`() { + val center = -300 + val tolerance = 10000 + val range = center amid tolerance + assertEquals(range.first, center - tolerance) + assertEquals(range.last, center + tolerance) + } + + @Test + fun `should multiply int range`() { + val original = -20..500 + val multiplier = 37 + val scaled = original * multiplier + val invertedScaled = multiplier * original + assertEquals(original.first * multiplier, scaled.first) + assertEquals(original.last * multiplier, scaled.last) + assertEquals(original.first * multiplier, invertedScaled.first) + assertEquals(original.last * multiplier, invertedScaled.last) + } + + @Test + fun `should divide int range`() { + val original = -20..500 + val divisor = 37 + val divided = original / divisor + assertEquals(original.first / divisor, divided.first) + assertEquals(original.last / divisor, divided.last) + } + + @Test + fun `should add to int range`() { + val original = -20..500 + val addend = 37 + val shifted = original + addend + assertEquals(original.first + addend, shifted.first) + assertEquals(original.last + addend, shifted.last) + } + + @Test + fun `should subtract from int range`() { + val original = -20..500 + val subtrahend = 37 + val shifted = original - subtrahend + assertEquals(original.first - subtrahend, shifted.first) + assertEquals(original.last - subtrahend, shifted.last) + } + + @Test + fun `should produce expected random int values`() { + val seed = 615843L + val start = 45 + val endInclusive = 30654 + val count = 10000 + val directValues = mutableListOf() + val rangeValues = mutableListOf() + with(java.util.Random(seed)) { + repeat(count) { + directValues += nextInt(1 + endInclusive - start) + start + } + } + val range = start..endInclusive + with(java.util.Random(seed)) { + repeat(count) { + rangeValues += range.random(this) + } + } + assertEquals(directValues, rangeValues) + } + + @Test + fun `should produce expected float range`() { + val center = -325f + val tolerance = 6000f + val range = center amid tolerance + assertEquals(range.start, center - tolerance) + assertEquals(range.endInclusive, center + tolerance) + } + + @Test + fun `should multiply float range`() { + val original = -20f..500f + val multiplier = 37f + val scaled = original * multiplier + val invertedScaled = multiplier * original + assertEquals(original.start * multiplier, scaled.start) + assertEquals(original.endInclusive * multiplier, scaled.endInclusive) + assertEquals(original.start * multiplier, invertedScaled.start) + assertEquals(original.endInclusive * multiplier, invertedScaled.endInclusive) + } + + @Test + fun `should divide float range`() { + val original = -20f..500f + val divisor = 37f + val divided = original / divisor + assertEquals(original.start / divisor, divided.start) + assertEquals(original.endInclusive / divisor, divided.endInclusive) + } + + @Test + fun `should add to float range`() { + val original = -20f..500f + val addend = 37f + val shifted = original + addend + assertEquals(original.start + addend, shifted.start) + assertEquals(original.endInclusive + addend, shifted.endInclusive) + } + + @Test + fun `should subtract from float range`() { + val original = -20f..500f + val subtrahend = 37f + val shifted = original - subtrahend + assertEquals(original.start - subtrahend, shifted.start) + assertEquals(original.endInclusive - subtrahend, shifted.endInclusive) + } + + @Test + fun `should produce expected random float values`() { + val seed = 15658L + val start = 3f + val end = 45000f + val count = 10000 + val directValues = mutableListOf() + val rangeValues = mutableListOf() + with(RandomXS128(seed)) { + repeat(count) { + directValues += nextFloat() * (end - start) + start + } + } + MathUtils.random = RandomXS128(seed) + val range = start..end + repeat(count) { + rangeValues += range.random() + } + assertEquals(directValues, rangeValues) + } + + @Test + fun `should produce gaussian distribution`() { + val count = 100000 + val allowableError = 0.03f + + val center = 350f + val tolerance = 6300f + val range = center amid tolerance + val fourSigma = center amid (tolerance * 2f / 3f) + val twoSigma = center amid (tolerance / 3f) + var withinRange = 0 + var withinFourSigma = 0 + var withinTwoSigma = 0 + repeat(count) { + val value = range.randomGaussian(false) + if (value in range) withinRange++ + if (value in fourSigma) withinFourSigma++ + if (value in twoSigma) withinTwoSigma++ + } + + val resultsToExpected = listOf( + withinRange.toFloat() / count to 0.9973f, + withinFourSigma.toFloat() / count to 0.9545f, + withinTwoSigma.toFloat() / count to 0.6827f) + for ((result, expected) in resultsToExpected) { + assert(abs(result - expected) / expected <= allowableError) + } + } + + @Test + fun `should produce symmetric triangular distribution`() { + val count = 100000 + val allowableError = 0.03f + + val range = -240f..9800f + val span = range.endInclusive - range.start + val values = Array(count) { + range.randomTriangular() + } + + val center = 0.5 * (range.endInclusive + range.start) + val beforeModeSpan = center - range.start + val afterModeSpan = range.endInclusive - center + val rangesToExpectedProbabilities = + listOf(0.2f, 0.4f, 0.6f, 0.8f) + .map { fraction -> + val checkRange = (beforeModeSpan * fraction + range.start)..(range.endInclusive - afterModeSpan * fraction) + val leftTriangleBase = checkRange.start - range.start + val leftTriangleArea = 0.5f * leftTriangleBase * (2 * leftTriangleBase / (span * beforeModeSpan)) + val probability = 1f - leftTriangleArea * 2 + checkRange to probability + } + for ((checkRange, probability) in rangesToExpectedProbabilities) { + var result = values.count { it in checkRange }.toFloat() / count + assert(abs(result - probability) / probability <= allowableError) + } + } + + @Test + fun `should produce triangular distribution`() { + val count = 100000 + val allowableError = 0.03f + + val range = -240f..9800f + val span = range.endInclusive - range.start + val fractionalMode = 0.73f + val values = Array(count) { + range.randomTriangular(fractionalMode) + } + + val expectedMode = fractionalMode * (range.endInclusive - range.start) + range.start + val beforeModeSpan = expectedMode - range.start + val afterModeSpan = range.endInclusive - expectedMode + val rangesToExpectedProbabilities = + listOf(0.2f, 0.4f, 0.6f, 0.8f) + .map { fraction -> + // For simplicity, all checked ranges span the mode + val checkRange = (beforeModeSpan * fraction + range.start)..(range.endInclusive - afterModeSpan * fraction) + val leftTriangleBase = checkRange.start - range.start + val leftTriangleArea = 0.5f * leftTriangleBase * (2 * leftTriangleBase / (span * beforeModeSpan)) + val rightTriangleBase = range.endInclusive - checkRange.endInclusive + val rightTriangleArea = 0.5f * rightTriangleBase * (2 * rightTriangleBase / (span * afterModeSpan)) + val probability = 1f - leftTriangleArea - rightTriangleArea + checkRange to probability + } + for ((checkRange, probability) in rangesToExpectedProbabilities) { + var result = values.count { it in checkRange }.toFloat() / count + assert(abs(result - probability) / probability <= allowableError) + } + } + +} \ No newline at end of file From 23f59e38777b6d36b8c5a3c544ddbd26880ecdba Mon Sep 17 00:00:00 2001 From: dakeese Date: Thu, 23 Jan 2020 21:42:32 -0500 Subject: [PATCH 26/32] Distribution checks for range random() functions --- math/src/main/kotlin/ktx/math/ranges.kt | 2 +- math/src/test/kotlin/ktx/math/rangesTest.kt | 114 +++++++++----------- 2 files changed, 54 insertions(+), 62 deletions(-) diff --git a/math/src/main/kotlin/ktx/math/ranges.kt b/math/src/main/kotlin/ktx/math/ranges.kt index 3d7b8081..eac8204c 100644 --- a/math/src/main/kotlin/ktx/math/ranges.kt +++ b/math/src/main/kotlin/ktx/math/ranges.kt @@ -127,4 +127,4 @@ fun ClosedRange.randomTriangular() = MathUtils.randomTriangular(start, en fun ClosedRange.randomTriangular(normalizedMode: Float) = MathUtils.randomTriangular(start, endInclusive, normalizedMode * (endInclusive - start) + start - ) \ No newline at end of file + ) diff --git a/math/src/test/kotlin/ktx/math/rangesTest.kt b/math/src/test/kotlin/ktx/math/rangesTest.kt index 2f6c23f2..cd378c93 100644 --- a/math/src/test/kotlin/ktx/math/rangesTest.kt +++ b/math/src/test/kotlin/ktx/math/rangesTest.kt @@ -1,7 +1,6 @@ package ktx.math import com.badlogic.gdx.math.MathUtils -import com.badlogic.gdx.math.RandomXS128 import org.junit.Assert.* import org.junit.Test import kotlin.math.abs @@ -60,25 +59,25 @@ class RangesTest { } @Test - fun `should produce expected random int values`() { - val seed = 615843L - val start = 45 - val endInclusive = 30654 - val count = 10000 - val directValues = mutableListOf() - val rangeValues = mutableListOf() - with(java.util.Random(seed)) { - repeat(count) { - directValues += nextInt(1 + endInclusive - start) + start - } - } - val range = start..endInclusive - with(java.util.Random(seed)) { - repeat(count) { - rangeValues += range.random(this) - } + fun `should produce uniform int distribution`() { + val count = 100000 + val allowableError = 0.03f + + val range = -4060..24500 + val values = Array(count) { + range.random(MathUtils.random) } - assertEquals(directValues, rangeValues) + + val numInnerRanges = 5 + val expectedCountEach = count / numInnerRanges.toFloat() + (range step ((range.last - range.first) / numInnerRanges)) + .zipWithNext() + .map { it.first until it.second } + .forEach { innerRange -> + val innerCount = values.count { it in innerRange } + assert(abs(innerCount.toFloat() - expectedCountEach) / expectedCountEach <= allowableError) + } + assert(values.all { it in range }) } @Test @@ -130,24 +129,25 @@ class RangesTest { } @Test - fun `should produce expected random float values`() { - val seed = 15658L - val start = 3f - val end = 45000f - val count = 10000 - val directValues = mutableListOf() - val rangeValues = mutableListOf() - with(RandomXS128(seed)) { - repeat(count) { - directValues += nextFloat() * (end - start) + start - } - } - MathUtils.random = RandomXS128(seed) - val range = start..end - repeat(count) { - rangeValues += range.random() + fun `should produce uniform float distribution`() { + val count = 100000 + val allowableError = 0.03f + + val range = -4060f..24500f + val values = Array(count) { + range.random() } - assertEquals(directValues, rangeValues) + + val numInnerRanges = 5 + val expectedCountEach = count / numInnerRanges.toFloat() + List(numInnerRanges + 1) { it * (range.endInclusive - range.start) / numInnerRanges + range.start } + .zipWithNext() + .map { it.first..it.second } + .forEach { innerRange -> + val innerCount = values.count { it in innerRange } + assert(abs(innerCount.toFloat() - expectedCountEach) / expectedCountEach <= allowableError) + } + assert(values.all { it in range }) } @Test @@ -193,19 +193,15 @@ class RangesTest { val center = 0.5 * (range.endInclusive + range.start) val beforeModeSpan = center - range.start val afterModeSpan = range.endInclusive - center - val rangesToExpectedProbabilities = - listOf(0.2f, 0.4f, 0.6f, 0.8f) - .map { fraction -> - val checkRange = (beforeModeSpan * fraction + range.start)..(range.endInclusive - afterModeSpan * fraction) - val leftTriangleBase = checkRange.start - range.start - val leftTriangleArea = 0.5f * leftTriangleBase * (2 * leftTriangleBase / (span * beforeModeSpan)) - val probability = 1f - leftTriangleArea * 2 - checkRange to probability - } - for ((checkRange, probability) in rangesToExpectedProbabilities) { - var result = values.count { it in checkRange }.toFloat() / count + for (fraction in listOf(0.2f, 0.4f, 0.6f, 0.8f)) { + val innerRange = (beforeModeSpan * fraction + range.start)..(range.endInclusive - afterModeSpan * fraction) + val leftTriangleBase = innerRange.start - range.start + val leftTriangleArea = 0.5f * leftTriangleBase * (2 * leftTriangleBase / (span * beforeModeSpan)) + val probability = 1f - leftTriangleArea * 2 + val result = values.count { it in innerRange }.toFloat() / count assert(abs(result - probability) / probability <= allowableError) } + assert(values.all { it in range }) } @Test @@ -223,22 +219,18 @@ class RangesTest { val expectedMode = fractionalMode * (range.endInclusive - range.start) + range.start val beforeModeSpan = expectedMode - range.start val afterModeSpan = range.endInclusive - expectedMode - val rangesToExpectedProbabilities = - listOf(0.2f, 0.4f, 0.6f, 0.8f) - .map { fraction -> - // For simplicity, all checked ranges span the mode - val checkRange = (beforeModeSpan * fraction + range.start)..(range.endInclusive - afterModeSpan * fraction) - val leftTriangleBase = checkRange.start - range.start - val leftTriangleArea = 0.5f * leftTriangleBase * (2 * leftTriangleBase / (span * beforeModeSpan)) - val rightTriangleBase = range.endInclusive - checkRange.endInclusive - val rightTriangleArea = 0.5f * rightTriangleBase * (2 * rightTriangleBase / (span * afterModeSpan)) - val probability = 1f - leftTriangleArea - rightTriangleArea - checkRange to probability - } - for ((checkRange, probability) in rangesToExpectedProbabilities) { - var result = values.count { it in checkRange }.toFloat() / count + for (fraction in listOf(0.2f, 0.4f, 0.6f, 0.8f)) { + // For simplicity, all checked ranges span the mode + val innerRange = (beforeModeSpan * fraction + range.start)..(range.endInclusive - afterModeSpan * fraction) + val leftTriangleBase = innerRange.start - range.start + val leftTriangleArea = 0.5f * leftTriangleBase * (2 * leftTriangleBase / (span * beforeModeSpan)) + val rightTriangleBase = range.endInclusive - innerRange.endInclusive + val rightTriangleArea = 0.5f * rightTriangleBase * (2 * rightTriangleBase / (span * afterModeSpan)) + val probability = 1f - leftTriangleArea - rightTriangleArea + val result = values.count { it in innerRange }.toFloat() / count assert(abs(result - probability) / probability <= allowableError) } + assert(values.all { it in range }) } } \ No newline at end of file From 489c53003437278e15f837d6375081660f62763e Mon Sep 17 00:00:00 2001 From: MJ Date: Fri, 24 Jan 2020 16:51:46 +0100 Subject: [PATCH 27/32] Added .editorconfig. #241 --- .editorconfig | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..613091e9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 + +[*.kt] +indent_style = space +indent_size = 2 From 9fd735b424bfeacb8158d3f919ccf0d76a26f1d3 Mon Sep 17 00:00:00 2001 From: MJ Date: Fri, 24 Jan 2020 17:02:22 +0100 Subject: [PATCH 28/32] Updated documentation of range extensions. #227 --- .github/CONTRIBUTORS.md | 3 ++- CHANGELOG.md | 8 +++++++- math/README.md | 5 ++++- math/src/test/kotlin/ktx/math/rangesTest.kt | 3 +-- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/CONTRIBUTORS.md b/.github/CONTRIBUTORS.md index e0b2c394..6b29c27d 100644 --- a/.github/CONTRIBUTORS.md +++ b/.github/CONTRIBUTORS.md @@ -54,7 +54,8 @@ Project contributors listed chronologically. * [@kvonspiczak](https://github.com/kvonspiczak) * Contributed documentation fix. * [@cypherdare](https://github.com/cypherdare) - * Contributed to numerous utilities in [actors](../actors), [collections](../collections) and [graphics](../graphics) modules. + * Contributed to numerous utilities in [actors](../actors), [collections](../collections), [graphics](../graphics) + and [math](../math) modules. * Added `AssetGroup` API to [assets](../assets). * [@jakewilson](https://github.com/jakewilson) * Added utilities to [graphics module](../graphics). diff --git a/CHANGELOG.md b/CHANGELOG.md index 57067ad1..f33e4e78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,14 @@ #### 1.9.10-SNAPSHOT +- **[FEATURE]** (`ktx-actors`) Added `onTouchDown`, `onTouchUp` and `onTouchEvent` extension methods that allow to attach `ClickListener` instances to actors. - **[CHANGE]** (`ktx-collections`) `Array.removeAll` and `retainAll` now return a boolean if any elements were removed. - **[CHANGE]** (`ktx-collections`) `Array.transfer` is now less strict about typing. -- **[FEATURE]** (`ktx-actors`) Added `onTouchDown`, `onTouchUp` and `onTouchEvent` extension methods that allow to attach `ClickListener` instances to actors. +- **[FEATURE]** (`ktx-math`) Added Kotlin ranges extensions that simplify creating ranges and rolling random numbers: + - `Int.amid`, `Float.amid`; + - `+`, `-`, `*` and `/` for ranges; + - `ClosedRange.random`, `IntRange.random`; + - `ClosedRange.randomGaussian`; + - `ClosedRange.randomTriangular`. - **[FEATURE]** (`ktx-tiled`) Added a new KTX module: Tiled API extensions. - Added `contains` (`in`) and `set` (`[]`) operators support to `MapProperties`. - Added extension methods that simplify properties extraction from `MapLayer`, `MapObject`, `TiledMap`, `TiledMapTile` and `TiledMapTileSet`: diff --git a/math/README.md b/math/README.md index bebd29b1..99d5d709 100644 --- a/math/README.md +++ b/math/README.md @@ -186,9 +186,10 @@ range is six standard deviations wide. - `ClosedRange.randomTriangular()` allow easy selection of a triangularly distributed number from the range. A a `normalizedMode` can be passed for asymmetrical distributions. -##### Usage example +##### Usage examples Suppose there is a class that has a random behavior. Its can be constructed by passing several ranges to its constructor. + ```kotlin class CreatureSpawner(val spawnIntervalRange: ClosedRange) { //... @@ -203,6 +204,7 @@ class CreatureSpawner(val spawnIntervalRange: ClosedRange) { ``` In a parent class, there are many of these instances set up. The ranges can be described intuitively: + ```kotlin val spawners = listOf( //... @@ -212,6 +214,7 @@ val spawners = listOf( ``` And as the design is iterated, the range can be adjusted quickly and intuitively by applying arithmetic operations: + ```kotlin val spawners = listOf( //... diff --git a/math/src/test/kotlin/ktx/math/rangesTest.kt b/math/src/test/kotlin/ktx/math/rangesTest.kt index cd378c93..2e7bac37 100644 --- a/math/src/test/kotlin/ktx/math/rangesTest.kt +++ b/math/src/test/kotlin/ktx/math/rangesTest.kt @@ -232,5 +232,4 @@ class RangesTest { } assert(values.all { it in range }) } - -} \ No newline at end of file +} From c6f4652f453cbf03bc855a70001d96c75d5613c1 Mon Sep 17 00:00:00 2001 From: MJ Date: Fri, 24 Jan 2020 20:06:56 +0100 Subject: [PATCH 29/32] Documentation fix. #138 --- graphics/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphics/README.md b/graphics/README.md index cecfd49e..c80c381e 100644 --- a/graphics/README.md +++ b/graphics/README.md @@ -1,4 +1,4 @@ -# KTX : Graphics utilities +# KTX: graphics utilities General utilities for handling LibGDX graphics-related API. From b22546fc9c2c3ac8c289cb87721da7f54e26973b Mon Sep 17 00:00:00 2001 From: MJ Date: Fri, 24 Jan 2020 20:10:31 +0100 Subject: [PATCH 30/32] Documentation fix. #233 --- tiled/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiled/README.md b/tiled/README.md index 39e85d12..8f3797ab 100644 --- a/tiled/README.md +++ b/tiled/README.md @@ -1,4 +1,4 @@ -# KTX : TiledMap utilities +# KTX: Tiled map editor utilities [Tiled](https://www.mapeditor.org/) API utilities. From d722643be78921a5aa1bf61d4de0b5b58198a36c Mon Sep 17 00:00:00 2001 From: MJ Date: Fri, 24 Jan 2020 20:14:34 +0100 Subject: [PATCH 31/32] Documentation fix. #233 --- tiled/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/tiled/README.md b/tiled/README.md index 8f3797ab..af4fa567 100644 --- a/tiled/README.md +++ b/tiled/README.md @@ -205,7 +205,6 @@ 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: From 7fc7bc84522c3b940525ab9725fd2937bacc2267 Mon Sep 17 00:00:00 2001 From: MJ Date: Fri, 24 Jan 2020 20:24:14 +0100 Subject: [PATCH 32/32] Changed KTX version to 1.9.10-b4. #235 --- CHANGELOG.md | 2 +- version.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f33e4e78..206e4b41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -#### 1.9.10-SNAPSHOT +#### 1.9.10-b4 - **[FEATURE]** (`ktx-actors`) Added `onTouchDown`, `onTouchUp` and `onTouchEvent` extension methods that allow to attach `ClickListener` instances to actors. - **[CHANGE]** (`ktx-collections`) `Array.removeAll` and `retainAll` now return a boolean if any elements were removed. diff --git a/version.txt b/version.txt index e7b756bc..91a74f33 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.9.10-SNAPSHOT +1.9.10-b4