diff --git a/packages/game/src/model/point.ts b/packages/game/src/model/point.ts deleted file mode 100644 index 521347da..00000000 --- a/packages/game/src/model/point.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface Point { - x: number - y: number -} diff --git a/packages/game/src/model/store.ts b/packages/game/src/model/store.ts index 306e1e4f..cdd48184 100644 --- a/packages/game/src/model/store.ts +++ b/packages/game/src/model/store.ts @@ -49,4 +49,5 @@ export interface GameEvents { }): void captureChanged(props: { rocket: RocketEntity; level: LevelEntity; started: boolean }): void + captured(props: { rocket: RocketEntity; level: LevelEntity }): void } diff --git a/packages/game/src/model/change-anchor.ts b/packages/game/src/model/utils.ts similarity index 62% rename from packages/game/src/model/change-anchor.ts rename to packages/game/src/model/utils.ts index 1a10f984..a627b294 100644 --- a/packages/game/src/model/change-anchor.ts +++ b/packages/game/src/model/utils.ts @@ -1,12 +1,29 @@ import cos from "@stdlib/math/base/special/cos" import sin from "@stdlib/math/base/special/sin" +export interface Point { + x: number + y: number +} + +export interface Rect { + left: number + top: number + right: number + bottom: number +} + +export interface Size { + width: number + height: number +} + export const changeAnchor = ( - position: { x: number; y: number }, + position: Point, rotation: number, - size: { width: number; height: number }, - sourceAnchor: { x: number; y: number }, - targetAnchor: { x: number; y: number }, + size: Size, + sourceAnchor: Point, + targetAnchor: Point, ) => ({ x: position.x + diff --git a/packages/game/src/modules/module-level.ts b/packages/game/src/modules/module-level.ts index 4a090b19..61698317 100644 --- a/packages/game/src/modules/module-level.ts +++ b/packages/game/src/modules/module-level.ts @@ -1,10 +1,17 @@ +import { LevelConfig } from "../../proto/world" import { EntityWith } from "../framework/entity" import { GameInput } from "../game" import { GameComponents, GameStore } from "../model/store" +import { Point, Rect, Size, changeAnchor } from "../model/utils" import { RocketEntity, rocketComponents } from "./module-rocket" export interface LevelComponent { + config: LevelConfig + + capturing: boolean completed: boolean + + cameraRect: Rect } export const levelComponents = ["level", "body"] satisfies (keyof GameComponents)[] @@ -28,22 +35,61 @@ export class ModuleLevel { }) } - onReset() {} + onReset() { + this.progress = undefined + this.progressLevel = undefined + + for (const level of this.store.entities.multipleCopy(...levelComponents)) { + this.store.entities.remove(level) + } + + const config = this.store.resources.get("config") + + let nearestLevel: LevelEntity | undefined + let nearestDistance = Infinity + + for (const levelConfig of config.levels) { + const level = this.constructLevel(levelConfig) + + const distance = Math.sqrt( + (levelConfig.positionX - config.rocket.positionX) ** 2 + + (levelConfig.positionY - config.rocket.positionY) ** 2, + ) + + if (distance < nearestDistance) { + nearestDistance = distance + nearestLevel = level + } + } + + if (nearestLevel === undefined) { + throw new Error("No levels") + } + + const level = nearestLevel.get("level") + level.completed = true + } onUpdate(_input: GameInput) { if (this.progress && this.progressLevel) { this.progress -= 1 if (this.progress <= 0) { - const world = this.store.resources.get("world") - - const body = this.progressLevel.get("body") - const level = this.progressLevel.get("level") + const captured = this.progressLevel + this.progressLevel = undefined + const world = this.store.resources.get("world") + const body = captured.get("body") world.removeRigidBody(body) + const level = captured.get("level") level.completed = true - this.progressLevel = undefined + level.capturing = false + + this.store.events.invoke.captured?.({ + rocket: this.store.entities.single(...rocketComponents)(), + level: captured, + }) } } } @@ -52,9 +98,13 @@ export class ModuleLevel { if (started) { this.progress = TICKS_TO_CAPTURE this.progressLevel = level + + level.get("level").capturing = true } else { this.progress = undefined this.progressLevel = undefined + + level.get("level").capturing = false } this.store.events.invoke.captureChanged?.({ @@ -63,6 +113,75 @@ export class ModuleLevel { started, }) } + + private constructLevel(levelConfig: LevelConfig) { + const rapier = this.store.resources.get("rapier") + const world = this.store.resources.get("world") + + const body = world.createRigidBody(new rapier.RigidBodyDesc(rapier.RigidBodyType.Fixed)) + + const captureBox = calculateCaptureBox( + { x: levelConfig.positionX, y: levelConfig.positionY }, + levelConfig.rotation, + levelConfig.captureAreaLeft, + levelConfig.captureAreaRight, + ) + + world.createCollider( + rapier.ColliderDesc.cuboid(captureBox.size.width, captureBox.size.height) + .setTranslation(captureBox.transformed.x, captureBox.transformed.y) + .setRotation(levelConfig.rotation) + .setSensor(true), + body, + ) + + const level = this.store.entities.create({ + body, + level: { + config: levelConfig, + + capturing: false, + completed: false, + + cameraRect: { + left: levelConfig.cameraTopLeftX, + top: levelConfig.cameraTopLeftY, + right: levelConfig.cameraBottomRightX, + bottom: levelConfig.cameraBottomRightY, + }, + }, + }) + + return level + } } const TICKS_TO_CAPTURE = 60 +const FLAG_CAPTURE_HEIGHT = 0.5 + +const LEVEL_SIZE: Size = { + width: 1.65, + height: 2.616, +} + +function calculateCaptureBox( + position: Point, + rotation: number, + captureDistanceLeft: number, + captureDistanceRight: number, +) { + const transformed = changeAnchor( + { x: position.x, y: position.y }, + rotation, + LEVEL_SIZE, + { x: 0.5, y: 1 }, + { x: 0.2, y: 0 }, + ) + + const size = { + width: captureDistanceLeft + captureDistanceRight, + height: FLAG_CAPTURE_HEIGHT, + } + + return { size, transformed } +} diff --git a/packages/game/src/modules/module-rocket.ts b/packages/game/src/modules/module-rocket.ts index 352cfbe8..fb6b8c4f 100644 --- a/packages/game/src/modules/module-rocket.ts +++ b/packages/game/src/modules/module-rocket.ts @@ -1,7 +1,7 @@ import { EntityWith } from "../framework/entity" import { GameInput } from "../game" -import { changeAnchor } from "../model/change-anchor" import { GameComponents, GameStore } from "../model/store" +import { Size, changeAnchor } from "../model/utils" export interface RocketComponent { thrust: boolean @@ -87,7 +87,7 @@ const ROCKET_COLLIDERS = [ [-0.9, -1.8, -0.894, -1.212, -0.24, -1.212], ] -export const ROCKET_SIZE = { +export const ROCKET_SIZE: Size = { width: 1.8, height: 3.6, }