Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

David
September 11, 2019

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!

(11 September 2019 - Oslo - JavaZone 2019)
Video: https://vimeo.com/362771304

David

September 11, 2019
Tweet

More Decks by David

Other Decks in Programming

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

    View Slide

  2. -Gilles Allain
    In a farm… far, far away…

    View Slide

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

    View Slide

  4. View Slide

  5. -Gilles Allain
    In a mighty move, he stole all the chickens of the farm...

    View Slide

  6. View Slide

  7. -Gilles Allain
    those chickens were our...friends.

    View Slide

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

    View Slide

  9. View Slide

  10. -Gilles Allain
    ...this lightning has given you power to bring back...
    ...our friends!

    View Slide

  11. Let's help him !

    (by making a game)

    View Slide

  12. David
    Wursteisen

    View Slide

  13. View Slide

  14. View Slide

  15. John Carmack

    View Slide

  16. View Slide

  17. View Slide

  18. View Slide

  19. Game 

    development
    Kotlin
    libGDX

    View Slide

  20. You, at the end of 

    this conference
    Game 

    development
    Kotlin
    libGDX

    View Slide

  21. Game Development?

    View Slide

  22. View Slide

  23. View Slide

  24. View Slide

  25. View Slide

  26. View Slide

  27. public void render(float delta) {

    movePlayer(delta);

    moveEnemies(delta);

    moveWorld(delta);


    renderWorld();

    renderEnemies();

    renderPlayer();

    }

    View Slide

  28. public void render(float delta) {

    movePlayer(delta);

    moveEnemies(delta);

    moveWorld(delta);


    renderWorld();

    renderEnemies();

    renderPlayer();

    }

    View Slide

  29. public void render(float delta) {

    movePlayer(delta);

    moveEnemies(delta);

    moveWorld(delta);


    renderWorld();

    renderEnemies();

    renderPlayer();

    }

    View Slide

  30. public void render(float delta) {

    movePlayer(delta);

    moveEnemies(delta);

    moveWorld(delta);


    renderWorld();

    renderEnemies();

    renderPlayer();

    }
    60x

    seconds

    View Slide

  31. (0,0)
    x
    y

    View Slide

  32. Why libGDX?

    View Slide

  33. View Slide


  34. View Slide

  35. Pavel Quest
    Destroy Blocks
    Mirage Realms

    View Slide

  36. View Slide

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

    View Slide

  38. View Slide

  39. Bob & Prickle - http://www.crealodegames.com/

    View Slide

  40. View Slide

  41. Let’s make a game!

    View Slide

  42. View Slide

  43. View Slide

  44. Core
    iOS
    GWT
    Android
    Desktop


    View Slide

  45. void render(float delta) {
    }

    View Slide

  46. fun render(delta: float) {
    }

    View Slide

  47. class HelloScreen : ScreenAdapter() {
    override fun render(delta: Float) {
    }
    }

    View Slide

  48. class HelloScreen : ScreenAdapter() {
    override fun render(delta: Float) {
    Gdx.gl.glClearColor(0f, 0f, 0f, 1f)
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
    }
    }

    View Slide

  49. class HelloScreen : ScreenAdapter() {
    private val shape = ShapeRenderer()
    override fun render(delta: Float) {
    Gdx.gl.glClearColor(0f, 0f, 0f, 1f)
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
    }
    }

    View Slide

  50. class HelloScreen : ScreenAdapter() {
    private val shape = ShapeRenderer()
    override fun render(delta: Float) {
    Gdx.gl.glClearColor(0f, 0f, 0f, 1f)
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
    shape.begin(ShapeRenderer.ShapeType.Filled)
    shape.end()
    }
    }

    View Slide

  51. class HelloScreen : ScreenAdapter() {
    private val shape = ShapeRenderer()
    override fun render(delta: Float) {
    Gdx.gl.glClearColor(0f, 0f, 0f, 1f)
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
    shape.begin(ShapeRenderer.ShapeType.Filled)
    shape.color = Color.RED
    shape.circle(0f, 0f, 20f)
    shape.end()
    }
    }

    View Slide

  52. View Slide

  53. View Slide

  54. class HelloScreen : ScreenAdapter() {
    override fun render(delta: Float) {
    Gdx.gl.glClearColor(0f, 0f, 0f, 1f)
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
    }
    }

    View Slide

  55. class HelloScreen : ScreenAdapter() {
    private val batch = SpriteBatch()
    override fun render(delta: Float) {
    Gdx.gl.glClearColor(0f, 0f, 0f, 1f)
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
    }
    }

    View Slide

  56. class HelloScreen : ScreenAdapter() {
    private val batch = SpriteBatch()
    private val texture = Texture("badlogic.jpg")
    override fun render(delta: Float) {
    Gdx.gl.glClearColor(0f, 0f, 0f, 1f)
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
    }
    }

    View Slide

  57. class HelloScreen : ScreenAdapter() {
    private val batch = SpriteBatch()
    private val texture = Texture("badlogic.jpg")
    override fun render(delta: Float) {
    Gdx.gl.glClearColor(0f, 0f, 0f, 1f)
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
    batch.begin()
    batch.end()
    }
    }

    View Slide

  58. class HelloScreen : ScreenAdapter() {
    private val batch = SpriteBatch()
    private val texture = Texture("badlogic.jpg")
    override fun render(delta: Float) {
    Gdx.gl.glClearColor(0f, 0f, 0f, 1f)
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
    batch.begin()
    batch.draw(texture, 0f, 0f)
    batch.end()
    }
    }

    View Slide

  59. Position = (0, 0)
    Position = (10, 0)
    Position = (0, -5)
    Position = (-10, 5)

    View Slide

  60. class MyScreen : ScreenAdapter() {
    private val batch = ShapeRenderer()
    private val position: Vector2 = Vector2(0f, 0f)
    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()
    }
    }

    View Slide

  61. class MyScreen : ScreenAdapter() {
    private val batch = ShapeRenderer()
    private val position: Vector2 = Vector2(0f, 0f)
    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()
    }
    }

    View Slide

  62. class MyScreen : ScreenAdapter() {
    private val batch = ShapeRenderer()
    private val position: Vector2 = Vector2(0f, 0f)
    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()
    }
    }

    View Slide

  63. class MyScreen : ScreenAdapter() {
    private val batch = ShapeRenderer()
    private val position: Vector2 = Vector2(0f, 0f)
    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()
    }
    }

    View Slide

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

    View Slide

  65. /**
    * 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

    View Slide

  66. /**
    * 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)

    View Slide

  67. 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)

    View Slide

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

    View Slide

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

    View Slide

  70. entity.add(Ball())
    .add(Position(-100 v2 -100f))
    .add(Size(8 v2 9))
    .add(Offset(4 v2 4))
    Create a 

    new Vector2
    infix fun Number.v2(other: Number): Vector2 {
    return Vector2(this.toFloat(), other.toFloat())
    }

    View Slide

  71. Collision engine

    View Slide

  72. View Slide

  73. Side
    Racket
    Ball

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  77. View Slide

  78. View Slide

  79. View Slide

  80. Box2D

    View Slide

  81. var world = World(Vector2(0, -10), true)
    // ...
    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()

    View Slide

  82. var world = World(Vector2(0, -10), true)
    // ...
    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

    View Slide

  83. var world = World(Vector2(0, -10), true)
    // ...
    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 »

    View Slide

  84. var world = World(Vector2(0, -10), true)
    // ...
    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()
    Attach data from your
    world to Box2D world

    View Slide

  85. var world = World(Vector2(0, -10), true)
    // ...
    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()

    View Slide

  86. var world = World(Vector2(0, -10), true)
    // ...
    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()

    View Slide

  87. Traversable

    View Slide

  88. The ball and boxes use 

    AABB Physic Engine
    Wreckages use 

    Box2D Physic Engine

    View Slide

  89. Animations

    View Slide

  90. View Slide

  91. View Slide

  92. // load texture
    val texture = Texture(Gdx.files.internal("texture.png"))
    val split = TextureRegion.split(texture, 128, 128)
    // select frames
    val frames = Array()
    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(100f, frames)
    // select frame to draw
    val keyToRender = animation.getKeyFrame(time)

    View Slide

  93. // load texture
    val texture = Texture(Gdx.files.internal("texture.png"))
    val split = TextureRegion.split(texture, 128, 128)
    // select frames
    val frames = Array()
    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(100f, frames)
    // select frame to draw
    val keyToRender = animation.getKeyFrame(time)

    View Slide

  94. // load texture
    val texture = Texture(Gdx.files.internal("texture.png"))
    val split = TextureRegion.split(texture, 128, 128)
    // select frames
    val frames = Array()
    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(100f, frames)
    // select frame to draw
    val keyToRender = animation.getKeyFrame(time)

    View Slide

  95. // 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()
    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(100f, frames)
    // select frame to draw
    val keyToRender = animation.getKeyFrame(time)
    import com.badlogic.gdx.utils.Array as GdxArray

    View Slide

  96. // load texture
    val texture = Texture(Gdx.files.internal("texture.png"))
    val split = TextureRegion.split(texture, 128, 128)
    // select frames
    val frames = GdxArray()
    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(100f, frames)
    // select frame to draw
    val keyToRender = animation.getKeyFrame(time)

    View Slide

  97. // load texture
    val texture = Texture(Gdx.files.internal("texture.png"))
    val split = TextureRegion.split(texture, 128, 128)
    // select frames
    val frames = GdxArray()
    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(100f, frames)
    // select frame to draw
    val keyToRender = animation.getKeyFrame(time)
    Duration per frame

    View Slide

  98. 100ms 100ms 100ms 100ms

    View Slide

  99. 800ms 100ms 100ms 100ms
    100ms
    100ms
    100ms
    100ms
    100ms
    100ms
    100ms

    View Slide

  100. y = Interpolation.bounceOut.apply(time) * 100f
    Non linear
    Should be a
    percentage (0..1)
    y will go from 0 to 100

    View Slide

  101. View Slide

  102. Linear Bounce Elastic Pow2

    View Slide

  103. Field Of View

    View Slide

  104. View Slide

  105. 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()

    View Slide

  106. 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

    View Slide

  107. 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

    View Slide

  108. viewport.camera.rotate(10f, 0f, 0f, 1f)

    View Slide

  109. View Slide

  110. View Slide

  111. View Slide

  112. View Slide

  113. Our world

    (Orientation: Portrait)

    View Slide

  114. ScreenViewport

    View Slide

  115. StretchViewport

    View Slide

  116. FitViewport
    Clear this
    zone
    Clear this
    zone

    View Slide

  117. FillViewport

    View Slide

  118. ExtendViewport
    Displaying
    this zone

    View Slide

  119. 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)
    }
    }

    View Slide

  120. 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

    View Slide

  121. 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)
    }
    }

    View Slide

  122. Around the game

    View Slide

  123. 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"
    }

    View Slide

  124. 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"
    }

    View Slide

  125. 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"
    }

    View Slide

  126. 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"
    }

    View Slide

  127. 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"
    }

    View Slide

  128. Entities & Ashley

    View Slide

  129. View Slide

  130. Entity
    Player Monster
    Knight Ghoul

    View Slide

  131. Composition 

    over 

    Inheritance

    View Slide

  132. Position
    Animation
    Player
    State
    Entity

    View Slide

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

    View Slide

  134. ComponentMapper player = ComponentMapper.getFor(Player.class);
    ComponentMapper position = ComponentMapper.getFor(Position.class);
    ComponentMapper animation = ComponentMapper.getFor(Animation.class);
    ComponentMapper state = ComponentMapper.getFor(StateComponent.class);
    ...
    player.get(entity).life;
    position.get(entity).xy;
    animation.get(entity).getFrame(time);
    state.get(entity).time;
    Extractor
    Extraction

    View Slide

  135. inline fun EntitySystem.get(): ComponentMapper = 

    ComponentMapper.getFor(T::class.java)
    inline operator fun Entity.get(mapper: ComponentMapper): T = 

    mapper.get(this)

    View Slide

  136. val player: ComponentMapper = get()
    val position: ComponentMapper = get()
    val animation: ComponentMapper = get()
    val state: ComponentMapper = get()
    // access to components
    entity[player].life
    entity[position].xy
    entity[animation].frame(time)
    entity[state].time

    View Slide

  137. val player = get()
    val position = get()
    val animation = get()
    val state = get()
    // access to components
    entity[player].life
    entity[position].xy
    entity[animation].frame(time)
    entity[state].time

    View Slide

  138. val player = get()
    val position = get()
    val animation = get()
    val state = get()
    // access to components
    entity[player].life
    entity[position].xy
    entity[animation].frame(time)
    entity[state].time
    Accessing like a map

    View Slide

  139. inline fun EntitySystem.get(): ComponentMapper = 

    ComponentMapper.getFor(T::class.java)
    inline operator fun Entity.get(mapper: ComponentMapper): T = 

    mapper.get(this)

    View Slide

  140. Close Open

    View Slide

  141. 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

    View Slide

  142. 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

    View Slide

  143. 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

    View Slide

  144. Loading…

    View Slide

  145. 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)

    View Slide

  146. 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)

    View Slide

  147. 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)

    View Slide

  148. 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)

    View Slide

  149. 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

    View Slide

  150. 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)

    View Slide

  151. 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 AssetManager.get(filename: String): T {
    return this.get(filename, T::class.java)
    }

    View Slide

  152. Integration with other tools

    View Slide

  153. View Slide

  154. View Slide

  155. View Slide

  156. View Slide

  157. View Slide

  158. // 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()

    View Slide

  159. // 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()

    View Slide

  160. // 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()

    View Slide

  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()

    View Slide

  162. View Slide

  163. hit 4
    sound chicken.wav

    View Slide

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

    View Slide

  165. 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
    }

    View Slide

  166. 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
    }

    View Slide

  167. 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

    View Slide

  168. inline operator fun 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
    }

    View Slide

  169. val chicken: Aseprite = assets["sheets/chicken"]
    val idleAnimation: Animation = chicken["idle"]
    Inverser slides. Je veux accéder aux
    animations -> export des metas ->
    méthode d'extensions. 

    -> explication j'ai fais un plugin qui
    fait ça pour moi.

    View Slide

  170. Json
    Json
    Json

    View Slide

  171. 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=
    or using aseprite extension in your build.gradle
    aseprite {
    exec=
    }
    MacOS specific : point to aseprite located into /Aseprite.app/Contents/MacOS/aseprite""")
    }
    }

    View Slide

  172. 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=
    or using aseprite extension in your build.gradle
    aseprite {
    exec=
    }
    MacOS specific : point to aseprite located into /Aseprite.app/Contents/MacOS/aseprite""")
    }
    }
    What happen if null?
    Encountered an error, lol!

    View Slide

  173. 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=
    or using aseprite extension in your build.gradle
    aseprite {
    exec=
    }
    MacOS specific : point to aseprite located into /Aseprite.app/Contents/MacOS/aseprite""")
    }
    }

    View Slide

  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=
    or using aseprite extension in your build.gradle
    aseprite {
    exec=
    }
    MacOS specific : point to aseprite located into /Aseprite.app/Contents/MacOS/aseprite""")
    }
    }

    View Slide

  175. Screens

    View Slide

  176. Scrollpane
    Label
    Button
    Select box

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  181. Game
    screen
    screen
    screen screen screen

    View Slide

  182. 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)
    }
    }
    }

    View Slide

  183. 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
    ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠

    View Slide

  184. Pitfalls

    View Slide

  185. Garbage Collector
    Garbage Collector
    Garbage Collector
    Garbage Collecto
    arbage C
    Garbage
    STOP THE
    WORLD

    View Slide

  186. 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

    View Slide

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

    View Slide

  188. val pool = object : Pool() {
    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)

    View Slide

  189. val pool = object : Pool() {
    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)

    View Slide

  190. val pool = object : Pool() {
    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)

    View Slide

  191. val pool = object : Pool() {
    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)

    View Slide

  192. Shaders
    This section has nothing to do with Kotlin

    View Slide

  193. 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)

    View Slide

  194. View Slide

  195. View Slide

  196. View Slide

  197. View Slide

  198. View Slide

  199. View Slide

  200. View Slide

  201. View Slide

  202. View Slide

  203. 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

    View Slide

  204. if (color_pixel < cutoff) {
    // light dark
    pixel = gl_FragColor = vec4(0.3, 0.2, 0.4., 1.0);
    } else {
    pixel = color_pixel;
    }

    View Slide

  205. View Slide

  206. View Slide

  207. View Slide

  208. https://www.shadertoy.com/view/ld3Gz2

    View Slide

  209. fun makeGames()

    View Slide

  210. fun makeGames()

    View Slide

  211. 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

    View Slide

  212. 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

    View Slide

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

    View Slide