// Player entity class class Player extends Entity { constructor(x, y, options = {}) { super(ENTITY_TYPES.PLAYER, x, y, { width: 8, // Player size height: 16, ...options }); // Load player sprite this.sprite = new Image(); this.sprite.src = 'sprites/citizen.png'; // Movement properties this.moveSpeed = 0.15; this.jumpForce = -0.5; this.gravity = 0.02; this.maxVelocity = 0.5; this.friction = 0.9; // State tracking this.isJumping = false; this.direction = 1; // 1 = right, -1 = left this.lastUpdate = performance.now(); // Animation properties this.frameWidth = 32; this.frameHeight = 32; this.frameCount = 4; this.currentFrame = 0; this.animationSpeed = 150; // ms per frame this.lastFrameUpdate = 0; this.isMoving = false; } update() { const now = performance.now(); const deltaTime = Math.min(50, now - this.lastUpdate); this.lastUpdate = now; // Apply gravity this.vy += this.gravity; // Cap velocity if (this.vx > this.maxVelocity) this.vx = this.maxVelocity; if (this.vx < -this.maxVelocity) this.vx = -this.maxVelocity; if (this.vy > this.maxVelocity * 2) this.vy = this.maxVelocity * 2; // Apply friction when not actively moving if (!this.isMoving) { this.vx *= this.friction; } // Calculate new position let newX = this.x + this.vx * deltaTime; let newY = this.y + this.vy * deltaTime; // Check for collisions const collisionResult = this.checkCollisions(newX, newY); if (collisionResult.collision) { if (collisionResult.horizontal) { newX = this.x; this.vx = 0; } if (collisionResult.vertical) { if (this.vy > 0) { this.isJumping = false; } newY = this.y; this.vy = 0; } } // Update position this.x = newX; this.y = newY; // Update animation this.updateAnimation(deltaTime); // Center camera on player this.centerCamera(); return true; } updateAnimation(deltaTime) { // Update animation frame if moving if (this.isMoving || Math.abs(this.vy) > 0.1) { this.lastFrameUpdate += deltaTime; if (this.lastFrameUpdate >= this.animationSpeed) { this.currentFrame = (this.currentFrame + 1) % this.frameCount; this.lastFrameUpdate = 0; } } else { // Reset to standing frame when not moving this.currentFrame = 0; } } render(ctx, offsetX, offsetY) { const screenX = (this.x - offsetX) * PIXEL_SIZE; const screenY = (this.y - offsetY) * PIXEL_SIZE; if (this.sprite && this.sprite.complete) { ctx.save(); ctx.translate(screenX, screenY); // Flip horizontally based on direction if (this.direction < 0) { ctx.scale(-1, 1); ctx.translate(-this.width * PIXEL_SIZE, 0); } // Draw the correct sprite frame ctx.drawImage( this.sprite, this.currentFrame * this.frameWidth, 0, this.frameWidth, this.frameHeight, -this.width * PIXEL_SIZE / 2, -this.height * PIXEL_SIZE / 2, this.width * PIXEL_SIZE, this.height * PIXEL_SIZE ); ctx.restore(); // Draw collision box in debug mode if (debugMode) { ctx.strokeStyle = '#00ff00'; ctx.lineWidth = 1; ctx.strokeRect( screenX - this.width * PIXEL_SIZE / 2, screenY - this.height * PIXEL_SIZE / 2, this.width * PIXEL_SIZE, this.height * PIXEL_SIZE ); } } } moveLeft() { this.vx = -this.moveSpeed; this.direction = -1; this.isMoving = true; } moveRight() { this.vx = this.moveSpeed; this.direction = 1; this.isMoving = true; } moveUp() { this.vy = -this.moveSpeed; this.isMoving = true; } moveDown() { this.vy = this.moveSpeed; this.isMoving = true; } stopMoving() { this.isMoving = false; } jump() { if (!this.isJumping) { this.vy = this.jumpForce; this.isJumping = true; } } centerCamera() { // Calculate the target world offset to center on player const targetOffsetX = this.x - (canvas.width / PIXEL_SIZE / 2); const targetOffsetY = this.y - (canvas.height / PIXEL_SIZE / 2); // Smoothly move the camera to the target position worldOffsetX += (targetOffsetX - worldOffsetX) * 0.1; worldOffsetY += (targetOffsetY - worldOffsetY) * 0.1; // Mark that the world has moved for rendering worldMoved = true; } }