Skip to content

Commit

Permalink
add coroutines and ktx assets-async (#40)
Browse files Browse the repository at this point in the history
* add coroutines and ktx assets-async

* Simplified loading screen #40 (#41)

* remove unnecessary warning suppression

Co-authored-by: MJ <czyzby@users.noreply.github.com>
  • Loading branch information
Quillraven and czyzby authored Apr 10, 2020
1 parent 9e36368 commit 014249f
Show file tree
Hide file tree
Showing 11 changed files with 238 additions and 230 deletions.
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ object Versions {
const val detekt = "1.5.0"
const val junit = "5.6.1"
const val mockk = "1.9.3"
const val coroutines = "1.3.4"
}

object Apps {
Expand Down
3 changes: 3 additions & 0 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ dependencies {
api("io.github.libktx:ktx-actors:${Versions.ktx}")
api("io.github.libktx:ktx-app:${Versions.ktx}")
api("io.github.libktx:ktx-ashley:${Versions.ktx}")
// async assets requires coroutines
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}")
api("io.github.libktx:ktx-assets-async:${Versions.ktx}")
api("io.github.libktx:ktx-box2d:${Versions.ktx}")
api("io.github.libktx:ktx-collections:${Versions.ktx}")
api("io.github.libktx:ktx-freetype:${Versions.ktx}")
Expand Down
98 changes: 54 additions & 44 deletions core/src/main/kotlin/com/github/quillraven/quillysadventure/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import com.badlogic.gdx.Application
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.InputMultiplexer
import com.badlogic.gdx.Preferences
import com.badlogic.gdx.assets.AssetManager
import com.badlogic.gdx.graphics.Pixmap
import com.badlogic.gdx.graphics.g2d.Batch
import com.badlogic.gdx.graphics.g2d.SpriteBatch
Expand All @@ -23,7 +22,8 @@ import com.badlogic.gdx.physics.box2d.World
import com.badlogic.gdx.scenes.scene2d.Stage
import com.badlogic.gdx.utils.viewport.FitViewport
import com.github.quillraven.quillysadventure.assets.I18nAssets
import com.github.quillraven.quillysadventure.assets.get
import com.github.quillraven.quillysadventure.assets.TextureAtlasAssets
import com.github.quillraven.quillysadventure.assets.loadAsync
import com.github.quillraven.quillysadventure.audio.DefaultAudioService
import com.github.quillraven.quillysadventure.audio.NullAudioService
import com.github.quillraven.quillysadventure.configuration.CharacterConfigurations
Expand All @@ -33,9 +33,12 @@ import com.github.quillraven.quillysadventure.configuration.loadItemConfiguratio
import com.github.quillraven.quillysadventure.event.GameEventManager
import com.github.quillraven.quillysadventure.screen.LoadingScreen
import com.github.quillraven.quillysadventure.ui.createSkin
import kotlinx.coroutines.launch
import ktx.app.KtxGame
import ktx.app.KtxScreen
import ktx.app.clearScreen
import ktx.assets.async.AssetStorage
import ktx.async.KtxAsync
import ktx.box2d.createWorld
import ktx.box2d.earthGravity
import ktx.graphics.use
Expand Down Expand Up @@ -127,50 +130,57 @@ class Main(
// init Box2D - the next call avoids some issues with older devices where the box2d libraries were not loaded correctly
Box2D.init()

// setup context and register stuff that should also be disposed at the end of the game lifecycle
ctx.register {
bindSingleton(ShaderPrograms())
bindSingleton(SpriteBatch(2048))
bindSingleton(AssetManager().apply {
// we use tmx tiled maps created via the Tiled tool and therefore
// we use the TmxMapLoader for our assetmanager to be able to
// load/unload .tmx files
setLoader(TiledMap::class.java, TmxMapLoader(fileHandleResolver))
})
bindSingleton(Stage(FitViewport(VIRTUAL_W.toFloat(), VIRTUAL_H.toFloat()), ctx.inject<SpriteBatch>()))
bindSingleton(createSkin(ctx.inject()))
bindSingleton(RayHandler(world))
bindSingleton(Box2DDebugRenderer())
bindSingleton(OrthogonalTiledMapRenderer(null, UNIT_SCALE, ctx.inject<SpriteBatch>()))
KtxAsync.initiate()
val assetStorage = AssetStorage().apply {
// we use tmx tiled maps created via the Tiled tool and therefore
// we use the TmxMapLoader to be able to load/unload .tmx files
setLoader<TiledMap> { TmxMapLoader(this.fileResolver) }
}

// we need a multiplexer to react on the following input events
// UI widget --> Stage
// keyboard --> InputProcessor (GameEventManager)
Gdx.input.inputProcessor = InputMultiplexer(gameEventManager, ctx.inject<Stage>())

// box2d light should not create shadows for dynamic game objects
Light.setGlobalContactFilter(FILTER_CATEGORY_LIGHT, 0, FILTER_MASK_LIGHTS)

// initial screen is the loading screen which is loading all assets for the game
addScreen(
LoadingScreen(
this, // game instance to switch screens
ctx.inject<AssetManager>()[I18nAssets.DEFAULT],
ctx.inject(), // stage
ctx.inject(), // assets
gameEventManager, // game event manager
audioService,
world, // physic world
ecsEngine, // entity component engine
ctx.inject(), // ray handler
ctx.inject(), // shaders
ctx.inject(), // sprite batch
ctx.inject(), // tiled map renderer
ctx.inject() // box2d debug renderer
KtxAsync.launch {
// load textures for skin
val uiAtlas = assetStorage.loadAsync(TextureAtlasAssets.UI)
val bundle = assetStorage.loadAsync(I18nAssets.DEFAULT)

// setup context and register stuff that should also be disposed at the end of the game lifecycle
ctx.register {
bindSingleton(ShaderPrograms())
bindSingleton(SpriteBatch(2048))
bindSingleton(assetStorage)
bindSingleton(Stage(FitViewport(VIRTUAL_W.toFloat(), VIRTUAL_H.toFloat()), ctx.inject<SpriteBatch>()))
bindSingleton(createSkin(uiAtlas.await()))
bindSingleton(RayHandler(world))
bindSingleton(Box2DDebugRenderer())
bindSingleton(OrthogonalTiledMapRenderer(null, UNIT_SCALE, ctx.inject<SpriteBatch>()))
}

// we need a multiplexer to react on the following input events
// UI widget --> Stage
// keyboard --> InputProcessor (GameEventManager)
Gdx.input.inputProcessor = InputMultiplexer(gameEventManager, ctx.inject<Stage>())

// box2d light should not create shadows for dynamic game objects
Light.setGlobalContactFilter(FILTER_CATEGORY_LIGHT, 0, FILTER_MASK_LIGHTS)

// initial screen is the loading screen which is loading all assets for the game
addScreen(
LoadingScreen(
this@Main, // game instance to switch screens
bundle.await(),
ctx.inject(), // stage
ctx.inject(), // assets
gameEventManager, // game event manager
audioService,
world, // physic world
ecsEngine, // entity component engine
ctx.inject(), // ray handler
ctx.inject(), // shaders
ctx.inject(), // sprite batch
ctx.inject(), // tiled map renderer
ctx.inject() // box2d debug renderer
)
)
)
setScreen<LoadingScreen>()
setScreen<LoadingScreen>()
}
}

override fun <Type : KtxScreen> setScreen(type: Class<Type>) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package com.github.quillraven.quillysadventure.assets

import com.badlogic.gdx.assets.AssetManager
import com.badlogic.gdx.assets.loaders.ParticleEffectLoader
import com.badlogic.gdx.audio.Music
import com.badlogic.gdx.audio.Sound
import com.badlogic.gdx.graphics.g2d.ParticleEffect
import com.badlogic.gdx.graphics.g2d.TextureAtlas
import com.badlogic.gdx.maps.tiled.TiledMap
import com.badlogic.gdx.utils.I18NBundle
import ktx.assets.getAsset
import ktx.assets.load
import ktx.assets.async.AssetStorage

// music
enum class MusicAssets(val filePath: String, val volumeScale: Float = 0.15f) {
Expand All @@ -22,8 +20,8 @@ enum class MusicAssets(val filePath: String, val volumeScale: Float = 0.15f) {
GAME_OVER("music/gameover.ogg")
}

fun AssetManager.load(asset: MusicAssets) = load<Music>(asset.filePath)
operator fun AssetManager.get(asset: MusicAssets) = this.getAsset<Music>(asset.filePath)
fun AssetStorage.loadAsync(asset: MusicAssets) = loadAsync<Music>(asset.filePath)
operator fun AssetStorage.get(asset: MusicAssets) = get<Music>(asset.filePath)

// sound
enum class SoundAssets(val filePath: String, val volumeScale: Float = 1f) {
Expand All @@ -45,17 +43,17 @@ enum class SoundAssets(val filePath: String, val volumeScale: Float = 1f) {
PING("sounds/ping.ogg")
}

fun AssetManager.load(asset: SoundAssets) = load<Sound>(asset.filePath)
operator fun AssetManager.get(asset: SoundAssets) = this.getAsset<Sound>(asset.filePath)
fun AssetStorage.loadAsync(asset: SoundAssets) = loadAsync<Sound>(asset.filePath)
operator fun AssetStorage.get(asset: SoundAssets) = get<Sound>(asset.filePath)

// texture atlas
enum class TextureAtlasAssets(val filePath: String) {
GAME_OBJECTS("graphics/gameObjects.atlas"),
UI("ui/UI.atlas")
}

fun AssetManager.load(asset: TextureAtlasAssets) = load<TextureAtlas>(asset.filePath)
operator fun AssetManager.get(asset: TextureAtlasAssets) = this.getAsset<TextureAtlas>(asset.filePath)
fun AssetStorage.loadAsync(asset: TextureAtlasAssets) = loadAsync<TextureAtlas>(asset.filePath)
operator fun AssetStorage.get(asset: TextureAtlasAssets) = get<TextureAtlas>(asset.filePath)

// tiled map
enum class MapAssets(val filePath: String) {
Expand All @@ -67,8 +65,8 @@ enum class MapAssets(val filePath: String) {
GAME_OVER("map/gameover.tmx")
}

fun AssetManager.load(asset: MapAssets) = load<TiledMap>(asset.filePath)
operator fun AssetManager.get(asset: MapAssets) = this.getAsset<TiledMap>(asset.filePath)
fun AssetStorage.loadAsync(asset: MapAssets) = loadAsync<TiledMap>(asset.filePath)
operator fun AssetStorage.get(asset: MapAssets) = get<TiledMap>(asset.filePath)

// particle effects
enum class ParticleAssets(val filePath: String, val scale: Float = 1f, val sound: SoundAssets = SoundAssets.UNKNOWN) {
Expand All @@ -78,16 +76,14 @@ enum class ParticleAssets(val filePath: String, val scale: Float = 1f, val sound
FIREBALL("particles/fireball.p", 0.3f, SoundAssets.FIRE_BALL)
}

fun AssetManager.load(asset: ParticleAssets, params: ParticleEffectLoader.ParticleEffectParameter) =
load(asset.filePath, ParticleEffect::class.java, params)
fun AssetStorage.loadAsync(asset: ParticleAssets, params: ParticleEffectLoader.ParticleEffectParameter) =
loadAsync<ParticleEffect>(asset.filePath, params)

operator fun AssetManager.get(asset: ParticleAssets) = this.getAsset<ParticleEffect>(asset.filePath)
operator fun AssetStorage.get(asset: ParticleAssets) = get<ParticleEffect>(asset.filePath)

enum class I18nAssets(val filePath: String) {
DEFAULT("ui/i18n")
}

fun AssetManager.load(asset: I18nAssets) =
load(asset.filePath, I18NBundle::class.java)

operator fun AssetManager.get(asset: I18nAssets) = this.getAsset<I18NBundle>(asset.filePath)
fun AssetStorage.loadAsync(asset: I18nAssets) = loadAsync<I18NBundle>(asset.filePath)
operator fun AssetStorage.get(asset: I18nAssets) = get<I18NBundle>(asset.filePath)
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.github.quillraven.quillysadventure.audio

import com.badlogic.gdx.assets.AssetManager
import com.badlogic.gdx.audio.Music
import com.badlogic.gdx.audio.Sound
import com.badlogic.gdx.math.MathUtils
Expand All @@ -10,10 +9,11 @@ import com.github.quillraven.quillysadventure.assets.SoundAssets
import com.github.quillraven.quillysadventure.assets.get
import com.github.quillraven.quillysadventure.event.GameEventManager
import com.github.quillraven.quillysadventure.map.Map
import ktx.assets.async.AssetStorage
import ktx.collections.iterate
import java.util.*

class DefaultAudioService(private val assets: AssetManager, gameEventManager: GameEventManager) : AudioService {
class DefaultAudioService(private val assets: AssetStorage, gameEventManager: GameEventManager) : AudioService {
private var music: Music? = null
private var musicType = MusicAssets.MENU
override var musicVolume = 1f
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.github.quillraven.quillysadventure.configuration

import com.badlogic.gdx.assets.AssetManager
import com.badlogic.gdx.graphics.g2d.TextureAtlas
import com.github.quillraven.quillysadventure.assets.TextureAtlasAssets
import com.github.quillraven.quillysadventure.assets.get
import ktx.assets.async.AssetStorage
import ktx.log.logger
import java.util.*

Expand All @@ -19,7 +19,7 @@ class ItemCfg(val region: TextureAtlas.AtlasRegion) {
var manaBonus = 0
}

class ItemConfigurations(assets: AssetManager) : EnumMap<Item, ItemCfg>(Item::class.java) {
class ItemConfigurations(assets: AssetStorage) : EnumMap<Item, ItemCfg>(Item::class.java) {
private val atlas = assets[TextureAtlasAssets.GAME_OBJECTS]
private val defaultRegion = atlas.findRegion("error")!!
private val defaultCfg = ItemCfg(defaultRegion)
Expand Down Expand Up @@ -51,10 +51,10 @@ class ItemConfigurations(assets: AssetManager) : EnumMap<Item, ItemCfg>(Item::cl
}
}

inline fun itemConfigurations(assets: AssetManager, init: ItemConfigurations.() -> Unit) =
inline fun itemConfigurations(assets: AssetStorage, init: ItemConfigurations.() -> Unit) =
ItemConfigurations(assets).apply(init)

fun loadItemConfigurations(assets: AssetManager): ItemConfigurations {
fun loadItemConfigurations(assets: AssetStorage): ItemConfigurations {
return itemConfigurations(assets) {
cfg(Item.POTION_GAIN_LIFE, "potion_green_plus") {
lifeBonus = 10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import com.badlogic.ashley.core.Engine
import com.badlogic.ashley.core.Entity
import com.badlogic.ashley.core.EntityListener
import com.badlogic.ashley.systems.IteratingSystem
import com.badlogic.gdx.assets.AssetManager
import com.badlogic.gdx.graphics.g2d.TextureAtlas
import com.badlogic.gdx.utils.Array
import com.github.quillraven.quillysadventure.UNIT_SCALE
import com.github.quillraven.quillysadventure.assets.SoundAssets
import com.github.quillraven.quillysadventure.assets.TextureAtlasAssets
import com.github.quillraven.quillysadventure.assets.get
import com.github.quillraven.quillysadventure.audio.AudioService
import com.github.quillraven.quillysadventure.ecs.component.Animation
import com.github.quillraven.quillysadventure.ecs.component.AnimationComponent
Expand All @@ -22,6 +20,7 @@ import com.github.quillraven.quillysadventure.ecs.component.aniCmp
import com.github.quillraven.quillysadventure.ecs.component.renderCmp
import ktx.ashley.allOf
import ktx.ashley.exclude
import ktx.assets.async.AssetStorage
import ktx.log.logger
import java.util.*

Expand All @@ -39,12 +38,13 @@ private const val DEFAULT_REGION_KEY = "error"
* Example for a player idle animation with two frames would be PLAYER/IDLE_0 and PLAYER/IDLE_1
* as keys for the regions of the atlas.
*/
class AnimationSystem(assets: AssetManager, private val audioService: AudioService) :
class AnimationSystem(assets: AssetStorage, private val audioService: AudioService) :
IteratingSystem(allOf(AnimationComponent::class, RenderComponent::class).exclude(RemoveComponent::class).get()),
EntityListener {
private val animationFamily = allOf(AnimationComponent::class).get()
private val animationCache = EnumMap<ModelType, EnumMap<AnimationType, Animation>>(ModelType::class.java)
private val textureAtlas = assets[TextureAtlasAssets.GAME_OBJECTS]
private val textureAtlas: TextureAtlas = assets[TextureAtlasAssets.GAME_OBJECTS.filePath]

// default texture region must not be null because we always want to render at least something
// even if the real animation is missing or wrongly defined
private val defaultRegion = textureAtlas.findRegion(DEFAULT_REGION_KEY)!!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import com.badlogic.ashley.core.Engine
import com.badlogic.ashley.core.Entity
import com.badlogic.ashley.core.EntityListener
import com.badlogic.ashley.systems.IteratingSystem
import com.badlogic.gdx.assets.AssetManager
import com.badlogic.gdx.graphics.g2d.ParticleEffect
import com.badlogic.gdx.graphics.g2d.ParticleEffectPool
import com.github.quillraven.quillysadventure.UNIT_SCALE
import com.github.quillraven.quillysadventure.assets.ParticleAssets
import com.github.quillraven.quillysadventure.assets.SoundAssets
import com.github.quillraven.quillysadventure.assets.get
import com.github.quillraven.quillysadventure.audio.AudioService
import com.github.quillraven.quillysadventure.ecs.component.ParticleComponent
import com.github.quillraven.quillysadventure.ecs.component.RemoveComponent
Expand All @@ -18,16 +17,18 @@ import com.github.quillraven.quillysadventure.ecs.component.particleCmp
import com.github.quillraven.quillysadventure.ecs.component.transfCmp
import ktx.ashley.allOf
import ktx.ashley.exclude
import ktx.assets.async.AssetStorage
import java.util.*

class ParticleSystem(private val assets: AssetManager, private val audioService: AudioService) :
class ParticleSystem(private val assets: AssetStorage, private val audioService: AudioService) :
IteratingSystem(allOf(ParticleComponent::class, TransformComponent::class).exclude(RemoveComponent::class).get()),
EntityListener {
private val effectPools = EnumMap<ParticleAssets, ParticleEffectPool>(ParticleAssets::class.java)

init {
ParticleAssets.values().forEach {
assets[it].run {
val particleEffect: ParticleEffect = assets[it.filePath]
particleEffect.run {
// in case of additive effects this increases the performance
// because the sprite batch will not automatically switch its
// blend mode. This means, we have to take care of the
Expand All @@ -54,7 +55,7 @@ class ParticleSystem(private val assets: AssetManager, private val audioService:
override fun entityAdded(entity: Entity) {
// create particle effect
with(entity.particleCmp) {
effect = effectPools.computeIfAbsent(type) { ParticleEffectPool(assets[type], 1, 2) }.obtain()
effect = effectPools.computeIfAbsent(type) { ParticleEffectPool(assets[type.filePath], 1, 2) }.obtain()
if (flipBy180Deg) {
// change all angles by 180 degrees
effect.emitters.forEach {
Expand Down
Loading

0 comments on commit 014249f

Please sign in to comment.