Beat the high-score: build a game using libGDX and Kotlin

7843bb075c05be6886a97b77e36758ff?s=47 David
October 04, 2018

Beat the high-score: build a game using libGDX and Kotlin

Playing games is fun but making games is even better, especially with Kotlin and libGDX. Let's build together a breakout game and let's explore the gaming framework libGDX.

This session will show some libGDX's concepts: how to draw and animate elements of our game, how collision system works to destroy our bricks, what Kotlin brings to libGDX. Then we will dive into more advanced topics like shaders, in order to handle pixels from our images. Why are we doing all of this? To break the high score, of course!

Code: https://github.com/dwursteisen/beat-the-high-score
Game: https://play.google.com/store/apps/details?id=com.github.dwursteisen.beat
Video: https://www.youtube.com/watch?v=kDxerDYelLs

(KotlinConf 2018 - 4th October 2018 - Amsterdam)

7843bb075c05be6886a97b77e36758ff?s=128

David

October 04, 2018
Tweet

Transcript

  1. Build a game using libGDX and Kotlin Build a game

    using libGDX and Kotlin Build a game using libGDX and Kotlin Build a game using libGDX and Kotlin Beat the High score Beat the High score Build a game using libGDX and Kotlin
  2. -Gilles Allain In a farm… far, far away…

  3. -Gilles Allain a sneaky fox appears in the darkness of

    the night.
  4. None
  5. -Gilles Allain In a mighty move, he stole all the

    chickens of the farm...
  6. None
  7. -Gilles Allain those chickens were our...friends.

  8. -Gilles Allain the same night, a thunder strikes you!

  9. None
  10. -Gilles Allain ...this strike have given you powers to bring

    back... ...our friends!
  11. David Wursteisen

  12. None
  13. None
  14. John Carmack

  15. None
  16. None
  17. Rota Pong

  18. Wiz 
 The Wizard

  19. Beat 
 The High 
 Score

  20. Game engines

  21. None
  22. Pavel Quest Destroy Blocks Mirage Realms

  23. Hair Dash - http://cleancutgames.com/

  24. None
  25. None
  26. None
  27. Core iOS GWT Android Desktop

  28. dependencies { compile "com.badlogicgames.gdx:gdx:$gdxVersion" compile "com.badlogicgames.gdx:gdx-box2d:$gdxVersion" compile "com.badlogicgames.ashley:ashley:$ashleyVersion" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"

    compile "io.github.libktx:ktx-ashley:$ktxVersion" compile "io.github.libktx:ktx-graphics:$ktxVersion" compile "io.github.libktx:ktx-scene2d:$ktxVersion" compile "io.github.libktx:ktx-log:$ktxVersion" compile "com.github.dwursteisen.libgdx-addons:aseprite-addons:$libgdx_addons" compile "com.github.dwursteisen.libgdx-addons:ashley-addons:$libgdx_addons" compile "com.strongjoshua:libgdx-inGameConsole:$console" }
  29. dependencies { compile "com.badlogicgames.gdx:gdx:$gdxVersion" compile "com.badlogicgames.gdx:gdx-box2d:$gdxVersion" compile "com.badlogicgames.ashley:ashley:$ashleyVersion" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"

    compile "io.github.libktx:ktx-ashley:$ktxVersion" compile "io.github.libktx:ktx-graphics:$ktxVersion" compile "io.github.libktx:ktx-scene2d:$ktxVersion" compile "io.github.libktx:ktx-log:$ktxVersion" compile "com.github.dwursteisen.libgdx-addons:aseprite-addons:$libgdx_addons" compile "com.github.dwursteisen.libgdx-addons:ashley-addons:$libgdx_addons" compile "com.strongjoshua:libgdx-inGameConsole:$console" }
  30. dependencies { compile "com.badlogicgames.gdx:gdx:$gdxVersion" compile "com.badlogicgames.gdx:gdx-box2d:$gdxVersion" compile "com.badlogicgames.ashley:ashley:$ashleyVersion" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"

    compile "io.github.libktx:ktx-ashley:$ktxVersion" compile "io.github.libktx:ktx-graphics:$ktxVersion" compile "io.github.libktx:ktx-scene2d:$ktxVersion" compile "io.github.libktx:ktx-log:$ktxVersion" compile "com.github.dwursteisen.libgdx-addons:aseprite-addons:$libgdx_addons" compile "com.github.dwursteisen.libgdx-addons:ashley-addons:$libgdx_addons" compile "com.strongjoshua:libgdx-inGameConsole:$console" }
  31. dependencies { compile "com.badlogicgames.gdx:gdx:$gdxVersion" compile "com.badlogicgames.gdx:gdx-box2d:$gdxVersion" compile "com.badlogicgames.ashley:ashley:$ashleyVersion" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"

    compile "io.github.libktx:ktx-ashley:$ktxVersion" compile "io.github.libktx:ktx-graphics:$ktxVersion" compile "io.github.libktx:ktx-scene2d:$ktxVersion" compile "io.github.libktx:ktx-log:$ktxVersion" compile "com.github.dwursteisen.libgdx-addons:aseprite-addons:$libgdx_addons" compile "com.github.dwursteisen.libgdx-addons:ashley-addons:$libgdx_addons" compile "com.strongjoshua:libgdx-inGameConsole:$console" }
  32. dependencies { compile "com.badlogicgames.gdx:gdx:$gdxVersion" compile "com.badlogicgames.gdx:gdx-box2d:$gdxVersion" compile "com.badlogicgames.ashley:ashley:$ashleyVersion" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"

    compile "io.github.libktx:ktx-ashley:$ktxVersion" compile "io.github.libktx:ktx-graphics:$ktxVersion" compile "io.github.libktx:ktx-scene2d:$ktxVersion" compile "io.github.libktx:ktx-log:$ktxVersion" compile "com.github.dwursteisen.libgdx-addons:aseprite-addons:$libgdx_addons" compile "com.github.dwursteisen.libgdx-addons:ashley-addons:$libgdx_addons" compile "com.strongjoshua:libgdx-inGameConsole:$console" }
  33. Let’s make a game!

  34. public void render(float delta) {
 movePlayer(delta);
 moveEnemies(delta);
 moveWorld(delta);
 
 renderWorld();


    renderEnemies();
 renderPlayer();
 }
  35. public void render(float delta) {
 movePlayer(delta);
 moveEnemies(delta);
 moveWorld(delta);
 
 renderWorld();


    renderEnemies();
 renderPlayer();
 }
  36. public void render(float delta) {
 movePlayer(delta);
 moveEnemies(delta);
 moveWorld(delta);
 
 renderWorld();


    renderEnemies();
 renderPlayer();
 }
  37. public void render(float delta) {
 movePlayer(delta);
 moveEnemies(delta);
 moveWorld(delta);
 
 renderWorld();


    renderEnemies();
 renderPlayer();
 } 60x
 seconds
  38. None
  39. class MyScreen : ScreenAdapter() { lateinit var batch: SpriteBatch override

    fun create() { batch = SpriteBatch() } override fun render() { batch.begin() batch.draw(image, x, y) batch.end() } }
  40. class MyScreen : ScreenAdapter() { lateinit var batch: SpriteBatch override

    fun create() { batch = SpriteBatch() } override fun render() { batch.begin() batch.draw(image, x, y) batch.end() } }
  41. class MyScreen : ScreenAdapter() { lateinit var batch: SpriteBatch override

    fun create() { batch = SpriteBatch() } override fun render() { batch.begin() batch.draw(image, x, y) batch.end() } }
  42. class MyScreen : ScreenAdapter() { lateinit var batch: SpriteBatch override

    fun create() { batch = SpriteBatch() } override fun render() { batch.begin() batch.draw(image, x, y) batch.end() } }
  43. class MyScreen : ScreenAdapter() { lateinit var batch: ShapeRenderer override

    fun create() { batch = ShapeRenderer() } override fun render() { batch.begin(ShapeRenderer.ShapeType.Filled) batch.color = Color.CHARTREUSE batch.circle(x, y, radius) batch.end() } }
  44. class MyScreen : ScreenAdapter() { lateinit var batch: ShapeRenderer override

    fun create() { batch = ShapeRenderer() } override fun render() { batch.begin(ShapeRenderer.ShapeType.Filled) batch.color = Color.CHARTREUSE batch.circle(x, y, radius) batch.end() } }
  45. class MyScreen : ScreenAdapter() { lateinit var batch: ShapeRenderer override

    fun create() { batch = ShapeRenderer() } override fun render() { batch.begin(ShapeRenderer.ShapeType.Filled) batch.color = Color.CHARTREUSE batch.circle(x, y, radius) batch.end() } }
  46. class MyScreen : ScreenAdapter() { lateinit var batch: ShapeRenderer override

    fun create() { batch = ShapeRenderer() } override fun render() { batch.begin(ShapeRenderer.ShapeType.Filled) batch.color = Color.CHARTREUSE batch.circle(x, y, radius) batch.end() } }
  47. class MyScreen : ScreenAdapter() { lateinit var batch: SpriteBatch override

    fun create() { batch = SpriteBatch() } override fun render() { batch.begin() batch.draw(image, x, y) batch.end() } }
  48. class MyScreen : KtxScreen { val batch: SpriteBatch = SpriteBatch()

    override fun render() { batch.use { it.draw(sprite, x, y) } } } Inherit
  49. (0,0) x y

  50. class MyScreen : ScreenAdapter() { lateinit var batch: ShapeRenderer private

    val position: Vector2 = Vector2(0f, 0f) override fun create() { batch = ShapeRenderer() } override fun render(delta: Float) { position.add(0f, -0.5f) batch.begin(ShapeRenderer.ShapeType.Filled) batch.color = Color.CHARTREUSE batch.circle(position.x, position.y, radius) batch.end() } }
  51. class MyScreen : ScreenAdapter() { lateinit var batch: ShapeRenderer private

    val position: Vector2 = Vector2(0f, 0f) override fun create() { batch = ShapeRenderer() } override fun render(delta: Float) { position.add(0f, -0.5f) batch.begin(ShapeRenderer.ShapeType.Filled) batch.color = Color.CHARTREUSE batch.circle(position.x, position.y, radius) batch.end() } }
  52. class MyScreen : ScreenAdapter() { lateinit var batch: ShapeRenderer private

    val position: Vector2 = Vector2(0f, 0f) override fun create() { batch = ShapeRenderer() } override fun render(delta: Float) { position.add(0f, -0.5f) batch.begin(ShapeRenderer.ShapeType.Filled) batch.color = Color.CHARTREUSE batch.circle(position.x, position.y, radius) batch.end() } }
  53. class MyScreen : ScreenAdapter() { lateinit var batch: ShapeRenderer private

    val position: Vector2 = Vector2(0f, 0f) override fun create() { batch = ShapeRenderer() } override fun render(delta: Float) { position.add(0f, -0.5f) batch.begin(ShapeRenderer.ShapeType.Filled) batch.color = Color.CHARTREUSE batch.circle(position.x, position.y, radius) batch.end() } }
  54. class MyScreen : ScreenAdapter() { lateinit var batch: ShapeRenderer private

    val position: Vector2 = Vector2(0f, 0f) private var time: Float = 0f override fun create() { batch = ShapeRenderer() } override fun render(delta: Float) { val (x, y) = position time += delta batch.begin(ShapeRenderer.ShapeType.Filled) batch.color = Color.CHARTREUSE batch.circle(x + MathUtils.cos(time), y, radius) batch.end() } }
  55. class MyScreen : ScreenAdapter() { lateinit var batch: ShapeRenderer private

    val position: Vector2 = Vector2(0f, 0f) private var time: Float = 0f override fun create() { batch = ShapeRenderer() } override fun render(delta: Float) { val (x, y) = position time += delta batch.begin(ShapeRenderer.ShapeType.Filled) batch.color = Color.CHARTREUSE batch.circle(x + MathUtils.cos(time), y, radius) batch.end() } }
  56. class MyScreen : ScreenAdapter() { lateinit var batch: ShapeRenderer private

    val position: Vector2 = Vector2(0f, 0f) private var time: Float = 0f override fun create() { batch = ShapeRenderer() } override fun render(delta: Float) { val (x, y) = position time += delta batch.begin(ShapeRenderer.ShapeType.Filled) batch.color = Color.CHARTREUSE batch.circle(x + MathUtils.cos(time), y, radius) batch.end() } }
  57. class MyScreen : ScreenAdapter() { lateinit var batch: ShapeRenderer private

    val position: Vector2 = Vector2(0f, 0f) private var time: Float = 0f override fun create() { batch = ShapeRenderer() } override fun render(delta: Float) { val (x, y) = position time += delta batch.begin(ShapeRenderer.ShapeType.Filled) batch.color = Color.CHARTREUSE batch.circle(x + MathUtils.cos(time), y, radius) batch.end() } }
  58. /** * Operator function that allows to deconstruct this vector.

    * @return X component. */ operator fun Vector2.component1(): Float = this.x /** * Operator function that allows to deconstruct this vector. * @return Y component. */ operator fun Vector2.component2(): Float = this.y val vector = Vector2(45f, 50f) val (x, y) = vector
  59. /** * Operator function that allows to deconstruct this vector.

    * @return X component. */ operator fun Vector2.component1(): Float = this.x /** * Operator function that allows to deconstruct this vector. * @return Y component. */ operator fun Vector2.component2(): Float = this.y val (x, y) = Vector2(45f, 50f)
  60. entity.add(Ball()) .add(Position(Vector2(-100f, -100f))) .add(Size(Vector2(8f, 9f))) .add(Rotation(Vector2(4f, 4f)))

  61. entity.add(Ball()) .add(Position(Vector2(-100f, -100f))) .add(Size(Vector2(8f, 9f))) .add(Rotation(Vector2(4f, 4f)))

  62. entity.add(Ball()) .add(Position(-100 v2 -100f)) .add(Size(8 v2 9)) .add(Rotation(4 v2 4))

    Create a 
 new Vector2 infix fun Number.v2(other: Number): Vector2 { return Vector2(this.toFloat(), other.toFloat()) }
  63. Math: back to school

  64. None
  65. Side Racket Ball

  66. Racket Side Side Ball

  67. Racket Side Side Ball dx = dx * -1

  68. Racket Side Side Ball dx = dx * -1

  69. Racket Side Side Ball Alpha dy = dy * -1

    * alpha dx = dx * alpha
  70. val alpha = Math.abs(ball.x - raquet.x) val direction: Vector2 =

    Vector2(10f, 20f) direction.scl(1f, -1f) .scl(alpha) .nor() .rotate(306f) .add(10f, 20f)
  71. val ball = Rectangle(68f, 64f, 5f, 5f) val player =

    Rectangle(64f, 64f, 100f, 10f) // the ball hit the player? player.overlaps(ball)
  72. val ball = Rectangle(68f, 64f, 5f, 5f) val player =

    Rectangle(64f, 64f, 100f, 10f) // the ball hit the player? player.overlaps(ball)
  73. val ball = Rectangle(68f, 64f, 5f, 5f) val player =

    Rectangle(64f, 64f, 100f, 10f) // the ball hit the player? player.overlaps(ball)
  74. None
  75. None
  76. None
  77. Box2D

  78. var world = World(Vector2(0, -10), true) // ... // Do

    in render method world.step(1/60f, 6, 2); val bodyDef = BodyDef() // Set its world position bodyDef.position.set(position.x, position.y) bodyDef.type = BodyDef.BodyType.DynamicBody bodyDef.angle = 0f val body = world.createBody(bodyDef) val shape = CircleShape() shape.radius = 8f // 2. Create a FixtureDef, as usual. val fd = FixtureDef() fd.density = 1f fd.friction = 2f fd.shape = shape body.createFixture(fd) body.userData = Any // attach sprite data shape.dispose()
  79. var world = World(Vector2(0, -10), true) // ... // Do

    in render method world.step(1/60f, 6, 2); val bodyDef = BodyDef() // Set its world position bodyDef.position.set(position.x, position.y) bodyDef.type = BodyDef.BodyType.DynamicBody bodyDef.angle = 0f val body = world.createBody(bodyDef) val shape = CircleShape() shape.radius = 8f // 2. Create a FixtureDef, as usual. val fd = FixtureDef() fd.density = 1f fd.friction = 2f fd.shape = shape body.createFixture(fd) body.userData = Any // attach sprite data shape.dispose() Gravity
  80. var world = World(Vector2(0, -10), true) // ... // Do

    in render method world.step(1/60f, 6, 2); val bodyDef = BodyDef() // Set its world position bodyDef.position.set(position.x, position.y) bodyDef.type = BodyDef.BodyType.DynamicBody bodyDef.angle = 0f val body = world.createBody(bodyDef) val shape = CircleShape() shape.radius = 8f // 2. Create a FixtureDef, as usual. val fd = FixtureDef() fd.density = 1f fd.friction = 2f fd.shape = shape body.createFixture(fd) body.userData = Any // attach sprite data shape.dispose()
  81. var world = World(Vector2(0, -10), true) // ... // Do

    in render method world.step(1/60f, 6, 2); val bodyDef = BodyDef() // Set its world position bodyDef.position.set(position.x, position.y) bodyDef.type = BodyDef.BodyType.DynamicBody bodyDef.angle = 0f val body = world.createBody(bodyDef) val shape = CircleShape() shape.radius = 8f // 2. Create a FixtureDef, as usual. val fd = FixtureDef() fd.density = 1f fd.friction = 2f fd.shape = shape body.createFixture(fd) body.userData = Any // attach sprite data shape.dispose() Configuration of the « thing »
  82. var world = World(Vector2(0, -10), true) // ... // Do

    in render method world.step(1/60f, 6, 2); val bodyDef = BodyDef() // Set its world position bodyDef.position.set(position.x, position.y) bodyDef.type = BodyDef.BodyType.DynamicBody bodyDef.angle = 0f val body = world.createBody(bodyDef) val shape = CircleShape() shape.radius = 8f // 2. Create a FixtureDef, as usual. val fd = FixtureDef() fd.density = 1f fd.friction = 2f fd.shape = shape body.createFixture(fd) body.userData = Any // attach sprite data shape.dispose()
  83. var world = World(Vector2(0, -10), true) // ... // Do

    in render method world.step(1/60f, 6, 2); val bodyDef = BodyDef() // Set its world position bodyDef.position.set(position.x, position.y) bodyDef.type = BodyDef.BodyType.DynamicBody bodyDef.angle = 0f val body = world.createBody(bodyDef) val shape = CircleShape() shape.radius = 8f // 2. Create a FixtureDef, as usual. val fd = FixtureDef() fd.density = 1f fd.friction = 2f fd.shape = shape body.createFixture(fd) body.userData = Any // attach sprite data shape.dispose()
  84. var world = World(Vector2(0, -10), true) // ... // Do

    in render method world.step(1/60f, 6, 2); val bodyDef = BodyDef() // Set its world position bodyDef.position.set(position.x, position.y) bodyDef.type = BodyDef.BodyType.DynamicBody bodyDef.angle = 0f val body = world.createBody(bodyDef) val shape = CircleShape() shape.radius = 8f // 2. Create a FixtureDef, as usual. val fd = FixtureDef() fd.density = 1f fd.friction = 2f fd.shape = shape body.createFixture(fd) body.userData = Any // attach sprite data shape.dispose()
  85. Traversable

  86. Math: Interpolation

  87. None
  88. None
  89. None
  90. y = Interpolation.bounceOut.apply(time) * 100f Non linear Should be a

    percentage (0..1) y will go from 0 to 100
  91. The screen, the viewport 
 and the camera

  92. Game screen screen screen screen screen

  93. open class MenuScreen(val game: Game) : ScreenAdapter() { override fun

    render(delta: Float) { // clear the screen Gdx.gl.glClearColor(0f, 0f, 0f, 1f) Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT) batch.begin() // ... batch.end() if (goToMenu) { game.setScreen(menuScreen) } } }
  94. open class MenuScreen(val game: Game) : ScreenAdapter() { override fun

    render(delta: Float) { // clear the screen Gdx.gl.glClearColor(0f, 0f, 0f, 1f) Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT) batch.begin() // ... batch.end() if (goToMenu) { game.setScreen(menuScreen) } } } ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠ game.setScreen(screen) 
 is not the same that
 game.screen = screen ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
  95. None
  96. None
  97. None
  98. class MyScreen : ScreenAdapter() { private lateinit var viewport: Viewport

    private lateinit var bath: SpriteBatch override fun show() { viewport = FitViewport(200f, 200f) bath = SpriteBath() } override fun render(delta: Float) { Gdx.gl.glClearColor(91f / 256f, 110f / 256f, 225f / 256f, 1f) Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT) batch.begin() batch.projectionMatrix = viewport.camera.combined // render batch.end() } override fun resize(width: Int, height: Int) { viewport.update(width, height) } }
  99. class MyScreen : ScreenAdapter() { private lateinit var viewport: Viewport

    private lateinit var bath: SpriteBatch override fun show() { viewport = FitViewport(200f, 200f) bath = SpriteBath() } override fun render(delta: Float) { Gdx.gl.glClearColor(91f / 256f, 110f / 256f, 225f / 256f, 1f) Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT) batch.begin() batch.projectionMatrix = viewport.camera.combined // render batch.end() } override fun resize(width: Int, height: Int) { viewport.update(width, height) } } Size of the world you’ll display on the screen
  100. class MyScreen : ScreenAdapter() { private lateinit var viewport: Viewport

    private lateinit var bath: SpriteBatch override fun show() { viewport = FitViewport(200f, 200f) bath = SpriteBath() } override fun render(delta: Float) { Gdx.gl.glClearColor(91f / 256f, 110f / 256f, 225f / 256f, 1f) Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT) batch.begin() batch.projectionMatrix = viewport.camera.combined // render batch.end() } override fun resize(width: Int, height: Int) { viewport.update(width, height) } }
  101. None
  102. ScreenViewport

  103. StretchViewport

  104. FitViewport Clear this zone Clear this zone

  105. FillViewport

  106. ExtendViewport Displaying this zone

  107. None
  108. None
  109. viewport.camera.position.add(0f, 40f, 0f) // Recalculates the projection and // view

    matrix of this camera // Use this after you've manipulated any of // the attributes of the camera. viewport.camera.update() viewport.camera.rotate(10f, 0f, 0f, 1f) viewport.camera.update()
  110. viewport.camera.position.add(0f, 40f, 0f) // Recalculates the projection and // view

    matrix of this camera // Use this after you've manipulated any of // the attributes of the camera. viewport.camera.update() viewport.camera.rotate(10f, 0f, 0f, 1f) viewport.camera.update() Will move the camera up
  111. viewport.camera.position.add(0f, 40f, 0f) // Recalculates the projection and // view

    matrix of this camera // Use this after you've manipulated any of // the attributes of the camera. viewport.camera.update() viewport.camera.rotate(10f, 0f, 0f, 1f) viewport.camera.update() Rotate the camera on the z Axis
  112. Around the game

  113. None
  114. None
  115. // load texture val texture = Texture(Gdx.files.internal("texture.png")) val split =

    TextureRegion.split(texture, 128, 128) // select frames val frames = Array<TextureRegion>() frames.add(split[0][0]) frames.add(split[0][1]) frames.add(split[0][2]) frames.add(split[0][4]) // create animation val animation = Animation<TextureRegion>(100f, frames) // select frame to draw val keyToRender = animation.getKeyFrame(time)
  116. // load texture val texture = Texture(Gdx.files.internal("texture.png")) val split =

    TextureRegion.split(texture, 128, 128) // select frames val frames = Array<TextureRegion>() frames.add(split[0][0]) frames.add(split[0][1]) frames.add(split[0][2]) frames.add(split[0][4]) // create animation val animation = Animation<TextureRegion>(100f, frames) // select frame to draw val keyToRender = animation.getKeyFrame(time)
  117. // load texture val texture = Texture(Gdx.files.internal("texture.png")) val split =

    TextureRegion.split(texture, 128, 128) // select frames val frames = Array<TextureRegion>() frames.add(split[0][0]) frames.add(split[0][1]) frames.add(split[0][2]) frames.add(split[0][4]) // create animation val animation = Animation<TextureRegion>(100f, frames) // select frame to draw val keyToRender = animation.getKeyFrame(time)
  118. // load texture val texture = Texture(Gdx.files.internal("texture.png")) val split =

    TextureRegion.split(texture, 128, 128) // select frames val frames = com.badlogic.gdx.utils.Array<TextureRegion>() frames.add(split[0][0]) frames.add(split[0][1]) frames.add(split[0][2]) frames.add(split[0][4]) // create animation val animation = Animation<TextureRegion>(100f, frames) // select frame to draw val keyToRender = animation.getKeyFrame(time) import com.badlogic.gdx.utils.Array as GdxArray
  119. // load texture val texture = Texture(Gdx.files.internal("texture.png")) val split =

    TextureRegion.split(texture, 128, 128) // select frames val frames = GdxArray<TextureRegion>() frames.add(split[0][0]) frames.add(split[0][1]) frames.add(split[0][2]) frames.add(split[0][4]) // create animation val animation = Animation<TextureRegion>(100f, frames) // select frame to draw val keyToRender = animation.getKeyFrame(time)
  120. // load texture val texture = Texture(Gdx.files.internal("texture.png")) val split =

    TextureRegion.split(texture, 128, 128) // select frames val frames = GdxArray<TextureRegion>() frames.add(split[0][0]) frames.add(split[0][1]) frames.add(split[0][2]) frames.add(split[0][4]) // create animation val animation = Animation<TextureRegion>(100f, frames) // select frame to draw val keyToRender = animation.getKeyFrame(time) Duration per frame
  121. 100ms 100ms 100ms 100ms

  122. 800ms 100ms 100ms 100ms 100ms 100ms 100ms 100ms 100ms 100ms

    100ms
  123. Loading…

  124. val assetsManager = AssetManager() assetsManager.load("player.png", Texture::class.java) assetsManager.load("sfx/beat_intro.ogg", Music::class.java) assetsManager.load("krungthep2.fnt", BitmapFont::class.java)

    assetsManager.load("sheets/intro", Aseprite::class.java) // call it until it's loaded val isLoaded = assetsManager.update() val texture = assetsManager.get("player.png", Texture::class.java)
  125. val assetsManager = AssetManager() assetsManager.load("player.png", Texture::class.java) assetsManager.load("sfx/beat_intro.ogg", Music::class.java) assetsManager.load("krungthep2.fnt", BitmapFont::class.java)

    assetsManager.load("sheets/intro", Aseprite::class.java) // call it until it's loaded val isLoaded = assetsManager.update() val texture = assetsManager.get("player.png", Texture::class.java)
  126. val assetsManager = AssetManager() assetsManager.load("player.png", Texture::class.java) assetsManager.load("sfx/beat_intro.ogg", Music::class.java) assetsManager.load("krungthep2.fnt", BitmapFont::class.java)

    assetsManager.load("sheets/intro", Aseprite::class.java) // call it until it's loaded val isLoaded = assetsManager.update() val texture = assetsManager.get("player.png", Texture::class.java)
  127. val assetsManager = AssetManager() assetsManager.load("player.png", Texture::class.java) assetsManager.load("sfx/beat_intro.ogg", Music::class.java) assetsManager.load("krungthep2.fnt", BitmapFont::class.java)

    assetsManager.load("sheets/intro", Aseprite::class.java) // call it until it's loaded val isLoaded = assetsManager.update() val texture = assetsManager.get("player.png", Texture::class.java)
  128. val assetsManager = AssetManager() assetsManager.load("player.png", Texture::class.java) assetsManager.load("sfx/beat_intro.ogg", Music::class.java) assetsManager.load("krungthep2.fnt", BitmapFont::class.java)

    assetsManager.load("sheets/intro", Aseprite::class.java) // call it until it's loaded val isLoaded = assetsManager.update() val texture = assetsManager.get("player.png", Texture::class.java) Call it in the render method
  129. val assetsManager = AssetManager() assetsManager.load("player.png", Texture::class.java) assetsManager.load("sfx/beat_intro.ogg", Music::class.java) assetsManager.load("krungthep2.fnt", BitmapFont::class.java)

    assetsManager.load("sheets/intro", Aseprite::class.java) // call it until it's loaded val isLoaded = assetsManager.update() val texture = assetsManager.get("player.png", Texture::class.java)
  130. val assetsManager = AssetManager() assetsManager.load("player.png", Texture::class.java) assetsManager.load("sfx/beat_intro.ogg", Music::class.java) assetsManager.load("krungthep2.fnt", BitmapFont::class.java)

    assetsManager.load("sheets/intro", Aseprite::class.java) // call it until it's loaded val isLoaded = assetsManager.update() val texture: Texture = assetsManager["player.png"] inline operator fun <reified T> AssetManager.get(filename: String): T { return this.get(filename, T::class.java) }
  131. Scrollpane Label Button Select box

  132. val scene = verticalGroup { setFillParent(true) pad(10f) label("Hello KotlinConf!") horizontalGroup

    { textButton("Click On Me") label("<-- here") } label("Another label...") selectBoxOf(GdxArray<String>().apply { add("value 1") add("value 2") add("value 3") }) } stage = Stage(FitViewport(screenWidth, screenHeight)) stage.addActor(scene)
  133. val scene = verticalGroup { setFillParent(true) pad(10f) label("Hello KotlinConf!") horizontalGroup

    { textButton("Click On Me") label("<-- here") } label("Another label...") selectBoxOf(GdxArray<String>().apply { add("value 1") add("value 2") add("value 3") }) } stage = Stage(FitViewport(screenWidth, screenHeight)) stage.addActor(scene)
  134. val scene = verticalGroup { setFillParent(true) pad(10f) label("Hello KotlinConf!") horizontalGroup

    { textButton("Click On Me") label("<-- here") } label("Another label...") selectBoxOf(GdxArray<String>().apply { add("value 1") add("value 2") add("value 3") }) } stage = Stage(FitViewport(screenWidth, screenHeight)) stage.addActor(scene)
  135. val scene = verticalGroup { setFillParent(true) pad(10f) label("Hello KotlinConf!") horizontalGroup

    { textButton("Click On Me") label("<-- here") } label("Another label...") selectBoxOf(GdxArray<String>().apply { add("value 1") add("value 2") add("value 3") }) } stage = Stage(FitViewport(screenWidth, screenHeight)) stage.addActor(scene)
  136. Entity System

  137. Position Animation Player State

  138. val player = Entity() player.add(Player())) player.add(Position(100 v2 100)) player.add(Animation("idle")) player.add(StateComponent())

    Position Animation Player State
  139. ComponentMapper<Player> player = ComponentMapper.getFor(Player.class); ComponentMapper<Position> position = ComponentMapper.getFor(Position.class); ComponentMapper<Animation> animation

    = ComponentMapper.getFor(Animation.class); ComponentMapper<StateComponent> state = ComponentMapper.getFor(StateComponent.class); ... player.get(entity).life; position.get(entity).xy; animation.get(entity).getFrame(time); state.get(entity).time; Extractor Extraction
  140. inline fun <reified T : Component> EntitySystem.get(): ComponentMapper<T> = 


    ComponentMapper.getFor(T::class.java) inline operator fun <reified T : Component> Entity.get(mapper: ComponentMapper<T>): T = 
 mapper.get(this)
  141. val player: ComponentMapper<Player> = get() val position: ComponentMapper<Position> = get()

    val animation: ComponentMapper<Animation> = get() val state: ComponentMapper<StateComponent> = get() // access to components entity[player].life entity[position].xy entity[animation].frame(time) entity[state].time
  142. val player: ComponentMapper<Player> = get() val position: ComponentMapper<Position> = get()

    val animation: ComponentMapper<Animation> = get() val state: ComponentMapper<StateComponent> = get() // access to components entity[player].life entity[position].xy entity[animation].frame(time) entity[state].time Accessing like a map
  143. Close Open

  144. fun on(event: Int, block: Transition): OnState { var currentTransitions =

    parent.transitions[state] ?: emptyMap() currentTransitions += event to block parent.transitions += state to currentTransitions return this } Lambda
  145. startWith(WAIT) onState(WAIT).on(GameEvents.EVENT_COMPUTER_ALLOW_MOVE) { entity, event -> if (entity[dragon].currentTurn > 3)

    { entity[dragon].currentTurn = -1 go(FIRE, entity, event) } else { go(MOVE, entity, event) } } onState(FIRE).on(GameEvents.EVENT_DRAGON_FIRE_BALL) { entity, event -> go(MOVE, entity, event) } Lambda
  146. startWith(WAIT) onState(WAIT).on(GameEvents.EVENT_COMPUTER_ALLOW_MOVE) { entity, event -> if (entity[dragon].currentTurn > 3)

    { entity[dragon].currentTurn = -1 go(FIRE, entity, event) } else { go(MOVE, entity, event) } } onState(FIRE).on(GameEvents.EVENT_DRAGON_FIRE_BALL) { entity, event -> go(MOVE, entity, event) } State Event Transition
  147. Pitfalls

  148. Garbage Collector Garbage Collector Garbage Collector Garbage Collecto arbage C

    Garbage STOP THE WORLD
  149. altLayers = map.layers .mapIndexed { index, layer -> index to

    layer } .filter { it.second.name.startsWith("alt_") } .map { it.first } .toIntArray() List 1 List 2 List 3 List 4 mutableList
  150. val ball = Rectangle(68f, 64f, 5f, 5f) val player =

    Rectangle(64f, 64f, 100f, 10f) // the ball hit the player? player.overlaps(ball)
  151. val pool = object : Pool<Rectangle>() { override fun newObject():

    Rectangle = Rectangle() } // ... val ball = pool.obtain().apply { set(68f, 64f, 5f, 5f) } val player = pool.obtain().apply { set(64f, 64f, 100f, 10f) } // the ball hit the player? player.overlaps(ball) pool.free(ball) pool.free(player)
  152. val pool = object : Pool<Rectangle>() { override fun newObject():

    Rectangle = Rectangle() } // ... val ball = pool.obtain().apply { set(68f, 64f, 5f, 5f) } val player = pool.obtain().apply { set(64f, 64f, 100f, 10f) } // the ball hit the player? player.overlaps(ball) pool.free(ball) pool.free(player)
  153. val pool = object : Pool<Rectangle>() { override fun newObject():

    Rectangle = Rectangle() } // ... val ball = pool.obtain().apply { set(68f, 64f, 5f, 5f) } val player = pool.obtain().apply { set(64f, 64f, 100f, 10f) } // the ball hit the player? player.overlaps(ball) pool.free(ball) pool.free(player)
  154. val pool = object : Pool<Rectangle>() { override fun newObject():

    Rectangle = Rectangle() } // ... val ball = pool.obtain().apply { set(68f, 64f, 5f, 5f) } val player = pool.obtain().apply { set(64f, 64f, 100f, 10f) } // the ball hit the player? player.overlaps(ball) pool.free(ball) pool.free(player)
  155. Integration with other tools

  156. None
  157. None
  158. None
  159. None
  160. None
  161. // can be loaded using AssetManager too val tmxMap =

    TmxMapLoader().load(mapName) tmxMap.layers["bricks"]?.objects?.forEach { brick -> // create game entity } mapRenderer = OrthogonalTiledMapRenderer(tmxMap) // render method mapRenderer.setView(viewport.camera as OrthographicCamera) mapRenderer.render()
  162. // can be loaded using AssetManager too val tmxMap =

    TmxMapLoader().load(mapName) tmxMap.layers["bricks"]?.objects?.forEach { brick -> // create game entity } mapRenderer = OrthogonalTiledMapRenderer(tmxMap) // render method mapRenderer.setView(viewport.camera as OrthographicCamera) mapRenderer.render()
  163. // can be loaded using AssetManager too val tmxMap =

    TmxMapLoader().load(mapName) tmxMap.layers["bricks"]?.objects?.forEach { brick -> // create game entity } mapRenderer = OrthogonalTiledMapRenderer(tmxMap) // render method mapRenderer.setView(viewport.camera as OrthographicCamera) mapRenderer.render()
  164. // can be loaded using AssetManager too val tmxMap =

    TmxMapLoader().load(mapName) tmxMap.layers["bricks"]?.objects?.forEach { brick -> // create game entity } mapRenderer = OrthogonalTiledMapRenderer(tmxMap) // render method mapRenderer.setView(viewport.camera as OrthographicCamera) mapRenderer.render()
  165. None
  166. hit 4 sound chicken.wav

  167. tmxMap.layers["bricks"].objects.forEach { brick -> val hit: Any = brick.properties["hit"] }

    Error prone Error prone
  168. class BrickProperties(properties: MapProperties) { val x: Double by properties val

    y: Double by properties val hit: Int by properties val sound: String by properties } tmxMap.layers["bricks"].objects.forEach { brick -> val props = BrickProperties(brick.properties) val hit = props.hit }
  169. class BrickProperties(properties: MapProperties) { val x: Double by properties val

    y: Double by properties val hit: Int by properties val sound: String by properties } tmxMap.layers["bricks"].objects.forEach { brick -> val props = BrickProperties(brick.properties) val hit = props.hit }
  170. class BrickProperties(properties: MapProperties) { val x: Double by properties val

    y: Double by properties val hit: Int by properties val sound: String by properties } tmxMap.layers["bricks"].objects.forEach { brick -> val props = BrickProperties(brick.properties) val hit = props.hit } Delegates
  171. inline operator fun <reified T> MapProperties.getValue(thisRef: Any?, property: KProperty<*>): T

    { val asStr = this[property.name].toString() return when (T::class) { Double::class -> asStr.toDouble() Int::class -> asStr.toInt() Boolean::class -> "true" == asStr String::class -> asStr else -> this[property.name] } as T }
  172. val chicken: Aseprite = assets["sheets/chicken"] val idleAnimation: Animation<TextureRegion> = chicken["idle"]

  173. Json Json Json

  174. open class AsepriteTask : DefaultTask() { // ... @TaskAction fun

    export() { val exec = getExecActionFactory().newExecAction() val exts = project.extensions.getByType(AsepritePluginExtentions::class.java) val aseprite = exts.exec ?: invalideAsepritePath() // ... } private fun invalideAsepritePath(): Nothing { TODO("""Missing aseprite executable path. Please configure it using aseprite.exec property (ie: in your ~/.gradle/gradle.properties) aseprite.exec=<path to exec> or using aseprite extension in your build.gradle aseprite { exec=<path to exec> } MacOS specific : point to aseprite located into <aseprite directory>/Aseprite.app/Contents/MacOS/aseprite""") } }
  175. open class AsepriteTask : DefaultTask() { // ... @TaskAction fun

    export() { val exec = getExecActionFactory().newExecAction() val exts = project.extensions.getByType(AsepritePluginExtentions::class.java) val aseprite = exts.exec ?: invalideAsepritePath() // ... } private fun invalideAsepritePath(): Nothing { TODO("""Missing aseprite executable path. Please configure it using aseprite.exec property (ie: in your ~/.gradle/gradle.properties) aseprite.exec=<path to exec> or using aseprite extension in your build.gradle aseprite { exec=<path to exec> } MacOS specific : point to aseprite located into <aseprite directory>/Aseprite.app/Contents/MacOS/aseprite""") } } What happens if null? Encountered an error, lol!
  176. open class AsepriteTask : DefaultTask() { // ... @TaskAction fun

    export() { val exec = getExecActionFactory().newExecAction() val exts = project.extensions.getByType(AsepritePluginExtentions::class.java) val aseprite = exts.exec ?: invalideAsepritePath() // ... } private fun invalideAsepritePath(): Nothing { TODO("""Missing aseprite executable path. Please configure it using aseprite.exec property (ie: in your ~/.gradle/gradle.properties) aseprite.exec=<path to exec> or using aseprite extension in your build.gradle aseprite { exec=<path to exec> } MacOS specific : point to aseprite located into <aseprite directory>/Aseprite.app/Contents/MacOS/aseprite""") } }
  177. open class AsepriteTask : DefaultTask() { // ... @TaskAction fun

    export() { val exec = getExecActionFactory().newExecAction() val exts = project.extensions.getByType(AsepritePluginExtentions::class.java) val aseprite = exts.exec ?: invalideAsepritePath() // ... } private fun invalideAsepritePath(): Nothing { TODO("""Missing aseprite executable path. Please configure it using aseprite.exec property (ie: in your ~/.gradle/gradle.properties) aseprite.exec=<path to exec> or using aseprite extension in your build.gradle aseprite { exec=<path to exec> } MacOS specific : point to aseprite located into <aseprite directory>/Aseprite.app/Contents/MacOS/aseprite""") } }
  178. Shaders

  179. void main() {
 vec4 sum = vec4(0);
 
 float step

    = iResolution.y;
 // y
 for(float i = -area ; i <= area ; i += 1.) {
 // x
 for(float j = -area ; j <= area ; j += 1.) {
 float x = v_texCoords.x + i / iResolution.x;
 float y = v_texCoords.y + j / iResolution.y;
 sum += texture2D(u_texture, vec2(x, y)) * 0.005;
 }
 }
 gl_FragColor = texture2D(u_texture, v_texCoords) + sum;
 } OpenGL Shading Language (GLSL)
  180. None
  181. None
  182. None
  183. None
  184. None
  185. None
  186. None
  187. None
  188. None
  189. if (color_pixel < cutoff) { // light dark pixel =

    gl_FragColor = vec4(0.3, 0.2, 0.4., 1.0); } else { pixel = color_pixel; } 0 1 Cut off
  190. if (color_pixel < cutoff) { // light dark pixel =

    gl_FragColor = vec4(0.3, 0.2, 0.4., 1.0); } else { pixel = color_pixel; }
  191. None
  192. None
  193. None
  194. https://www.shadertoy.com/view/ld3Gz2

  195. fun makeGames()

  196. fun makeGames()

  197. @dwursteisen http://bit.ly/2poORfk

  198. Beat The High Score Game https://github.com/dwursteisen/beat-the-high-score libGDX Lib https://libgdx.badlogicgames.com/ KTX

    
 (This is NOT Android KTX) Lib https://github.com/libktx/ktx/ libGDX addons Lib https://github.com/dwursteisen/libgdx-addons/ kTerminal Lib https://github.com/heatherhaks/kterminal Game Services Lib https://github.com/MrStahlfelge/gdx-gamesvcs In Game Console
 (A la quake) Lib https://github.com/StrongJoshua/libgdx-inGameConsole Gif Recorder Lib https://github.com/Anuken/GDXGifRecorder Tiled Editor Editor https://www.mapeditor.org Aseprite Editor https://www.aseprite.org