feat: Implement climbing system and tree collision avoidance for player

This commit adds:
1. Player climbing mechanism for small steps
2. Collision avoidance with trees (WOOD and LEAF pixels)
3. Enhanced animation states for climbing and jumping
4. Improved player movement and collision detection
This commit is contained in:
Kacper Kostka (aider) 2025-04-05 18:52:50 +02:00
parent bb1e25e753
commit c5b7f2f224
2 changed files with 69 additions and 18 deletions

View File

@ -149,6 +149,20 @@ class Entity {
isPixelSolid(x, y) {
// Use ceiling for y coordinate to better detect ground below
const pixel = getPixel(Math.floor(x), Math.ceil(y));
// For player entity, don't collide with trees (WOOD and LEAF)
if (this.type === ENTITY_TYPES.PLAYER) {
return pixel !== EMPTY &&
pixel !== WATER &&
pixel !== FIRE &&
pixel !== SQUARE &&
pixel !== CIRCLE &&
pixel !== TRIANGLE &&
pixel !== WOOD &&
pixel !== LEAF;
}
// For other entities, use the original collision detection
return pixel !== EMPTY &&
pixel !== WATER &&
pixel !== FIRE &&

View File

@ -23,6 +23,7 @@ class Player extends Entity {
this.direction = 1; // 1 = right, -1 = left
this.lastUpdate = performance.now();
this.lastDirection = 1; // Track last direction to prevent unnecessary flipping
this.isClimbing = false; // Track climbing state
// Animation properties
this.frameWidth = 32;
@ -62,8 +63,15 @@ class Player extends Entity {
if (collisionResult.collision) {
if (collisionResult.horizontal) {
newX = this.x;
this.vx = 0;
// Try to climb up if there's a 1-pixel step
if (this.tryClimbing(newX, newY)) {
// Successfully climbed, continue with adjusted position
newY -= 1; // Move up one pixel to climb
} else {
// Can't climb, stop horizontal movement
newX = this.x;
this.vx = 0;
}
}
if (collisionResult.vertical) {
@ -89,24 +97,9 @@ class Player extends Entity {
}
updateAnimation(deltaTime) {
// Update animation timer consistently
this.animationTimer += deltaTime;
/*
// Only change animation frame at fixed intervals to prevent blinking
if (this.animationTimer >= this.animationSpeed) {
// Only update animation if actually moving horizontally
if (Math.abs(this.vx) > 0.005 && this.isMoving) {
this.currentFrame = (this.currentFrame + 1) % this.frameCount;
} else if (!this.isMoving || Math.abs(this.vx) < 0.005) {
// Reset to standing frame when not moving
this.currentFrame = 0;
}
// Reset timer but keep remainder to maintain smooth timing
this.animationTimer %= this.animationSpeed;
}
*/
// Only update direction when it actually changes to prevent flipping
if (Math.abs(this.vx) > 0.005) {
const newDirection = this.vx > 0 ? 1 : -1;
@ -115,6 +108,24 @@ class Player extends Entity {
this.lastDirection = newDirection;
}
}
// Update animation frame based on climbing state
if (this.isClimbing) {
// Use a specific frame for climbing
this.currentFrame = 1; // Use frame 1 for climbing animation
} else if (this.isJumping) {
// Use a specific frame for jumping
this.currentFrame = 2; // Use frame 2 for jumping animation
} else if (Math.abs(this.vx) > 0.01 && this.isMoving) {
// Animate walking
if (this.animationTimer >= this.animationSpeed) {
this.currentFrame = (this.currentFrame + 1) % this.frameCount;
this.animationTimer %= this.animationSpeed;
}
} else {
// Standing still
this.currentFrame = 0;
}
}
render(ctx, offsetX, offsetY) {
@ -215,6 +226,32 @@ class Player extends Entity {
}
}
// Try to climb up a small step
tryClimbing(newX, newY) {
const halfWidth = this.width / 2;
// Check if there's a solid pixel in front of the player
const frontX = newX + (this.direction * halfWidth);
const frontY = newY;
// Check if there's a solid pixel at the current level
if (this.isPixelSolid(frontX, frontY)) {
// Check if there's empty space one pixel above
if (!this.isPixelSolid(frontX, frontY - 1) &&
!this.isPixelSolid(this.x, this.y - 1)) {
// Check if there's ground to stand on after climbing
if (this.isPixelSolid(frontX, frontY + 1)) {
this.isClimbing = true;
return true;
}
}
}
this.isClimbing = false;
return false;
}
centerCamera() {
// Get current camera center in world coordinates
const cameraWidth = canvas.width / PIXEL_SIZE;