Enhanced game over experience: Added louder game over sound, improved game over animation, and added haptic feedback

This commit is contained in:
cmclark00 2025-03-30 19:02:27 -04:00
parent ce19427cca
commit 292ea656f8
6 changed files with 134 additions and 2 deletions

View file

@ -366,6 +366,12 @@ class MainActivity : AppCompatActivity() {
// Flag to track if high score screen will be shown // Flag to track if high score screen will be shown
var showingHighScore = false var showingHighScore = false
// Play game over sound and trigger animation
if (isSoundEnabled) {
gameMusic.playGameOver()
}
gameView.startGameOverAnimation()
// Show progression screen first with XP animation // Show progression screen first with XP animation
showProgressionScreen(xpGained, newRewards) showProgressionScreen(xpGained, newRewards)

View file

@ -9,12 +9,14 @@ import com.mintris.R
class GameMusic(private val context: Context) { class GameMusic(private val context: Context) {
private var mediaPlayer: MediaPlayer? = null private var mediaPlayer: MediaPlayer? = null
private var gameOverPlayer: MediaPlayer? = null
private var isEnabled = true private var isEnabled = true
private var isPrepared = false private var isPrepared = false
init { init {
try { try {
setupMediaPlayer() setupMediaPlayer()
setupGameOverPlayer()
} catch (e: Exception) { } catch (e: Exception) {
Log.e("GameMusic", "Error initializing: ${e.message}") Log.e("GameMusic", "Error initializing: ${e.message}")
} }
@ -46,6 +48,49 @@ class GameMusic(private val context: Context) {
} }
} }
private fun setupGameOverPlayer() {
try {
Log.d("GameMusic", "Setting up GameOver MediaPlayer")
gameOverPlayer = MediaPlayer.create(context, R.raw.game_over).apply {
setVolume(1.0f, 1.0f) // Increased from 0.7f to 1.0f for maximum volume
// Set audio attributes for better performance
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_GAME)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build()
)
}
}
Log.d("GameMusic", "GameOver MediaPlayer setup complete")
} catch (e: Exception) {
Log.e("GameMusic", "Error setting up GameOver MediaPlayer", e)
gameOverPlayer = null
}
}
fun playGameOver() {
if (isEnabled && gameOverPlayer != null) {
try {
Log.d("GameMusic", "Playing game over sound")
// Temporarily lower background music volume
mediaPlayer?.setVolume(0.2f, 0.2f)
// Play game over sound
gameOverPlayer?.start()
// Restore background music volume after a delay
gameOverPlayer?.setOnCompletionListener {
mediaPlayer?.setVolume(0.5f, 0.5f)
}
} catch (e: Exception) {
Log.e("GameMusic", "Error playing game over sound: ${e.message}")
}
}
}
fun start() { fun start() {
if (isEnabled && mediaPlayer != null && isPrepared) { if (isEnabled && mediaPlayer != null && isPrepared) {
try { try {
@ -107,7 +152,9 @@ class GameMusic(private val context: Context) {
try { try {
Log.d("GameMusic", "Releasing MediaPlayer") Log.d("GameMusic", "Releasing MediaPlayer")
mediaPlayer?.release() mediaPlayer?.release()
gameOverPlayer?.release()
mediaPlayer = null mediaPlayer = null
gameOverPlayer = null
isPrepared = false isPrepared = false
} catch (e: Exception) { } catch (e: Exception) {
Log.e("GameMusic", "Error releasing music: ${e.message}") Log.e("GameMusic", "Error releasing music: ${e.message}")

View file

@ -91,4 +91,26 @@ class GameHaptics(private val context: Context) {
vibrator.vibrate(vibrationEffect) vibrator.vibrate(vibrationEffect)
} }
} }
fun vibrateForGameOver() {
Log.d(TAG, "Attempting to vibrate for game over")
// Only proceed if the device has a vibrator and it's available
if (!vibrator.hasVibrator()) return
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create a strong, long vibration effect
val vibrationEffect = VibrationEffect.createOneShot(300L, VibrationEffect.DEFAULT_AMPLITUDE)
vibrator.vibrate(vibrationEffect)
Log.d(TAG, "Game over vibration triggered successfully")
} else {
@Suppress("DEPRECATION")
vibrator.vibrate(300L)
Log.w(TAG, "Device does not support vibration effects (Android < 8.0)")
}
} catch (e: Exception) {
Log.e(TAG, "Error triggering game over vibration", e)
}
}
} }

View file

@ -178,6 +178,9 @@ class GameView @JvmOverloads constructor(
private var pulseAlpha = 0f private var pulseAlpha = 0f
private var isPulsing = false private var isPulsing = false
private var linesToPulse = mutableListOf<Int>() // Track which lines are being cleared private var linesToPulse = mutableListOf<Int>() // Track which lines are being cleared
private var gameOverAnimator: ValueAnimator? = null
private var gameOverAlpha = 0f
private var isGameOverAnimating = false
private val ghostPaint = Paint().apply { private val ghostPaint = Paint().apply {
style = Paint.Style.STROKE style = Paint.Style.STROKE
@ -439,6 +442,18 @@ class GameView @JvmOverloads constructor(
// Draw active piece // Draw active piece
drawActivePiece(canvas) drawActivePiece(canvas)
} }
// Draw game over effect if animating
if (isGameOverAnimating) {
val gameOverPaint = Paint().apply {
color = Color.WHITE
alpha = (128 * gameOverAlpha).toInt()
isAntiAlias = true
style = Paint.Style.FILL
maskFilter = BlurMaskFilter(48f * gameOverAlpha, BlurMaskFilter.Blur.OUTER)
}
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), gameOverPaint)
}
} }
/** /**
@ -1133,4 +1148,41 @@ class GameView @JvmOverloads constructor(
super.setBackgroundColor(color) super.setBackgroundColor(color)
invalidate() invalidate()
} }
/**
* Start the game over animation
*/
fun startGameOverAnimation() {
Log.d(TAG, "Starting game over animation")
// Cancel any existing animations
pulseAnimator?.cancel()
gameOverAnimator?.cancel()
// Trigger haptic feedback
gameHaptics?.vibrateForGameOver()
// Create new game over animation
gameOverAnimator = ValueAnimator.ofFloat(0f, 1f, 0.7f).apply {
duration = 1500L // 1.5 seconds total
interpolator = LinearInterpolator()
addUpdateListener { animation ->
gameOverAlpha = animation.animatedValue as Float
isGameOverAnimating = true
invalidate()
Log.d(TAG, "Game over animation update: alpha = $gameOverAlpha")
}
addListener(object : android.animation.AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: android.animation.Animator) {
isGameOverAnimating = false
gameOverAlpha = 0.7f // Keep at 70% opacity
invalidate()
Log.d(TAG, "Game over animation ended")
}
})
}
gameOverAnimator?.start()
}
} }

View file

@ -136,10 +136,10 @@ class GameBoard(
currentPiece = nextPiece currentPiece = nextPiece
spawnNextPiece() spawnNextPiece()
// Center the piece horizontally // Center the piece horizontally and spawn one unit higher
currentPiece?.apply { currentPiece?.apply {
x = (width - getWidth()) / 2 x = (width - getWidth()) / 2
y = 0 y = -1 // Spawn one unit above the top of the screen
Log.d(TAG, "spawnPiece() - new piece spawned at position (${x},${y}), type=${type}") Log.d(TAG, "spawnPiece() - new piece spawned at position (${x},${y}), type=${type}")
@ -326,6 +326,11 @@ class GameBoard(
if (boardY >= 0 && grid[boardY][boardX]) { if (boardY >= 0 && grid[boardY][boardX]) {
return false return false
} }
// Check if the position is more than one unit above the top of the screen
if (boardY < -1) {
return false
}
} }
} }
} }

Binary file not shown.