From 37e5efa87433f17f9975df1434558c8afee2e43e Mon Sep 17 00:00:00 2001 From: lightheel Date: Mon, 4 Aug 2025 12:57:35 -0400 Subject: [PATCH] Started setting up sprite animations. --- .../vbhelper/battle/AnimatedSpriteImage.kt | 62 ++++++++++ .../vbhelper/battle/BattleSpriteManager.kt | 2 +- .../vbhelper/battle/DigimonAnimationState.kt | 113 ++++++++++++++++++ .../vbhelper/screens/BattlesScreen.kt | 32 ++++- 4 files changed, 202 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt create mode 100644 app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt new file mode 100644 index 0000000..0394d85 --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/AnimatedSpriteImage.kt @@ -0,0 +1,62 @@ +package com.github.nacabaro.vbhelper.battle + +import androidx.compose.foundation.Image +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import kotlinx.coroutines.launch + +@Composable +fun AnimatedSpriteImage( + characterId: String, + animationType: DigimonAnimationType = DigimonAnimationType.IDLE, + modifier: Modifier = Modifier, + contentScale: ContentScale = ContentScale.Fit +) { + val context = LocalContext.current + val spriteManager = remember { BattleSpriteManager(context) } + val animationStateMachine = remember { DigimonAnimationStateMachine(characterId) } + val coroutineScope = rememberCoroutineScope() + + var bitmap by remember { mutableStateOf(null) } + + // Start the animation when the component is first created + LaunchedEffect(characterId) { + coroutineScope.launch { + animationStateMachine.playAnimation(DigimonAnimationType.IDLE) + } + } + + // Change animation when animationType changes + LaunchedEffect(animationType) { + coroutineScope.launch { + animationStateMachine.playAnimation(animationType) + } + } + + // Update sprite when animation state changes + LaunchedEffect(animationStateMachine.currentSpriteIndex) { + val spriteName = animationStateMachine.getCurrentSpriteName() + val atlasName = animationStateMachine.getCurrentAtlasName() + + println("Loading animated sprite: $spriteName from atlas: $atlasName") + bitmap = spriteManager.loadSprite(spriteName, atlasName) + + if (bitmap == null) { + println("Failed to load animated sprite: $spriteName from atlas: $atlasName") + } else { + println("Successfully loaded animated sprite: $spriteName from atlas: $atlasName") + } + } + + bitmap?.let { bmp -> + Image( + bitmap = bmp.asImageBitmap(), + contentDescription = "Animated Sprite: $characterId - ${animationStateMachine.currentAnimation}", + modifier = modifier, + contentScale = contentScale + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt index 5347c5e..316d486 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/BattleSpriteManager.kt @@ -73,7 +73,7 @@ class BattleSpriteManager(private val context: Context) { } // Load the specific sprite data file - val spriteDataFile = File(spriteBaseDir, "sprites/${spriteName}_sprite_00.json") + val spriteDataFile = File(spriteBaseDir, "sprites/${spriteName}.json") if (!spriteDataFile.exists()) { println("Sprite data file not found: ${spriteDataFile.absolutePath}") return null diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt b/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt new file mode 100644 index 0000000..bd3a9c0 --- /dev/null +++ b/app/src/main/java/com/github/nacabaro/vbhelper/battle/DigimonAnimationState.kt @@ -0,0 +1,113 @@ +package com.github.nacabaro.vbhelper.battle + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import kotlinx.coroutines.delay + +enum class DigimonAnimationType { + IDLE, + IDLE2, + WALK, + WALK2, + RUN, + RUN2, + WORKOUT, + WORKOUT2, + HAPPY, + SLEEP, + ATTACK, + FLEE +} + +data class AnimationState( + val type: DigimonAnimationType, + val spriteIndex: Int, // 00, 01, 02, etc. + val duration: Long = 100L, // Duration in milliseconds + val loop: Boolean = true +) + +class DigimonAnimationStateMachine( + private val characterId: String +) { + var currentAnimation by mutableStateOf(DigimonAnimationType.IDLE) + private set + + var currentSpriteIndex by mutableStateOf(0) + private set + + var isPlaying by mutableStateOf(false) + private set + + // Animation mapping - maps animation types to sprite indices + // For now, we'll assume the sprite indices 0-11 correspond to the 12 animation types + private val animationMapping = mapOf( + DigimonAnimationType.IDLE to 0, + DigimonAnimationType.IDLE2 to 1, + DigimonAnimationType.WALK to 2, + DigimonAnimationType.WALK2 to 3, + DigimonAnimationType.RUN to 4, + DigimonAnimationType.RUN2 to 5, + DigimonAnimationType.WORKOUT to 6, + DigimonAnimationType.WORKOUT2 to 7, + DigimonAnimationType.HAPPY to 8, + DigimonAnimationType.SLEEP to 9, + DigimonAnimationType.ATTACK to 10, + DigimonAnimationType.FLEE to 11 + ) + + // Animation durations for each type + private val animationDurations = mapOf( + DigimonAnimationType.IDLE to 500L, + DigimonAnimationType.IDLE2 to 500L, + DigimonAnimationType.WALK to 200L, + DigimonAnimationType.WALK2 to 200L, + DigimonAnimationType.RUN to 150L, + DigimonAnimationType.RUN2 to 150L, + DigimonAnimationType.WORKOUT to 300L, + DigimonAnimationType.WORKOUT2 to 300L, + DigimonAnimationType.HAPPY to 400L, + DigimonAnimationType.SLEEP to 1000L, + DigimonAnimationType.ATTACK to 300L, // Longer for attack animation + DigimonAnimationType.FLEE to 150L + ) + + suspend fun playAnimation(animationType: DigimonAnimationType) { + if (currentAnimation == animationType && isPlaying) { + return // Already playing this animation + } + + currentAnimation = animationType + isPlaying = true + + val spriteIndex = animationMapping[animationType] ?: 0 + currentSpriteIndex = spriteIndex + + val duration = animationDurations[animationType] ?: 100L + + // For non-looping animations like ATTACK, play once and return to IDLE + if (animationType == DigimonAnimationType.ATTACK) { + delay(duration) + playAnimation(DigimonAnimationType.IDLE) + } else { + // For looping animations, keep playing + while (isPlaying && currentAnimation == animationType) { + delay(duration) + // For now, we'll just keep the same sprite + // In the future, we could cycle through multiple sprites for each animation + } + } + } + + fun stopAnimation() { + isPlaying = false + } + + fun getCurrentSpriteName(): String { + return "${characterId}_sprite_${String.format("%02d", currentSpriteIndex)}" + } + + fun getCurrentAtlasName(): String { + return characterId + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt index 3b2fdcf..8ed70b7 100644 --- a/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt +++ b/app/src/main/java/com/github/nacabaro/vbhelper/screens/BattlesScreen.kt @@ -49,6 +49,8 @@ import com.github.nacabaro.vbhelper.battle.SpriteImage import com.github.nacabaro.vbhelper.battle.AttackSpriteImage import com.github.nacabaro.vbhelper.battle.SpriteFileManager import com.github.nacabaro.vbhelper.battle.ArenaBattleSystem +import com.github.nacabaro.vbhelper.battle.DigimonAnimationType +import com.github.nacabaro.vbhelper.battle.AnimatedSpriteImage @Composable fun BattleScreen( @@ -266,9 +268,18 @@ fun PlayerBattleView( .size(80.dp), contentAlignment = Alignment.CenterStart ) { - SpriteImage( - spriteName = activeCharacter?.charaId ?: "dim011_mon01", - atlasName = activeCharacter?.charaId ?: "dim011_mon01", + // Determine animation type based on battle state + val animationType = when (battleSystem.attackPhase) { + 1 -> DigimonAnimationType.ATTACK // Player attack on player screen + 2 -> DigimonAnimationType.ATTACK // Player attack on opponent screen + 3 -> DigimonAnimationType.IDLE // Opponent attack on opponent screen + 4 -> DigimonAnimationType.IDLE // Opponent attack on player screen + else -> DigimonAnimationType.IDLE + } + + AnimatedSpriteImage( + characterId = activeCharacter?.charaId ?: "dim011_mon01", + animationType = animationType, modifier = Modifier .size(80.dp) .scale(-1f, 1f), // Flip player Digimon horizontally @@ -473,9 +484,18 @@ fun OpponentBattleView( .size(80.dp), contentAlignment = Alignment.CenterEnd ) { - SpriteImage( - spriteName = activeCharacter?.charaId ?: "dim011_mon01", - atlasName = activeCharacter?.charaId ?: "dim011_mon01", + // Determine animation type based on battle state + val animationType = when (battleSystem.attackPhase) { + 1 -> DigimonAnimationType.IDLE // Player attack on player screen + 2 -> DigimonAnimationType.IDLE // Player attack on opponent screen + 3 -> DigimonAnimationType.ATTACK // Opponent attack on opponent screen + 4 -> DigimonAnimationType.ATTACK // Opponent attack on player screen + else -> DigimonAnimationType.IDLE + } + + AnimatedSpriteImage( + characterId = activeCharacter?.charaId ?: "dim011_mon01", + animationType = animationType, modifier = Modifier.size(80.dp), contentScale = ContentScale.Fit )