diff --git a/src/scene/act-scene.ts b/src/scene/act-scene.ts index ce5a57f..f7f2c83 100644 --- a/src/scene/act-scene.ts +++ b/src/scene/act-scene.ts @@ -31,6 +31,8 @@ import { } from '../game/d2map.ts' import type { CollisionGrid } from '../game/d2map.ts' import { depthInsertIndex } from '../game/map.ts' +import { GameEngine, syncEngineState } from "../game/engine.ts" +import { DEMO_MONSTERS, DEMO_EXPERIENCE, DEMO_BASES, DEMO_AFFIXES, DEMO_SKILLS, DEMO_NPCS, DEMO_QUESTS } from "../game/demo-data.ts" import { buildAtlas } from '../render/atlas.ts' import type { AtlasFrame, SpriteAtlas } from '../render/atlas.ts' import { SpriteRenderer } from '../render/renderer.ts' @@ -702,7 +704,6 @@ function runScene(runtime: MapRuntime, renderer: SpriteRenderer, started: number const actSelect = document.querySelector('#act')! const variantSelect = document.querySelector('#variant')! - const player = { x: runtime.spawn.x, y: runtime.spawn.y, facing: 0, walkFrame: 0, moving: false } const input = new KeyboardInput() input.attach() let skippedDraws = 0 @@ -711,34 +712,76 @@ function runScene(runtime: MapRuntime, renderer: SpriteRenderer, started: number const camera = new ViewportCamera(canvas, runtime.widthPx, runtime.heightPx) camera.attach() + + let playerMoving = false + let playerWalkFrame = 0 + const engine = new GameEngine( + { + widthPx: runtime.widthPx, + heightPx: runtime.heightPx, + overlap: (x, y) => (collides(runtime.grid, x, y) ? 1 : 0), + }, + { + spawn: runtime.spawn, + stats: [], + xpTable: DEMO_EXPERIENCE, + itemBases: DEMO_BASES, + prefixAffixes: DEMO_AFFIXES, + suffixAffixes: DEMO_AFFIXES, + skills: DEMO_SKILLS, + npcDefs: DEMO_NPCS, + questDefs: DEMO_QUESTS, + combatOptions: { + playerSpeed: WALK_SPEED, + playerReach: 52, + playerCooldownTicks: 12, + playerDamage: 7, + playerManaPerAttack: 2, + respawnTicks: 75, + }, + lootSeed: 0x5eed, + talkRadius: 80, + pickupRadius: 64, + inventoryCols: 10, + inventoryRows: 4, + } + ) + const loop = new GameLoop({ tickRate: 25, onTick: () => { + engine.tick({ + movement: input.movement(), + attacking: input.attacking, + pickingUp: input.pickingUp, + talking: input.talking, + digits: input.digits, + saving: input.saving, + loading: input.loading, + }) + + const player = engine.world.player const movement = input.movement() - player.moving = movement.x !== 0 || movement.y !== 0 - if (player.moving) { - // Input is screen-space: the diamond lattice is rotated 45° from the - // screen axes, so "north" walks along a cell diagonal. - const length = Math.hypot(movement.x, movement.y) - const step = (WALK_SPEED / 25) / length - if (!collides(runtime.grid, player.x + movement.x * step, player.y)) player.x += movement.x * step - if (!collides(runtime.grid, player.x, player.y + movement.y * step)) player.y += movement.y * step - player.facing = facingOf(movement.x, movement.y) - player.walkFrame = (player.walkFrame + 1) % 24 + let playerMoving = movement.x !== 0 || movement.y !== 0 + if (playerMoving) { + playerWalkFrame = (playerWalkFrame + 1) % 24 } else { - player.walkFrame = 0 + playerWalkFrame = 0 } - state.tick = loop.tick + + state.tick = engine.world.tick state.x = player.x state.y = player.y state.facing = player.facing + + syncEngineState(engine, state) }, onRender: () => { if (state.pagesAtFirstFrame < 0) { state.pagesAtFirstFrame = runtime.pages.filter(page => page !== null).length state.firstFrameMs = Math.round(performance.now() - started) } - renderer.begin({ x: player.x, y: player.y - 16, zoom: camera.zoom }, [0.03, 0.03, 0.04]) + renderer.begin({ x: engine.world.player.x, y: engine.world.player.y - 16, zoom: camera.zoom }, [0.03, 0.03, 0.04]) skippedDraws = 0 const drawTiles = (list: readonly Drawable[], from: number, to: number): void => { for (let index = from; index < to; index += 1) { @@ -749,7 +792,7 @@ function runScene(runtime: MapRuntime, renderer: SpriteRenderer, started: number } } drawTiles(runtime.floors, 0, runtime.floors.length) - const cell = cellOf(runtime.grid, player.x, player.y) + const cell = cellOf(runtime.grid, engine.world.player.x, engine.world.player.y) const playerDepth = cell.x + cell.y let wallIdx = 0 @@ -790,20 +833,20 @@ function runScene(runtime: MapRuntime, renderer: SpriteRenderer, started: number drawUpToDepth(playerDepth) if (character === null) { // Placeholder until the character archive has been read. - renderer.drawSolid(player.x - MARKER_WIDTH / 2, player.y - MARKER_HEIGHT, MARKER_WIDTH, MARKER_HEIGHT, [0.85, 0.75, 0.45, 1]) - renderer.drawSolid(player.x - MARKER_WIDTH / 2, player.y - 6, MARKER_WIDTH, 6, [0.35, 0.25, 0.15, 1]) + renderer.drawSolid(engine.world.player.x - MARKER_WIDTH / 2, engine.world.player.y - MARKER_HEIGHT, MARKER_WIDTH, MARKER_HEIGHT, [0.85, 0.75, 0.45, 1]) + renderer.drawSolid(engine.world.player.x - MARKER_WIDTH / 2, engine.world.player.y - 6, MARKER_WIDTH, 6, [0.35, 0.25, 0.15, 1]) } else { - const direction = facingToDirection(player.facing, character.directions) - const groupIndex = (player.moving ? character.walk : character.stand) + direction + const direction = facingToDirection(engine.world.player.facing, character.directions) + const groupIndex = (playerMoving ? character.walk : character.stand) + direction const group = character.groups[groupIndex] ?? character.groups[character.walk + direction] - const frameIndex = player.moving - ? Math.floor(player.walkFrame / 3) % Math.max(1, group?.length ?? 1) + const frameIndex = playerMoving + ? Math.floor(playerWalkFrame / 3) % Math.max(1, group?.length ?? 1) : 0 const frame = group?.[frameIndex] if (frame !== undefined) { - // Feet at the player's position: the composited frame is anchored by its + // Feet at the engine.world.player's position: the composited frame is anchored by its // bottom edge, which is where the COF puts the sprite's origin. - renderer.draw(frame, player.x - frame.width / 2, player.y - frame.height + FEET_HEIGHT / 2, { atlas: character.handle }) + renderer.draw(frame, engine.world.player.x - frame.width / 2, engine.world.player.y - frame.height + FEET_HEIGHT / 2, { atlas: character.handle }) state.characterFrames += 1 state.characterGroup = groupIndex } @@ -823,7 +866,7 @@ function runScene(runtime: MapRuntime, renderer: SpriteRenderer, started: number hud.textContent = `${runtime.level} · ${runtime.quadrant} · ${runtime.source === 'pack' ? '资源包' : '读归档'}` + ` · ${String(runtime.cellsX)}×${String(runtime.cellsY)} 格 · ${String(runtime.floors.length)} 地面 / ${String(runtime.walls.length)} 墙` + ` · 页 ${String(state.pagesLoaded)}/${String(state.pagesTotal)}` - + ` · 对象 ${String(runtime.objects)} · ${loop.tickRate.toFixed(1)} tps · pos ${player.x.toFixed(0)},${player.y.toFixed(0)}` + + ` · 对象 ${String(runtime.objects)} · ${loop.tickRate.toFixed(1)} tps · pos ${engine.world.player.x.toFixed(0)},${engine.world.player.y.toFixed(0)}` + (skippedDraws > 0 ? ` · 待页 ${String(skippedDraws)}` : '') }, })