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
Win
Mac
Linux
JS
Browser
Native
Android
IOS
08.2018
Kotlin Multiplatform
01.2017
01.2017
12.2016
2.5.3
K
or
G
E
JVM
Win
Mac
Linux
JS
Browser
Native
Android
IOS
Kotlin Multiplatform
K
or
G
E
JVM
Win
Mac
Linux
JS
Browser
Win
Mac
Linux
Native
Android
IOS
Common
Code
Kotlin
Compiler
LLVM IR
Kotlin Multiplatform
K
or
G
E
Kotlin Multiplatform
JVM
Win
Mac
Linux
JS
Browser
Win
Mac
Linux
Native
Android
IOS
KorGE
Libs
Common
Code
Kotlin
Compiler
LLVM IR
K
or
G
E
Kotlin Multiplatform
JVM
Win
Mac
Linux
JS
Browser
Win
Mac
Linux
Native
Android
IOS
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
runAndroidRelease
runJs
runJvm
runNativeRelease
Native
Android
Native
IOS
Win
Mac
Linux
Native
JVM
Win
Mac
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
runJvm
jar
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
- Tiled (Level Editor)
- Spine (Paid, Character Animation)
- Free Sprite Sheet Packer (Web App)
- Texture Packer (Paid)
- Hiero (Bitmap Font Editor)
-
KorGE IDE Plugin
- Visual Editor
- Particle Editor
- Level Editor
KorGE Game Eninge
By Tobse Fritz
KorGE Game Eninge
Spieleprogrammierung mit KorGE - Der Kotlin Multiplatform Game Engine
- 1,113