// Define world object types
const WORLD_OBJECTS = {
PLATFORM: 'platform',
FIR_BACKGROUND: 'fir_background',
FIR_FOREGROUND: 'fir_foreground',
MAPLE_BACKGROUND: 'maple_background',
MAPLE_FOREGROUND: 'maple_foreground',
ROCK_BACKGROUND: 'rock_background',
ROCK_FOREGROUND: 'rock_foreground',
GRASS_BACKGROUND: 'grass_background',
GRASS_FOREGROUND: 'grass_foreground'
};
// Separate configurations for different tree types
const FIR_TYPES = {
SMALL: {
type: 'fir',
width: 80,
height: 120,
canopyOffset: 40,
canopyColor: '#2a4',
trunkColor: '#531'
},
LARGE: {
type: 'fir',
width: 120,
height: 200,
canopyOffset: 60,
canopyColor: '#1a4',
trunkColor: '#420'
}
};
const MAPLE_TYPES = {
SMALL: {
type: 'maple',
width: 100,
height: 130,
trunkHeight: 60,
canopyRadius: 35,
leafClusters: 5,
canopyColor: '#3a2',
trunkColor: '#642'
},
LARGE: {
type: 'maple',
width: 160,
height: 200,
trunkHeight: 85,
canopyRadius: 55,
leafClusters: 7,
canopyColor: '#2a2',
trunkColor: '#531'
}
};
// Rock configurations
const ROCK_TYPES = {
SMALL: {
width: 40,
height: 30,
color: '#666',
highlights: '#888'
},
MEDIUM: {
width: 70,
height: 45,
color: '#555',
highlights: '#777'
},
LARGE: {
width: 100,
height: 60,
color: '#444',
highlights: '#666'
}
};
// Add grass configurations
const GRASS_TYPES = {
TALL: {
type: 'grass',
width: 30,
height: 40,
blades: 5,
color: '#3a5',
shadowColor: '#294'
},
SHORT: {
type: 'grass',
width: 20,
height: 25,
blades: 4,
color: '#3b6',
shadowColor: '#2a5'
}
};
// Create a world with platforms, trees, and rocks
const createWorld = () => ({
groundHeight: 12,
// Separate arrays for different layers
backgroundTrees: [
{
type: WORLD_OBJECTS.FIR_BACKGROUND,
x: -400,
config: FIR_TYPES.LARGE
},
{
type: WORLD_OBJECTS.MAPLE_BACKGROUND,
x: -250,
config: MAPLE_TYPES.SMALL
},
{
type: WORLD_OBJECTS.FIR_BACKGROUND,
x: 50,
config: FIR_TYPES.LARGE
},
{
type: WORLD_OBJECTS.MAPLE_BACKGROUND,
x: 250,
config: MAPLE_TYPES.LARGE
},
{
type: WORLD_OBJECTS.FIR_BACKGROUND,
x: 500,
config: FIR_TYPES.SMALL
},
{
type: WORLD_OBJECTS.MAPLE_BACKGROUND,
x: 650,
config: MAPLE_TYPES.SMALL
},
{
type: WORLD_OBJECTS.FIR_BACKGROUND,
x: 900,
config: FIR_TYPES.LARGE
},
{
type: WORLD_OBJECTS.MAPLE_BACKGROUND,
x: 1100,
config: MAPLE_TYPES.LARGE
}
],
backgroundRocks: [
{
type: WORLD_OBJECTS.ROCK_BACKGROUND,
x: -300,
config: ROCK_TYPES.MEDIUM
},
{
type: WORLD_OBJECTS.ROCK_BACKGROUND,
x: -100,
config: ROCK_TYPES.SMALL
},
{
type: WORLD_OBJECTS.ROCK_BACKGROUND,
x: 150,
config: ROCK_TYPES.LARGE
},
{
type: WORLD_OBJECTS.ROCK_BACKGROUND,
x: 400,
config: ROCK_TYPES.SMALL
},
{
type: WORLD_OBJECTS.ROCK_BACKGROUND,
x: 750,
config: ROCK_TYPES.MEDIUM
},
{
type: WORLD_OBJECTS.ROCK_BACKGROUND,
x: 1000,
config: ROCK_TYPES.SMALL
}
],
platforms: [
{
type: WORLD_OBJECTS.PLATFORM,
x: 300,
y: 300,
width: 200,
height: 20,
color: '#484'
},
{
type: WORLD_OBJECTS.PLATFORM,
x: 600,
y: 200,
width: 200,
height: 20,
color: '#484'
},
{
type: WORLD_OBJECTS.PLATFORM,
x: -200,
y: 250,
width: 200,
height: 20,
color: '#484'
}
],
foregroundTrees: [
{
type: WORLD_OBJECTS.MAPLE_FOREGROUND,
x: -150,
config: MAPLE_TYPES.SMALL
},
{
type: WORLD_OBJECTS.FIR_FOREGROUND,
x: 200,
config: FIR_TYPES.SMALL
},
{
type: WORLD_OBJECTS.MAPLE_FOREGROUND,
x: 450,
config: MAPLE_TYPES.LARGE
},
{
type: WORLD_OBJECTS.FIR_FOREGROUND,
x: 800,
config: FIR_TYPES.LARGE
},
{
type: WORLD_OBJECTS.MAPLE_FOREGROUND,
x: 1200,
config: MAPLE_TYPES.SMALL
}
],
foregroundRocks: [
{
type: WORLD_OBJECTS.ROCK_FOREGROUND,
x: 0,
config: ROCK_TYPES.SMALL
},
{
type: WORLD_OBJECTS.ROCK_FOREGROUND,
x: 300,
config: ROCK_TYPES.MEDIUM
},
{
type: WORLD_OBJECTS.ROCK_FOREGROUND,
x: 700,
config: ROCK_TYPES.SMALL
},
{
type: WORLD_OBJECTS.ROCK_FOREGROUND,
x: 950,
config: ROCK_TYPES.LARGE
}
],
backgroundGrass: [
{ type: WORLD_OBJECTS.GRASS_BACKGROUND, x: -350, config: GRASS_TYPES.SHORT },
{ type: WORLD_OBJECTS.GRASS_BACKGROUND, x: -180, config: GRASS_TYPES.TALL },
{ type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 100, config: GRASS_TYPES.TALL },
{ type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 320, config: GRASS_TYPES.SHORT },
{ type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 580, config: GRASS_TYPES.TALL },
{ type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 750, config: GRASS_TYPES.SHORT },
{ type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 950, config: GRASS_TYPES.TALL },
{ type: WORLD_OBJECTS.GRASS_BACKGROUND, x: 1050, config: GRASS_TYPES.SHORT }
],
foregroundGrass: [
{ type: WORLD_OBJECTS.GRASS_FOREGROUND, x: -280, config: GRASS_TYPES.TALL },
{ type: WORLD_OBJECTS.GRASS_FOREGROUND, x: -50, config: GRASS_TYPES.SHORT },
{ type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 150, config: GRASS_TYPES.TALL },
{ type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 420, config: GRASS_TYPES.SHORT },
{ type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 680, config: GRASS_TYPES.TALL },
{ type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 880, config: GRASS_TYPES.SHORT },
{ type: WORLD_OBJECTS.GRASS_FOREGROUND, x: 1150, config: GRASS_TYPES.TALL }
],
// Track grass animation states
grassStates: {}
});
// Rename current tree render function
const renderFirTree = (ctx, tree, groundY) => {
const { x, config } = tree;
const { width, height, canopyOffset, trunkColor, canopyColor } = config;
// Calculate trunk dimensions
const trunkWidth = width/3;
const trunkHeight = height - (height - canopyOffset)/2;
// Draw trunk
ctx.fillStyle = trunkColor;
ctx.fillRect(
x - trunkWidth/2,
groundY - trunkHeight,
trunkWidth,
trunkHeight
);
// Draw triangular canopy
ctx.fillStyle = canopyColor;
ctx.beginPath();
ctx.moveTo(x - width/2, groundY - canopyOffset);
ctx.lineTo(x + width/2, groundY - canopyOffset);
ctx.lineTo(x, groundY - height);
ctx.closePath();
ctx.fill();
};
// Add new maple tree render function
const renderMapleTree = (ctx, tree, groundY) => {
const { x, config } = tree;
const {
width, height, trunkHeight, canopyRadius,
leafClusters, trunkColor, canopyColor
} = config;
// Draw trunk
const trunkWidth = width/4;
ctx.fillStyle = trunkColor;
ctx.fillRect(
x - trunkWidth/2,
groundY - trunkHeight,
trunkWidth,
trunkHeight
);
// Draw leaf clusters as overlapping circles
ctx.fillStyle = canopyColor;
// Center cluster
ctx.beginPath();
ctx.arc(x, groundY - trunkHeight - canopyRadius, canopyRadius, 0, Math.PI * 2);
ctx.fill();
// Surrounding clusters
for (let i = 0; i < leafClusters; i++) {
const angle = (i / leafClusters) * Math.PI * 2;
const clusterX = x + Math.cos(angle) * (canopyRadius * 0.8);
const clusterY = groundY - trunkHeight - canopyRadius + Math.sin(angle) * (canopyRadius * 0.8);
ctx.beginPath();
ctx.arc(clusterX, clusterY, canopyRadius * 0.9, 0, Math.PI * 2);
ctx.fill();
}
};
// Main tree render function that handles both types
const renderTree = (ctx, tree, groundY) => {
if (tree.config.type === 'fir') {
renderFirTree(ctx, tree, groundY);
} else if (tree.config.type === 'maple') {
renderMapleTree(ctx, tree, groundY);
}
};
const renderRock = (ctx, rock, groundY) => {
const { x, config } = rock;
const { width, height, color, highlights } = config;
// Draw main rock shape (slightly irregular pentagon)
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(x - width/2, groundY);
ctx.lineTo(x - width/2 + width/6, groundY - height);
ctx.lineTo(x + width/3, groundY - height);
ctx.lineTo(x + width/2, groundY - height/2);
ctx.lineTo(x + width/2, groundY);
ctx.closePath();
ctx.fill();
// Add highlights
ctx.fillStyle = highlights;
ctx.beginPath();
ctx.moveTo(x - width/4, groundY - height);
ctx.lineTo(x, groundY - height);
ctx.lineTo(x + width/6, groundY - height/2);
ctx.lineTo(x - width/6, groundY - height/2);
ctx.closePath();
ctx.fill();
};
// Add grass rendering function
const renderGrass = (ctx, grass, groundY, time) => {
const { x, config } = grass;
const { width, height, blades, color, shadowColor } = config;
// Get or initialize grass state
const stateKey = `grass_${x}`;
if (!window.gameState.world.grassStates[stateKey]) {
window.gameState.world.grassStates[stateKey] = {
rustleAmount: 0,
rustleDecay: 0.95
};
}
const state = window.gameState.world.grassStates[stateKey];
// Check for player interaction
const player = window.gameState.player;
const interactionDistance = width;
const distanceToPlayer = Math.abs(player.x + player.width/2 - x);
if (distanceToPlayer < interactionDistance) {
state.rustleAmount = Math.min(1, state.rustleAmount + 0.3);
} else {
state.rustleAmount *= state.rustleDecay;
}
// Draw each blade of grass
for (let i = 0; i < blades; i++) {
const bladeX = x + (i - blades/2) * (width/blades);
const bladeWidth = width / blades * 0.7;
// Calculate blade curve based on rustle and time
const rustleOffset = Math.sin(time/300 + i) * 5 * state.rustleAmount;
const baseWave = Math.sin(time/1000 + i) * 2; // Gentle wave motion
// Draw blade
ctx.fillStyle = i % 2 === 0 ? color : shadowColor;
ctx.beginPath();
ctx.moveTo(bladeX, groundY);
// Control points for quadratic curve
const cp1x = bladeX + rustleOffset + baseWave;
const cp1y = groundY - height/2;
const cp2x = bladeX + rustleOffset * 1.5 + baseWave;
const cp2y = groundY - height;
// Draw curved blade
ctx.quadraticCurveTo(cp1x, cp1y, cp2x, cp2y);
ctx.quadraticCurveTo(cp1x + bladeWidth, cp1y, bladeX + bladeWidth, groundY);
ctx.fill();
}
};
// Collision detection helper
const checkCollision = (player, platform) => {
return player.x < platform.x + platform.width &&
player.x + player.width > platform.x &&
player.y < platform.y + platform.height &&
player.y + player.height > platform.y;
};
// World physics helper
const handleWorldCollisions = (player, world) => {
let onGround = false;
// Check ground collision first
const groundY = window.innerHeight - world.groundHeight;
if (player.y + player.height > groundY) {
player.y = groundY - player.height;
player.velocityY = 0;
onGround = true;
}
// Then check platform collisions
for (const platform of world.platforms) {
if (checkCollision(player, platform)) {
// Calculate overlap on each axis
const overlapX = Math.min(
player.x + player.width - platform.x,
platform.x + platform.width - player.x
);
const overlapY = Math.min(
player.y + player.height - platform.y,
platform.y + platform.height - player.y
);
// Resolve collision on the axis with smallest overlap
if (overlapX < overlapY) {
// Horizontal collision
if (player.x < platform.x) {
player.x = platform.x - player.width;
} else {
player.x = platform.x + platform.width;
}
player.velocityX = 0;
} else {
// Vertical collision
if (player.y < platform.y) {
player.y = platform.y - player.height;
player.velocityY = 0;
onGround = true;
} else {
player.y = platform.y + platform.height;
player.velocityY = 0;
}
}
}
}
return { ...player, jumping: !onGround };
};