Skip to content

Commit

Permalink
refactor landscape rendering; simplify rendering logic for mountains,…
Browse files Browse the repository at this point in the history
… snow ground, and trees, and enhance snow ground generation
  • Loading branch information
gtanczyk committed Dec 2, 2024
1 parent 7e8131f commit 6d541f2
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 298 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ import { renderSnowGrounds } from './snow-ground/snow-ground-renderer';
import { ViewportState } from '../render-state';
import { devConfig } from '../../dev/dev-config';

/**
* Render the complete landscape with all its components
* Rendering order (back to front):
* 1. Stars (background)
* 2. Mountains (with parallax)
* 3. Snow Ground (simplified)
* 4. Trees (simplified)
*/
export function renderLandscape(ctx: CanvasRenderingContext2D, state: LandscapeState, viewport: ViewportState): void {
// Save the current context state
ctx.save();
Expand All @@ -16,19 +24,19 @@ export function renderLandscape(ctx: CanvasRenderingContext2D, state: LandscapeS
// Render stars (background)
renderStars(ctx, state.stars.stars);

// Render mountains (back to front)
// Render mountains (with parallax)
if (devConfig.renderMountains) {
renderMountains(ctx, state.mountains.mountains, viewport);
}

// Render snow ground (between mountains and trees)
// Render snow ground (simplified, no parallax)
if (devConfig.renderSnowGround) {
renderSnowGrounds(ctx, state.snowGround.grounds, viewport);
renderSnowGrounds(ctx, state.snowGround.grounds);
}

// Render trees (back to front)
// Render trees (simplified, no parallax)
if (devConfig.renderTrees) {
renderTrees(ctx, state.trees.trees, viewport);
renderTrees(ctx, state.trees.trees);
}

// Restore the context state
Expand Down
Original file line number Diff line number Diff line change
@@ -1,72 +1,36 @@
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
* Render a single snow ground piece using pixel art style
*/
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);
function renderSnowGround(ctx: CanvasRenderingContext2D, ground: SnowGround): void {
// Calculate screen position
const screenX = Math.round(ground.x);
const screenY = Math.round(GAME_WORLD_HEIGHT);

// Calculate dimensions
const width = Math.round(ground.width);
const height = Math.round(GAME_WORLD_HEIGHT * 0.025); // 10% of world height for snow ground
const height = Math.round(GAME_WORLD_HEIGHT * SNOW_GROUND.HEIGHT_RATIO);

// Draw rectangular snow ground piece
ctx.beginPath();
ctx.rect(basePosition.x, basePosition.y - height, width, height);
ctx.rect(screenX, screenY - height, width, height);
ctx.fill();
}

/**
* Render snow grounds by layer with parallax effect
* Render all snow grounds with uniform appearance
*/
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<number, SnowGround[]>);

export function renderSnowGrounds(ctx: CanvasRenderingContext2D, grounds: SnowGround[]): void {
// 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));
// Set snow ground color
ctx.fillStyle = SNOW_GROUND.COLOR;

// Render all grounds in this layer
layerGrounds.forEach((ground) => renderSnowGround(ctx, ground, viewport));
});
// Render all snow ground pieces
grounds.forEach((ground) => renderSnowGround(ctx, ground));

// Restore context state
ctx.restore();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,24 @@ 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
* Generate snow ground pieces with uniform distribution
*/
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[] {
function generateGrounds(): SnowGround[] {
const grounds: SnowGround[] = [];
const count = getGroundDensityForLayer(layer);
const baseSpacing = GAME_WORLD_WIDTH / count;
const baseSpacing = GAME_WORLD_WIDTH / SNOW_GROUND.DENSITY;

for (let i = 0; i < count; i++) {
for (let i = 0; i < SNOW_GROUND.DENSITY; 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 baseX = (i * GAME_WORLD_WIDTH) / SNOW_GROUND.DENSITY;
const spacingVariation = baseSpacing * SNOW_GROUND.SPACING_VARIATION;
const x = baseX + (Math.random() - 0.5) * spacingVariation;

grounds.push({
x,
width,
layer,
parallaxFactor: getParallaxFactor(layer),
});
}

Expand All @@ -58,19 +31,14 @@ function generateGrounds(layer: number): SnowGround[] {
* 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 };
return {
grounds: generateGrounds(),
};
}

/**
* Update snow ground state
* Currently snow grounds are static, but this function is included for consistency
* Snow grounds are static, but this function is included for consistency
* and potential future animations
*/
export function updateSnowGroundState(_world: GameWorldState, state: SnowGroundState): SnowGroundState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ 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
* Represents a single piece of snow ground with position and width
*/
export interface SnowGround {
x: number; // X position of the snow ground piece
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
}

/**
Expand All @@ -23,35 +21,15 @@ export interface SnowGroundState {
* 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
},
MIN_WIDTH: GAME_WORLD_WIDTH / 8, // Minimum width of a snow ground piece
MAX_WIDTH: GAME_WORLD_WIDTH / 4, // Maximum width of a snow ground piece

// 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
},
// Distribution configuration
DENSITY: Math.floor(GAME_WORLD_WIDTH / 100), // Number of snow ground pieces
SPACING_VARIATION: 0.2, // 20% variation in spacing

// 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;
// Visual configuration
COLOR: '#ffffff', // Pure white for snow
HEIGHT_RATIO: 0.025, // Height as ratio of world height (2.5%)
} as const;
Original file line number Diff line number Diff line change
@@ -1,100 +1,48 @@
import { GAME_WORLD_HEIGHT } from '../../../game-world/game-world-consts';
import { Tree, TREE_COLORS } from './tree-types';
import { ViewportState } from '../../render-state';
import { SNOW_GROUND } from '../snow-ground/snow-ground-types';
import { Tree, TREE_COLOR } from './tree-types';

/**
* Apply parallax translation to a point based on viewport position and parallax factor
* Returns pixel-perfect coordinates for smooth rendering
* Render a single tree using pixel art style
*/
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 tree layer
*/
function getLayerColor(layer: number): string {
return layer === 0
? TREE_COLORS.DISTANT
: layer === 1
? TREE_COLORS.MIDDLE
: TREE_COLORS.NEAR;
}

/**
* Render a single tree using pixel art style with parallax effect
*/
function renderTree(
ctx: CanvasRenderingContext2D,
tree: Tree,
viewport: ViewportState
): void {
// Calculate base position with parallax
const basePosition = applyParallaxTranslation(
tree.x,
GAME_WORLD_HEIGHT,
viewport,
tree.parallaxFactor
);
function renderTree(ctx: CanvasRenderingContext2D, tree: Tree): void {
// Calculate screen position
const screenX = Math.round(tree.x);
const screenY = Math.round(GAME_WORLD_HEIGHT) - GAME_WORLD_HEIGHT * SNOW_GROUND.HEIGHT_RATIO;

// Calculate tree dimensions
const width = Math.round(tree.width);
const height = Math.round(tree.height);
// Calculate tree points with parallax
const baseLeft = basePosition.x;
const baseRight = basePosition.x + width;
const baseY = basePosition.y;
const topX = basePosition.x + width / 2;
const topY = basePosition.y - height;

// Calculate tree points
const baseLeft = screenX;
const baseRight = screenX + width;
const baseY = screenY;
const topX = screenX + width / 2;
const topY = screenY - height;

// Draw triangular tree shape
ctx.beginPath();
ctx.moveTo(baseLeft, baseY); // Base left
ctx.lineTo(topX, topY); // Top
ctx.lineTo(topX, topY); // Top
ctx.lineTo(baseRight, baseY); // Base right
ctx.closePath();
ctx.fill();
}

/**
* Render trees by layer with parallax effect
* Render all trees with uniform appearance
*/
export function renderTrees(
ctx: CanvasRenderingContext2D,
trees: Tree[],
viewport: ViewportState
): void {
// Sort trees by layer (back to front)
const sortedTrees = [...trees].sort((a, b) => a.layer - b.layer);

// Group trees by layer for efficient rendering
const treesByLayer = sortedTrees.reduce((acc, tree) => {
acc[tree.layer] = acc[tree.layer] || [];
acc[tree.layer].push(tree);
return acc;
}, {} as Record<number, Tree[]>);

export function renderTrees(ctx: CanvasRenderingContext2D, trees: Tree[]): void {
// Save current context state
ctx.save();

// Render each layer with its color
Object.entries(treesByLayer).forEach(([layer, layerTrees]) => {
// Set layer-specific color
ctx.fillStyle = getLayerColor(Number(layer));
// Set tree color
ctx.fillStyle = TREE_COLOR;

// Render all trees in this layer
layerTrees.forEach((tree) => renderTree(ctx, tree, viewport));
});
// Render all trees
trees.forEach((tree) => renderTree(ctx, tree));

// Restore context state
ctx.restore();
}
}
Loading

0 comments on commit 6d541f2

Please sign in to comment.