Started setting up sprite animations.

This commit is contained in:
lightheel 2025-08-04 12:57:35 -04:00
parent c5cebd8213
commit 37e5efa874
4 changed files with 202 additions and 7 deletions

View File

@ -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<android.graphics.Bitmap?>(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
)
}
}

View File

@ -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

View File

@ -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>(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
}
}

View File

@ -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
)