mirror of
https://github.com/cmclark00/mintris.git
synced 2025-05-18 00:15:20 +01:00
Enhanced haptic feedback for gamepad users: - Added gamepad connection tracking - Increased vibration intensity by 50% when using gamepad - Adjusted vibration durations and amplitudes for better feedback
This commit is contained in:
parent
dbaebb8b60
commit
c4f103ae1e
3 changed files with 92 additions and 32 deletions
|
@ -269,6 +269,15 @@ class MainActivity : AppCompatActivity(),
|
||||||
updateUI(score, level, lines)
|
updateUI(score, level, lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Track pieces placed using callback
|
||||||
|
gameBoard.onPieceLock = {
|
||||||
|
// Increment pieces placed counter
|
||||||
|
piecesPlaced++
|
||||||
|
|
||||||
|
// Provide haptic feedback
|
||||||
|
gameHaptics.vibrateForPieceLock()
|
||||||
|
}
|
||||||
|
|
||||||
gameView.onGameOver = { finalScore ->
|
gameView.onGameOver = { finalScore ->
|
||||||
// Pause music on game over
|
// Pause music on game over
|
||||||
gameMusic.pause()
|
gameMusic.pause()
|
||||||
|
@ -325,15 +334,6 @@ class MainActivity : AppCompatActivity(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track pieces placed using callback
|
|
||||||
gameView.onPieceLock = {
|
|
||||||
// Increment pieces placed counter
|
|
||||||
piecesPlaced++
|
|
||||||
|
|
||||||
// Provide haptic feedback
|
|
||||||
gameHaptics.vibrateForPieceLock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up button click listeners with haptic feedback
|
// Set up button click listeners with haptic feedback
|
||||||
binding.playAgainButton.setOnClickListener {
|
binding.playAgainButton.setOnClickListener {
|
||||||
gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
|
gameHaptics.performHapticFeedback(it, HapticFeedbackConstants.VIRTUAL_KEY)
|
||||||
|
@ -484,6 +484,12 @@ class MainActivity : AppCompatActivity(),
|
||||||
Log.d("MainActivity", "Triggering game over animation")
|
Log.d("MainActivity", "Triggering game over animation")
|
||||||
gameView.startGameOverAnimation()
|
gameView.startGameOverAnimation()
|
||||||
|
|
||||||
|
// Hide game UI elements in landscape mode
|
||||||
|
if (resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
|
binding.leftControlsPanel?.visibility = View.GONE
|
||||||
|
binding.rightControlsPanel?.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
// Wait a moment before showing progression screen to let animation be visible
|
// Wait a moment before showing progression screen to let animation be visible
|
||||||
Handler(Looper.getMainLooper()).postDelayed({
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
// Show progression screen first with XP animation
|
// Show progression screen first with XP animation
|
||||||
|
@ -660,10 +666,12 @@ class MainActivity : AppCompatActivity(),
|
||||||
* Start a new game
|
* Start a new game
|
||||||
*/
|
*/
|
||||||
private fun startGame() {
|
private fun startGame() {
|
||||||
|
// Reset pieces placed counter
|
||||||
|
piecesPlaced = 0
|
||||||
|
|
||||||
// Set initial game state
|
// Set initial game state
|
||||||
currentScore = 0
|
currentScore = 0
|
||||||
currentLevel = selectedLevel
|
currentLevel = selectedLevel
|
||||||
piecesPlaced = 0
|
|
||||||
gameStartTime = System.currentTimeMillis()
|
gameStartTime = System.currentTimeMillis()
|
||||||
|
|
||||||
// Update UI to show initial values
|
// Update UI to show initial values
|
||||||
|
@ -673,8 +681,23 @@ class MainActivity : AppCompatActivity(),
|
||||||
binding.comboText.text = "0"
|
binding.comboText.text = "0"
|
||||||
|
|
||||||
// Reset game view and game board
|
// Reset game view and game board
|
||||||
|
gameBoard.reset()
|
||||||
gameView.reset()
|
gameView.reset()
|
||||||
|
|
||||||
|
// Show game elements
|
||||||
|
gameView.visibility = View.VISIBLE
|
||||||
|
binding.gameControlsContainer.visibility = View.VISIBLE
|
||||||
|
binding.gameOverContainer.visibility = View.GONE
|
||||||
|
binding.pauseContainer.visibility = View.GONE
|
||||||
|
titleScreen.visibility = View.GONE
|
||||||
|
progressionScreen.visibility = View.GONE
|
||||||
|
|
||||||
|
// Show game UI elements in landscape mode
|
||||||
|
if (resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
|
binding.leftControlsPanel?.visibility = View.VISIBLE
|
||||||
|
binding.rightControlsPanel?.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
// Configure callbacks
|
// Configure callbacks
|
||||||
gameView.onGameStateChanged = { score, level, lines ->
|
gameView.onGameStateChanged = { score, level, lines ->
|
||||||
currentScore = score
|
currentScore = score
|
||||||
|
@ -729,15 +752,19 @@ class MainActivity : AppCompatActivity(),
|
||||||
statsManager.recordLineClear(lineCount)
|
statsManager.recordLineClear(lineCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the game
|
// Reset pause button state
|
||||||
gameView.start()
|
binding.pauseStartButton.visibility = View.VISIBLE
|
||||||
gameMusic.setEnabled(isMusicEnabled)
|
binding.resumeButton.visibility = View.GONE
|
||||||
|
|
||||||
// Start background music if enabled
|
// Start background music if enabled
|
||||||
if (isMusicEnabled) {
|
if (isMusicEnabled) {
|
||||||
gameMusic.start()
|
gameMusic.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start the game
|
||||||
|
gameView.start()
|
||||||
|
gameMusic.setEnabled(isMusicEnabled)
|
||||||
|
|
||||||
// Reset session stats
|
// Reset session stats
|
||||||
statsManager.startNewSession()
|
statsManager.startNewSession()
|
||||||
progressionManager.startNewSession()
|
progressionManager.startNewSession()
|
||||||
|
@ -957,6 +984,8 @@ class MainActivity : AppCompatActivity(),
|
||||||
override fun onGamepadConnected(gamepadName: String) {
|
override fun onGamepadConnected(gamepadName: String) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
Toast.makeText(this, "Gamepad connected: $gamepadName", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, "Gamepad connected: $gamepadName", Toast.LENGTH_SHORT).show()
|
||||||
|
// Set gamepad connected state in haptics
|
||||||
|
gameHaptics.setGamepadConnected(true)
|
||||||
// Provide haptic feedback for gamepad connection
|
// Provide haptic feedback for gamepad connection
|
||||||
gameHaptics.vibrateForPieceLock()
|
gameHaptics.vibrateForPieceLock()
|
||||||
|
|
||||||
|
@ -970,6 +999,8 @@ class MainActivity : AppCompatActivity(),
|
||||||
override fun onGamepadDisconnected(gamepadName: String) {
|
override fun onGamepadDisconnected(gamepadName: String) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
Toast.makeText(this, "Gamepad disconnected: $gamepadName", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, "Gamepad disconnected: $gamepadName", Toast.LENGTH_SHORT).show()
|
||||||
|
// Set gamepad disconnected state in haptics
|
||||||
|
gameHaptics.setGamepadConnected(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,19 @@ class GameHaptics(private val context: Context) {
|
||||||
context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Track if gamepad is connected
|
||||||
|
private var isGamepadConnected = false
|
||||||
|
|
||||||
|
// Set gamepad connection state
|
||||||
|
fun setGamepadConnected(connected: Boolean) {
|
||||||
|
isGamepadConnected = connected
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get vibration multiplier based on input method
|
||||||
|
private fun getVibrationMultiplier(): Float {
|
||||||
|
return if (isGamepadConnected) 1.5f else 1.0f
|
||||||
|
}
|
||||||
|
|
||||||
// Vibrate for line clear (more intense for more lines)
|
// Vibrate for line clear (more intense for more lines)
|
||||||
fun vibrateForLineClear(lineCount: Int) {
|
fun vibrateForLineClear(lineCount: Int) {
|
||||||
Log.d(TAG, "Attempting to vibrate for $lineCount lines")
|
Log.d(TAG, "Attempting to vibrate for $lineCount lines")
|
||||||
|
@ -34,22 +47,22 @@ class GameHaptics(private val context: Context) {
|
||||||
// Only proceed if the device has a vibrator and it's available
|
// Only proceed if the device has a vibrator and it's available
|
||||||
if (!vibrator.hasVibrator()) return
|
if (!vibrator.hasVibrator()) return
|
||||||
|
|
||||||
// Scale duration and amplitude based on line count
|
// Scale duration and amplitude based on line count and input method
|
||||||
// More lines = longer and stronger vibration
|
val multiplier = getVibrationMultiplier()
|
||||||
val duration = when(lineCount) {
|
val duration = when(lineCount) {
|
||||||
1 -> 50L // Single line: short vibration
|
1 -> (50L * multiplier).toLong() // Single line: short vibration
|
||||||
2 -> 80L // Double line: slightly longer
|
2 -> (80L * multiplier).toLong() // Double line: slightly longer
|
||||||
3 -> 120L // Triple line: even longer
|
3 -> (120L * multiplier).toLong() // Triple line: even longer
|
||||||
4 -> 200L // Tetris: longest vibration
|
4 -> (200L * multiplier).toLong() // Tetris: longest vibration
|
||||||
else -> 50L
|
else -> (50L * multiplier).toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
val amplitude = when(lineCount) {
|
val amplitude = when(lineCount) {
|
||||||
1 -> 80 // Single line: mild vibration (80/255)
|
1 -> (80 * multiplier).toInt().coerceAtMost(255) // Single line: mild vibration
|
||||||
2 -> 120 // Double line: medium vibration (120/255)
|
2 -> (120 * multiplier).toInt().coerceAtMost(255) // Double line: medium vibration
|
||||||
3 -> 180 // Triple line: strong vibration (180/255)
|
3 -> (180 * multiplier).toInt().coerceAtMost(255) // Triple line: strong vibration
|
||||||
4 -> 255 // Tetris: maximum vibration (255/255)
|
4 -> 255 // Tetris: maximum vibration
|
||||||
else -> 80
|
else -> (80 * multiplier).toInt().coerceAtMost(255)
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Vibration parameters - Duration: ${duration}ms, Amplitude: $amplitude")
|
Log.d(TAG, "Vibration parameters - Duration: ${duration}ms, Amplitude: $amplitude")
|
||||||
|
@ -79,15 +92,20 @@ class GameHaptics(private val context: Context) {
|
||||||
|
|
||||||
fun vibrateForPieceLock() {
|
fun vibrateForPieceLock() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val vibrationEffect = VibrationEffect.createOneShot(50L, VibrationEffect.DEFAULT_AMPLITUDE)
|
val multiplier = getVibrationMultiplier()
|
||||||
|
val duration = (50L * multiplier).toLong()
|
||||||
|
val amplitude = (VibrationEffect.DEFAULT_AMPLITUDE * multiplier).toInt().coerceAtMost(255)
|
||||||
|
val vibrationEffect = VibrationEffect.createOneShot(duration, amplitude)
|
||||||
vibrator.vibrate(vibrationEffect)
|
vibrator.vibrate(vibrationEffect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun vibrateForPieceMove() {
|
fun vibrateForPieceMove() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val amplitude = (VibrationEffect.DEFAULT_AMPLITUDE * 0.3).toInt().coerceAtLeast(1)
|
val multiplier = getVibrationMultiplier()
|
||||||
val vibrationEffect = VibrationEffect.createOneShot(20L, amplitude)
|
val duration = (20L * multiplier).toLong()
|
||||||
|
val amplitude = (VibrationEffect.DEFAULT_AMPLITUDE * 0.3 * multiplier).toInt().coerceAtLeast(1).coerceAtMost(255)
|
||||||
|
val vibrationEffect = VibrationEffect.createOneShot(duration, amplitude)
|
||||||
vibrator.vibrate(vibrationEffect)
|
vibrator.vibrate(vibrationEffect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,8 +118,10 @@ class GameHaptics(private val context: Context) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
// Create a strong, long vibration effect
|
val multiplier = getVibrationMultiplier()
|
||||||
val vibrationEffect = VibrationEffect.createOneShot(300L, VibrationEffect.DEFAULT_AMPLITUDE)
|
val duration = (300L * multiplier).toLong()
|
||||||
|
val amplitude = (VibrationEffect.DEFAULT_AMPLITUDE * multiplier).toInt().coerceAtMost(255)
|
||||||
|
val vibrationEffect = VibrationEffect.createOneShot(duration, amplitude)
|
||||||
vibrator.vibrate(vibrationEffect)
|
vibrator.vibrate(vibrationEffect)
|
||||||
Log.d(TAG, "Game over vibration triggered successfully")
|
Log.d(TAG, "Game over vibration triggered successfully")
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -245,6 +245,7 @@ class TitleScreen @JvmOverloads constructor(
|
||||||
// Draw high scores using pre-allocated manager
|
// Draw high scores using pre-allocated manager
|
||||||
val highScores: List<HighScore> = highScoreManager.getHighScores()
|
val highScores: List<HighScore> = highScoreManager.getHighScores()
|
||||||
val highScoreY = height * 0.5f
|
val highScoreY = height * 0.5f
|
||||||
|
var lastHighScoreY = highScoreY
|
||||||
if (highScores.isNotEmpty()) {
|
if (highScores.isNotEmpty()) {
|
||||||
// Calculate the starting X position to center the entire block of scores
|
// Calculate the starting X position to center the entire block of scores
|
||||||
val maxScoreWidth = highScorePaint.measureText("99. PLAYER: 999999")
|
val maxScoreWidth = highScorePaint.measureText("99. PLAYER: 999999")
|
||||||
|
@ -252,6 +253,7 @@ class TitleScreen @JvmOverloads constructor(
|
||||||
|
|
||||||
highScores.forEachIndexed { index: Int, score: HighScore ->
|
highScores.forEachIndexed { index: Int, score: HighScore ->
|
||||||
val y = highScoreY + (index * 80f)
|
val y = highScoreY + (index * 80f)
|
||||||
|
lastHighScoreY = y // Track the last high score's Y position
|
||||||
// Pad the rank number to ensure alignment
|
// Pad the rank number to ensure alignment
|
||||||
val rank = (index + 1).toString().padStart(2, ' ')
|
val rank = (index + 1).toString().padStart(2, ' ')
|
||||||
// Pad the name to ensure score alignment
|
// Pad the name to ensure score alignment
|
||||||
|
@ -260,8 +262,15 @@ class TitleScreen @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw "touch to start" prompt
|
// Draw "touch to start" prompt below the high scores
|
||||||
canvas.drawText("touch to start", width / 2f, height * 0.7f, promptPaint)
|
val promptY = if (resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
|
// In landscape mode, position below the last high score with some padding
|
||||||
|
lastHighScoreY + 100f
|
||||||
|
} else {
|
||||||
|
// In portrait mode, use the original position
|
||||||
|
height * 0.7f
|
||||||
|
}
|
||||||
|
canvas.drawText("touch to start", width / 2f, promptY, promptPaint)
|
||||||
|
|
||||||
// Request another frame
|
// Request another frame
|
||||||
invalidate()
|
invalidate()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue