mirror of
https://github.com/nacabaro/vbhelper.git
synced 2026-06-05 22:02: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.ArenaBattleSystem
|
||||||
import com.github.nacabaro.vbhelper.battle.DigimonAnimationType
|
import com.github.nacabaro.vbhelper.battle.DigimonAnimationType
|
||||||
import com.github.nacabaro.vbhelper.battle.AnimatedSpriteImage
|
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.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import kotlin.math.sin
|
import kotlin.math.sin
|
||||||
@ -162,6 +163,20 @@ fun BattleScreen(
|
|||||||
var playerDamageValue by remember { mutableStateOf(0) }
|
var playerDamageValue by remember { mutableStateOf(0) }
|
||||||
var opponentDamageValue 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
|
// Critical bar timer
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -190,6 +205,7 @@ fun BattleScreen(
|
|||||||
2 -> {
|
2 -> {
|
||||||
// Phase 2: Player attack on enemy screen
|
// Phase 2: Player attack on enemy screen
|
||||||
println("Starting 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
|
battleSystem.switchToView(2) // Enemy screen
|
||||||
var progress = 0f
|
var progress = 0f
|
||||||
while (progress < 1f) {
|
while (progress < 1f) {
|
||||||
@ -202,6 +218,9 @@ fun BattleScreen(
|
|||||||
// Player attack hits enemy
|
// Player attack hits enemy
|
||||||
println("Player attack hits enemy at progress $progress")
|
println("Player attack hits enemy at progress $progress")
|
||||||
battleSystem.startOpponentHit()
|
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
|
// Show damage number when attack reaches enemy
|
||||||
if (pendingOpponentDamage > 0) {
|
if (pendingOpponentDamage > 0) {
|
||||||
showOpponentDamageNumber = true
|
showOpponentDamageNumber = true
|
||||||
@ -241,6 +260,7 @@ fun BattleScreen(
|
|||||||
3 -> {
|
3 -> {
|
||||||
// Phase 3: Enemy attack on player screen
|
// Phase 3: Enemy attack on player screen
|
||||||
println("Starting 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
|
battleSystem.switchToView(1) // Player screen
|
||||||
var progress = 0f
|
var progress = 0f
|
||||||
while (progress < 1f) {
|
while (progress < 1f) {
|
||||||
@ -255,6 +275,9 @@ fun BattleScreen(
|
|||||||
// Enemy attack hits player
|
// Enemy attack hits player
|
||||||
println("Enemy attack hits player at progress $progress")
|
println("Enemy attack hits player at progress $progress")
|
||||||
battleSystem.startPlayerHit()
|
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
|
// Show damage number when attack reaches player
|
||||||
if (pendingPlayerDamage > 0) {
|
if (pendingPlayerDamage > 0) {
|
||||||
showPlayerDamageNumber = true
|
showPlayerDamageNumber = true
|
||||||
@ -493,6 +516,20 @@ fun BattleScreen(
|
|||||||
//.background(Color.Yellow.copy(alpha = 0.3f)) // Debug background
|
//.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
|
// Debug text overlay
|
||||||
/*
|
/*
|
||||||
Text(
|
Text(
|
||||||
@ -516,6 +553,20 @@ fun BattleScreen(
|
|||||||
.align(Alignment.Center)
|
.align(Alignment.Center)
|
||||||
.offset(y = (-50).dp)
|
.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