Added hit sprites.

This commit is contained in:
lightheel 2025-08-06 18:03:03 -04:00
parent 28cb824bf3
commit d833a89c17
3 changed files with 330 additions and 0 deletions

View File

@ -0,0 +1,103 @@
package com.github.nacabaro.vbhelper.battle
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@Composable
fun HitEffectOverlay(
isVisible: Boolean,
modifier: Modifier = Modifier,
isPlayerScreen: Boolean = false,
onAnimationComplete: () -> Unit = {}
) {
if (!isVisible) return
val context = LocalContext.current
val hitEffectManager = remember { HitEffectSpriteManager(context) }
val coroutineScope = rememberCoroutineScope()
var currentFrame by remember { mutableStateOf(0) }
var currentSprite by remember { mutableStateOf<android.graphics.Bitmap?>(null) }
var animationProgress by remember { mutableStateOf(0f) }
var scale by remember { mutableStateOf(0.5f) }
var alpha by remember { mutableStateOf(1f) }
LaunchedEffect(isVisible) {
if (isVisible) {
println("DEBUG: Starting hit effect animation")
// Randomly choose between hit_01, hit_02, and hit_02_white
val hitSpriteName = when (kotlin.random.Random.nextInt(3)) {
0 -> "hit_01"
1 -> "hit_02"
else -> "hit_02_white"
}
currentSprite = hitEffectManager.loadHitSprite(hitSpriteName)
if (currentSprite != null) {
// Animate the hit effect
animationProgress = 0f
scale = 0.5f
alpha = 1f
// Scale up animation - slowed down
while (scale < 1.2f) {
scale += 0.05f // Reduced from 0.1f to 0.05f
delay(32) // Increased from 16ms to 32ms
}
// Hold for a moment - increased duration
delay(300) // Increased from 100ms to 300ms
// Fade out - slowed down
while (alpha > 0f) {
alpha -= 0.03f // Reduced from 0.05f to 0.03f
delay(32) // Increased from 16ms to 32ms
}
println("DEBUG: Hit effect animation completed")
onAnimationComplete()
} else {
println("DEBUG: Failed to load hit sprite")
onAnimationComplete()
}
}
}
currentSprite?.let { sprite ->
Box(
modifier = modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Image(
bitmap = sprite.asImageBitmap(),
contentDescription = "Hit Effect",
modifier = Modifier
.size((sprite.width * scale).dp, (sprite.height * scale).dp)
.offset(
x = if (isPlayerScreen) {
// On player screen, position further to the left
(-sprite.width * scale / 2 - 100).dp
} else {
// On enemy screen, position further to the right
(-sprite.width * scale / 2 + 150).dp
},
y = (-sprite.height * scale / 2 + 40).dp // Position lower on screen (was -60, now +40)
),
contentScale = ContentScale.Fit
)
}
}
}

View File

@ -0,0 +1,176 @@
package com.github.nacabaro.vbhelper.battle
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Rect
import java.io.File
class HitEffectSpriteManager(private val context: Context) {
private val spriteCache = mutableMapOf<String, Bitmap>()
// Base directory where hit effect sprites are stored
private val hitSpritesDir = File(context.filesDir, "battle_sprites/extracted_hit_sprites")
/**
* Load a hit sprite (hit_01.png, hit_02.png, hit_02_white.png)
* @param spriteName The sprite name (e.g., "hit_01", "hit_02", "hit_02_white")
* @return Bitmap of the hit sprite, or null if not found
*/
fun loadHitSprite(spriteName: String): Bitmap? {
val cacheKey = "hit_$spriteName"
// Check cache first
if (spriteCache.containsKey(cacheKey)) {
return spriteCache[cacheKey]
}
try {
val spriteFile = File(hitSpritesDir, "$spriteName.png")
if (!spriteFile.exists()) {
println("Hit sprite file not found: ${spriteFile.absolutePath}")
return null
}
val bitmap = BitmapFactory.decodeFile(spriteFile.absolutePath)
if (bitmap == null) {
println("Failed to decode hit sprite file: ${spriteFile.absolutePath}")
return null
}
println("Successfully loaded hit sprite: $spriteName.png (${bitmap.width}x${bitmap.height})")
// Cache the result
spriteCache[cacheKey] = bitmap
return bitmap
} catch (e: Exception) {
println("Error loading hit sprite: ${e.message}")
e.printStackTrace()
return null
}
}
/**
* Load a damage effect sprite from spritesheet
* @param spritesheetName The spritesheet name (e.g., "dmg_ef1", "dmg_ef2")
* @param frameIndex The frame index (0-3 for dmg_ef1 and dmg_ef2, 0 for dmg_ef3)
* @return Bitmap of the damage effect frame, or null if not found
*/
fun loadDamageEffectSprite(spritesheetName: String, frameIndex: Int = 0): Bitmap? {
val cacheKey = "dmg_${spritesheetName}_frame_${frameIndex}"
// Check cache first
if (spriteCache.containsKey(cacheKey)) {
return spriteCache[cacheKey]
}
try {
val spritesheetFile = File(hitSpritesDir, "$spritesheetName.png")
if (!spritesheetFile.exists()) {
println("Damage effect spritesheet not found: ${spritesheetFile.absolutePath}")
return null
}
val spritesheet = BitmapFactory.decodeFile(spritesheetFile.absolutePath)
if (spritesheet == null) {
println("Failed to decode damage effect spritesheet: ${spritesheetFile.absolutePath}")
return null
}
// Extract frame from spritesheet
val frameBitmap = when (spritesheetName) {
"dmg_ef1", "dmg_ef2" -> {
// These are 2x2 spritesheets (4 frames)
val frameWidth = spritesheet.width / 2
val frameHeight = spritesheet.height / 2
val row = frameIndex / 2
val col = frameIndex % 2
val x = col * frameWidth
val y = row * frameHeight
Bitmap.createBitmap(spritesheet, x, y, frameWidth, frameHeight)
}
"dmg_ef3" -> {
// This is a single sprite
spritesheet
}
else -> {
println("Unknown spritesheet name: $spritesheetName")
return null
}
}
println("Successfully loaded damage effect frame: $spritesheetName frame $frameIndex (${frameBitmap.width}x${frameBitmap.height})")
// Cache the result
spriteCache[cacheKey] = frameBitmap
return frameBitmap
} catch (e: Exception) {
println("Error loading damage effect sprite: ${e.message}")
e.printStackTrace()
return null
}
}
/**
* Get available hit sprite names
* @return List of available hit sprite names
*/
fun getAvailableHitSprites(): List<String> {
try {
if (!hitSpritesDir.exists()) {
return emptyList()
}
val hitFiles = hitSpritesDir.listFiles { file ->
file.name.startsWith("hit_") && file.name.endsWith(".png")
} ?: emptyArray()
return hitFiles.map { file ->
file.name.substringBefore(".png")
}.sorted()
} catch (e: Exception) {
println("Error getting available hit sprites: ${e.message}")
e.printStackTrace()
return emptyList()
}
}
/**
* Get available damage effect spritesheet names
* @return List of available damage effect spritesheet names
*/
fun getAvailableDamageEffectSpritesheets(): List<String> {
try {
if (!hitSpritesDir.exists()) {
return emptyList()
}
val dmgFiles = hitSpritesDir.listFiles { file ->
file.name.startsWith("dmg_ef") && file.name.endsWith(".png")
} ?: emptyArray()
return dmgFiles.map { file ->
file.name.substringBefore(".png")
}.sorted()
} catch (e: Exception) {
println("Error getting available damage effect spritesheets: ${e.message}")
e.printStackTrace()
return emptyList()
}
}
/**
* Clear the sprite cache
*/
fun clearCache() {
spriteCache.clear()
}
}

View File

@ -52,6 +52,7 @@ 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
import com.github.nacabaro.vbhelper.battle.HitEffectOverlay
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import kotlin.math.sin
@ -162,6 +163,20 @@ fun BattleScreen(
var playerDamageValue by remember { mutableStateOf(0) }
var opponentDamageValue by remember { mutableStateOf(0) }
// Hit effect animation state
var showPlayerHitEffect by remember { mutableStateOf(false) }
var showOpponentHitEffect by remember { mutableStateOf(false) }
// Reset hit effect states when attack phase returns to idle
LaunchedEffect(battleSystem.attackPhase) {
if (battleSystem.attackPhase == 0) {
// Reset hit effect states when returning to idle
showPlayerHitEffect = false
showOpponentHitEffect = false
println("DEBUG: Reset hit effect states - attack phase returned to idle")
}
}
// Critical bar timer
LaunchedEffect(Unit) {
while (true) {
@ -190,6 +205,7 @@ fun BattleScreen(
2 -> {
// Phase 2: Player attack on enemy screen
println("Starting Phase 2: Player attack on enemy screen")
println("DEBUG: Phase 2 - showPlayerHitEffect=$showPlayerHitEffect, showOpponentHitEffect=$showOpponentHitEffect")
battleSystem.switchToView(2) // Enemy screen
var progress = 0f
while (progress < 1f) {
@ -202,6 +218,9 @@ fun BattleScreen(
// Player attack hits enemy
println("Player attack hits enemy at progress $progress")
battleSystem.startOpponentHit()
// Show hit effect and damage effect
println("DEBUG: Setting showOpponentHitEffect = true (player attack hits enemy)")
showOpponentHitEffect = true
// Show damage number when attack reaches enemy
if (pendingOpponentDamage > 0) {
showOpponentDamageNumber = true
@ -241,6 +260,7 @@ fun BattleScreen(
3 -> {
// Phase 3: Enemy attack on player screen
println("Starting Phase 3: Enemy attack on player screen")
println("DEBUG: Phase 3 - showPlayerHitEffect=$showPlayerHitEffect, showOpponentHitEffect=$showOpponentHitEffect")
battleSystem.switchToView(1) // Player screen
var progress = 0f
while (progress < 1f) {
@ -255,6 +275,9 @@ fun BattleScreen(
// Enemy attack hits player
println("Enemy attack hits player at progress $progress")
battleSystem.startPlayerHit()
// Show hit effect and damage effect
println("DEBUG: Setting showPlayerHitEffect = true (enemy attack hits player)")
showPlayerHitEffect = true
// Show damage number when attack reaches player
if (pendingPlayerDamage > 0) {
showPlayerDamageNumber = true
@ -493,6 +516,20 @@ fun BattleScreen(
//.background(Color.Yellow.copy(alpha = 0.3f)) // Debug background
)
// Player hit effects
HitEffectOverlay(
isVisible = showPlayerHitEffect,
modifier = Modifier.fillMaxSize(),
isPlayerScreen = true,
onAnimationComplete = {
println("DEBUG: Player hit effect animation completed, setting showPlayerHitEffect = false")
showPlayerHitEffect = false
println("DEBUG: Player hit effect animation completed")
}
)
// Debug text overlay
/*
Text(
@ -516,6 +553,20 @@ fun BattleScreen(
.align(Alignment.Center)
.offset(y = (-50).dp)
)
// Enemy hit effects
HitEffectOverlay(
isVisible = showOpponentHitEffect,
modifier = Modifier.fillMaxSize(),
isPlayerScreen = false,
onAnimationComplete = {
println("DEBUG: Enemy hit effect animation completed, setting showOpponentHitEffect = false")
showOpponentHitEffect = false
println("DEBUG: Enemy hit effect animation completed")
}
)
}
}
}