mirror of
https://github.com/nacabaro/vbhelper.git
synced 2026-06-05 13:52:54 +00:00
Added hit sprites.
This commit is contained in:
parent
28cb824bf3
commit
d833a89c17
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -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")
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user