mirror of
https://github.com/cmclark00/tetris-3d.git
synced 2025-05-17 23:25:21 +01:00
Fix hard drop issues and improve controller responsiveness - Complete animation state reset for hard drops - Increase controller polling rate from 100ms to 16ms - Add button holding support for continuous movement
This commit is contained in:
parent
9d3a190888
commit
a659666571
1 changed files with 283 additions and 105 deletions
388
script.js
388
script.js
|
@ -86,8 +86,19 @@ let controllerMapping = {
|
||||||
pause: [9, 'start'] // Start button
|
pause: [9, 'start'] // Start button
|
||||||
};
|
};
|
||||||
let lastControllerState = {};
|
let lastControllerState = {};
|
||||||
let controllerPollingRate = 100; // ms
|
let controllerPollingRate = 16; // Increased polling rate (from 100ms to 16ms for ~60fps)
|
||||||
let controllerInterval;
|
let controllerInterval;
|
||||||
|
let buttonHoldDelays = {
|
||||||
|
left: 150, // Initial delay before repeating
|
||||||
|
right: 150, // Initial delay before repeating
|
||||||
|
down: 50 // Fast repeat for down
|
||||||
|
};
|
||||||
|
let buttonRepeatRates = {
|
||||||
|
left: 100, // Repeat rate after initial delay
|
||||||
|
right: 100, // Repeat rate after initial delay
|
||||||
|
down: 50 // Fast repeat rate for down
|
||||||
|
};
|
||||||
|
let buttonHoldTimers = {};
|
||||||
|
|
||||||
// Fireworks array
|
// Fireworks array
|
||||||
let fireworks = [];
|
let fireworks = [];
|
||||||
|
@ -576,78 +587,67 @@ class Piece {
|
||||||
|
|
||||||
// Rotate with 3D animation
|
// Rotate with 3D animation
|
||||||
rotate(direction) {
|
rotate(direction) {
|
||||||
// Direction can be 'right' or 'left'
|
// Skip if already in an animation
|
||||||
if (gameOver || paused) return;
|
if (this.rotationTransition || this.showCompletionEffect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Square piece (O) doesn't rotate
|
// Reset animation state
|
||||||
|
this.resetAnimationState();
|
||||||
|
|
||||||
|
// Square pieces (O) don't need to rotate
|
||||||
if (this.tetrominoType === 'O') {
|
if (this.tetrominoType === 'O') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If animations are disabled, do simple rotation
|
||||||
|
if (!enableSpinAnimations || !enable3DEffects) {
|
||||||
|
// Use standard rotation without animation
|
||||||
|
let nextPattern = (direction === 'right') ?
|
||||||
|
(this.tetrominoN + 1) % this.tetromino.length :
|
||||||
|
(this.tetrominoN + this.tetromino.length - 1) % this.tetromino.length;
|
||||||
|
|
||||||
|
// Try to rotate and apply kicks if needed
|
||||||
|
const kickResult = this.tryRotateWithKicks(nextPattern);
|
||||||
|
|
||||||
|
if (kickResult.success) {
|
||||||
|
// Apply rotation
|
||||||
|
this.x += kickResult.kick;
|
||||||
|
this.tetrominoN = nextPattern;
|
||||||
|
this.activeTetromino = this.tetromino[this.tetrominoN];
|
||||||
|
this.shadowTetromino = this.activeTetromino;
|
||||||
|
|
||||||
|
// Update shadow
|
||||||
|
this.calculateShadowY();
|
||||||
|
|
||||||
|
// Play sound
|
||||||
|
playPieceSound('rotate');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the piece
|
||||||
|
this.draw();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the next pattern based on rotation direction
|
||||||
|
let nextPattern = (direction === 'right') ?
|
||||||
|
(this.tetrominoN + 1) % this.tetromino.length :
|
||||||
|
(this.tetrominoN + this.tetromino.length - 1) % this.tetromino.length;
|
||||||
|
|
||||||
|
// Try to rotate with kicks
|
||||||
|
const kickResult = this.tryRotateWithKicks(nextPattern);
|
||||||
|
|
||||||
|
if (!kickResult.success) {
|
||||||
|
return; // Rotation failed
|
||||||
|
}
|
||||||
|
|
||||||
|
// We found a valid kick - save for animation completion
|
||||||
|
const validKick = kickResult.kick;
|
||||||
|
|
||||||
// Clear previous position
|
// Clear previous position
|
||||||
this.undraw();
|
this.undraw();
|
||||||
clearPreviousPiecePosition();
|
clearPreviousPiecePosition();
|
||||||
|
|
||||||
// For I tetromino, rotation pattern is different (cycles through 0-1)
|
|
||||||
// For other tetrominoes, it cycles through 0-1-2-3
|
|
||||||
let nextPattern;
|
|
||||||
|
|
||||||
if (direction === 'right') {
|
|
||||||
// Rotate clockwise
|
|
||||||
if (this.tetrominoType === 'I') {
|
|
||||||
nextPattern = (this.tetrominoN + 1) % 2;
|
|
||||||
} else {
|
|
||||||
nextPattern = (this.tetrominoN + 1) % 4;
|
|
||||||
}
|
|
||||||
} else if (direction === 'left') {
|
|
||||||
// Rotate counter-clockwise
|
|
||||||
if (this.tetrominoType === 'I') {
|
|
||||||
nextPattern = (this.tetrominoN - 1 + 2) % 2;
|
|
||||||
} else {
|
|
||||||
nextPattern = (this.tetrominoN - 1 + 4) % 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wall kick testing for rotation
|
|
||||||
// Try the standard position first, then the kickTable positions
|
|
||||||
const kicks = this.getKicks(this.tetrominoN, nextPattern);
|
|
||||||
|
|
||||||
// Try each kick position until we find one that works
|
|
||||||
let validKick = null;
|
|
||||||
|
|
||||||
for (let i = 0; i < kicks.length; i++) {
|
|
||||||
const [kickX, kickY] = kicks[i];
|
|
||||||
|
|
||||||
if (!this.collision(kickX, kickY, this.tetromino[nextPattern])) {
|
|
||||||
validKick = kickX;
|
|
||||||
// If a successful position is found, break the loop
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no valid kick position found, keep the original position
|
|
||||||
if (validKick === null) {
|
|
||||||
this.draw();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If animations disabled, apply rotation immediately
|
|
||||||
if (!enableSpinAnimations) {
|
|
||||||
this.x += validKick;
|
|
||||||
this.tetrominoN = nextPattern;
|
|
||||||
this.activeTetromino = this.tetromino[this.tetrominoN];
|
|
||||||
this.shadowTetromino = this.activeTetromino;
|
|
||||||
|
|
||||||
// Play rotation sound
|
|
||||||
playPieceSound('rotate');
|
|
||||||
|
|
||||||
// Update shadow position
|
|
||||||
this.calculateShadowY();
|
|
||||||
|
|
||||||
this.draw();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store for animation completion
|
// Store for animation completion
|
||||||
this.targetPattern = nextPattern;
|
this.targetPattern = nextPattern;
|
||||||
this.targetKick = validKick;
|
this.targetKick = validKick;
|
||||||
|
@ -675,20 +675,14 @@ class Piece {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel any ongoing rotations or animations
|
// First, completely reset all animation state variables
|
||||||
this.rotationTransition = false;
|
// to prevent any lingering effects from rotations
|
||||||
this.showCompletionEffect = false;
|
this.resetAnimationState();
|
||||||
this.rotationAngleX = 0;
|
|
||||||
this.rotationAngleY = 0;
|
|
||||||
this.rotationAngleZ = 0;
|
|
||||||
|
|
||||||
// Clear rotation state completely
|
// Save current state for proper undraw
|
||||||
this.rotationDirection = null;
|
const currentTetromino = this.activeTetromino;
|
||||||
this.targetTetromino = null;
|
const currentX = this.x;
|
||||||
this.targetPattern = undefined;
|
const currentY = this.y;
|
||||||
this.targetKick = undefined;
|
|
||||||
this.originalTetromino = null;
|
|
||||||
this.rotationProgress = 0;
|
|
||||||
|
|
||||||
// Clear previous position
|
// Clear previous position
|
||||||
this.undraw();
|
this.undraw();
|
||||||
|
@ -699,10 +693,10 @@ class Piece {
|
||||||
this.y++;
|
this.y++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock the piece
|
// Lock the piece at its final position
|
||||||
this.lock();
|
this.lock();
|
||||||
|
|
||||||
// Replace with getNextPiece function call instead of direct assignment
|
// Get the next piece
|
||||||
getNextPiece();
|
getNextPiece();
|
||||||
|
|
||||||
// Calculate shadow for new piece
|
// Calculate shadow for new piece
|
||||||
|
@ -715,6 +709,23 @@ class Piece {
|
||||||
playPieceSound('hardDrop');
|
playPieceSound('hardDrop');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset all animation state
|
||||||
|
resetAnimationState() {
|
||||||
|
this.rotationTransition = false;
|
||||||
|
this.showCompletionEffect = false;
|
||||||
|
this.rotationAngleX = 0;
|
||||||
|
this.rotationAngleY = 0;
|
||||||
|
this.rotationAngleZ = 0;
|
||||||
|
this.rotationDirection = null;
|
||||||
|
this.rotationProgress = 0;
|
||||||
|
this.rotationEasing = false;
|
||||||
|
this.completionEffectProgress = 0;
|
||||||
|
this.targetTetromino = null;
|
||||||
|
this.targetPattern = undefined;
|
||||||
|
this.targetKick = undefined;
|
||||||
|
this.originalTetromino = null;
|
||||||
|
}
|
||||||
|
|
||||||
// 3D horizontal rotation effect (around Y axis)
|
// 3D horizontal rotation effect (around Y axis)
|
||||||
rotate3DY() {
|
rotate3DY() {
|
||||||
// Square pieces (O) shouldn't rotate
|
// Square pieces (O) shouldn't rotate
|
||||||
|
@ -722,6 +733,14 @@ class Piece {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip if already in an animation
|
||||||
|
if (this.rotationTransition || this.showCompletionEffect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset animation state
|
||||||
|
this.resetAnimationState();
|
||||||
|
|
||||||
// Clear previous position
|
// Clear previous position
|
||||||
this.undraw();
|
this.undraw();
|
||||||
clearPreviousPiecePosition();
|
clearPreviousPiecePosition();
|
||||||
|
@ -783,6 +802,14 @@ class Piece {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip if already in an animation
|
||||||
|
if (this.rotationTransition || this.showCompletionEffect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset animation state
|
||||||
|
this.resetAnimationState();
|
||||||
|
|
||||||
// Clear previous position
|
// Clear previous position
|
||||||
this.undraw();
|
this.undraw();
|
||||||
clearPreviousPiecePosition();
|
clearPreviousPiecePosition();
|
||||||
|
@ -1316,6 +1343,24 @@ class Piece {
|
||||||
return kickTable[key] || [[0, 0]];
|
return kickTable[key] || [[0, 0]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try rotation with wall kicks
|
||||||
|
tryRotateWithKicks(nextPattern) {
|
||||||
|
// Get kicks for this rotation
|
||||||
|
const kicks = this.getKicks(this.tetrominoN, nextPattern);
|
||||||
|
|
||||||
|
// Try each kick position until we find one that works
|
||||||
|
for (let i = 0; i < kicks.length; i++) {
|
||||||
|
const [kickX, kickY] = kicks[i];
|
||||||
|
|
||||||
|
if (!this.collision(kickX, kickY, this.tetromino[nextPattern])) {
|
||||||
|
return { success: true, kick: kickX };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No valid kick position found
|
||||||
|
return { success: false, kick: 0 };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1765,11 +1810,50 @@ function dropPiece() {
|
||||||
|
|
||||||
// Show game over modal
|
// Show game over modal
|
||||||
function showGameOver() {
|
function showGameOver() {
|
||||||
|
// Stop game interval
|
||||||
clearInterval(gameInterval);
|
clearInterval(gameInterval);
|
||||||
|
|
||||||
|
// Clear all controller button hold timers
|
||||||
|
clearAllButtonHoldTimers();
|
||||||
|
|
||||||
|
// Update final score
|
||||||
finalScoreElement.textContent = score;
|
finalScoreElement.textContent = score;
|
||||||
|
|
||||||
|
// Show modal
|
||||||
gameOverModal.classList.add('active');
|
gameOverModal.classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear all button hold timers
|
||||||
|
function clearAllButtonHoldTimers() {
|
||||||
|
['left', 'right', 'down'].forEach(action => {
|
||||||
|
if (buttonHoldTimers[action]) {
|
||||||
|
clearInterval(buttonHoldTimers[action]);
|
||||||
|
buttonHoldTimers[action] = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle pause
|
||||||
|
function togglePause() {
|
||||||
|
if (gameOver) return;
|
||||||
|
|
||||||
|
paused = !paused;
|
||||||
|
|
||||||
|
if (paused) {
|
||||||
|
clearInterval(gameInterval);
|
||||||
|
// Clear all controller button hold timers
|
||||||
|
clearAllButtonHoldTimers();
|
||||||
|
pauseBtn.textContent = 'Resume';
|
||||||
|
// Show pause message
|
||||||
|
showMessage('PAUSED', 'Press P to Resume');
|
||||||
|
} else {
|
||||||
|
gameInterval = setInterval(dropPiece, Math.max(100, 1000 - (level * 100)));
|
||||||
|
pauseBtn.textContent = 'Pause';
|
||||||
|
// Hide pause message
|
||||||
|
hideMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reset the game
|
// Reset the game
|
||||||
function resetGame() {
|
function resetGame() {
|
||||||
// Reset game variables
|
// Reset game variables
|
||||||
|
@ -1805,19 +1889,6 @@ function resetGame() {
|
||||||
gameInterval = setInterval(dropPiece, 1000);
|
gameInterval = setInterval(dropPiece, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle pause
|
|
||||||
function togglePause() {
|
|
||||||
paused = !paused;
|
|
||||||
|
|
||||||
if (paused) {
|
|
||||||
clearInterval(gameInterval);
|
|
||||||
pauseBtn.textContent = "Resume";
|
|
||||||
} else {
|
|
||||||
gameInterval = setInterval(dropPiece, Math.max(100, 1000 - (level * 100)));
|
|
||||||
pauseBtn.textContent = "Pause";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle options modal
|
// Toggle options modal
|
||||||
function toggleOptionsModal() {
|
function toggleOptionsModal() {
|
||||||
if (optionsModal.classList.contains('active')) {
|
if (optionsModal.classList.contains('active')) {
|
||||||
|
@ -2074,7 +2145,7 @@ function initControllerSupport() {
|
||||||
gamepadConnected = true;
|
gamepadConnected = true;
|
||||||
controllers[e.gamepad.index] = e.gamepad;
|
controllers[e.gamepad.index] = e.gamepad;
|
||||||
|
|
||||||
// Start polling for controller input if not already
|
// Start polling for controller input at faster rate
|
||||||
if (!controllerInterval) {
|
if (!controllerInterval) {
|
||||||
controllerInterval = setInterval(pollControllers, controllerPollingRate);
|
controllerInterval = setInterval(pollControllers, controllerPollingRate);
|
||||||
}
|
}
|
||||||
|
@ -2092,14 +2163,18 @@ function initControllerSupport() {
|
||||||
gamepadConnected = false;
|
gamepadConnected = false;
|
||||||
clearInterval(controllerInterval);
|
clearInterval(controllerInterval);
|
||||||
controllerInterval = null;
|
controllerInterval = null;
|
||||||
|
|
||||||
|
// Clear any ongoing button holds
|
||||||
|
clearAllButtonHoldTimers();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show controller disconnected message
|
// Show controller disconnected message
|
||||||
showControllerMessage('Controller disconnected');
|
showControllerMessage('Controller disconnected');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initial scan
|
// Initial scan and setup
|
||||||
if (Object.keys(controllers).length > 0) {
|
if (Object.keys(controllers).length > 0) {
|
||||||
|
gamepadConnected = true;
|
||||||
controllerInterval = setInterval(pollControllers, controllerPollingRate);
|
controllerInterval = setInterval(pollControllers, controllerPollingRate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2132,10 +2207,13 @@ function pollControllers() {
|
||||||
if (!lastControllerState[controller.index]) {
|
if (!lastControllerState[controller.index]) {
|
||||||
lastControllerState[controller.index] = {
|
lastControllerState[controller.index] = {
|
||||||
buttons: Array(controller.buttons.length).fill(false),
|
buttons: Array(controller.buttons.length).fill(false),
|
||||||
axes: Array(controller.axes.length).fill(0)
|
axes: Array(controller.axes.length).fill(0),
|
||||||
|
holdStartTimes: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
// Check buttons
|
// Check buttons
|
||||||
for (let action in controllerMapping) {
|
for (let action in controllerMapping) {
|
||||||
const buttonIndex = controllerMapping[action][0];
|
const buttonIndex = controllerMapping[action][0];
|
||||||
|
@ -2144,12 +2222,42 @@ function pollControllers() {
|
||||||
if (buttonIndex >= 0 && buttonIndex < controller.buttons.length) {
|
if (buttonIndex >= 0 && buttonIndex < controller.buttons.length) {
|
||||||
const button = controller.buttons[buttonIndex];
|
const button = controller.buttons[buttonIndex];
|
||||||
const pressed = button.pressed || button.value > 0.5;
|
const pressed = button.pressed || button.value > 0.5;
|
||||||
|
const wasPressed = lastControllerState[controller.index].buttons[buttonIndex];
|
||||||
|
|
||||||
// Check if this is a new press (wasn't pressed last time)
|
// New button press (wasn't pressed last time)
|
||||||
if (pressed && !lastControllerState[controller.index].buttons[buttonIndex]) {
|
if (pressed && !wasPressed) {
|
||||||
// Handle controller action
|
// Start tracking hold time for repeatable actions
|
||||||
|
if (['left', 'right', 'down'].includes(action)) {
|
||||||
|
lastControllerState[controller.index].holdStartTimes[action] = now;
|
||||||
|
// Clear any existing timers
|
||||||
|
if (buttonHoldTimers[action]) {
|
||||||
|
clearInterval(buttonHoldTimers[action]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule repeating actions
|
||||||
|
buttonHoldTimers[action] = setInterval(() => {
|
||||||
|
if (gamepadConnected && !gameOver && !paused) {
|
||||||
|
handleControllerAction(action);
|
||||||
|
} else {
|
||||||
|
// Stop repeating if game state changes
|
||||||
|
clearInterval(buttonHoldTimers[action]);
|
||||||
|
buttonHoldTimers[action] = null;
|
||||||
|
}
|
||||||
|
}, buttonRepeatRates[action]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Immediate action on first press
|
||||||
handleControllerAction(action);
|
handleControllerAction(action);
|
||||||
}
|
}
|
||||||
|
// Button released
|
||||||
|
else if (!pressed && wasPressed) {
|
||||||
|
// Stop repeating on release
|
||||||
|
if (['left', 'right', 'down'].includes(action) && buttonHoldTimers[action]) {
|
||||||
|
clearInterval(buttonHoldTimers[action]);
|
||||||
|
buttonHoldTimers[action] = null;
|
||||||
|
delete lastControllerState[controller.index].holdStartTimes[action];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
lastControllerState[controller.index].buttons[buttonIndex] = pressed;
|
lastControllerState[controller.index].buttons[buttonIndex] = pressed;
|
||||||
|
@ -2157,17 +2265,87 @@ function pollControllers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle analog stick for movement
|
// Handle analog stick for movement
|
||||||
|
const deadzone = 0.5;
|
||||||
|
|
||||||
// Left stick horizontal
|
// Left stick horizontal
|
||||||
if (controller.axes[0] < -0.5 && lastControllerState[controller.index].axes[0] >= -0.5) {
|
if (controller.axes[0] < -deadzone) {
|
||||||
handleControllerAction('left');
|
if (lastControllerState[controller.index].axes[0] >= -deadzone) {
|
||||||
|
// Initial movement
|
||||||
|
handleControllerAction('left');
|
||||||
|
|
||||||
|
// Setup holding behavior
|
||||||
|
lastControllerState[controller.index].holdStartTimes['left'] = now;
|
||||||
|
if (buttonHoldTimers['left']) clearInterval(buttonHoldTimers['left']);
|
||||||
|
|
||||||
|
buttonHoldTimers['left'] = setInterval(() => {
|
||||||
|
if (gamepadConnected && !gameOver && !paused) {
|
||||||
|
handleControllerAction('left');
|
||||||
|
} else {
|
||||||
|
clearInterval(buttonHoldTimers['left']);
|
||||||
|
buttonHoldTimers['left'] = null;
|
||||||
|
}
|
||||||
|
}, buttonRepeatRates['left']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (controller.axes[0] > 0.5 && lastControllerState[controller.index].axes[0] <= 0.5) {
|
else if (controller.axes[0] > deadzone) {
|
||||||
handleControllerAction('right');
|
if (lastControllerState[controller.index].axes[0] <= deadzone) {
|
||||||
|
// Initial movement
|
||||||
|
handleControllerAction('right');
|
||||||
|
|
||||||
|
// Setup holding behavior
|
||||||
|
lastControllerState[controller.index].holdStartTimes['right'] = now;
|
||||||
|
if (buttonHoldTimers['right']) clearInterval(buttonHoldTimers['right']);
|
||||||
|
|
||||||
|
buttonHoldTimers['right'] = setInterval(() => {
|
||||||
|
if (gamepadConnected && !gameOver && !paused) {
|
||||||
|
handleControllerAction('right');
|
||||||
|
} else {
|
||||||
|
clearInterval(buttonHoldTimers['right']);
|
||||||
|
buttonHoldTimers['right'] = null;
|
||||||
|
}
|
||||||
|
}, buttonRepeatRates['right']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Stick returned to center, clear horizontal timers
|
||||||
|
if (Math.abs(lastControllerState[controller.index].axes[0]) > deadzone) {
|
||||||
|
['left', 'right'].forEach(action => {
|
||||||
|
if (buttonHoldTimers[action]) {
|
||||||
|
clearInterval(buttonHoldTimers[action]);
|
||||||
|
buttonHoldTimers[action] = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Left stick vertical
|
// Left stick vertical - down only
|
||||||
if (controller.axes[1] > 0.5 && lastControllerState[controller.index].axes[1] <= 0.5) {
|
if (controller.axes[1] > deadzone) {
|
||||||
handleControllerAction('down');
|
if (lastControllerState[controller.index].axes[1] <= deadzone) {
|
||||||
|
// Initial movement
|
||||||
|
handleControllerAction('down');
|
||||||
|
|
||||||
|
// Setup holding behavior
|
||||||
|
lastControllerState[controller.index].holdStartTimes['down'] = now;
|
||||||
|
if (buttonHoldTimers['down']) clearInterval(buttonHoldTimers['down']);
|
||||||
|
|
||||||
|
buttonHoldTimers['down'] = setInterval(() => {
|
||||||
|
if (gamepadConnected && !gameOver && !paused) {
|
||||||
|
handleControllerAction('down');
|
||||||
|
} else {
|
||||||
|
clearInterval(buttonHoldTimers['down']);
|
||||||
|
buttonHoldTimers['down'] = null;
|
||||||
|
}
|
||||||
|
}, buttonRepeatRates['down']); // Faster repeat for down
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Stick returned to center, clear down timer
|
||||||
|
if (lastControllerState[controller.index].axes[1] > deadzone) {
|
||||||
|
if (buttonHoldTimers['down']) {
|
||||||
|
clearInterval(buttonHoldTimers['down']);
|
||||||
|
buttonHoldTimers['down'] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update axes state
|
// Update axes state
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue