From ef10b35f63301b8435be9fc23641cf695b28854b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Ta=C5=84czyk?= Date: Thu, 22 Aug 2024 23:12:03 +0300 Subject: [PATCH] tsunami rendering improvement --- .../src/game-states/gameplay/game-logic.ts | 1 + .../src/game-states/gameplay/game-render.ts | 12 ++-- .../gameplay/levels/01-the-first-step.ts | 2 +- .../gameplay/render/bonus-effect-render.ts | 69 +++++++++++-------- .../gameplay/render/grid-render.ts | 30 -------- 5 files changed, 50 insertions(+), 64 deletions(-) diff --git a/js13k2024/game/src/game-states/gameplay/game-logic.ts b/js13k2024/game/src/game-states/gameplay/game-logic.ts index 7abc1fd6..501081e4 100644 --- a/js13k2024/game/src/game-states/gameplay/game-logic.ts +++ b/js13k2024/game/src/game-states/gameplay/game-logic.ts @@ -335,6 +335,7 @@ const handleTsunamiEffect = (gameState: GameState): void => { startGameOverAnimation(gameState); } gameState.monsters = []; + gameState.tsunamiLevel = 0; } }; diff --git a/js13k2024/game/src/game-states/gameplay/game-render.ts b/js13k2024/game/src/game-states/gameplay/game-render.ts index f3d00424..79a46083 100644 --- a/js13k2024/game/src/game-states/gameplay/game-render.ts +++ b/js13k2024/game/src/game-states/gameplay/game-render.ts @@ -10,7 +10,7 @@ import { calculateShakeOffset, interpolatePosition } from './render/animation-ut import { drawTooltip } from './render/tooltip-render'; import { drawElectricalDischarges } from './render/discharges-render'; import { drawPlatform } from './render/grid-render'; -import { drawBlasterShot, drawSlideTrail, drawTsunamiEffect } from './render/bonus-effect-render'; +import { drawBlasterShot, drawSlideTrail, drawTsunamiWave, generateTsunamiWaves } from './render/bonus-effect-render'; export const PLATFORM_HEIGHT = 20; @@ -42,10 +42,8 @@ export const drawGameState = ( // Draw the grid drawGrid(ctx, gridSize, gameState); - // Draw tsunami effect - if (gameState.tsunamiLevel > 0) { - drawTsunamiEffect(ctx, gameState, gridSize, cellSize); - } + // Generate tsunami effect + const tsunamiWaves = gameState.tsunamiLevel > 0 ? generateTsunamiWaves(gameState, gridSize, cellSize) : []; // Draw electrical discharges drawElectricalDischarges(ctx, gridSize, gameState.monsterSpawnSteps, gameState.player.moveTimestamp, cellSize); @@ -57,6 +55,7 @@ export const drawGameState = ( // Prepare all game objects for sorting const allObjects = [ + ...tsunamiWaves.map((obj) => ({ position: obj.grid, type: 'wave', obj }) as const), ...gameState.obstacles.map((obj) => ({ position: obj.position, type: 'obstacle', obj }) as const), ...gameState.bonuses.map((obj) => ({ position: obj.position, type: 'bonus', obj }) as const), ...gameState.landMines.map((obj) => ({ position: obj, type: 'landMine' }) as const), @@ -82,6 +81,9 @@ export const drawGameState = ( for (const sortedObject of sortedObjects) { const { type, position } = sortedObject; switch (type) { + case 'wave': + drawTsunamiWave(ctx, sortedObject.obj); + break; case 'obstacle': drawObstacles(ctx, [sortedObject.obj], cellSize); break; diff --git a/js13k2024/game/src/game-states/gameplay/levels/01-the-first-step.ts b/js13k2024/game/src/game-states/gameplay/levels/01-the-first-step.ts index cb6565da..2894e8fa 100644 --- a/js13k2024/game/src/game-states/gameplay/levels/01-the-first-step.ts +++ b/js13k2024/game/src/game-states/gameplay/levels/01-the-first-step.ts @@ -17,7 +17,7 @@ export const generateLevel = (): [GameState, LevelConfig, string] => { state.goal = createPosition(6, 3); state.monsters = [createMonster(3, 0)]; state.obstacles = [createObstacle(2, 3), createObstacle(5, 2), createObstacle(5, 3)]; - state.bonuses = [createBonus(1, 3, BonusType.Sokoban)]; + state.bonuses = [createBonus(1, 3, BonusType.Tsunami)]; return [state, config, config.levelStory]; }; diff --git a/js13k2024/game/src/game-states/gameplay/render/bonus-effect-render.ts b/js13k2024/game/src/game-states/gameplay/render/bonus-effect-render.ts index 6b475bc2..e62ccf98 100644 --- a/js13k2024/game/src/game-states/gameplay/render/bonus-effect-render.ts +++ b/js13k2024/game/src/game-states/gameplay/render/bonus-effect-render.ts @@ -2,45 +2,58 @@ import { interpolatePosition } from './animation-utils'; import { BlasterShot, Direction, GameState, Position } from '../gameplay-types'; import { toIsometric } from './isometric-utils'; -export const drawTsunamiEffect = ( - ctx: CanvasRenderingContext2D, - gameState: GameState, - gridSize: number, - cellSize: number, -) => { +type TsunamiWave = { + tsunamiLevel: number; + grid: Position; + iso: [Position, Position, Position, Position]; +}; + +export const generateTsunamiWaves = (gameState: GameState, gridSize: number, cellSize: number): TsunamiWave[] => { const { tsunamiLevel } = gameState; const maxWaterHeight = cellSize * 0.8; // Maximum water height when tsunamiLevel reaches 13 - ctx.save(); - ctx.globalAlpha = 0.6; // Make the water slightly transparent + const waves: TsunamiWave[] = []; for (let y = 0; y < gridSize; y++) { for (let x = 0; x < gridSize; x++) { - const { x: isoX, y: isoY } = toIsometric(x, y); + const topL = toIsometric(x, y); + const topR = toIsometric(x + 1, y); + const bottomL = toIsometric(x, y + 1); + const bottomR = toIsometric(x + 1, y + 1); const waterHeight = (tsunamiLevel / 13) * maxWaterHeight; - ctx.fillStyle = `rgba(0, 100, 255, ${tsunamiLevel / 13})`; // Blue color with increasing opacity - ctx.beginPath(); - ctx.moveTo(isoX, isoY); - ctx.lineTo(isoX + cellSize / 2, isoY + cellSize / 4); - ctx.lineTo(isoX, isoY + cellSize / 2); - ctx.lineTo(isoX - cellSize / 2, isoY + cellSize / 4); - ctx.closePath(); - ctx.fill(); - - // Add wave effect - ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)'; - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.moveTo(isoX - cellSize / 2, isoY + cellSize / 4 - waterHeight + Math.sin(Date.now() / 200 + x * 0.5) * 5); - ctx.lineTo( - isoX + cellSize / 2, - isoY + cellSize / 4 - waterHeight + Math.sin(Date.now() / 200 + (x + 1) * 0.5) * 5, - ); - ctx.stroke(); + const wave1 = waterHeight + Math.sin(Date.now() / 200 + x * 0.5) * 5; + const wave2 = waterHeight + Math.sin(Date.now() / 200 + (x + 1) * 0.5) * 5; + + waves.push({ + tsunamiLevel, + grid: { x, y }, + iso: [ + { x: topL.x, y: topL.y - wave1 }, + { x: bottomL.x, y: bottomL.y - wave2 }, + { x: bottomR.x, y: bottomR.y - wave2 }, + { x: topR.x, y: topR.y - wave1 }, + ], + }); } } + return waves; +}; + +export const drawTsunamiWave = (ctx: CanvasRenderingContext2D, wave: TsunamiWave) => { + ctx.save(); + ctx.globalAlpha = 0.6; // Make the water slightly transparent + + ctx.fillStyle = `rgba(0, 100, 255, ${wave.tsunamiLevel / 13})`; // Blue color with increasing opacity + ctx.beginPath(); + ctx.moveTo(wave.iso[0].x, wave.iso[0].y); + for (let i = 1; i < wave.iso.length; i++) { + ctx.lineTo(wave.iso[i].x, wave.iso[i].y); + } + ctx.closePath(); + ctx.fill(); + ctx.restore(); }; diff --git a/js13k2024/game/src/game-states/gameplay/render/grid-render.ts b/js13k2024/game/src/game-states/gameplay/render/grid-render.ts index 22d2dcdc..0e5be5ae 100644 --- a/js13k2024/game/src/game-states/gameplay/render/grid-render.ts +++ b/js13k2024/game/src/game-states/gameplay/render/grid-render.ts @@ -63,11 +63,6 @@ export const drawGrid = (ctx: CanvasRenderingContext2D, gridSize: number, gameSt ctx.stroke(); } - // Draw Tsunami effect - if (gameState.tsunamiLevel > 0) { - drawTsunamiTile(ctx, x, y, gameState.tsunamiLevel); - } - // Draw Slide effect if active if (gameState.isSliding) { drawSlideTile(ctx, x, y); @@ -76,31 +71,6 @@ export const drawGrid = (ctx: CanvasRenderingContext2D, gridSize: number, gameSt } }; -const drawTsunamiTile = (ctx: CanvasRenderingContext2D, x: number, y: number, tsunamiLevel: number) => { - const { x: isoX, y: isoY } = toIsometric(x, y); - const waterHeight = (tsunamiLevel / 13) * TILE_HEIGHT * 0.5; // Max water height is half of tile height - - ctx.fillStyle = `rgba(0, 100, 255, ${tsunamiLevel / 26})`; // Increasing opacity as tsunami level increases - ctx.beginPath(); - ctx.moveTo(isoX, isoY - waterHeight); - ctx.lineTo(isoX + TILE_WIDTH / 2, isoY + TILE_HEIGHT / 2 - waterHeight); - ctx.lineTo(isoX, isoY + TILE_HEIGHT - waterHeight); - ctx.lineTo(isoX - TILE_WIDTH / 2, isoY + TILE_HEIGHT / 2 - waterHeight); - ctx.closePath(); - ctx.fill(); - - // Add wave effect - ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)'; - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.moveTo(isoX - TILE_WIDTH / 2, isoY + TILE_HEIGHT / 2 - waterHeight + Math.sin(Date.now() / 200 + x * 0.5) * 3); - ctx.lineTo( - isoX + TILE_WIDTH / 2, - isoY + TILE_HEIGHT / 2 - waterHeight + Math.sin(Date.now() / 200 + (x + 1) * 0.5) * 3, - ); - ctx.stroke(); -}; - const drawSlideTile = (ctx: CanvasRenderingContext2D, x: number, y: number) => { const { x: isoX, y: isoY } = toIsometric(x, y);