Compare commits

..

No commits in common. "b82f415f4feb61f29582d2f0bd07dd9ef598dedd" and "ebb96846ed3e430eaa8e0841eaa1f410d99556be" have entirely different histories.

21 changed files with 36 additions and 1676 deletions

View File

@ -20,12 +20,7 @@
<button id="tree-seed-btn">Tree Seed</button>
<button id="fire-btn">Fire</button>
<button id="lava-btn">Lava</button>
<button id="rabbit-btn">Rabbit</button>
<button id="square-btn">Square</button>
<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>
@ -49,10 +44,6 @@
<script src="js/elements/plants.js"></script>
<script src="js/elements/trees.js"></script>
<script src="js/elements/fire.js"></script>
<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>

View File

@ -1,7 +1,7 @@
// Game constants
const CHUNK_SIZE = 200;
let PIXEL_SIZE = 4;
const GRAVITY = 1.5; // Increased gravity (3x stronger)
const PIXEL_SIZE = 4;
const GRAVITY = 0.5;
const WATER_SPREAD = 3;
// Base Colors
@ -60,9 +60,6 @@ const LEAF = 12;
const FIRE = 13;
const LAVA = 14;
const RABBIT = 15;
const SQUARE = 16;
const CIRCLE = 17;
const TRIANGLE = 18;
// Flammable materials
const FLAMMABLE_MATERIALS = [GRASS, WOOD, SEED, GRASS_BLADE, FLOWER, TREE_SEED, LEAF];

View File

@ -1,24 +1,11 @@
// Basic element behaviors (sand, water, dirt)
function updateSand(x, y) {
// Try to move down with stronger gravity (up to 5 pixels at once)
let maxFall = 5;
let newY = y;
// Check how far down we can fall
for (let i = 1; i <= maxFall; i++) {
if (getPixel(x, y + i) === EMPTY) {
newY = y + i;
} else {
break;
}
}
if (newY > y) {
// Fall straight down as far as possible
// Try to move down
if (getPixel(x, y + 1) === EMPTY) {
setPixel(x, y, EMPTY);
setPixel(x, newY, SAND);
setPixel(x, y + 1, SAND);
return true;
}
}
// Try to move down-left or down-right
else if (getPixel(x - 1, y + 1) === EMPTY) {
setPixel(x, y, EMPTY);
@ -62,26 +49,13 @@ function updateWater(x, y) {
setMetadata(x, y, metadata);
}
// Try to move down with stronger gravity (up to 4 pixels at once)
let maxFall = 4;
let newY = y;
// Check how far down we can fall
for (let i = 1; i <= maxFall; i++) {
if (getPixel(x, y + i) === EMPTY) {
newY = y + i;
} else {
break;
}
}
if (newY > y) {
// Fall straight down as far as possible
// Try to move down
if (getPixel(x, y + 1) === EMPTY) {
setPixel(x, y, EMPTY);
setPixel(x, newY, WATER);
moveMetadata(x, y, x, newY);
setPixel(x, y + 1, WATER);
moveMetadata(x, y, x, y + 1);
return true;
}
}
// Try to move down-left or down-right
else if (getPixel(x - 1, y + 1) === EMPTY) {
setPixel(x, y, EMPTY);
@ -154,25 +128,12 @@ function updateWater(x, y) {
}
function updateDirt(x, y) {
// Try to move down with stronger gravity (up to 5 pixels at once)
let maxFall = 5;
let newY = y;
// Check how far down we can fall
for (let i = 1; i <= maxFall; i++) {
if (getPixel(x, y + i) === EMPTY) {
newY = y + i;
} else {
break;
}
}
if (newY > y) {
// Fall straight down as far as possible
// Try to move down
if (getPixel(x, y + 1) === EMPTY) {
setPixel(x, y, EMPTY);
setPixel(x, newY, DIRT);
setPixel(x, y + 1, DIRT);
return true;
}
}
// Try to move down-left or down-right
else if (getPixel(x - 1, y + 1) === EMPTY) {
setPixel(x, y, EMPTY);

View File

@ -1,265 +0,0 @@
// Physics objects (square, circle, triangle)
// Constants are already defined in constants.js
// Physics object properties
const PHYSICS_OBJECT_COLORS = ['#FF5733', '#33FF57', '#3357FF', '#F3FF33', '#FF33F3'];
// Physics constants
const GRAVITY_ACCELERATION = 0.2;
const BOUNCE_FACTOR = 0.7;
const FRICTION = 0.98;
const ROTATION_SPEED = 0.05;
// Store physics objects
const physicsObjects = [];
// Physics object class
class PhysicsObject {
constructor(type, x, y, size) {
this.type = type;
this.x = x;
this.y = y;
this.size = size || 10;
this.vx = 0;
this.vy = 0;
this.rotation = 0;
this.angularVelocity = (Math.random() - 0.5) * ROTATION_SPEED;
this.color = PHYSICS_OBJECT_COLORS[Math.floor(Math.random() * PHYSICS_OBJECT_COLORS.length)];
this.isStatic = false;
this.lastUpdate = performance.now();
}
update() {
const now = performance.now();
const deltaTime = Math.min(50, now - this.lastUpdate); // Cap at 50ms to prevent huge jumps
this.lastUpdate = now;
if (this.isStatic) return false;
// Apply gravity
this.vy += GRAVITY_ACCELERATION;
// Calculate new position
const newX = this.x + this.vx;
const newY = this.y + this.vy;
// Check for collisions with world elements
const collisionResult = this.checkCollisions(newX, newY);
if (collisionResult.collision) {
// Handle collision response
if (collisionResult.horizontal) {
this.vx *= -BOUNCE_FACTOR;
}
if (collisionResult.vertical) {
this.vy *= -BOUNCE_FACTOR;
}
// Apply friction
this.vx *= FRICTION;
this.vy *= FRICTION;
// If object is almost stopped, make it static
if (Math.abs(this.vx) < 0.1 && Math.abs(this.vy) < 0.1 && Math.abs(this.angularVelocity) < 0.01) {
this.isStatic = true;
}
} else {
// No collision, update position
this.x = newX;
this.y = newY;
}
// Update rotation
this.rotation += this.angularVelocity;
this.angularVelocity *= FRICTION;
return true;
}
checkCollisions(newX, newY) {
const result = {
collision: false,
horizontal: false,
vertical: false
};
// Check points around the object based on its type
const checkPoints = this.getCollisionCheckPoints(newX, newY);
for (const point of checkPoints) {
const pixel = getPixel(Math.floor(point.x), Math.floor(point.y));
if (pixel !== EMPTY && pixel !== WATER &&
pixel !== SQUARE && pixel !== CIRCLE && pixel !== TRIANGLE) {
result.collision = true;
// Determine collision direction
if (point.type === 'horizontal') {
result.horizontal = true;
} else if (point.type === 'vertical') {
result.vertical = true;
} else {
// Corner collision, check both directions
result.horizontal = true;
result.vertical = true;
}
}
}
return result;
}
getCollisionCheckPoints(x, y) {
const points = [];
const halfSize = this.size / 2;
if (this.type === SQUARE) {
// For a square, check corners and edges
const corners = [
{ x: x - halfSize, y: y - halfSize },
{ x: x + halfSize, y: y - halfSize },
{ x: x - halfSize, y: y + halfSize },
{ x: x + halfSize, y: y + halfSize }
];
// Add rotated corners
for (const corner of corners) {
const rotatedCorner = this.rotatePoint(corner.x, corner.y, x, y, this.rotation);
points.push({ x: rotatedCorner.x, y: rotatedCorner.y, type: 'corner' });
}
// Add edge midpoints
points.push({ x: x, y: y - halfSize, type: 'vertical' });
points.push({ x: x, y: y + halfSize, type: 'vertical' });
points.push({ x: x - halfSize, y: y, type: 'horizontal' });
points.push({ x: x + halfSize, y: y, type: 'horizontal' });
} else if (this.type === CIRCLE) {
// For a circle, check points around the circumference
const numPoints = 12;
for (let i = 0; i < numPoints; i++) {
const angle = (i / numPoints) * Math.PI * 2;
points.push({
x: x + Math.cos(angle) * halfSize,
y: y + Math.sin(angle) * halfSize,
type: angle < Math.PI / 4 || angle > Math.PI * 7/4 || (angle > Math.PI * 3/4 && angle < Math.PI * 5/4) ? 'horizontal' : 'vertical'
});
}
} else if (this.type === TRIANGLE) {
// For a triangle, check vertices and edges
const vertices = [
{ x: x, y: y - halfSize },
{ x: x - halfSize, y: y + halfSize },
{ x: x + halfSize, y: y + halfSize }
];
// Add rotated vertices
for (const vertex of vertices) {
const rotatedVertex = this.rotatePoint(vertex.x, vertex.y, x, y, this.rotation);
points.push({ x: rotatedVertex.x, y: rotatedVertex.y, type: 'corner' });
}
// Add edge midpoints
const midpoints = [
{ x: (vertices[0].x + vertices[1].x) / 2, y: (vertices[0].y + vertices[1].y) / 2, type: 'edge' },
{ x: (vertices[1].x + vertices[2].x) / 2, y: (vertices[1].y + vertices[2].y) / 2, type: 'horizontal' },
{ x: (vertices[2].x + vertices[0].x) / 2, y: (vertices[2].y + vertices[0].y) / 2, type: 'edge' }
];
for (const midpoint of midpoints) {
const rotatedMidpoint = this.rotatePoint(midpoint.x, midpoint.y, x, y, this.rotation);
points.push({ x: rotatedMidpoint.x, y: rotatedMidpoint.y, type: midpoint.type });
}
}
return points;
}
rotatePoint(px, py, cx, cy, angle) {
const s = Math.sin(angle);
const c = Math.cos(angle);
// Translate point back to origin
px -= cx;
py -= cy;
// Rotate point
const xnew = px * c - py * s;
const ynew = px * s + py * c;
// Translate point back
return {
x: xnew + cx,
y: ynew + cy
};
}
render(ctx, offsetX, offsetY) {
const screenX = (this.x - offsetX) * PIXEL_SIZE;
const screenY = (this.y - offsetY) * PIXEL_SIZE;
ctx.save();
ctx.translate(screenX, screenY);
ctx.rotate(this.rotation);
ctx.fillStyle = this.color;
// Draw collision box in debug mode
if (debugMode) {
ctx.strokeStyle = '#ff0000';
ctx.lineWidth = 1;
// Draw a circle for the collision radius
ctx.beginPath();
ctx.arc(0, 0, this.size * PIXEL_SIZE / 2, 0, Math.PI * 2);
ctx.stroke();
// Draw a dot at the center
ctx.fillStyle = '#ffff00';
ctx.beginPath();
ctx.arc(0, 0, 2, 0, Math.PI * 2);
ctx.fill();
// Restore original fill color
ctx.fillStyle = this.color;
}
if (this.type === SQUARE) {
const halfSize = this.size / 2 * PIXEL_SIZE;
ctx.fillRect(-halfSize, -halfSize, this.size * PIXEL_SIZE, this.size * PIXEL_SIZE);
} else if (this.type === CIRCLE) {
ctx.beginPath();
ctx.arc(0, 0, this.size / 2 * PIXEL_SIZE, 0, Math.PI * 2);
ctx.fill();
} else if (this.type === TRIANGLE) {
const halfSize = this.size / 2 * PIXEL_SIZE;
ctx.beginPath();
ctx.moveTo(0, -halfSize);
ctx.lineTo(-halfSize, halfSize);
ctx.lineTo(halfSize, halfSize);
ctx.closePath();
ctx.fill();
}
ctx.restore();
}
}
function createPhysicsObject(type, x, y, size) {
const obj = new PhysicsObject(type, x, y, size || 10);
physicsObjects.push(obj);
return obj;
}
function updatePhysicsObjects() {
// Update all physics objects
for (let i = physicsObjects.length - 1; i >= 0; i--) {
physicsObjects[i].update();
}
}
function renderPhysicsObjects(ctx, offsetX, offsetY) {
// Render all physics objects
for (const obj of physicsObjects) {
obj.render(ctx, offsetX, offsetY);
}
}

View File

@ -1,25 +1,11 @@
// Plant element behaviors (grass, seeds, trees)
function updateGrass(x, y) {
// Grass behaves like dirt for physics with stronger gravity
let maxFall = 5;
let newY = y;
// Check how far down we can fall
for (let i = 1; i <= maxFall; i++) {
if (getPixel(x, y + i) === EMPTY) {
newY = y + i;
} else {
break;
}
}
if (newY > y) {
// Fall straight down as far as possible
// Grass behaves like dirt for physics
if (getPixel(x, y + 1) === EMPTY) {
setPixel(x, y, EMPTY);
setPixel(x, newY, GRASS);
setPixel(x, y + 1, GRASS);
return true;
}
// Try to move down-left or down-right
}
else if (getPixel(x - 1, y + 1) === EMPTY) {
setPixel(x, y, EMPTY);
setPixel(x - 1, y + 1, GRASS);
@ -60,27 +46,13 @@ function updateGrass(x, y) {
}
function updateSeed(x, y) {
// Seeds fall like sand with stronger gravity
let maxFall = 5;
let newY = y;
// Check how far down we can fall
for (let i = 1; i <= maxFall; i++) {
if (getPixel(x, y + i) === EMPTY) {
newY = y + i;
} else {
break;
}
}
if (newY > y) {
// Fall straight down as far as possible
// Seeds fall like sand
if (getPixel(x, y + 1) === EMPTY) {
setPixel(x, y, EMPTY);
setPixel(x, newY, SEED);
moveMetadata(x, y, x, newY);
setPixel(x, y + 1, SEED);
moveMetadata(x, y, x, y + 1);
return true;
}
// Try to move down-left or down-right
}
else if (getPixel(x - 1, y + 1) === EMPTY) {
setPixel(x, y, EMPTY);
setPixel(x - 1, y + 1, SEED);

View File

@ -1,26 +1,12 @@
// Tree element behaviors
function updateTreeSeed(x, y) {
// Tree seeds fall like other seeds with stronger gravity
let maxFall = 5;
let newY = y;
// Check how far down we can fall
for (let i = 1; i <= maxFall; i++) {
if (getPixel(x, y + i) === EMPTY) {
newY = y + i;
} else {
break;
}
}
if (newY > y) {
// Fall straight down as far as possible
// Tree seeds fall like other seeds
if (getPixel(x, y + 1) === EMPTY) {
setPixel(x, y, EMPTY);
setPixel(x, newY, TREE_SEED);
moveMetadata(x, y, x, newY);
setPixel(x, y + 1, TREE_SEED);
moveMetadata(x, y, x, y + 1);
return true;
}
// Try to move down-left or down-right
}
else if (getPixel(x - 1, y + 1) === EMPTY) {
setPixel(x, y, EMPTY);
setPixel(x - 1, y + 1, TREE_SEED);

View File

@ -1,207 +0,0 @@
// Base entity system
const ENTITY_TYPES = {
RABBIT: 'rabbit',
PLAYER: 'player'
};
// Store all entities
const entities = [];
// Base Entity class
class Entity {
constructor(type, x, y, options = {}) {
this.type = type;
this.x = x;
this.y = y;
this.vx = 0;
this.vy = 0;
this.width = options.width || 10;
this.height = options.height || 10;
this.rotation = 0;
this.sprite = null;
this.flipped = false;
this.isStatic = false;
this.lastUpdate = performance.now();
this.id = Entity.nextId++;
}
static nextId = 1;
update() {
// Override in subclasses
return false;
}
render(ctx, offsetX, offsetY) {
// Default rendering - override in subclasses
const screenX = (this.x - offsetX) * PIXEL_SIZE;
const screenY = (this.y - offsetY) * PIXEL_SIZE;
ctx.save();
ctx.translate(screenX, screenY);
ctx.rotate(this.rotation);
if (this.sprite && this.sprite.complete) {
const width = this.width * PIXEL_SIZE;
const height = this.height * PIXEL_SIZE;
if (this.flipped) {
ctx.scale(-1, 1);
ctx.drawImage(this.sprite, -width/2, -height/2, width, height);
} else {
ctx.drawImage(this.sprite, -width/2, -height/2, width, height);
}
} else {
// Fallback if sprite not loaded
ctx.fillStyle = '#FF00FF';
ctx.fillRect(
-this.width/2 * PIXEL_SIZE,
-this.height/2 * PIXEL_SIZE,
this.width * PIXEL_SIZE,
this.height * PIXEL_SIZE
);
}
// Draw collision box in debug mode
if (debugMode) {
ctx.strokeStyle = '#00ff00';
ctx.lineWidth = 1;
ctx.strokeRect(
-this.width/2 * PIXEL_SIZE,
-this.height/2 * PIXEL_SIZE,
this.width * PIXEL_SIZE,
this.height * PIXEL_SIZE
);
// Draw a dot at the entity's center
ctx.fillStyle = '#ffff00';
ctx.beginPath();
ctx.arc(0, 0, 2, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
checkCollisions(newX, newY) {
const result = {
collision: false,
horizontal: false,
vertical: false,
ground: false
};
// Check points around the entity
const halfWidth = this.width / 2;
const halfHeight = this.height / 2;
// Check bottom points for ground collision - check multiple points along the bottom
const numBottomPoints = 5;
let groundCollision = false;
// For player entity, adjust the collision detection to match sprite feet position
const yOffset = this.type === ENTITY_TYPES.PLAYER ? 2 : 0;
for (let i = 0; i < numBottomPoints; i++) {
const ratio = i / (numBottomPoints - 1);
const bottomX = newX - halfWidth + (2 * halfWidth * ratio);
const bottomY = newY + halfHeight + yOffset;
if (this.isPixelSolid(bottomX, bottomY)) {
groundCollision = true;
break;
}
}
if (groundCollision) {
result.collision = true;
result.vertical = true;
result.ground = true;
}
// Check side points for horizontal collision
// For player entity, adjust the collision detection to match sprite position
const yAdjust = this.type === ENTITY_TYPES.PLAYER ? -1 : 0;
const leftMiddle = { x: newX - halfWidth, y: newY + yAdjust };
const rightMiddle = { x: newX + halfWidth, y: newY + yAdjust };
if (this.isPixelSolid(leftMiddle.x, leftMiddle.y)) {
result.collision = true;
result.horizontal = true;
}
if (this.isPixelSolid(rightMiddle.x, rightMiddle.y)) {
result.collision = true;
result.horizontal = true;
}
// Check top for ceiling collision
const topMiddle = { x: newX, y: newY - halfHeight + (this.type === ENTITY_TYPES.PLAYER ? -2 : 0) };
if (this.isPixelSolid(topMiddle.x, topMiddle.y)) {
result.collision = true;
result.vertical = true;
}
return result;
}
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 &&
pixel !== SQUARE &&
pixel !== CIRCLE &&
pixel !== TRIANGLE;
}
}
// Function to create and register an entity
function createEntity(type, x, y, options = {}) {
let entity;
switch(type) {
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;
}
entities.push(entity);
return entity;
}
// Update all entities
function updateEntities() {
for (let i = entities.length - 1; i >= 0; i--) {
entities[i].update();
}
}
// Render all entities
function renderEntities(ctx, offsetX, offsetY) {
for (const entity of entities) {
entity.render(ctx, offsetX, offsetY);
}
}

View File

@ -1,543 +0,0 @@
// Player entity class
class Player extends Entity {
constructor(x, y, options = {}) {
super(ENTITY_TYPES.PLAYER, x, y, {
width: 3, // 50% smaller collision box width
height: 6, // 50% smaller collision box height
...options
});
// Load player sprite
this.sprite = new Image();
this.sprite.src = 'sprites/player.png';
// Movement properties
this.moveSpeed = 0.03;
this.jumpForce = -0.2;
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();
this.lastDirection = 1; // Track last direction to prevent unnecessary flipping
this.isClimbing = false; // Track climbing state
// Player stats
this.maxHealth = 100;
this.health = 100;
this.breakingPower = 1;
this.breakingRange = 10; // Increased from 3 to 10 pixels
this.isBreaking = false;
this.breakingCooldown = 0;
this.breakingCooldownMax = 10;
// Inventory
this.inventory = {
sand: 0,
water: 0,
dirt: 0,
stone: 0,
wood: 0,
grass: 0,
seed: 0
};
// Animation properties
this.frameWidth = 32;
this.frameHeight = 30;
this.frameCount = 4;
this.currentFrame = 0;
this.animationSpeed = 150; // ms per frame
this.lastFrameUpdate = 0;
this.isMoving = false;
this.animationTimer = 0; // Consistent timer for animation
}
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) {
// 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) {
if (this.vy > 0) {
this.isJumping = false;
}
newY = this.y;
this.vy = 0;
}
}
// Update position
this.x = newX;
this.y = newY;
// Update breaking cooldown
if (this.breakingCooldown > 0) {
this.breakingCooldown--;
}
// Handle breaking action
if (this.isBreaking && this.breakingCooldown <= 0) {
this.breakBlock();
this.breakingCooldown = this.breakingCooldownMax;
}
// Update animation
this.updateAnimation(deltaTime);
// Center camera on player
this.centerCamera();
// Update HUD
this.updateHUD();
return true;
}
updateAnimation(deltaTime) {
// Update animation timer consistently
this.animationTimer += deltaTime;
// Only update direction when it actually changes to prevent flipping
if (Math.abs(this.vx) > 0.005) {
const newDirection = this.vx > 0 ? 1 : -1;
if (newDirection !== this.lastDirection) {
this.direction = newDirection;
this.lastDirection = newDirection;
}
}
}
render(ctx, offsetX, offsetY) {
const screenX = (this.x - offsetX) * PIXEL_SIZE;
const screenY = (this.y - offsetY) * PIXEL_SIZE;
if (this.sprite && this.sprite.complete) {
// Set pixelated rendering (nearest neighbor)
ctx.imageSmoothingEnabled = false;
ctx.save();
ctx.translate(screenX, screenY);
// Use 50% smaller dimensions for the sprite
const spriteDisplayWidth = 12 * (PIXEL_SIZE / 2); // 50% smaller sprite
const spriteDisplayHeight = 12 * (PIXEL_SIZE / 2); // 50% smaller sprite
// Flip horizontally based on direction
if (this.direction < 0) {
ctx.scale(-1, 1);
}
// Draw the correct sprite frame
// Center the sprite on the entity position, with y-offset to align feet with collision box
// Stretch the sprite vertically to match the collision box height
ctx.drawImage(
this.sprite,
this.currentFrame * this.frameWidth, 0,
this.frameWidth, this.frameHeight,
-spriteDisplayWidth / 2, -spriteDisplayHeight / 2, // Remove the negative offset that caused levitation
spriteDisplayWidth, spriteDisplayHeight * 1.2 // Stretch sprite vertically by 20% to match collision box
);
ctx.restore();
// Reset image smoothing for other rendering
ctx.imageSmoothingEnabled = true;
// 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
);
// Also draw sprite boundary in debug mode
ctx.strokeStyle = '#ff00ff';
ctx.lineWidth = 1;
ctx.strokeRect(
screenX - spriteDisplayWidth / 2,
screenY - spriteDisplayHeight / 2, // Match the updated sprite drawing position
spriteDisplayWidth,
spriteDisplayHeight * 1.2 // Match the stretched sprite height
);
// Draw a dot at the entity's exact position
ctx.fillStyle = '#ffff00';
ctx.beginPath();
ctx.arc(screenX, screenY, 2, 0, Math.PI * 2);
ctx.fill();
}
}
}
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;
}
}
startBreaking() {
this.isBreaking = true;
}
stopBreaking() {
this.isBreaking = false;
}
breakBlock() {
// Get mouse position in world coordinates
const worldX = Math.floor(currentMouseX / PIXEL_SIZE) + worldOffsetX;
const worldY = Math.floor(currentMouseY / PIXEL_SIZE) + worldOffsetY;
// Calculate distance from player to target block
const distance = Math.sqrt(
Math.pow(worldX - this.x, 2) +
Math.pow(worldY - this.y, 2)
);
// Only break blocks within range
if (distance <= this.breakingRange) {
// Get the block type at that position
const blockType = getPixel(worldX, worldY);
// Only break non-empty blocks that aren't special entities
if (blockType !== EMPTY &&
blockType !== WATER &&
blockType !== FIRE &&
blockType !== SQUARE &&
blockType !== CIRCLE &&
blockType !== TRIANGLE) {
// Add to inventory based on block type
this.addToInventory(blockType);
// Replace with empty space
setPixel(worldX, worldY, EMPTY);
// Create a breaking effect (particles)
this.createBreakingEffect(worldX, worldY, blockType);
}
}
}
addToInventory(blockType) {
// Map block type to inventory item
switch(blockType) {
case SAND:
this.inventory.sand++;
break;
case DIRT:
this.inventory.dirt++;
break;
case STONE:
this.inventory.stone++;
break;
case GRASS:
this.inventory.grass++;
break;
case WOOD:
this.inventory.wood++;
break;
case SEED:
case TREE_SEED:
this.inventory.seed++;
break;
}
}
createBreakingEffect(x, y, blockType) {
// Create a simple particle effect at the breaking location
// This could be expanded with a proper particle system
const numParticles = 5;
// For now, we'll just create a visual feedback by setting nearby pixels
// to a different color briefly, then clearing them
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
if (dx === 0 && dy === 0) continue;
// Skip if the pixel is not empty
if (getPixel(x + dx, y + dy) !== EMPTY) continue;
// Set a temporary pixel
setPixel(x + dx, y + dy, EMPTY);
// Mark the chunk as dirty for rendering
const { chunkX, chunkY } = getChunkCoordinates(x + dx, y + dy);
const key = getChunkKey(chunkX, chunkY);
dirtyChunks.add(key);
}
}
}
updateHUD() {
// Get or create the HUD container
let hudContainer = document.getElementById('player-hud');
if (!hudContainer) {
hudContainer = document.createElement('div');
hudContainer.id = 'player-hud';
hudContainer.style.position = 'fixed';
hudContainer.style.bottom = '10px';
hudContainer.style.left = '10px';
hudContainer.style.width = '300px';
hudContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
hudContainer.style.color = 'white';
hudContainer.style.padding = '10px';
hudContainer.style.borderRadius = '5px';
hudContainer.style.fontFamily = 'Arial, sans-serif';
hudContainer.style.zIndex = '1000';
document.body.appendChild(hudContainer);
// Create health bar container
const healthBarContainer = document.createElement('div');
healthBarContainer.id = 'health-bar-container';
healthBarContainer.style.width = '100%';
healthBarContainer.style.height = '20px';
healthBarContainer.style.backgroundColor = 'rgba(255, 255, 255, 0.3)';
healthBarContainer.style.marginBottom = '10px';
healthBarContainer.style.borderRadius = '3px';
// Create health bar
const healthBar = document.createElement('div');
healthBar.id = 'health-bar';
healthBar.style.width = '100%';
healthBar.style.height = '100%';
healthBar.style.backgroundColor = '#4CAF50';
healthBar.style.borderRadius = '3px';
healthBar.style.transition = 'width 0.3s';
healthBarContainer.appendChild(healthBar);
hudContainer.appendChild(healthBarContainer);
// Create inventory container
const inventoryContainer = document.createElement('div');
inventoryContainer.id = 'inventory-container';
inventoryContainer.style.display = 'grid';
inventoryContainer.style.gridTemplateColumns = 'repeat(7, 1fr)';
inventoryContainer.style.gap = '5px';
// Create inventory slots
const inventoryItems = ['sand', 'dirt', 'stone', 'grass', 'wood', 'water', 'seed'];
inventoryItems.forEach(item => {
const slot = document.createElement('div');
slot.id = `inventory-${item}`;
slot.className = 'inventory-slot';
slot.style.width = '30px';
slot.style.height = '30px';
slot.style.backgroundColor = 'rgba(255, 255, 255, 0.2)';
slot.style.borderRadius = '3px';
slot.style.display = 'flex';
slot.style.flexDirection = 'column';
slot.style.alignItems = 'center';
slot.style.justifyContent = 'center';
slot.style.fontSize = '10px';
slot.style.position = 'relative';
// Create item icon
const icon = document.createElement('div');
icon.style.width = '20px';
icon.style.height = '20px';
icon.style.borderRadius = '3px';
// Set color based on item type
switch(item) {
case 'sand': icon.style.backgroundColor = '#e6c588'; break;
case 'dirt': icon.style.backgroundColor = '#8B4513'; break;
case 'stone': icon.style.backgroundColor = '#A9A9A9'; break;
case 'grass': icon.style.backgroundColor = '#7CFC00'; break;
case 'wood': icon.style.backgroundColor = '#8B5A2B'; break;
case 'water': icon.style.backgroundColor = '#4a80f5'; break;
case 'seed': icon.style.backgroundColor = '#654321'; break;
}
// Create count label
const count = document.createElement('div');
count.id = `${item}-count`;
count.style.position = 'absolute';
count.style.bottom = '2px';
count.style.right = '2px';
count.style.fontSize = '8px';
count.style.fontWeight = 'bold';
count.textContent = '0';
slot.appendChild(icon);
slot.appendChild(count);
inventoryContainer.appendChild(slot);
});
hudContainer.appendChild(inventoryContainer);
// Create controls help text
const controlsHelp = document.createElement('div');
controlsHelp.style.marginTop = '10px';
controlsHelp.style.fontSize = '10px';
controlsHelp.style.color = '#aaa';
controlsHelp.innerHTML = 'Controls: A/D - Move, W/Space - Jump, E - Break blocks';
hudContainer.appendChild(controlsHelp);
}
// Update health bar
const healthBar = document.getElementById('health-bar');
if (healthBar) {
const healthPercent = (this.health / this.maxHealth) * 100;
healthBar.style.width = `${healthPercent}%`;
// Change color based on health
if (healthPercent > 60) {
healthBar.style.backgroundColor = '#4CAF50'; // Green
} else if (healthPercent > 30) {
healthBar.style.backgroundColor = '#FFC107'; // Yellow
} else {
healthBar.style.backgroundColor = '#F44336'; // Red
}
}
// Update inventory counts
for (const [item, count] of Object.entries(this.inventory)) {
const countElement = document.getElementById(`${item}-count`);
if (countElement) {
countElement.textContent = count;
}
}
}
// 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;
const cameraHeight = canvas.height / PIXEL_SIZE;
const cameraCenterX = worldOffsetX + cameraWidth / 2;
const cameraCenterY = worldOffsetY + cameraHeight / 2;
// Calculate distance from player to camera center
const distanceX = Math.abs(this.x - cameraCenterX);
const distanceY = Math.abs(this.y - cameraCenterY);
// Define thresholds for camera movement (percentage of screen size)
const thresholdX = cameraWidth * 0.2; // Move when player is 30% away from center
const thresholdY = cameraHeight * 0.2;
// Only move camera when player gets close to the edge of current view
let needsUpdate = false;
if (distanceX > thresholdX) {
// Calculate target position with chunk-based snapping
const chunkSize = CHUNK_SIZE;
const playerChunkX = Math.floor(this.x / chunkSize);
const targetX = this.x - (canvas.width / PIXEL_SIZE / 2);
// Smooth transition to the target position
worldOffsetX += (targetX - worldOffsetX) * 0.2;
needsUpdate = true;
}
if (distanceY > thresholdY) {
// Calculate target position with chunk-based snapping
const chunkSize = CHUNK_SIZE;
const playerChunkY = Math.floor(this.y / chunkSize);
const targetY = this.y - (canvas.height / PIXEL_SIZE / 2);
// Smooth transition to the target position
worldOffsetY += (targetY - worldOffsetY) * 0.1;
needsUpdate = true;
}
// Only mark world as moved if we actually updated the camera
if (needsUpdate) {
worldMoved = true;
}
}
}

View File

@ -1,148 +0,0 @@
// Rabbit entity
class Rabbit extends Entity {
constructor(x, y, options = {}) {
super(ENTITY_TYPES.RABBIT, x, y, {
width: 6, // Smaller size for rabbit
height: 6,
...options
});
// Load rabbit sprite
this.sprite = new Image();
this.sprite.src = 'sprites/rabbit.png';
// Rabbit specific properties
this.jumpCooldown = 0;
this.jumpForce = -3.5;
this.moveSpeed = 0.8;
this.direction = Math.random() > 0.5 ? 1 : -1; // 1 for right, -1 for left
this.isJumping = false;
this.thinkTimer = 0;
this.actionDuration = 0;
this.currentAction = 'idle';
// Apply gravity
this.gravity = 0.2;
}
update() {
const now = performance.now();
const deltaTime = Math.min(50, now - this.lastUpdate);
this.lastUpdate = now;
// Apply gravity
this.vy += this.gravity;
// Calculate new position
let newX = this.x + this.vx;
let newY = this.y + this.vy;
// Check for collisions
const collisionResult = this.checkCollisions(newX, newY);
if (collisionResult.collision) {
if (collisionResult.horizontal) {
// Hit a wall, reverse direction
this.direction *= -1;
this.vx = this.moveSpeed * this.direction;
newX = this.x; // Don't move horizontally this frame
}
if (collisionResult.vertical) {
if (collisionResult.ground) {
// Landed on ground
this.vy = 0;
this.isJumping = false;
// Find exact ground position
while (this.isPixelSolid(this.x, newY)) {
newY--;
}
newY = Math.floor(newY) + 0.5; // Position just above ground (reduced from 0.99)
} else {
// Hit ceiling
this.vy = 0;
newY = this.y;
}
}
}
// Update position
this.x = newX;
this.y = newY;
// Update jump cooldown
if (this.jumpCooldown > 0) {
this.jumpCooldown--;
}
// AI behavior
this.thinkTimer++;
if (this.thinkTimer >= 30) { // Think every 30 frames
this.thinkTimer = 0;
this.think();
}
// Update action duration
if (this.actionDuration > 0) {
this.actionDuration--;
} else if (this.currentAction !== 'idle') {
this.currentAction = 'idle';
this.vx = 0;
}
// Update sprite direction but only flip the sprite, not rotate it
this.flipped = this.direction < 0;
// Only apply rotation when jumping
if (this.isJumping) {
this.rotation = this.direction < 0 ? -0.2 : 0.2;
} else {
this.rotation = 0;
}
return true;
}
think() {
// Only make decisions when on the ground and not already in an action
if (!this.isJumping && this.actionDuration <= 0) {
const decision = Math.random();
if (decision < 0.5) { // Increased from 0.2 to 0.5 for more frequent jumping
// Jump
this.jump();
} else if (decision < 0.8) { // Adjusted range
// Move
this.move();
} else {
// Idle
this.idle();
}
}
}
jump() {
if (!this.isJumping && this.jumpCooldown <= 0) {
this.vy = this.jumpForce;
this.vx = this.moveSpeed * this.direction;
this.isJumping = true;
this.jumpCooldown = 20;
this.currentAction = 'jump';
this.actionDuration = 30;
}
}
move() {
this.direction = Math.random() > 0.5 ? 1 : -1;
this.vx = this.moveSpeed * this.direction;
this.currentAction = 'move';
this.actionDuration = 60 + Math.floor(Math.random() * 60);
}
idle() {
this.vx = 0;
this.currentAction = 'idle';
this.actionDuration = 30 + Math.floor(Math.random() * 30);
}
}

View File

@ -4,58 +4,6 @@ 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;
// Toggle debug mode with F3
if (e.code === 'F3') {
toggleDebug();
e.preventDefault();
}
// Start breaking blocks with E key or left mouse button
if (e.code === 'KeyE' && player) {
player.startBreaking();
}
// Prevent default behavior for game control keys
if (['KeyW', 'KeyA', 'KeyS', 'KeyD', 'Space', 'KeyE'].includes(e.code)) {
e.preventDefault();
}
});
window.addEventListener('keyup', (e) => {
keyState[e.code] = false;
// Stop breaking blocks when E key is released
if (e.code === 'KeyE' && player) {
player.stopBreaking();
}
});
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'));
@ -80,14 +28,6 @@ function setTool(tool) {
document.getElementById('fire-btn').classList.add('active');
} else if (tool === LAVA) {
document.getElementById('lava-btn').classList.add('active');
} else if (tool === RABBIT) {
document.getElementById('rabbit-btn').classList.add('active');
} else if (tool === SQUARE) {
document.getElementById('square-btn').classList.add('active');
} else if (tool === CIRCLE) {
document.getElementById('circle-btn').classList.add('active');
} else if (tool === TRIANGLE) {
document.getElementById('triangle-btn').classList.add('active');
} else if (tool === EMPTY) {
document.getElementById('eraser-btn').classList.add('active');
}
@ -106,15 +46,9 @@ function handleMouseDown(e) {
worldOffsetXBeforeDrag = worldOffsetX;
worldOffsetYBeforeDrag = worldOffsetY;
} else {
// Left mouse button for drawing or breaking blocks
if (player) {
// If player exists, start breaking blocks
player.startBreaking();
} else {
// Otherwise use normal drawing
isDrawing = true;
draw(x, y);
}
// Left mouse button for drawing
isDrawing = true;
draw(x, y);
}
}
@ -155,11 +89,6 @@ function handleMouseUp(e) {
}
}
isDragging = false;
// Stop breaking blocks if player exists
if (player) {
player.stopBreaking();
}
}
function draw(x, y) {
@ -169,14 +98,6 @@ function draw(x, y) {
const worldX = Math.floor(x / PIXEL_SIZE) + worldOffsetX;
const worldY = Math.floor(y / PIXEL_SIZE) + worldOffsetY;
// Special handling for physics objects
if (currentTool === SQUARE || currentTool === CIRCLE || currentTool === TRIANGLE) {
// Create a physics object at the cursor position
const size = 10; // Default size
createPhysicsObject(currentTool, worldX, worldY, size);
return;
}
// Draw a small brush (3x3)
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
@ -193,11 +114,6 @@ function draw(x, y) {
colorIndex: Math.floor(Math.random() * FIRE_COLORS.length)
});
}
}
// Special handling for rabbits - create rabbit entity
else if (currentTool === RABBIT) {
createEntity(ENTITY_TYPES.RABBIT, pixelX, pixelY);
return; // Only create one rabbit per click
} else {
setPixel(pixelX, pixelY, currentTool);
@ -269,26 +185,4 @@ function handleTouchMove(e) {
function toggleDebug() {
debugMode = !debugMode;
document.getElementById('debug-btn').classList.toggle('active');
// Update UI to show debug mode is active
if (debugMode) {
// Show a temporary notification
const notification = document.createElement('div');
notification.textContent = 'Debug Mode: ON';
notification.style.position = 'fixed';
notification.style.top = '10px';
notification.style.left = '50%';
notification.style.transform = 'translateX(-50%)';
notification.style.backgroundColor = 'rgba(0, 255, 0, 0.7)';
notification.style.color = 'white';
notification.style.padding = '10px 20px';
notification.style.borderRadius = '5px';
notification.style.zIndex = '1000';
document.body.appendChild(notification);
// Remove notification after 2 seconds
setTimeout(() => {
document.body.removeChild(notification);
}, 2000);
}
}

View File

@ -5,15 +5,6 @@ let lastFrameTime = 0;
let fps = 0;
let debugMode = false;
// Sky background variables
const SKY_COLORS = [
'#4a90e2', // Brighter blue
'#74b9ff', // Light blue
'#81ecec', // Very light blue/cyan
];
let skyAnimationTime = 0;
let skyAnimationSpeed = 0.0005; // Controls how fast the sky position animates (not color)
// Initialize the simulation
window.onload = function() {
canvas = document.getElementById('simulation-canvas');
@ -34,15 +25,8 @@ window.onload = function() {
document.getElementById('tree-seed-btn').addEventListener('click', () => setTool(TREE_SEED));
document.getElementById('fire-btn').addEventListener('click', () => setTool(FIRE));
document.getElementById('lava-btn').addEventListener('click', () => setTool(LAVA));
document.getElementById('rabbit-btn').addEventListener('click', () => setTool(RABBIT));
document.getElementById('square-btn').addEventListener('click', () => setTool(SQUARE));
document.getElementById('circle-btn').addEventListener('click', () => setTool(CIRCLE));
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));
@ -77,11 +61,6 @@ window.onload = function() {
// Start the simulation loop
requestAnimationFrame(simulationLoop);
// Initialize physics variables
window.physicsUpdateRate = 16; // ms between physics updates
window.lastPhysicsTime = 0;
window.fireUpdateCounter = 0;
};
function resizeCanvas() {
@ -89,50 +68,6 @@ 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';
// Resize canvas to full screen first
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Set zoom level to 50% more zoomed in (from default 4 to 6)
PIXEL_SIZE = 6;
// Create player at specified coordinates
// Position adjusted for proper sprite alignment with smaller sprite
player = createEntity(ENTITY_TYPES.PLAYER, 229, 40); // Adjusted Y position for smaller player
// Focus camera on player
worldOffsetX = player.x - (canvas.width / PIXEL_SIZE / 2);
worldOffsetY = player.y - (canvas.height / PIXEL_SIZE / 2);
worldMoved = true;
// Clear chunk cache to force redraw at new zoom level
chunkCanvasCache.clear();
// Remove the event listener for the spawn button to prevent multiple spawns
document.getElementById('spawn-player-btn').removeEventListener('click', spawnPlayer);
// Create CSS for player HUD
const style = document.createElement('style');
style.textContent = `
#player-hud {
transition: opacity 0.3s;
}
.inventory-slot {
transition: transform 0.1s;
}
.inventory-slot:hover {
transform: scale(1.1);
background-color: rgba(255, 255, 255, 0.3) !important;
}
`;
document.head.appendChild(style);
}
function simulationLoop(timestamp) {
// Calculate FPS
const deltaTime = timestamp - lastFrameTime;
@ -140,18 +75,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);
// Render - skip rendering if FPS is too low to prevent death spiral
if (fps > 10 || timestamp % 3 < 1) {
render();
}
// Render
render();
// Memory management: Clean up chunk cache for chunks that are far away
if (timestamp % 5000 < 16) { // Run every ~5 seconds

View File

@ -7,16 +7,6 @@ function updatePhysics(timestamp) {
lastPhysicsTime = timestamp || 0;
// Update physics objects
if (typeof updatePhysicsObjects === 'function') {
updatePhysicsObjects();
}
// Update entities
if (typeof updateEntities === 'function') {
updateEntities();
}
// Get visible chunks
const visibleChunks = getVisibleChunks();

View File

@ -6,19 +6,8 @@ function render() {
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Set pixelated rendering for the entire canvas
ctx.imageSmoothingEnabled = false;
// Draw animated sky background
renderSky();
// Display FPS in debug mode
if (debugMode) {
displayDebugInfo();
}
// Get visible chunks - limit the number of chunks processed per frame
const visibleChunks = getVisibleChunks().slice(0, 20);
// Get visible chunks
const visibleChunks = getVisibleChunks();
// Render each visible chunk
for (const { chunkX, chunkY, isVisible } of visibleChunks) {
@ -74,23 +63,6 @@ function render() {
// Reset world moved flag after rendering
worldMoved = false;
// Update cloud position animation only (not colors or shapes)
skyAnimationTime += skyAnimationSpeed;
if (skyAnimationTime > 1) skyAnimationTime -= 1;
// Render physics objects
renderPhysicsObjects(ctx, worldOffsetX, worldOffsetY);
// Render entities
if (typeof renderEntities === 'function') {
renderEntities(ctx, worldOffsetX, worldOffsetY);
}
// Render breaking range indicator if player is breaking
if (player && player.isBreaking) {
renderBreakingIndicator(ctx, worldOffsetX, worldOffsetY);
}
// Draw cursor position and update debug info
if (currentMouseX !== undefined && currentMouseY !== undefined) {
const worldX = Math.floor(currentMouseX / PIXEL_SIZE) + worldOffsetX;
@ -124,108 +96,6 @@ function render() {
}
}
// Render the animated sky background
function renderSky() {
// Create a gradient with fixed colors (no animation of colors)
const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
// Use fixed color positions for a brighter, static sky
gradient.addColorStop(0, SKY_COLORS[0]);
gradient.addColorStop(0.5, SKY_COLORS[1]);
gradient.addColorStop(1, SKY_COLORS[2]);
// Fill the background with the gradient
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Add a subtle horizon line to separate sky from world
const horizonGradient = ctx.createLinearGradient(0, canvas.height * 0.4, 0, canvas.height * 0.6);
horizonGradient.addColorStop(0, 'rgba(255, 255, 255, 0)');
horizonGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
horizonGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
ctx.fillStyle = horizonGradient;
ctx.fillRect(0, canvas.height * 0.4, canvas.width, canvas.height * 0.2);
}
// Display debug information
function displayDebugInfo() {
ctx.save();
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
ctx.fillRect(10, 10, 200, 80);
ctx.font = '14px monospace';
ctx.fillStyle = 'white';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
// Display FPS
ctx.fillText(`FPS: ${fps}`, 20, 20);
// Display world coordinates
ctx.fillText(`World: ${Math.floor(worldOffsetX)}, ${Math.floor(worldOffsetY)}`, 20, 40);
// Display chunk coordinates
const chunkX = Math.floor(worldOffsetX / CHUNK_SIZE);
const chunkY = Math.floor(worldOffsetY / CHUNK_SIZE);
ctx.fillText(`Chunk: ${chunkX}, ${chunkY}`, 20, 60);
ctx.restore();
}
// Render breaking indicator for player
function renderBreakingIndicator(ctx, offsetX, offsetY) {
// Get mouse position in world coordinates
const worldX = Math.floor(currentMouseX / PIXEL_SIZE) + worldOffsetX;
const worldY = Math.floor(currentMouseY / PIXEL_SIZE) + worldOffsetY;
// Calculate distance from player to target block
const distance = Math.sqrt(
Math.pow(worldX - player.x, 2) +
Math.pow(worldY - player.y, 2)
);
// Convert to screen coordinates
const screenX = (worldX - offsetX) * PIXEL_SIZE;
const screenY = (worldY - offsetY) * PIXEL_SIZE;
const playerScreenX = (player.x - offsetX) * PIXEL_SIZE;
const playerScreenY = (player.y - offsetY) * PIXEL_SIZE;
// Draw breaking range circle
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(
playerScreenX,
playerScreenY,
player.breakingRange * PIXEL_SIZE,
0,
Math.PI * 2
);
ctx.stroke();
// Draw target indicator
if (distance <= player.breakingRange) {
ctx.strokeStyle = '#ff0000';
} else {
ctx.strokeStyle = '#888888'; // Gray when out of range
}
ctx.lineWidth = 2;
ctx.strokeRect(
screenX - PIXEL_SIZE/2,
screenY - PIXEL_SIZE/2,
PIXEL_SIZE,
PIXEL_SIZE
);
// Draw line from player to target point
ctx.beginPath();
ctx.moveTo(playerScreenX, playerScreenY);
ctx.lineTo(screenX, screenY);
ctx.stroke();
}
// Render a chunk to an offscreen canvas and cache it
function renderChunkToCache(chunkX, chunkY, key) {
const chunk = chunks.get(key);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 644 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 335 B

View File

@ -39,18 +39,6 @@ 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;
}
@ -75,57 +63,3 @@ body {
background-color: #000;
cursor: crosshair;
}
#player-hud {
position: fixed;
bottom: 10px;
left: 10px;
width: 300px;
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 10px;
border-radius: 5px;
font-family: Arial, sans-serif;
z-index: 1000;
}
#health-bar-container {
width: 100%;
height: 20px;
background-color: rgba(255, 255, 255, 0.3);
margin-bottom: 10px;
border-radius: 3px;
}
#health-bar {
width: 100%;
height: 100%;
background-color: #4CAF50;
border-radius: 3px;
transition: width 0.3s;
}
#inventory-container {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 5px;
}
.inventory-slot {
width: 30px;
height: 30px;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 3px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 10px;
position: relative;
transition: transform 0.1s;
}
.inventory-slot:hover {
transform: scale(1.1);
background-color: rgba(255, 255, 255, 0.3);
}