Spieleprogrammierung mit KorGE

Der Kotlin Multiplatform Game Engine

https://slides.com/tobsefritz/korge/

Tobse Fritz

ITscope GmbH

Java Entwickler

Kotlin Fanboy

Hobby Fotograf

Gaming Nerd

3D Printer

Daddy

My Games

Hospital

Kathis Kleiner Sattelit

My Games

Swing Plus Remake

My Games

Hit Klack Remake

My Games

My Games

Candy Crush Clone

Magic Maze Online Multiplayer

My Games

https://wallpapercave.com/w/wp2967926

It's ME

https://wallpapercave.com/w/wp2967926

KorGE

It's ME

https://wallpapercave.com/w/wp2967926

KorGE

It's ME

Kotlin

https://wallpapercave.com/w/wp2967926

KorGE

It's ME

Kotlin

BeiSpiel

https://wallpapercave.com/w/wp2967926

KorGE

It's ME

Kotlin

BeiSpiel

https://wallpapercave.com/w/wp2967926

Corutines

KorGE

It's ME

Kotlin

BeiSpiel

Performance

https://wallpapercave.com/w/wp2967926

Corutines

KorGE

It's ME

Kotlin

BeiSpiel

Tools

Performance

https://wallpapercave.com/w/wp2967926

Corutines

KorGE

It's ME

Kotlin

BeiSpiel

Tools

Performance

https://wallpapercave.com/w/wp2967926

Testing

Corutines

KorGE

It's ME

Kotlin

BeiSpiel

Tools

Performance

https://wallpapercave.com/w/wp2967926

Testing

Multiplayer

Corutines

KorGE

It's ME

Kotlin

BeiSpiel

Tools

Performance

https://wallpapercave.com/w/wp2967926

Testing

Multiplayer

Corutines

Press Start

  • 😁Weil es Spaß macht
  • 🔭Neues ausprobieren
  • 💡 Programmier-Skills verbessern
  • 👾Kleine abgeschlossene Projekte
  • 👨‍👦Für Entwicklung begeistern
  • 🤝Zusammenarbeit im Team

Warum ein Spiel programmieren?

K

or

G

E

K

or

otlin

G

E

K

or

otlin

C

outines

G

E

K

or

otlin

C

outines

G

E

ame

ngine

K

or

G

E

JVM

image/svg+xml
image/svg+xml

Win

Mac

image/svg+xml
image/svg+xml

Linux

JS

Browser

Native

image/svg+xml

Android

IOS

image/svg+xml

08.2018

Kotlin Multiplatform 

01.2017

01.2017

12.2016

2.5.3

K

or

G

E

JVM

image/svg+xml
image/svg+xml

Win

Mac

image/svg+xml
image/svg+xml

Linux

JS

Browser

Native

image/svg+xml

Android

IOS

image/svg+xml

Kotlin Multiplatform

K

or

G

E

JVM

image/svg+xml
image/svg+xml

Win

Mac

image/svg+xml
image/svg+xml

Linux

JS

Browser

image/svg+xml

Win

Mac

image/svg+xml

Linux

image/svg+xml

Native

image/svg+xml

Android

IOS

image/svg+xml

Common
Code

Kotlin
Compiler

LLVM IR

Kotlin Multiplatform

K

or

G

E

Kotlin Multiplatform

JVM

image/svg+xml
image/svg+xml

Win

Mac

image/svg+xml
image/svg+xml

Linux

JS

Browser

image/svg+xml

Win

Mac

image/svg+xml

Linux

image/svg+xml

Native

image/svg+xml

Android

IOS

image/svg+xml

KorGE
Libs

Common
Code

Kotlin
Compiler

LLVM IR

K

or

G

E

Kotlin Multiplatform

JVM

image/svg+xml
image/svg+xml

Win

Mac

image/svg+xml
image/svg+xml

Linux

JS

Browser

image/svg+xml

Win

Mac

image/svg+xml

Linux

image/svg+xml

Native

image/svg+xml

Android

IOS

image/svg+xml

KorGE
Libs

Common
Code

expect
expect fun play(file: Sound)
actual
actual
actual
actual
actual

K

or

G

E

.org

K

or

G

E

  • 2D Game Engine
  • Open Source
  • Native Multiplatform
  • Live Debugging
  • IDE Integration
  • Visual editor
  • 100 % Kotlin

⭐Features

Kotlin

Spiele... warum in

Kotlin

  • Prägnant
  • Sicher
  • Interoperabel
  • Optimales Tooling
  • Frei & OpenSource

Features

Kotlin

public class Person {

    private final String firstName;
    private final String lastName;
    private final LocalDate birthDay;

    public Person(String firstName, String lastName, 
                  LocalDate birthDay) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.birthDay = birthDay;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public LocalDate getBirthDay() {
        return birthDay;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(firstName, person.firstName) &&
                Objects.equals(lastName, person.lastName) &&
                Objects.equals(birthDay, person.birthDay);
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstName, lastName, birthDay);
    }

    @Override
    public String toString() {
        return "Person{" +
                "firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", birthDay=" + birthDay +
                '}';
    }
}
data class PersonK(
    val firstName: String,
    val lastName: String,
    val birthDay: LocalDate
)

Java

Kotlin

Prägnanz

Kotlin

public class RectangleJava {

    private int x = 0;
    private int y = 0;
    private int width = 0;
    private int height = 0;

    public boolean contains(int x, int y) {
        var inWidth = (x >= this.x) && (x <= this.x + width);
        var inHeight = (y >= this.y) && (y <= this.y + height);
        return inWidth && inHeight;
    }

}

Java

Kotlin

public class RectangleKotlin {

    private val x = 0;
    private val y = 0;
    private val width = 0;
    private val height = 0;

    public fun contains(x: Int, y: Int): Boolean {
        val inWidth = (x >= this.x) && (x <= this.x + width);
        val inHeight = (y >= this.y) && (y <= this.y + height);
        return inWidth && inHeight;
    }

}

Kotlin

public fun
public
private fun
new
;

Kotlin

class RectangleKotlinNice {

    private val x = 0
    private val y = 0
    private val width = 0
    private val height = 0

    fun contains(x: Int, y: Int): Boolean {
        val inWidth = x in (x .. this.x + width)
        val inHeight = y in (y .. this.y + height)
        return inWidth && inHeight
    }

}

Kotlin

Kotlin

  • Prägnant
  • Sicher
  • Interoperabel
  • Optimales Tooling
  • Frei & OpenSource

Features

Kotlin

Prägnant

Sicher

Interoperabel

Optimales Tooling

Frei & OpenSource

Inline Classes

Extensions

Contracts

Operator Overloading

Gradle DSL

Data Classes

Coroutines

KorGE

The Tree Game

KorGE

The Tree Game
The Tree Game

JS

Browser

Gradle icon

runAndroidRelease 

runJs 

runJvm 

runNativeRelease 

Native

image/svg+xml

Android

Native

IOS

image/svg+xml
image/svg+xml

Win

Mac

image/svg+xml

Linux

image/svg+xml

Native

JVM

image/svg+xml
image/svg+xml

Win

Mac

image/svg+xml
image/svg+xml

Linux

Tasks > run

iosRunSimulatorDebug

Lesson learned

The Tree Game

Lesson learned

image(backgroundBitmap){
    size(gameWidth, gameHeight)
}
image(treeBitmap).centerOnStage()
Image(appleBitmap).addTo(this)
container {
  name = "Happy Sun"
  circle(radius = 50.0, fill = YELLOW)
  circle(radius = 8.0, fill = BLACK).position(60, 20)
  circle(radius = 8.0, fill = BLACK).position(24, 20)
  circle(radius = 10.0, fill = RED).position(50, 60)
  position(50, 50)
}
The Tree Game
class Bird(...){
	
  init {
    onClick {
      hit()
    }
}
class Bird(...){
	
  init {
    onCollision(filter = { it is Image}) {
      if (it.name == "apple") {
        it.removeFromParent()
        eatApple()
      }
    }
  }
}

Lesson learned

resourcesVfs["bird.mp3"].readSound().play()
The Tree Game
private suspend fun hit() {
  tween(this::y[gameHeight], 
        time = 1.seconds, easing = EASE_IN)
  tween(this::rotation[Random[-40, 40].degrees], 
        time = 100.milliseconds, easing = EASE_IN)
}
EASE_IN_ELASTIC
EASE_OUT_ELASTIC
EASE_OUT_BOUNCE
LINEAR
EASE_IN
EASE_OUT
EASE_IN_OUT
EASE_OUT_IN
EASE_IN_BACK
EASE_OUT_BACK
EASE_IN_OUT_BACK
EASE_OUT_IN_BACK
EASE_IN_OUT_ELASTIC
EASE_OUT_IN_ELASTIC
EASE_IN_BOUNCE
EASE_IN_OUT_BOUNCE
EASE_OUT_IN_BOUNCE
EASE_IN_QUAD
EASE_OUT_QUAD
EASE_IN_OUT_QUAD

Lesson learned

The Tree Game
private suspend fun hit() {
  tween(this::y[gameHeight], 
        time = 1.seconds, easing = EASE_IN)
  tween(this::rotation[Random[-40, 40].degrees], 
        time = 100.milliseconds, easing = EASE_IN)
}

Lesson learned

class Bird(...){
	
  suspend fun startFlying() {
    while (!hit) {
      x += 5
      delay(20.milliseconds)
      if (x > gameWidth) {
        respawn()
      }
    }
  }
}

Coroutines

synchron & blockierend

main Thread

IO

Verarbeitung

App tut nix. CPU schläft.

synchron & blockierend

Neue Threads
sind teuer!

komplex!

main Thread

wait & join

Coroutines

Coroutinen sind leichtgewichtige Threads.
Sie können angehalten werden,
ohne den Thread zu blockieren.

Suspending

Launch startet eine Coroutine und

gibt einen Job zurück.

Async ist wie Launch, nur mit Rückgabewert.

Await wartet auf den Rückgabewert.

Async / Await

Launch

Coroutines

fun loopWithThreads() {
  val c = AtomicLong()
  println("🦙🦙🦙🦙🦙🦙🦙🦙🦙🦙")

  measureTime {
    for (i in 1..100_000) {
      thread(start = true) {
        c.addAndGet(1).printProgress()
      }
    }
  }
}
fun loopWithCoroutine() {
  val c = AtomicLong()
    println("🦙🦙🦙🦙🦙🦙🦙🦙🦙🦙")

    measureTime {
      for (i in 1..100_000) {
        GlobalScope.launch {
          c.addAndGet(1).printProgress()
          }
      }
   }
}
🦙🦙🦙🦙🦙🦙🦙🦙🦙🦙
🥩🥩🥩
Lama Progress Bar

Coroutines

🦙🦙🦙🦙🦙🦙🦙🦙🦙🦙
🥩🥩🥩🥩🥩🥩🥩🥩🥩🥩
time: 0m 5s 363ms
🦙🦙🦙🦙🦙🦙🦙🦙🦙🦙
🥩🥩🥩🥩🥩🥩🥩🥩🥩🥩
time: 0m 0s 145ms
fun loopWithThreads() {
  val c = AtomicLong()
  println("🦙🦙🦙🦙🦙🦙🦙🦙🦙🦙")

  measureTime {
    for (i in 1..100_000) {
      thread(start = true) {
        c.addAndGet(1).printProgress()
      }
    }
  }
}
fun loopWithCoroutine() {
  val c = AtomicLong()
    println("🦙🦙🦙🦙🦙🦙🦙🦙🦙🦙")

    measureTime {
      for (i in 1..100_000) {
        GlobalScope.launch {
          c.addAndGet(1).printProgress()
          }
      }
   }
}

Coroutines

Coroutines

mario.tween(mario::x[120], time = 4.seconds)
lugigi.tween(mario::x[120], time = 4.seconds)

Coroutines

mario.tween(mario::x[120], time = 4.seconds)
lugigi.tween(mario::x[120], time = 4.seconds)

Coroutines

mario.tween(mario::x[120], time = 4.seconds)
lugigi.tween(mario::x[120], time = 4.seconds)

Coroutines

launch{
  mario.tween(mario::x[120], time = 4.seconds)
}
launch{
  lugigi.tween(mario::x[120], time = 4.seconds)
}

Coroutines

launch{
  mario.tween(mario::x[120], time = 4.seconds)
}
launch{
  lugigi.tween(mario::x[120], time = 4.seconds)
}
  • Leserlich wie imperativer Code
  • Keine Callback Hell
  • Leichtgewichtiger als Threads
    • ​Geringerer Speicherverbrauch
    • Schneller Wechsel
  • Syntax ist unabhängig von Implementierung
  • Verfügbar in JVM, JS und Native Kotlin

Coroutines

The Tree Game

Coming soon

🆕
Jetzt mit  Hörnchen! 

The Tree Game
gamepad.down(0, GameButton.LEFT) {
  x -= 40
}
gamepad.down(0, GameButton.RIGHT) {
  x += 40
}
gamepad.down(0, GameButton.BUTTON0) {
  shoot()
}
keys {
  down {
    if (it.key == Key.LEFT) {
      x -= 20
    }
    if (it.key == Key.RIGHT) {
      x += 20
    }
    if (it.key == Key.SPACE) {
      shoot()
    }
  }
}

Coming soon

Box2D

solidRect(50, 50, Colors.RED)
solidRect(50, 50, Colors.RED)
solidRect(50, 50, Colors.RED)
solidRect(600, 100, Colors.WHITE)

Box2D

solidRect(50, 50, Colors.RED)
    .registerBodyWithFixture(type = BodyType.DYNAMIC, density = 2, friction = 0.01)
solidRect(50, 50, Colors.RED)
    .registerBodyWithFixture(type = BodyType.DYNAMIC)
solidRect(50, 50, Colors.RED)
    .registerBodyWithFixture(type = BodyType.DYNAMIC)
solidRect(600, 100, Colors.WHITE)
    .registerBodyWithFixture(
	    type = BodyType.STATIC,
	    friction = 0.2
)

Box2D

solidRect(50, 50, Colors.RED)
    .registerBodyWithFixture(type = BodyType.DYNAMIC, density = 2, friction = 0.01)
solidRect(50, 50, Colors.RED)
    .registerBodyWithFixture(type = BodyType.DYNAMIC)
solidRect(50, 50, Colors.RED)
    .registerBodyWithFixture(type = BodyType.DYNAMIC)
solidRect(600, 100, Colors.WHITE)
    .registerBodyWithFixture(
	    type = BodyType.STATIC,
	    friction = 0.2
)

onClick {
	val pos = it.currentPosLocal
	solidRect(50, 50, Colors.RED).position(pos.x, pos.y).rotation(randomAngle())
		.registerBodyWithFixture(type = BodyType.DYNAMIC)
}

fun randomAngle(): Angle = Random.nextInt(0, 90).degrees

Box2D

Kor

UI

val container = fixedSizeContainer(width, height)
container.korui {
  vertical {
    layoutChildrenPadding = 2
    horizontal {
      preferredWidth = 100.percent
      button("HELLO", {
        preferredWidth = 70.percent
      })
      button("WORLD", {
        preferredWidth = 30.percent
        preferredHeight = 32.pt
      })
    }
    button("TEST")
    checkBox("CheckBox", checked = true)
    comboBox("test", listOf("test", "demo"))
  }

}

DSL

Kor

UI

Absolute Positionierung 

uiButton(256.0, 32.0) {
  text = "Disabled Button"
  position(128, 128)
}
val emitter = resourcesVfs["particle2.pex"].readParticleEmitter()
particleEmitter(emitter)

Particles

Particles

Hiero

Axiom Verge

Brigador

2016

2015

Edit Worlds

Performance

10K

Performance

10K

1Mio

100K

need
more?

Performance

GRÖßE

flag.png 230 Bytes

GRÖßE

suspend fun main() = Korge(width = 512, height = 512) {
  val flagImage = resourcesVfs["flag.png"].readBitmap()
  val flagFilter = FlagFilter()

  // "Flag Pole"
  solidRect(10, 582, Colors.BLACK).position(30, 30)
  solidRect(20, 5, Colors.BLACK).position(25, 25)

  // Flag
  image(flagImage) {
    position(40, 40)
    filter = FlagFilter()
  }

  text("FREE UKRAINE", textSize = 64.0).position(90, 420)

  // Propagates the wave over time
  addUpdater { dt: TimeSpan -> flagFilter.time = flagFilter.time.plus(dt) }
}
flag.png 230 Bytes

GRÖßE

Unit Tests

Vorbereiten

Ausrühren

Verifizieren

val world = loadGame()
val mario = Mario()
mario.setPos(0, 5)
mario.jump()
assertEquals(
  toad, level[0, 1]
)

Multiplayer

Play online together

Multiplayer

Game Server

Browser Clients

Websockets

Multiplayer

Game Model

Websockets

WebApp

Java Fat Jar

Ktor

Gradle icon
runJvm 
jar
Gradle icon

Multiplayer

👉 https://mem.tobse.eu

Multiplayer

👉 https://mem.tobse.eu

https://www.hdwallpapers.in/super_mario_3d_world-wallpapers.html

Jetzt bist Du dran!

https://globalgamejam.org/news/poster-showcase-2018-0

git clone https://github.com/korlibs/korge-hello-world

😊

Tools