From 66b0af0d4cabfa75a1af5c40f8f8ecdef67eb1ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Ta=C5=84czyk?= Date: Sun, 1 Dec 2024 11:18:29 +0100 Subject: [PATCH] enhance landscape rendering; add snow ground rendering with parallax effects and state management --- .../landscape/landscape-renderer.ts | 8 +- .../game-render/landscape/landscape-state.ts | 7 +- .../snow-ground/snow-ground-renderer.ts | 73 +++++++++++++++++ .../snow-ground/snow-ground-state.ts | 78 +++++++++++++++++++ .../snow-ground/snow-ground-types.ts | 57 ++++++++++++++ .../game-render/landscape/tree/tree-types.ts | 32 ++++---- 6 files changed, 236 insertions(+), 19 deletions(-) create mode 100644 games/xmas/src/screens/play/game-render/landscape/snow-ground/snow-ground-renderer.ts create mode 100644 games/xmas/src/screens/play/game-render/landscape/snow-ground/snow-ground-state.ts create mode 100644 games/xmas/src/screens/play/game-render/landscape/snow-ground/snow-ground-types.ts diff --git a/games/xmas/src/screens/play/game-render/landscape/landscape-renderer.ts b/games/xmas/src/screens/play/game-render/landscape/landscape-renderer.ts index c2f62080..d5197a7c 100644 --- a/games/xmas/src/screens/play/game-render/landscape/landscape-renderer.ts +++ b/games/xmas/src/screens/play/game-render/landscape/landscape-renderer.ts @@ -2,6 +2,7 @@ import { LandscapeState } from './landscape-state'; import { renderStars } from './star/star-renderer'; import { renderMountains } from './mountain/mountain-renderer'; import { renderTrees } from './tree/tree-renderer'; +import { renderSnowGrounds } from './snow-ground/snow-ground-renderer'; import { ViewportState } from '../render-state'; export function renderLandscape(ctx: CanvasRenderingContext2D, state: LandscapeState, viewport: ViewportState): void { @@ -11,15 +12,18 @@ export function renderLandscape(ctx: CanvasRenderingContext2D, state: LandscapeS // Enable crisp pixel art rendering ctx.imageSmoothingEnabled = false; - // Render stars + // Render stars (background) renderStars(ctx, state.stars.stars); // Render mountains (back to front) renderMountains(ctx, state.mountains.mountains, viewport); + // Render snow ground (between mountains and trees) + renderSnowGrounds(ctx, state.snowGround.grounds, viewport); + // Render trees (back to front) renderTrees(ctx, state.trees.trees, viewport); // Restore the context state ctx.restore(); -} +} \ No newline at end of file diff --git a/games/xmas/src/screens/play/game-render/landscape/landscape-state.ts b/games/xmas/src/screens/play/game-render/landscape/landscape-state.ts index 99fd7d20..f893802b 100644 --- a/games/xmas/src/screens/play/game-render/landscape/landscape-state.ts +++ b/games/xmas/src/screens/play/game-render/landscape/landscape-state.ts @@ -5,12 +5,15 @@ import { createTreeState, updateTreeState } from './tree/tree-state'; import { createStarState, updateStarState } from './star/star-state'; import { TreeState } from './tree/tree-types'; import { StarState } from './star/star-types'; +import { SnowGroundState } from './snow-ground/snow-ground-types'; +import { createSnowGroundState, updateSnowGroundState } from './snow-ground/snow-ground-state'; // Combined landscape state type export type LandscapeState = { mountains: MountainState; trees: TreeState; stars: StarState; + snowGround: SnowGroundState; }; /** @@ -22,6 +25,7 @@ export function createLandscapeState(): LandscapeState { mountains: createMountainState(), trees: createTreeState(), stars: createStarState(), + snowGround: createSnowGroundState(), }; } @@ -34,5 +38,6 @@ export function updateLandscapeState(world: GameWorldState, state: LandscapeStat mountains: updateMountainState(world, state.mountains), trees: updateTreeState(world, state.trees), stars: updateStarState(world, state.stars, deltaTime), + snowGround: updateSnowGroundState(world, state.snowGround), }; -} +} \ No newline at end of file diff --git a/games/xmas/src/screens/play/game-render/landscape/snow-ground/snow-ground-renderer.ts b/games/xmas/src/screens/play/game-render/landscape/snow-ground/snow-ground-renderer.ts new file mode 100644 index 00000000..7a062f11 --- /dev/null +++ b/games/xmas/src/screens/play/game-render/landscape/snow-ground/snow-ground-renderer.ts @@ -0,0 +1,73 @@ +import { GAME_WORLD_HEIGHT } from '../../../game-world/game-world-consts'; +import { SnowGround, SNOW_GROUND } from './snow-ground-types'; +import { ViewportState } from '../../render-state'; + +/** + * Apply parallax translation to a point based on viewport position and parallax factor + * Returns pixel-perfect coordinates for smooth rendering + */ +function applyParallaxTranslation( + x: number, + y: number, + viewport: ViewportState, + parallaxFactor: number, +): { x: number; y: number } { + return { + x: Math.round(x + viewport.x * parallaxFactor), + y: Math.round(y + viewport.y * parallaxFactor), + }; +} + +/** + * Get color for specific snow ground layer + */ +function getLayerColor(layer: number): string { + return layer === 0 ? SNOW_GROUND.COLORS.DISTANT : layer === 1 ? SNOW_GROUND.COLORS.MIDDLE : SNOW_GROUND.COLORS.NEAR; +} + +/** + * Render a single snow ground piece using pixel art style with parallax effect + */ +function renderSnowGround(ctx: CanvasRenderingContext2D, ground: SnowGround, viewport: ViewportState): void { + // Calculate base position with parallax + const basePosition = applyParallaxTranslation(ground.x, GAME_WORLD_HEIGHT, viewport, ground.parallaxFactor); + + // Calculate dimensions + const width = Math.round(ground.width); + const height = Math.round(GAME_WORLD_HEIGHT * 0.025); // 10% of world height for snow ground + + // Draw rectangular snow ground piece + ctx.beginPath(); + ctx.rect(basePosition.x, basePosition.y - height, width, height); + ctx.fill(); +} + +/** + * Render snow grounds by layer with parallax effect + */ +export function renderSnowGrounds(ctx: CanvasRenderingContext2D, grounds: SnowGround[], viewport: ViewportState): void { + // Sort grounds by layer (back to front) + const sortedGrounds = [...grounds].sort((a, b) => a.layer - b.layer); + + // Group grounds by layer for efficient rendering + const groundsByLayer = sortedGrounds.reduce((acc, ground) => { + acc[ground.layer] = acc[ground.layer] || []; + acc[ground.layer].push(ground); + return acc; + }, {} as Record); + + // Save current context state + ctx.save(); + + // Render each layer with its color + Object.entries(groundsByLayer).forEach(([layer, layerGrounds]) => { + // Set layer-specific color + ctx.fillStyle = getLayerColor(Number(layer)); + + // Render all grounds in this layer + layerGrounds.forEach((ground) => renderSnowGround(ctx, ground, viewport)); + }); + + // Restore context state + ctx.restore(); +} diff --git a/games/xmas/src/screens/play/game-render/landscape/snow-ground/snow-ground-state.ts b/games/xmas/src/screens/play/game-render/landscape/snow-ground/snow-ground-state.ts new file mode 100644 index 00000000..07740b19 --- /dev/null +++ b/games/xmas/src/screens/play/game-render/landscape/snow-ground/snow-ground-state.ts @@ -0,0 +1,78 @@ +import { GAME_WORLD_WIDTH } from '../../../game-world/game-world-consts'; +import { GameWorldState } from '../../../game-world/game-world-types'; +import { SnowGround, SNOW_GROUND, SnowGroundState } from './snow-ground-types'; + +/** + * Get parallax factor for a specific layer with slight randomization + */ +function getParallaxFactor(layer: number): number { + // Get base parallax factor for the layer + const baseParallax = + layer === 0 ? SNOW_GROUND.PARALLAX.DISTANT : layer === 1 ? SNOW_GROUND.PARALLAX.MIDDLE : SNOW_GROUND.PARALLAX.NEAR; + + // Add random variation within the specified range + const variation = (Math.random() * 2 - 1) * SNOW_GROUND.PARALLAX.VARIATION; + return Math.max(0, Math.min(1, baseParallax + variation)); +} + +/** + * Get number of snow ground pieces for a specific layer + */ +function getGroundDensityForLayer(layer: number): number { + return layer === 0 + ? SNOW_GROUND.DENSITY.DISTANT + : layer === 1 + ? SNOW_GROUND.DENSITY.MIDDLE + : SNOW_GROUND.DENSITY.NEAR; +} + +/** + * Generate snow ground pieces for a specific layer + */ +function generateGrounds(layer: number): SnowGround[] { + const grounds: SnowGround[] = []; + const count = getGroundDensityForLayer(layer); + const baseSpacing = GAME_WORLD_WIDTH / count; + + for (let i = 0; i < count; i++) { + // Calculate width with random variation + const width = SNOW_GROUND.MIN_WIDTH + Math.random() * (SNOW_GROUND.MAX_WIDTH - SNOW_GROUND.MIN_WIDTH); + + // Calculate position with spacing variation + const baseX = (i * GAME_WORLD_WIDTH) / count; + const spacingVariation = baseSpacing * SNOW_GROUND.SPACING_VARIATION; + const x = baseX + (Math.random() - 0.5) * spacingVariation; + + grounds.push({ + x, + width, + layer, + parallaxFactor: getParallaxFactor(layer), + }); + } + + return grounds; +} + +/** + * Create initial snow ground state + */ +export function createSnowGroundState(): SnowGroundState { + const grounds: SnowGround[] = []; + + // Generate snow ground pieces for each layer + for (let layer = 0; layer < SNOW_GROUND.LAYERS; layer++) { + grounds.push(...generateGrounds(layer)); + } + + return { grounds }; +} + +/** + * Update snow ground state + * Currently snow grounds are static, but this function is included for consistency + * and potential future animations + */ +export function updateSnowGroundState(_world: GameWorldState, state: SnowGroundState): SnowGroundState { + return state; +} diff --git a/games/xmas/src/screens/play/game-render/landscape/snow-ground/snow-ground-types.ts b/games/xmas/src/screens/play/game-render/landscape/snow-ground/snow-ground-types.ts new file mode 100644 index 00000000..557466c6 --- /dev/null +++ b/games/xmas/src/screens/play/game-render/landscape/snow-ground/snow-ground-types.ts @@ -0,0 +1,57 @@ +import { GAME_WORLD_WIDTH } from '../../../game-world/game-world-consts'; + +/** + * Snow ground piece interface + * Represents a single piece of snow ground with position, width, and layer information + */ +export interface SnowGround { + x: number; // X position of the snow ground piece + width: number; // Width of the snow ground piece + layer: number; // Layer number (0: distant, 1: middle, 2: near) + parallaxFactor: number; // Factor for parallax movement +} + +/** + * Snow ground state interface + * Contains all snow ground pieces + */ +export interface SnowGroundState { + grounds: SnowGround[]; +} + +/** + * Snow ground constants + */ +export const SNOW_GROUND = { + // Number of layers for depth perception + LAYERS: 3, + + // Width configuration + MIN_WIDTH: GAME_WORLD_WIDTH / 8, + MAX_WIDTH: GAME_WORLD_WIDTH / 4, + + // Spacing between snow ground pieces + SPACING_VARIATION: 0.2, // 20% variation in spacing + + // Density (number of pieces) per layer + DENSITY: { + DISTANT: 8, // Fewer pieces in the distance + MIDDLE: 10, // Medium density in the middle + NEAR: 12, // More pieces in the foreground + }, + + // Parallax movement factors + PARALLAX: { + DISTANT: 0.2, // Slowest movement in the background + MIDDLE: 0.5, // Medium movement in the middle + NEAR: 0.8, // Fastest movement in the foreground + VARIATION: 0.05, // Small random variation in parallax + }, + + // Colors for each layer + COLORS: { + DISTANT: '#a3b5c7', // Light grayish blue for distant snow + MIDDLE: '#cad5e0', // Lighter grayish blue for middle ground + NEAR: '#ffffff', // Pure white for nearest snow + }, +} as const; diff --git a/games/xmas/src/screens/play/game-render/landscape/tree/tree-types.ts b/games/xmas/src/screens/play/game-render/landscape/tree/tree-types.ts index d6c0a2d9..826bd0af 100644 --- a/games/xmas/src/screens/play/game-render/landscape/tree/tree-types.ts +++ b/games/xmas/src/screens/play/game-render/landscape/tree/tree-types.ts @@ -3,34 +3,34 @@ import { GAME_WORLD_HEIGHT, GAME_WORLD_WIDTH } from '../../../game-world/game-wo // Tree silhouette colors from back to front export const TREE_COLORS = { DISTANT: '#000033', // Most distant trees - MIDDLE: '#000022', // Middle layer trees - NEAR: '#000011', // Nearest trees + MIDDLE: '#000022', // Middle layer trees + NEAR: '#000011', // Nearest trees } as const; // Tree configuration export const TREES = { LAYERS: 3, // Number of tree layers for depth MAX_HEIGHT: GAME_WORLD_HEIGHT * 0.15, // Maximum tree height - MIN_HEIGHT: GAME_WORLD_HEIGHT * 0.1, // Minimum tree height + MIN_HEIGHT: GAME_WORLD_HEIGHT * 0.1, // Minimum tree height WIDTH_RATIO: 0.6, // Tree width as ratio of height DENSITY: { // Trees per layer (more trees in closer layers) DISTANT: Math.floor(GAME_WORLD_WIDTH / 150), // Fewest trees, most distant - MIDDLE: Math.floor(GAME_WORLD_WIDTH / 100), // Medium number of trees - NEAR: Math.floor(GAME_WORLD_WIDTH / 50), // Most trees, nearest layer + MIDDLE: Math.floor(GAME_WORLD_WIDTH / 100), // Medium number of trees + NEAR: Math.floor(GAME_WORLD_WIDTH / 50), // Most trees, nearest layer }, PARALLAX: { // Parallax factors for each layer (0 = no movement, 1 = full movement) - DISTANT: 0.65, // Continuing from mountain parallax (mountains end at 0.6) - MIDDLE: 0.8, // Medium movement - NEAR: 0.95, // Fastest movement, nearest layer + DISTANT: 0.75, // Continuing from mountain parallax (mountains end at 0.6) + MIDDLE: 0.8, // Medium movement + NEAR: 0.95, // Fastest movement, nearest layer VARIATION: 0.05, // Random variation in parallax factor }, // Size multipliers for each layer to enhance depth perception SIZE_MULTIPLIER: { - DISTANT: 0.7, // Smallest trees in distance - MIDDLE: 0.85, // Medium sized trees - NEAR: 1.0, // Full size trees in front + DISTANT: 0.7, // Smallest trees in distance + MIDDLE: 0.85, // Medium sized trees + NEAR: 1.0, // Full size trees in front }, // Spacing variation to avoid uniform appearance SPACING_VARIATION: 0.3, // How much random variation in tree spacing @@ -38,14 +38,14 @@ export const TREES = { // Tree type definition export type Tree = { - x: number; // Base x position - height: number; // Height of the tree - width: number; // Width of the tree - layer: number; // Which layer (0 = distant, 1 = middle, 2 = near) + x: number; // Base x position + height: number; // Height of the tree + width: number; // Width of the tree + layer: number; // Which layer (0 = distant, 1 = middle, 2 = near) parallaxFactor: number; // Factor affecting parallax movement }; // Tree state type export type TreeState = { trees: Tree[]; -}; \ No newline at end of file +};