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

Kotlin pour le développement d’un jeu avec libGDX : avantage & inconvénient

David
March 14, 2018

Kotlin pour le développement d’un jeu avec libGDX : avantage & inconvénient

libGDX est un framework de développement de jeux vidéo Java. Mais quand on développe un jeu, on veut se concentrer sur le design du jeu, itérer rapidement : on ne veut pas perdre de temps avec des problèmes de syntaxes ou autre. Mais Kotlin s’intègre t’il facilement avec les APIs Java de libGDX ? Qu’apporte Kotlin dans ce contexte et surtout. Les avantages de Kotlin sont-ils toujours des avantages dans le code d’un jeu vidéo ?

(Kotlin Paris Meetup - https://www.meetup.com/fr-FR/Kotlin-Paris-Meetup/events/247733641/ )

David

March 14, 2018
Tweet

More Decks by David

Other Decks in Programming

Transcript

  1. Kotlin pour le développement
    d’un jeu avec libGDX
    Avantages & inconvénients

    View Slide

  2. David
    Wursteisen

    View Slide

  3. View Slide

  4. John Carmack

    View Slide

  5. View Slide

  6. View Slide

  7. View Slide

  8. View Slide

  9. View Slide


  10. View Slide

  11. PRODUCTIVITYYYYYYYY

    View Slide

  12. Core
    iOS GWT
    Android
    Desktop

    View Slide

  13. Core
    Android
    Desktop

    View Slide

  14. class HelloWorld : ScreenAdapter() {
    override fun show() {
    }
    override fun render(delta: Float) {
    }
    }
    Initialisation de l’écran
    de jeu
    Rendu de l’écran de jeu

    View Slide

  15. class HelloWorld : ScreenAdapter() {
    var batch: SpriteBatch?
    override fun show() {
    batch = SpriteBatch()
    }
    override fun render(delta: Float) {
    batch!!.begin()
    // ...
    batch!!.end()
    }
    }

    View Slide

  16. class HelloWorld : ScreenAdapter() {
    var batch: SpriteBatch?
    override fun show() {
    batch = SpriteBatch()
    }
    override fun render(delta: Float) {
    batch!!.begin()
    // ...
    batch!!.end()
    }
    }

    View Slide

  17. class HelloWorld : ScreenAdapter() {
    lateinit var batch: SpriteBatch
    override fun show() {
    batch = SpriteBatch()
    }
    override fun render(delta: Float) {
    batch.begin()
    // ...
    batch.end()
    }
    }

    View Slide

  18. class HelloWorld : ScreenAdapter() {
    lateinit var batch: SpriteBatch
    override fun show() {
    batch = SpriteBatch()
    }
    override fun render(delta: Float) {
    batch.begin()
    // ...
    batch.end()
    }
    }

    View Slide

  19. fun String.toColor(): Color = Color.valueOf(this)
    val background = « 0089d5".toColor()
    fun ShapeRenderer.rect(pos: Vector2, size: Vector2) {
    this.rect(pos.x, pos.y, size.x, size.y)
    }
    batch.rect(pos1, pos2)

    View Slide

  20. fun String.toColor(): Color = Color.valueOf(this)
    val background = « 0089d5".toColor()
    fun ShapeRenderer.rect(pos: Vector2, size: Vector2) {
    this.rect(pos.x, pos.y, size.x, size.y)
    }
    batch.rect(pos1, pos2)
    Méthode d’extension
    =
    étendre une API à
    son besoin

    View Slide

  21. Hexagonal Architecture ?

    View Slide

  22. object EntityFactory {
    fun player(engine: PooledEngine, assets: AssetManager): Unit {
    // …
    }
    Couplage !
    Couplage !

    View Slide

  23. const val TOUCH_DOWN = 1
    const val TOUCH_UP = 2
    const val TOUCH_MOVE = 3
    Compile-Time Constants
    Properties the value of which is known at compile time can be marked as compile time
    constants using the const modifier. Such properties need to fulfil the following requirements:
    Top-level or member of an object
    Initialized with a value of type String or a primitive type
    No custom getter

    View Slide

  24. private void readMeasureHeader(InputStream is, int measureIndex) throws IOException {
    int header = helper.readUnsignedByte(is);
    boolean repeatStart = ((header & ParserHelper.BITMASK_3) != 0);
    boolean doubleBar = ((header & ParserHelper.BITMASK_8) != 0);
    int numerator = 0;
    if ((header & ParserHelper.BITMASK_1) != 0) {
    numerator = helper.readByte(is);
    }
    int denominator = 0;
    if ((header & ParserHelper.BITMASK_2) != 0) {
    denominator = helper.readByte(is);
    }
    }

    View Slide

  25. private void readMeasureHeader(InputStream is, int measureIndex) throws IOException {
    int header = helper.readUnsignedByte(is);
    boolean repeatStart = ((header & ParserHelper.BITMASK_3) != 0);
    boolean doubleBar = ((header & ParserHelper.BITMASK_8) != 0);
    int numerator = 0;
    if ((header & ParserHelper.BITMASK_1) != 0) {
    numerator = helper.readByte(is);
    }
    int denominator = 0;
    if ((header & ParserHelper.BITMASK_2) != 0) {
    denominator = helper.readByte(is);
    }
    }

    View Slide

  26. TOUCH_DOWN shl 3
    TOUCH_DOWN and 2
    TOUCH_DOWN << 3
    TOUCH_DOWN & 2

    View Slide

  27. Direction
    Position

    View Slide

  28. Vector2(3f, 5f).set(entity[cameraData].direction)
    .scl(5f)
    .add(entity[position].value)
    .add(halfX, halfY
    val v1 = Vector2(3f, 5f) + Vector2(4f, 2f)
    v2 = v1 * 5f

    View Slide

  29. Vector2(3f, 5f).set(entity[cameraData].direction)
    .scl(5f)
    .add(entity[position].value)
    .add(halfX, halfY
    val v1 = Vector2(3f, 5f) + Vector2(4f, 2f)
    v2 = v1 * 5f
    Surcharge d’opérateur (add)
    Surcharge d’opérateur (scl)

    View Slide

  30. engine.addEntity({ e ->
    val left = spriteDef.slices("left")
    val leftRegion = left.textureRegion(assets["sheets/pico8/keyboard.png"])
    e.add(Controller())
    e.add(Position(42 v2 26))
    e.add(Size(16 v2 16))
    e.add(EntityRender(texture = leftRegion))
    e.add(ButtonComponent(ButtonSystem.LEFT))
    e.add(StateComponent())
    })
    Vector2

    View Slide

  31. engine.addEntity({ e ->
    val left = spriteDef.slices("left")
    val leftRegion = left.textureRegion(assets["sheets/pico8/keyboard.png"])
    e.add(Controller())
    e.add(Position(42 v2 26))
    e.add(Size(16 v2 16))
    e.add(EntityRender(texture = leftRegion))
    e.add(ButtonComponent(ButtonSystem.LEFT))
    e.add(StateComponent())
    })
    infix fun Int.v2(that: Int): Vector2 = Vector2(this.toFloat(), that.toFloat())

    View Slide

  32. View Slide

  33. View Slide

  34. Position
    Couleur
    Joueur
    Etat

    View Slide

  35. val player = Entity()
    player.add(Player()))
    player.add(Position(100 v2 100))
    player.add(ColorSplash(Color.GREEN))
    player.add(StateComponent())
    Position
    Couleur
    Joueur
    Etat

    View Slide

  36. ComponentMapper pm = ComponentMapper.getFor(PositionComponent.class);
    ComponentMapper vm = ComponentMapper.getFor(VelocityComponent.class);
    ...
    PositionComponent position = pm.get(entity);
    VelocityComponent velocity = vm.get(entity);
    Extracteur
    Extraction

    View Slide

  37. inline fun EntitySystem.get(): ComponentMapper = ComponentMapper.getFor(T::class.java)
    private val move: ComponentMapper = ComponentMapper.getFor(TurnMove::class.java)
    Extracteur

    View Slide

  38. inline fun EntitySystem.get(): ComponentMapper = ComponentMapper.getFor(T::class.java)
    private val move: ComponentMapper = ComponentMapper.getFor(TurnMove::class.java)
    Inline + reified

    View Slide

  39. inline fun EntitySystem.get(): ComponentMapper = ComponentMapper.getFor(T::class.java)
    private val move: ComponentMapper = get()

    View Slide

  40. private val move: ComponentMapper = get()
    private val position: ComponentMapper = get()
    private val state: ComponentMapper = get()
    private val direction: ComponentMapper = get()

    View Slide

  41. PositionComponent position = pm.get(entity);
    Extraction

    View Slide

  42. val position: Position = pm.get(entity)

    View Slide

  43. val position: Position = entity.get(pm)

    View Slide

  44. val position: Position = entity[pm]

    View Slide

  45. entity[move].turn(entity[direction].value)
    entity[state].time = 0f
    entity[animated].animation = animation["dragon_move"]
    Direction
    Etat

    View Slide

  46. public void render(float delta) {

    movePlayer(delta);

    moveEnemies(delta);

    moveWorld(delta);


    renderWorld();

    renderEnemies();

    renderPlayer();

    }

    View Slide

  47. public void render(float delta) {

    movePlayer(delta);

    moveEnemies(delta);

    moveWorld(delta);


    renderWorld();

    renderEnemies();

    renderPlayer();

    }
    60x

    secondes

    View Slide

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

    View Slide

  49. altLayers = map.layers
    .mapIndexed { index, layer -> index to layer }
    .filter { it.second.name.startsWith("alt_") }
    .map { it.first }
    .toIntArray()

    mutableList !

    View Slide

  50. val a = Array(5, { "" })
    val b = Array()
    Différence ?
    Array libGDX

    View Slide

  51. import com.badlogic.gdx.utils.Array as GdxArray
    val a = Array(5, { "" })
    val b = GdxArray()

    View Slide

  52. interface EventListener {
    fun onEvent(event: Event, eventData: EventData)
    }
    fun register(eventListener: EventListener, vararg events: Event) {
    events.forEach {
    val lst = listeners[it]
    if (lst == null) {
    listeners.put(it, listOf(eventListener))
    } else {
    listeners.put(it, lst + eventListener)
    }
    }
    }

    View Slide

  53. interface EventListener {
    fun onEvent(event: Event, eventData: EventData)
    }
    fun register(eventListener: EventListener, vararg events: Event) {
    events.forEach {
    val lst = listeners[it]
    if (lst == null) {
    listeners.put(it, listOf(eventListener))
    } else {
    listeners.put(it, lst + eventListener)
    }
    }
    }
    Interface avec une
    méthode

    View Slide

  54. eventBus.register(object : EventListener {
    override fun onEvent(event: Event, eventData: EventData) {
    game.loadingScreen()
    }
    }, GameEvents.EVENT_GAME_MAIN_MENU)
    Class anonyme

    View Slide

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

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

  57. 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)
    }
    Etat Event Transition

    View Slide


  58. View Slide

  59. On reste en contact ?
    @dwursteisen

    View Slide

  60. https://goo.gl/4UcQYx
    En attendant… téléchargez 

    Wiz le magicien 

    sur le Play Store Android

    View Slide