feat: Add player entity with WASD/space controls and spawn functionality
This commit is contained in:
parent
d86baa8f99
commit
db5b49ee7f
@ -25,6 +25,7 @@
|
||||
<button id="circle-btn">Circle</button>
|
||||
<button id="triangle-btn">Triangle</button>
|
||||
<button id="eraser-btn">Eraser</button>
|
||||
<button id="spawn-player-btn">Spawn Player</button>
|
||||
</div>
|
||||
<div class="navigation">
|
||||
<button id="move-left">←</button>
|
||||
@ -51,6 +52,7 @@
|
||||
<script src="js/elements/physics_objects.js"></script>
|
||||
<script src="js/entities/entity.js"></script>
|
||||
<script src="js/entities/rabbit.js"></script>
|
||||
<script src="js/entities/player.js"></script>
|
||||
<script src="js/render.js"></script>
|
||||
<script src="js/input.js"></script>
|
||||
<script src="js/physics.js"></script>
|
||||
|
@ -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;
|
||||
|
187
js/entities/player.js
Normal file
187
js/entities/player.js
Normal file
@ -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;
|
||||
}
|
||||
}
|
36
js/input.js
36
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'));
|
||||
|
32
js/main.js
32
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);
|
||||
|
||||
|
12
styles.css
12
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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user