Implement keyboard controls for player movement and interaction

- Added keyboard input handling for movement using arrow keys and WASD.
- Integrated spacebar for jump functionality and 'E' key for interacting with nearest objects.
- Enhanced player movement logic to prioritize keyboard input over mouse movement.
- Created visual effects for player jumping, including cooldown management and overlay animations.
This commit is contained in:
Z. Cliffe Schreuders
2025-10-22 13:20:05 +01:00
parent f1471c7d29
commit 1b313fbdaa
4 changed files with 507 additions and 7 deletions

View File

@@ -17,6 +17,16 @@ export let isMoving = false;
export let lastPlayerPosition = { x: 0, y: 0 };
let gameRef = null;
// Keyboard input state
const keyboardInput = {
up: false,
down: false,
left: false,
right: false,
space: false
};
let isKeyboardMoving = false;
// Create player sprite
export function createPlayer(gameInstance) {
gameRef = gameInstance;
@@ -63,9 +73,135 @@ export function createPlayer(gameInstance) {
// Store player globally immediately for safety
window.player = player;
// Setup keyboard input listeners
setupKeyboardInput();
return player;
}
function setupKeyboardInput() {
// Handle keydown events
document.addEventListener('keydown', (event) => {
const key = event.key.toLowerCase();
// Spacebar for jump
if (key === ' ') {
keyboardInput.space = true;
if (window.createPlayerJump) {
window.createPlayerJump();
}
event.preventDefault();
return;
}
// E key for interaction
if (key === 'e') {
if (window.tryInteractWithNearest) {
window.tryInteractWithNearest();
}
event.preventDefault();
return;
}
// Arrow keys
if (key === 'arrowup') {
keyboardInput.up = true;
isKeyboardMoving = true;
event.preventDefault();
} else if (key === 'arrowdown') {
keyboardInput.down = true;
isKeyboardMoving = true;
event.preventDefault();
} else if (key === 'arrowleft') {
keyboardInput.left = true;
isKeyboardMoving = true;
event.preventDefault();
} else if (key === 'arrowright') {
keyboardInput.right = true;
isKeyboardMoving = true;
event.preventDefault();
}
// WASD keys
if (key === 'w') {
keyboardInput.up = true;
isKeyboardMoving = true;
event.preventDefault();
} else if (key === 's') {
keyboardInput.down = true;
isKeyboardMoving = true;
event.preventDefault();
} else if (key === 'a') {
keyboardInput.left = true;
isKeyboardMoving = true;
event.preventDefault();
} else if (key === 'd') {
keyboardInput.right = true;
isKeyboardMoving = true;
event.preventDefault();
}
});
// Handle keyup events
document.addEventListener('keyup', (event) => {
const key = event.key.toLowerCase();
// Spacebar
if (key === ' ') {
keyboardInput.space = false;
event.preventDefault();
return;
}
// Arrow keys
if (key === 'arrowup') {
keyboardInput.up = false;
event.preventDefault();
} else if (key === 'arrowdown') {
keyboardInput.down = false;
event.preventDefault();
} else if (key === 'arrowleft') {
keyboardInput.left = false;
event.preventDefault();
} else if (key === 'arrowright') {
keyboardInput.right = false;
event.preventDefault();
}
// WASD keys
if (key === 'w') {
keyboardInput.up = false;
event.preventDefault();
} else if (key === 's') {
keyboardInput.down = false;
event.preventDefault();
} else if (key === 'a') {
keyboardInput.left = false;
event.preventDefault();
} else if (key === 'd') {
keyboardInput.right = false;
event.preventDefault();
}
// Check if any keys are still pressed
isKeyboardMoving = keyboardInput.up || keyboardInput.down || keyboardInput.left || keyboardInput.right;
});
}
function getAnimationKey(direction) {
// Map left directions to their right counterparts (sprite is flipped)
switch(direction) {
case 'left':
return 'right';
case 'down-left':
return 'down-right';
case 'up-left':
return 'up-right';
default:
return direction;
}
}
function createPlayerAnimations() {
// Create walking animations with correct frame numbers from original
gameRef.anims.create({
@@ -133,6 +269,25 @@ function createPlayerAnimations() {
frames: [{ key: 'hacker', frame: 20 }],
frameRate: 1
});
// Create left-facing idle animations (same frames as right, but sprite will be flipped)
gameRef.anims.create({
key: 'idle-left',
frames: [{ key: 'hacker', frame: 0 }],
frameRate: 1
});
gameRef.anims.create({
key: 'idle-down-left',
frames: [{ key: 'hacker', frame: 20 }],
frameRate: 1
});
gameRef.anims.create({
key: 'idle-up-left',
frames: [{ key: 'hacker', frame: 15 }],
frameRate: 1
});
}
export function movePlayerToPoint(x, y) {
@@ -194,6 +349,118 @@ export function updatePlayerMovement() {
return;
}
// Handle keyboard movement (takes priority over mouse movement)
if (isKeyboardMoving) {
updatePlayerKeyboardMovement();
return;
}
// Handle mouse-based movement (original behavior)
updatePlayerMouseMovement();
}
function updatePlayerKeyboardMovement() {
// Calculate movement direction based on keyboard input
let dirX = 0;
let dirY = 0;
if (keyboardInput.right) dirX += 1;
if (keyboardInput.left) dirX -= 1;
if (keyboardInput.down) dirY += 1;
if (keyboardInput.up) dirY -= 1;
// Normalize diagonal movement to maintain consistent speed
let velocityX = 0;
let velocityY = 0;
if (dirX !== 0 || dirY !== 0) {
const magnitude = Math.sqrt(dirX * dirX + dirY * dirY);
velocityX = (dirX / magnitude) * MOVEMENT_SPEED;
velocityY = (dirY / magnitude) * MOVEMENT_SPEED;
}
// Check if movement is being blocked by collisions
let isBlocked = false;
if (velocityX !== 0 || velocityY !== 0) {
// Check if blocked in the direction we want to move
if (velocityX > 0 && player.body.blocked.right) isBlocked = true;
if (velocityX < 0 && player.body.blocked.left) isBlocked = true;
if (velocityY > 0 && player.body.blocked.down) isBlocked = true;
if (velocityY < 0 && player.body.blocked.up) isBlocked = true;
}
// Apply velocity
player.body.setVelocity(velocityX, velocityY);
// Update player depth based on actual player position
updatePlayerDepth(player.x, player.y);
// Update last player position for depth calculations
lastPlayerPosition.x = player.x;
lastPlayerPosition.y = player.y;
// Determine direction based on velocity
const absVX = Math.abs(velocityX);
const absVY = Math.abs(velocityY);
// Set player direction and animation
if (velocityX === 0 && velocityY === 0) {
// No movement - stop
if (player.isMoving) {
player.isMoving = false;
const animDir = getAnimationKey(player.direction);
player.anims.stop(); // Stop current animation
player.anims.play(`idle-${animDir}`, true);
}
} else if (isBlocked) {
// Blocked by collision - play idle animation in the direction we're facing
if (player.isMoving) {
player.isMoving = false;
const animDir = getAnimationKey(player.direction);
player.anims.stop(); // Stop current animation
player.anims.play(`idle-${animDir}`, true);
}
} else if (absVX > absVY * 2) {
// Mostly horizontal movement
player.direction = velocityX > 0 ? 'right' : 'left'; // Track both left and right directions
player.setFlipX(velocityX < 0); // Flip sprite horizontally if moving left
if (!player.isMoving || player.lastDirection !== player.direction) {
// Use 'right' animation for both left and right (flip handled by setFlipX)
player.anims.play(`walk-right`, true);
player.isMoving = true;
player.lastDirection = player.direction;
}
} else if (absVY > absVX * 2) {
// Mostly vertical movement
player.direction = velocityY > 0 ? 'down' : 'up';
player.setFlipX(false);
if (!player.isMoving || player.lastDirection !== player.direction) {
player.anims.play(`walk-${player.direction}`, true);
player.isMoving = true;
player.lastDirection = player.direction;
}
} else {
// Diagonal movement
if (velocityY > 0) {
player.direction = velocityX > 0 ? 'down-right' : 'down-left';
} else {
player.direction = velocityX > 0 ? 'up-right' : 'up-left';
}
player.setFlipX(velocityX < 0); // Flip sprite horizontally if moving left
if (!player.isMoving || player.lastDirection !== player.direction) {
// Use the base direction for animation (right or left for horizontal component)
const baseDir = velocityY > 0 ? 'down-right' : 'up-right';
player.anims.play(`walk-${baseDir}`, true);
player.isMoving = true;
player.lastDirection = player.direction;
}
}
}
function updatePlayerMouseMovement() {
if (!isMoving || !targetPoint) {
if (player.body.velocity.x !== 0 || player.body.velocity.y !== 0) {
player.body.setVelocity(0, 0);
@@ -221,10 +488,12 @@ export function updatePlayerMovement() {
if (distanceSq < ARRIVAL_THRESHOLD * ARRIVAL_THRESHOLD) {
isMoving = false;
player.body.setVelocity(0, 0);
player.isMoving = false;
// Play idle animation based on last direction
player.anims.play(`idle-${player.direction}`, true);
if (player.isMoving) {
player.isMoving = false;
const animDir = getAnimationKey(player.direction);
player.anims.stop(); // Stop current animation
player.anims.play(`idle-${animDir}`, true);
}
return;
}
@@ -274,8 +543,12 @@ export function updatePlayerMovement() {
if (player.body.blocked.none === false) {
isMoving = false;
player.body.setVelocity(0, 0);
player.isMoving = false;
player.anims.play(`idle-${player.direction}`, true);
if (player.isMoving) {
player.isMoving = false;
const animDir = getAnimationKey(player.direction);
player.anims.stop(); // Stop current animation
player.anims.play(`idle-${animDir}`, true);
}
}
}

View File

@@ -875,6 +875,7 @@ window.checkDoorTransitions = checkDoorTransitions;
window.setupDoorOverlapChecks = setupDoorOverlapChecks;
window.updateDoorZoneVisibility = updateDoorZoneVisibility;
window.processAllDoorCollisions = processAllDoorCollisions;
window.handleDoorInteraction = handleDoorInteraction;
// Export functions for use by other modules
export { unlockDoor };
export { unlockDoor, handleDoorInteraction };

View File

@@ -2,6 +2,7 @@
import { INTERACTION_RANGE, INTERACTION_RANGE_SQ, INTERACTION_CHECK_INTERVAL } from '../utils/constants.js?v=7';
import { rooms } from '../core/rooms.js?v=16';
import { handleUnlock } from './unlock-system.js';
import { handleDoorInteraction } from './doors.js';
import { collectFingerprint, handleBiometricScan } from './biometrics.js';
import { addToInventory, removeFromInventory, createItemIdentifier } from './inventory.js';
@@ -562,7 +563,136 @@ function handleContainerInteraction(sprite) {
}
}
// Try to interact with the nearest interactable object within range
export function tryInteractWithNearest() {
const player = window.player;
if (!player) {
return;
}
const px = player.x;
const py = player.y;
let nearestObject = null;
let nearestDistance = INTERACTION_RANGE; // Only consider objects within interaction range
// Get player's facing direction and convert to angle for direction filtering
const playerDirection = player.direction || 'down';
let facingAngle = 0;
let angleTolerance = 70; // degrees - how wide the cone in front of player is
// Determine facing angle based on direction
// In canvas/Phaser: right=0°, down=90°, left=180°, up=270°
switch(playerDirection) {
case 'right':
facingAngle = 0;
break;
case 'down-right':
facingAngle = 45;
break;
case 'down':
facingAngle = 90;
break;
case 'down-left':
facingAngle = 135;
break;
case 'left':
facingAngle = 180;
break;
case 'up-left':
facingAngle = 225;
break;
case 'up':
facingAngle = 270; // Use 270 instead of -90
break;
case 'up-right':
facingAngle = 315; // Use 315 instead of -45
break;
default: // Fallback for any unknown directions
facingAngle = 90; // Default to down
}
// Helper function to check if an object is in front of the player
function isInFrontOfPlayer(objX, objY) {
const dx = objX - px;
const dy = objY - py;
// Calculate angle to object (in canvas coordinates where Y increases downward)
let angleToObject = Math.atan2(dy, dx) * 180 / Math.PI;
// Normalize to 0-360
angleToObject = (angleToObject + 360) % 360;
// Calculate angular difference
let angleDiff = Math.abs(facingAngle - angleToObject);
if (angleDiff > 180) {
angleDiff = 360 - angleDiff;
}
return angleDiff <= angleTolerance;
}
// Check all objects in all rooms
Object.entries(rooms).forEach(([roomId, room]) => {
if (!room.objects) return;
Object.values(room.objects).forEach(obj => {
// Only consider interactable, active, and visible objects
if (!obj.active || !obj.interactable || !obj.visible) {
return;
}
const dx = px - obj.x;
const dy = py - obj.y;
const distance = Math.sqrt(dx * dx + dy * dy);
// Check if within range and in front of player
if (distance <= INTERACTION_RANGE && isInFrontOfPlayer(obj.x, obj.y)) {
if (distance < nearestDistance) {
nearestDistance = distance;
nearestObject = obj;
}
}
});
// Also check door sprites (including all doors, not just locked ones)
if (room.doorSprites) {
Object.values(room.doorSprites).forEach(door => {
// Only consider active doors (check all doors, not just locked)
if (!door.active || !door.doorProperties) {
return;
}
const dx = px - door.x;
const dy = py - door.y;
const distance = Math.sqrt(dx * dx + dy * dy);
// Check if within range and in front of player
if (distance <= INTERACTION_RANGE && isInFrontOfPlayer(door.x, door.y)) {
if (distance < nearestDistance) {
nearestDistance = distance;
nearestObject = door;
}
}
});
}
});
// Interact with the nearest object if one was found
if (nearestObject) {
// Check if this is a door (doors have doorProperties instead of scenarioData)
if (nearestObject.doorProperties) {
// Handle door interaction - triggers unlock/open sequence based on lock state
handleDoorInteraction(nearestObject);
} else {
// Handle regular object interaction
handleObjectInteraction(nearestObject);
}
}
}
// Export for global access
window.checkObjectInteractions = checkObjectInteractions;
window.handleObjectInteraction = handleObjectInteraction;
window.handleContainerInteraction = handleContainerInteraction;
window.tryInteractWithNearest = tryInteractWithNearest;

View File

@@ -18,7 +18,9 @@ let lastPlayerPosition = { x: 0, y: 0 };
let steppedOverItems = new Set(); // Track items we've already stepped over
let playerVisualOverlay = null; // Visual overlay for hop effect
let lastHopTime = 0; // Track when last hop occurred
let lastJumpTime = 0; // Track when last jump occurred
const HOP_COOLDOWN = 300; // 300ms cooldown between hops
const JUMP_COOLDOWN = 600; // 600ms cooldown between jumps
// Initialize player effects system
export function initializePlayerEffects(gameInstance, roomsRef) {
@@ -188,6 +190,99 @@ export function createPlayerBumpEffect() {
});
}
// Create player jump effect when spacebar is pressed
export function createPlayerJump() {
if (!window.player || isPlayerBumping) return;
// Check cooldown to prevent rapid jumping
const currentTime = Date.now();
if (currentTime - lastJumpTime < JUMP_COOLDOWN) {
return; // Still in cooldown, skip this jump
}
const player = window.player;
// Update jump time
lastJumpTime = currentTime;
isPlayerBumping = true;
// Create hop effect using visual overlay (same as bump effect)
if (playerBumpTween) {
playerBumpTween.destroy();
}
// Create a visual overlay sprite that follows the player
if (playerVisualOverlay) {
playerVisualOverlay.destroy();
}
playerVisualOverlay = gameRef.add.sprite(player.x, player.y, player.texture.key);
playerVisualOverlay.setFrame(player.frame.name);
playerVisualOverlay.setScale(player.scaleX, player.scaleY);
playerVisualOverlay.setFlipX(player.flipX); // Copy horizontal flip state
playerVisualOverlay.setFlipY(player.flipY); // Copy vertical flip state
playerVisualOverlay.setDepth(player.depth + 1);
playerVisualOverlay.setAlpha(0.8);
// Hide the original player temporarily
player.setAlpha(0);
// Jump upward - negative Y values move sprite up on screen
const jumpHeight = -20; // Consistent upward jump
// Debug: Log the jump details
console.log(`Jump triggered - Player Y: ${player.y}, Overlay Y: ${playerVisualOverlay.y}, Jump Height: ${jumpHeight}, Target Y: ${playerVisualOverlay.y + jumpHeight}`);
// Start the jump animation with a simple up-down motion
playerBumpTween = gameRef.tweens.add({
targets: { jumpOffset: 0 },
jumpOffset: jumpHeight,
duration: 150,
ease: 'Power2',
yoyo: true,
onUpdate: (tween) => {
if (playerVisualOverlay && playerVisualOverlay.active) {
// Apply the jump offset to the current player position
playerVisualOverlay.setY(player.y + tween.getValue());
}
},
onComplete: () => {
// Clean up overlay and restore player
if (playerVisualOverlay) {
playerVisualOverlay.destroy();
playerVisualOverlay = null;
}
player.setAlpha(1); // Restore player visibility
isPlayerBumping = false;
playerBumpTween = null;
}
});
// Make overlay follow player movement during jump
const followPlayer = () => {
if (playerVisualOverlay && playerVisualOverlay.active) {
// Update X position and flip states, Y is handled by the tween
playerVisualOverlay.setX(player.x);
playerVisualOverlay.setFlipX(player.flipX); // Update flip state
playerVisualOverlay.setFlipY(player.flipY); // Update flip state
}
};
// Update overlay position every frame during jump
const followInterval = setInterval(() => {
if (!playerVisualOverlay || !playerVisualOverlay.active) {
clearInterval(followInterval);
return;
}
followPlayer();
}, 16); // ~60fps
// Clean up interval when jump completes
setTimeout(() => {
clearInterval(followInterval);
}, 280); // Slightly longer than animation duration
}
// Create plant animation effect when player bumps into animated plants
export function createPlantBumpEffect() {
if (!window.player) return;
@@ -231,4 +326,5 @@ export function createPlantBumpEffect() {
// Export for global access
window.createPlayerBumpEffect = createPlayerBumpEffect;
window.createPlayerJump = createPlayerJump;
window.createPlantBumpEffect = createPlantBumpEffect;