Implemented continuous D-pad movement and Start button pause menu functionality

This commit is contained in:
Corey 2025-03-31 07:35:58 -04:00
parent 0ac25eb3a9
commit 36559eac4c
2 changed files with 107 additions and 8 deletions

View file

@ -39,7 +39,9 @@ import android.content.BroadcastReceiver
import android.content.IntentFilter
import android.app.AlertDialog
class MainActivity : AppCompatActivity(), GamepadController.GamepadConnectionListener {
class MainActivity : AppCompatActivity(),
GamepadController.GamepadConnectionListener,
GamepadController.GamepadMenuListener {
companion object {
private const val TAG = "MainActivity"
@ -121,6 +123,7 @@ class MainActivity : AppCompatActivity(), GamepadController.GamepadConnectionLis
// Initialize gamepad controller
gamepadController = GamepadController(gameView)
gamepadController.setGamepadConnectionListener(this)
gamepadController.setGamepadMenuListener(this)
// Set up touch event forwarding
binding.touchInterceptor?.setOnTouchListener { _, event ->
@ -1067,4 +1070,23 @@ class MainActivity : AppCompatActivity(), GamepadController.GamepadConnectionLis
// Display progression data
progressionScreen.showProgress(progressionManager, xpGained, newRewards, currentTheme)
}
/**
* Implements GamepadMenuListener to handle start button press
*/
override fun onPauseRequested() {
runOnUiThread {
if (gameView.visibility == View.VISIBLE && !gameView.isPaused && !gameView.isGameOver()) {
gameView.pause()
gameMusic.pause()
showPauseMenu()
binding.pauseStartButton.visibility = View.GONE
binding.resumeButton.visibility = View.VISIBLE
} else if (binding.pauseContainer.visibility == View.VISIBLE) {
// If pause menu is showing, handle as a resume
hidePauseMenu()
resumeGame()
}
}
}
}

View file

@ -10,6 +10,8 @@ import android.os.Build
import android.os.VibrationEffect
import android.view.InputDevice.MotionRange
import android.os.Vibrator
import android.os.Handler
import android.os.Looper
/**
* GamepadController handles gamepad input for the Mintris game.
@ -34,6 +36,9 @@ class GamepadController(
private const val HARD_DROP_COOLDOWN_MS = 200L
private const val HOLD_COOLDOWN_MS = 250L
// Continuous movement repeat delay
private const val CONTINUOUS_MOVEMENT_DELAY_MS = 80L
// Rumble patterns
private const val RUMBLE_MOVE_DURATION_MS = 20L
private const val RUMBLE_ROTATE_DURATION_MS = 30L
@ -102,14 +107,51 @@ class GamepadController(
private var isMovingRight = false
private var isMovingDown = false
// Handler for continuous movement
private val handler = Handler(Looper.getMainLooper())
private val moveLeftRunnable = object : Runnable {
override fun run() {
if (isMovingLeft && gameView.isActive()) {
gameView.moveLeft()
vibrateForPieceMove()
handler.postDelayed(this, CONTINUOUS_MOVEMENT_DELAY_MS)
}
}
}
private val moveRightRunnable = object : Runnable {
override fun run() {
if (isMovingRight && gameView.isActive()) {
gameView.moveRight()
vibrateForPieceMove()
handler.postDelayed(this, CONTINUOUS_MOVEMENT_DELAY_MS)
}
}
}
private val moveDownRunnable = object : Runnable {
override fun run() {
if (isMovingDown && gameView.isActive()) {
gameView.softDrop()
vibrateForPieceMove()
handler.postDelayed(this, CONTINUOUS_MOVEMENT_DELAY_MS)
}
}
}
// Callback interfaces
interface GamepadConnectionListener {
fun onGamepadConnected(gamepadName: String)
fun onGamepadDisconnected(gamepadName: String)
}
// Listener for gamepad connection
interface GamepadMenuListener {
fun onPauseRequested()
}
// Listeners
private var connectionListener: GamepadConnectionListener? = null
private var menuListener: GamepadMenuListener? = null
// Currently active gamepad for rumble
private var activeGamepad: InputDevice? = null
@ -121,6 +163,13 @@ class GamepadController(
connectionListener = listener
}
/**
* Set a listener for gamepad menu events (pause/start button)
*/
fun setGamepadMenuListener(listener: GamepadMenuListener) {
menuListener = listener
}
/**
* Function to check for newly connected gamepads.
* Call this periodically from the activity to detect connection changes.
@ -219,29 +268,35 @@ class GamepadController(
when (keyCode) {
// D-pad and analog movement
KeyEvent.KEYCODE_DPAD_LEFT -> {
if (!isMovingLeft && currentTime - lastMoveTime > MOVE_COOLDOWN_MS) {
if (!isMovingLeft) {
gameView.moveLeft()
vibrateForPieceMove()
lastMoveTime = currentTime
isMovingLeft = true
// Start continuous movement after initial input
handler.postDelayed(moveLeftRunnable, CONTINUOUS_MOVEMENT_DELAY_MS)
return true
}
}
KeyEvent.KEYCODE_DPAD_RIGHT -> {
if (!isMovingRight && currentTime - lastMoveTime > MOVE_COOLDOWN_MS) {
if (!isMovingRight) {
gameView.moveRight()
vibrateForPieceMove()
lastMoveTime = currentTime
isMovingRight = true
// Start continuous movement after initial input
handler.postDelayed(moveRightRunnable, CONTINUOUS_MOVEMENT_DELAY_MS)
return true
}
}
KeyEvent.KEYCODE_DPAD_DOWN -> {
if (!isMovingDown && currentTime - lastMoveTime > MOVE_COOLDOWN_MS) {
if (!isMovingDown) {
gameView.softDrop()
vibrateForPieceMove()
lastMoveTime = currentTime
isMovingDown = true
// Start continuous movement after initial input
handler.postDelayed(moveDownRunnable, CONTINUOUS_MOVEMENT_DELAY_MS)
return true
}
}
@ -253,6 +308,11 @@ class GamepadController(
return true
}
}
// Start button (pause)
KeyEvent.KEYCODE_BUTTON_START -> {
menuListener?.onPauseRequested()
return true
}
// Rotation buttons - supporting multiple buttons for different controllers
KeyEvent.KEYCODE_BUTTON_A,
KeyEvent.KEYCODE_BUTTON_X,
@ -281,14 +341,17 @@ class GamepadController(
when (keyCode) {
KeyEvent.KEYCODE_DPAD_LEFT -> {
isMovingLeft = false
handler.removeCallbacks(moveLeftRunnable)
return true
}
KeyEvent.KEYCODE_DPAD_RIGHT -> {
isMovingRight = false
handler.removeCallbacks(moveRightRunnable)
return true
}
KeyEvent.KEYCODE_DPAD_DOWN -> {
isMovingDown = false
handler.removeCallbacks(moveDownRunnable)
return true
}
}
@ -320,38 +383,52 @@ class GamepadController(
// Apply deadzone
if (Math.abs(axisX) > STICK_DEADZONE) {
if (axisX > 0 && !isMovingRight && currentTime - lastMoveTime > MOVE_COOLDOWN_MS) {
if (axisX > 0 && !isMovingRight) {
gameView.moveRight()
vibrateForPieceMove()
lastMoveTime = currentTime
isMovingRight = true
isMovingLeft = false
// Start continuous movement after initial input
handler.removeCallbacks(moveLeftRunnable)
handler.postDelayed(moveRightRunnable, CONTINUOUS_MOVEMENT_DELAY_MS)
return true
} else if (axisX < 0 && !isMovingLeft && currentTime - lastMoveTime > MOVE_COOLDOWN_MS) {
} else if (axisX < 0 && !isMovingLeft) {
gameView.moveLeft()
vibrateForPieceMove()
lastMoveTime = currentTime
isMovingLeft = true
isMovingRight = false
// Start continuous movement after initial input
handler.removeCallbacks(moveRightRunnable)
handler.postDelayed(moveLeftRunnable, CONTINUOUS_MOVEMENT_DELAY_MS)
return true
}
} else {
// Reset horizontal movement flags when stick returns to center
isMovingLeft = false
isMovingRight = false
handler.removeCallbacks(moveLeftRunnable)
handler.removeCallbacks(moveRightRunnable)
}
if (Math.abs(axisY) > STICK_DEADZONE) {
if (axisY > 0 && !isMovingDown && currentTime - lastMoveTime > MOVE_COOLDOWN_MS) {
if (axisY > 0 && !isMovingDown) {
gameView.softDrop()
vibrateForPieceMove()
lastMoveTime = currentTime
isMovingDown = true
// Start continuous movement after initial input
handler.postDelayed(moveDownRunnable, CONTINUOUS_MOVEMENT_DELAY_MS)
return true
}
} else {
// Reset vertical movement flag when stick returns to center
isMovingDown = false
handler.removeCallbacks(moveDownRunnable)
}
// Check right analog stick for rotation