diff --git a/index.html b/index.html
index 63fa73b..045827c 100644
--- a/index.html
+++ b/index.html
@@ -25,6 +25,7 @@
+
@@ -51,6 +52,7 @@
+
diff --git a/js/entities/entity.js b/js/entities/entity.js
index ec5f9fc..1d8bce3 100644
--- a/js/entities/entity.js
+++ b/js/entities/entity.js
@@ -1,6 +1,7 @@
// Base entity system
const ENTITY_TYPES = {
- RABBIT: 'rabbit'
+ RABBIT: 'rabbit',
+ PLAYER: 'player'
};
// Store all entities
@@ -141,6 +142,9 @@ function createEntity(type, x, y, options = {}) {
case ENTITY_TYPES.RABBIT:
entity = new Rabbit(x, y, options);
break;
+ case ENTITY_TYPES.PLAYER:
+ entity = new Player(x, y, options);
+ break;
default:
console.error(`Unknown entity type: ${type}`);
return null;
diff --git a/js/entities/player.js b/js/entities/player.js
new file mode 100644
index 0000000..9fccfff
--- /dev/null
+++ b/js/entities/player.js
@@ -0,0 +1,187 @@
+// 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;
+ }
+}
diff --git a/js/input.js b/js/input.js
index f2772b6..9b6b70c 100644
--- a/js/input.js
+++ b/js/input.js
@@ -4,6 +4,42 @@ let isDragging = false;
let lastMouseX, lastMouseY;
let currentMouseX, currentMouseY;
+// Keyboard state tracking
+const keyState = {};
+let player = null;
+
+// Handle keyboard input for player movement
+window.addEventListener('keydown', (e) => {
+ keyState[e.code] = true;
+
+ // Prevent default behavior for game control keys
+ if (['KeyW', 'KeyA', 'KeyS', 'KeyD', 'Space'].includes(e.code)) {
+ e.preventDefault();
+ }
+});
+
+window.addEventListener('keyup', (e) => {
+ keyState[e.code] = false;
+});
+
+function updatePlayerMovement() {
+ if (!player) return;
+
+ // Reset movement flag
+ player.stopMoving();
+
+ // Handle movement
+ if (keyState['KeyA']) {
+ player.moveLeft();
+ }
+ if (keyState['KeyD']) {
+ player.moveRight();
+ }
+ if (keyState['KeyW'] || keyState['Space']) {
+ player.jump();
+ }
+}
+
function setTool(tool) {
currentTool = tool;
document.querySelectorAll('.tools button').forEach(btn => btn.classList.remove('active'));
diff --git a/js/main.js b/js/main.js
index 825532d..fb3361e 100644
--- a/js/main.js
+++ b/js/main.js
@@ -40,6 +40,9 @@ window.onload = function() {
document.getElementById('triangle-btn').addEventListener('click', () => setTool(TRIANGLE));
document.getElementById('eraser-btn').addEventListener('click', () => setTool(EMPTY));
+ // Add player spawn button
+ document.getElementById('spawn-player-btn').addEventListener('click', spawnPlayer);
+
// Navigation controls
document.getElementById('move-left').addEventListener('click', () => moveWorld(-CHUNK_SIZE/2, 0));
document.getElementById('move-right').addEventListener('click', () => moveWorld(CHUNK_SIZE/2, 0));
@@ -86,6 +89,30 @@ function resizeCanvas() {
canvas.height = window.innerHeight - document.querySelector('.controls').offsetHeight;
}
+// Function to spawn player
+function spawnPlayer() {
+ // Hide HUD elements
+ document.querySelector('.controls').style.display = 'none';
+
+ // Set zoom level to 50%
+ PIXEL_SIZE = 2;
+
+ // Create player at specified coordinates
+ player = createEntity(ENTITY_TYPES.PLAYER, 229, 61);
+
+ // Focus camera on player
+ worldOffsetX = player.x - (canvas.width / PIXEL_SIZE / 2);
+ worldOffsetY = player.y - (canvas.height / PIXEL_SIZE / 2);
+ worldMoved = true;
+
+ // Resize canvas to full screen
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+
+ // Remove the event listener for the spawn button to prevent multiple spawns
+ document.getElementById('spawn-player-btn').removeEventListener('click', spawnPlayer);
+}
+
function simulationLoop(timestamp) {
// Calculate FPS
const deltaTime = timestamp - lastFrameTime;
@@ -93,6 +120,11 @@ function simulationLoop(timestamp) {
fps = Math.round(1000 / deltaTime);
document.getElementById('fps').textContent = `FPS: ${fps}`;
+ // Update player movement if player exists
+ if (player) {
+ updatePlayerMovement();
+ }
+
// Update physics with timestamp for rate limiting
updatePhysics(timestamp);
diff --git a/styles.css b/styles.css
index 22a5c9c..a834ee4 100644
--- a/styles.css
+++ b/styles.css
@@ -39,6 +39,18 @@ body {
background-color: #ff9800;
}
+#spawn-player-btn {
+ background-color: #4CAF50;
+ color: white;
+ font-weight: bold;
+ padding: 10px 15px;
+ margin-left: 10px;
+}
+
+#spawn-player-btn:hover {
+ background-color: #45a049;
+}
+
.navigation {
display: flex;
}