Skip to content

Commit

Permalink
refactor animation setting of entity (=introduce AnimationService)
Browse files Browse the repository at this point in the history
  • Loading branch information
Quillraven committed Apr 1, 2024
1 parent 30ec739 commit 729019a
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 115 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.quillraven.github.quillyjumper

import com.badlogic.gdx.graphics.g2d.Animation.PlayMode
import com.badlogic.gdx.graphics.g2d.Sprite
import com.badlogic.gdx.graphics.g2d.TextureAtlas
import com.badlogic.gdx.graphics.g2d.TextureRegion
import com.github.quillraven.fleks.ComponentType
import com.github.quillraven.fleks.Entity
import com.github.quillraven.fleks.World
import com.quillraven.github.quillyjumper.component.*
import ktx.app.gdxError
import ktx.log.logger

class AnimationService(private val objectAtlas: TextureAtlas) {
private val animationCache = mutableMapOf<String, GdxAnimation>()

fun World.entityAnimation(
entity: Entity,
type: AnimationType,
playMode: PlayMode,
cmpType: ComponentType<Animation>,
) {
val (gameObject) = entity[Tiled]
val gdxAnimation = gdxAnimation(gameObject, type)

var aniCmp = entity.getOrNull(cmpType)
if (aniCmp == null) {
aniCmp = Animation(gdxAnimation, playMode, type = cmpType)
entity.configure { it += aniCmp }
} else {
aniCmp.gdxAnimation = gdxAnimation
aniCmp.playMode = playMode
}

val (sprite) = entity[Graphic]
sprite.updateRegion(gdxAnimation.getKeyFrame(0f))
}

fun gdxAnimation(
gameObject: GameObject,
type: AnimationType
): GdxAnimation {
val animationAtlasKey = "${gameObject.atlasKey}/${type.atlasKey}"
val gdxAnimation = animationCache.getOrPut(animationAtlasKey) {
val regions = objectAtlas.findRegions(animationAtlasKey)
if (regions.isEmpty) {
gdxError("There are no regions for the animation $animationAtlasKey")
}
GdxAnimation(Animation.DEFAULT_FRAME_DURATION, regions)
}

if (animationCache.size > 100) {
log.info { "Animation cache is larger than 100" }
}
return gdxAnimation
}

companion object {
private val log = logger<AnimationService>()

fun Sprite.updateRegion(region: TextureRegion) {
val flipX = isFlipX
val flipY = isFlipY
setRegion(region)
setFlip(flipX, flipY)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ import com.github.quillraven.fleks.Component
import com.github.quillraven.fleks.ComponentType
import com.github.quillraven.fleks.Entity
import com.github.quillraven.fleks.World
import com.quillraven.github.quillyjumper.AnimationService
import com.quillraven.github.quillyjumper.component.*
import com.quillraven.github.quillyjumper.util.entityAnimation
import kotlin.math.atan2

data class AiEntity(val entity: Entity, val world: World) {
data class AiEntity(
val entity: Entity,
val world: World,
private val animationService: AnimationService
) {

fun animation(type: AnimationType, playMode: PlayMode = PlayMode.LOOP) {
world.entityAnimation(entity, type, playMode)
fun animation(type: AnimationType, playMode: PlayMode = PlayMode.LOOP) = with(animationService) {
world.entityAnimation(entity, type, playMode, Animation.NORMAL_ANIMATION)
}

inline operator fun <reified T : Component<*>> get(type: ComponentType<T>): T = with(world) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,10 @@ import com.badlogic.gdx.graphics.g2d.Animation.PlayMode
import com.badlogic.gdx.graphics.g2d.TextureRegion
import com.github.quillraven.fleks.Component
import com.github.quillraven.fleks.ComponentType
import com.github.quillraven.fleks.World
import com.github.quillraven.fleks.componentTypeOf
import com.quillraven.github.quillyjumper.GameObject
import com.quillraven.github.quillyjumper.system.AnimationSystem

typealias GdxAnimation = com.badlogic.gdx.graphics.g2d.Animation<TextureRegion>

fun gdxAnimation(world: World, gameObject: GameObject, type: AnimationType): GdxAnimation {
// TODO can we optimize it to retrieve the system directly without looping through all systems?
return world.system<AnimationSystem>().gdxAnimation(gameObject, type)
}

enum class AnimationType {
IDLE, RUN, JUMP, FALL, HIT, DOUBLE_JUMP, AGGRO;

Expand All @@ -32,6 +24,8 @@ data class Animation(

companion object {
val NORMAL_ANIMATION = componentTypeOf<Animation>()
// global animation has higher priority than normal animation which means if an entity
// has a global animation then it is played instead of the normal animation
val GLOBAL_ANIMATION = componentTypeOf<Animation>()
const val DEFAULT_FRAME_DURATION = 1 / 15f
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,13 @@ import com.badlogic.gdx.scenes.scene2d.Stage
import com.badlogic.gdx.utils.viewport.FitViewport
import com.badlogic.gdx.utils.viewport.Viewport
import com.github.quillraven.fleks.configureWorld
import com.quillraven.github.quillyjumper.Assets
import com.quillraven.github.quillyjumper.GameProperties
import com.quillraven.github.quillyjumper.MapAsset
import com.quillraven.github.quillyjumper.*
import com.quillraven.github.quillyjumper.Quillyjumper.Companion.GRAVITY
import com.quillraven.github.quillyjumper.audio.AudioService
import com.quillraven.github.quillyjumper.event.GameEventDispatcher
import com.quillraven.github.quillyjumper.event.GameEventListener
import com.quillraven.github.quillyjumper.event.MapChangeEvent
import com.quillraven.github.quillyjumper.input.KeyboardInputProcessor
import com.quillraven.github.quillyjumper.inputMultiplexer
import com.quillraven.github.quillyjumper.system.*
import com.quillraven.github.quillyjumper.tiled.TiledService
import com.quillraven.github.quillyjumper.ui.GameModel
Expand All @@ -38,8 +35,9 @@ class GameScreen(
private val uiViewport: Viewport = FitViewport(320f, 180f)
private val stage = Stage(uiViewport, batch)
private val physicWorld = createWorld(gravity = GRAVITY).apply { autoClearForces = false }
private val animationService = AnimationService(assets[TextureAtlasAsset.GAMEOBJECT])
private val world = createEntityWorld(batch, audioService, gameProperties)
private val tiledService = TiledService(world, physicWorld, assets)
private val tiledService = TiledService(world, physicWorld, assets, animationService)
private val keyboardProcessor = KeyboardInputProcessor(world)
private val gameModel = GameModel(world)

Expand Down Expand Up @@ -99,6 +97,7 @@ class GameScreen(
add(batch)
add(physicWorld)
add(audioService)
add(animationService)
}

systems {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,15 @@
package com.quillraven.github.quillyjumper.system

import com.badlogic.gdx.graphics.g2d.Animation.PlayMode
import com.badlogic.gdx.graphics.g2d.Sprite
import com.badlogic.gdx.graphics.g2d.TextureRegion
import com.github.quillraven.fleks.Entity
import com.github.quillraven.fleks.IteratingSystem
import com.github.quillraven.fleks.World
import com.github.quillraven.fleks.World.Companion.inject
import com.quillraven.github.quillyjumper.Assets
import com.quillraven.github.quillyjumper.GameObject
import com.quillraven.github.quillyjumper.TextureAtlasAsset
import com.quillraven.github.quillyjumper.component.*
import com.quillraven.github.quillyjumper.AnimationService.Companion.updateRegion
import com.quillraven.github.quillyjumper.component.Animation
import com.quillraven.github.quillyjumper.component.Animation.Companion.GLOBAL_ANIMATION
import com.quillraven.github.quillyjumper.component.Animation.Companion.NORMAL_ANIMATION
import ktx.app.gdxError
import ktx.log.logger
import com.quillraven.github.quillyjumper.component.Graphic

class AnimationSystem(
assets: Assets = inject(),
) : IteratingSystem(World.family { all(NORMAL_ANIMATION, Graphic) }) {

private val objectAtlas = assets[TextureAtlasAsset.GAMEOBJECT]
private val animationCache = mutableMapOf<String, GdxAnimation>()
class AnimationSystem : IteratingSystem(World.family { all(NORMAL_ANIMATION, Graphic) }) {

override fun onTickEntity(entity: Entity) {
val globalAniCmp = entity.getOrNull(GLOBAL_ANIMATION)
Expand All @@ -47,45 +35,4 @@ class AnimationSystem(
animationCmp.timer += deltaTime
}

fun entityAnimation(entity: Entity, type: AnimationType, playMode: PlayMode) {
val (gameObject) = entity[Tiled]
val gdxAnimation = gdxAnimation(gameObject, type)

val aniCmp = entity[NORMAL_ANIMATION]
aniCmp.gdxAnimation = gdxAnimation
aniCmp.playMode = playMode
val (sprite) = entity[Graphic]
sprite.updateRegion(gdxAnimation.getKeyFrame(0f))
}

private fun Sprite.updateRegion(region: TextureRegion) {
val flipX = isFlipX
val flipY = isFlipY
setRegion(region)
setFlip(flipX, flipY)
}

fun gdxAnimation(
gameObject: GameObject,
type: AnimationType
): GdxAnimation {
val animationAtlasKey = "${gameObject.atlasKey}/${type.atlasKey}"
val gdxAnimation = animationCache.getOrPut(animationAtlasKey) {
val regions = objectAtlas.findRegions(animationAtlasKey)
if (regions.isEmpty) {
gdxError("There are no regions for the animation $animationAtlasKey")
}
GdxAnimation(Animation.DEFAULT_FRAME_DURATION, regions)
}

if (animationCache.size > 100) {
log.info { "Animation cache is larger than 100" }
}
return gdxAnimation
}

companion object {
private val log = logger<AnimationSystem>()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@ import com.github.quillraven.fleks.Entity
import com.github.quillraven.fleks.IteratingSystem
import com.github.quillraven.fleks.World.Companion.family
import com.github.quillraven.fleks.World.Companion.inject
import com.quillraven.github.quillyjumper.AnimationService
import com.quillraven.github.quillyjumper.SoundAsset
import com.quillraven.github.quillyjumper.audio.AudioService
import com.quillraven.github.quillyjumper.component.*
import com.quillraven.github.quillyjumper.component.Animation.Companion.GLOBAL_ANIMATION
import com.quillraven.github.quillyjumper.event.EntityDamageEvent
import com.quillraven.github.quillyjumper.event.GameEventDispatcher
import ktx.log.logger

class DamageSystem(
private val audioService: AudioService = inject(),
private val animationService: AnimationService = inject()
) : IteratingSystem(family { all(DamageTaken, Life).none(Invulnerable) }) {

override fun onTickEntity(entity: Entity) {
override fun onTickEntity(entity: Entity) = with(animationService) {
val (damageAmount) = entity[DamageTaken]
val lifeCmp = entity[Life]
lifeCmp.current = (lifeCmp.current - damageAmount).coerceAtLeast(0f)
Expand All @@ -31,11 +34,7 @@ class DamageSystem(
it += Invulnerable(1.5f)
it += Blink(maxTime = 1.5f, blinkRatio = 0.1f)
it += Flash(color = Color.RED, weight = 0.75f, amount = 1, delay = 0.15f)
it += Animation(
gdxAnimation = gdxAnimation(world, it[Tiled].gameObject, AnimationType.HIT),
type = Animation.GLOBAL_ANIMATION,
playMode = PlayMode.NORMAL
)
world.entityAnimation(it, AnimationType.HIT, PlayMode.NORMAL, GLOBAL_ANIMATION)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,9 @@ import com.badlogic.gdx.physics.box2d.CircleShape
import com.badlogic.gdx.physics.box2d.FixtureDef
import com.badlogic.gdx.physics.box2d.PolygonShape
import com.github.quillraven.fleks.World
import com.quillraven.github.quillyjumper.Assets
import com.quillraven.github.quillyjumper.GameObject
import com.quillraven.github.quillyjumper.PhysicWorld
import com.quillraven.github.quillyjumper.*
import com.quillraven.github.quillyjumper.Quillyjumper.Companion.OBJECT_FIXTURES
import com.quillraven.github.quillyjumper.Quillyjumper.Companion.UNIT_SCALE
import com.quillraven.github.quillyjumper.TextureAtlasAsset
import com.quillraven.github.quillyjumper.component.*
import com.quillraven.github.quillyjumper.event.GameEvent
import com.quillraven.github.quillyjumper.event.GameEventListener
Expand All @@ -44,6 +41,7 @@ class TiledService(
private val world: World,
private val physicWorld: PhysicWorld,
private val assets: Assets,
private val animationService: AnimationService,
) : GameEventListener {

override fun onEvent(event: GameEvent) {
Expand Down Expand Up @@ -158,8 +156,8 @@ class TiledService(
zIndex > 0 -> it += EntityTag.FOREGROUND
}
configureEntityTags(it, tile)
configureAnimation(it, tile, world, gameObject)
configureState(it, tile, world)
configureAnimation(it, tile, animationService, gameObject)
configureState(it, tile, world, animationService)
configureJump(it, tile)
configureLife(it, tile)
configureMove(it, tile)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.badlogic.gdx.math.Vector2
import com.github.quillraven.fleks.Entity
import com.github.quillraven.fleks.EntityCreateContext
import com.github.quillraven.fleks.World
import com.quillraven.github.quillyjumper.AnimationService
import com.quillraven.github.quillyjumper.GameObject
import com.quillraven.github.quillyjumper.Quillyjumper.Companion.OBJECT_FIXTURES
import com.quillraven.github.quillyjumper.Quillyjumper.Companion.UNIT_SCALE
Expand All @@ -31,17 +32,30 @@ fun EntityCreateContext.configureEntityTags(entity: Entity, tile: TiledMapTile)
}
}

fun EntityCreateContext.configureAnimation(entity: Entity, tile: TiledMapTile, world: World, gameObject: GameObject) {
fun EntityCreateContext.configureAnimation(
entity: Entity,
tile: TiledMapTile,
animationService: AnimationService,
gameObject: GameObject
) {
val hasAnimation = tile.property<Boolean>("hasAnimation", false)
if (hasAnimation) {
entity += Animation(gdxAnimation(world, gameObject, AnimationType.IDLE), type = Animation.NORMAL_ANIMATION)
entity += Animation(
animationService.gdxAnimation(gameObject, AnimationType.IDLE),
type = Animation.NORMAL_ANIMATION
)
}
}

fun EntityCreateContext.configureState(entity: Entity, tile: TiledMapTile, world: World) {
fun EntityCreateContext.configureState(
entity: Entity,
tile: TiledMapTile,
world: World,
animationService: AnimationService
) {
val initialState = tile.property<String>("initialState", "")
if (initialState.isNotBlank()) {
entity += State(AiEntity(entity, world), GameObjectState.valueOf(initialState))
entity += State(AiEntity(entity, world, animationService), GameObjectState.valueOf(initialState))
}
}

Expand Down

This file was deleted.

Loading

0 comments on commit 729019a

Please sign in to comment.