Skip to content

Commit

Permalink
Major web-game refactor for new core game
Browse files Browse the repository at this point in the history
  • Loading branch information
phisn committed Sep 21, 2024
1 parent e9b16b6 commit c5d72e8
Show file tree
Hide file tree
Showing 29 changed files with 436 additions and 3,499 deletions.
122 changes: 122 additions & 0 deletions packages/web-game/src/game/model/interpolation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { EntityWith } from "game/src/framework/entity"
import { Game } from "game/src/game"
import { GameComponents } from "game/src/model/store"

export interface Interpolation {
x: number
y: number
rotation: number
}

interface MappingEntry {
interpolation: Interpolation
entity: EntityWith<GameComponents, "body">

previousX: number
previousY: number
previousRotation: number
}

export class InterpolationStore {
private mapping: Map<number, MappingEntry> = new Map()

constructor(game: Game) {
game.store.entities.listen(
["body"],
entity => {
const body = entity.get("body")

if (body.isFixed() === false) {
this.mapping.set(entity.id, {
interpolation: {
x: body.translation().x,
y: body.translation().y,
rotation: body.rotation(),
},
entity,

previousX: body.translation().x,
previousY: body.translation().y,
previousRotation: body.rotation(),
})
}
},
entity => {
this.mapping.delete(entity.id)
},
)
}

get(id: number): Interpolation | undefined {
return this.mapping.get(id)?.interpolation
}

*interpolations() {
for (const [id, { interpolation }] of this.mapping) {
yield [id, interpolation] as const
}
}

reset(id: number) {
const entry = this.mapping.get(id)

if (entry === undefined) {
return
}

const { entity, interpolation } = entry

interpolation.x = entity.get("body").translation().x
interpolation.y = entity.get("body").translation().y
interpolation.rotation = entity.get("body").rotation()

entry.previousX = interpolation.x
entry.previousY = interpolation.y
entry.previousRotation = interpolation.rotation
}

onUpdate(_delta: number, overstep: number) {
for (const entry of this.mapping.values()) {
const { entity, interpolation } = entry

const translation = entity.get("body").translation()
const rotation = entity.get("body").rotation()

interpolation.x = lerp(entry.previousX, translation.x, overstep)
interpolation.y = lerp(entry.previousY, translation.y, overstep)
interpolation.rotation = slerp(entry.previousRotation, rotation, overstep)

entry.previousX = interpolation.x
entry.previousY = interpolation.y
entry.previousRotation = interpolation.rotation
}
}

onLastFixedUpdate() {
for (const entry of this.mapping.values()) {
const { entity, interpolation } = entry

const translation = entity.get("body").translation()
const rotation = entity.get("body").rotation()

interpolation.x = translation.x
interpolation.y = translation.y
interpolation.rotation = rotation

entry.previousX = translation.x
entry.previousY = translation.y
entry.previousRotation = rotation
}
}
}

export function lerp(previous: number, next: number, t: number) {
return (1 - t) * previous + t * next
}

export function slerp(previous: number, next: number, t: number) {
const difference = next - previous
const shortestAngle = (((difference % (2 * Math.PI)) + 3 * Math.PI) % (2 * Math.PI)) - Math.PI

return previous + shortestAngle * t
}
8 changes: 7 additions & 1 deletion packages/web-game/src/game/model/store.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import * as RAPIER from "@dimforge/rapier2d"
import { Game } from "game/src/game"
import { Scene, WebGLRenderer } from "three"
import { InterpolationStore } from "./interpolation"
import { GameSettings } from "./settings"

export class WebGameStore {
public game: Game
public interpolation: InterpolationStore
public renderer: WebGLRenderer
public scene: Scene

constructor(settings: GameSettings, renderer: WebGLRenderer) {
constructor(
public settings: GameSettings,
renderer: WebGLRenderer,
) {
this.game = new Game(settings, { rapier: RAPIER })
this.interpolation = new InterpolationStore(this.game)
this.renderer = renderer
this.scene = new Scene()
}
Expand Down
98 changes: 49 additions & 49 deletions packages/web-game/src/game/modules/module-camera.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { LevelCapturedMessage } from "runtime/src/core/level-capture/level-captured-message"
import { RuntimeComponents } from "runtime/src/core/runtime-components"
import { levelComponents, LevelEntity } from "game/src/modules/module-level"
import { rocketComponents, RocketEntity } from "game/src/modules/module-rocket"
import * as THREE from "three"
import { EntityWith, MessageCollector } from "../../../../_runtime-framework/src"
import { ExtendedComponents } from "../runtime-extension/extended-components"
import { ExtendedRuntime } from "../runtime-extension/new-extended-runtime"
import { WebGameStore } from "../model/store"

type TransitionAnimation =
| undefined
Expand Down Expand Up @@ -31,7 +29,7 @@ export class ModuleCamera extends THREE.OrthographicCamera {
private configAnimationSpeed: number
private configZoom: number

private rocket: EntityWith<ExtendedComponents, "rocket" | "interpolation">
private getRocket: () => RocketEntity

private cameraTargetSize: THREE.Vector2 = new THREE.Vector2()
private levelConstraintPosition: THREE.Vector2 = new THREE.Vector2()
Expand All @@ -41,25 +39,22 @@ export class ModuleCamera extends THREE.OrthographicCamera {
private transitionAnimation: TransitionAnimation = undefined
private startAnimation: StartAnimation = undefined

private levelCapturedCollector: MessageCollector<LevelCapturedMessage>

constructor(private runtime: ExtendedRuntime) {
constructor(private store: WebGameStore) {
super()

this.position.z = 5
this.configAnimationSpeed = 1.0 / 1000.0
this.configZoom = 1920 * 0.04

const [rocket] = runtime.factoryContext.store.find("rocket", "rigidBody", "interpolation")
this.rocket = rocket
this.position.x = rocket.components.rigidBody.translation().x
this.position.y = rocket.components.rigidBody.translation().y

this.levelCapturedCollector = runtime.factoryContext.messageStore.collect("levelCaptured")
this.getRocket = store.game.store.entities.single(...rocketComponents)
const rocket = this.getRocket()
const body = rocket.get("body")
this.position.x = body.translation().x
this.position.y = body.translation().y

const firstLevel = runtime.factoryContext.store
.find("level")
.find(x => x.components.level.captured)
const firstLevel = store.game.store.entities
.multiple(...levelComponents)
.find(x => x.get("level").completed)

if (!firstLevel) {
throw new Error("No first level found")
Expand All @@ -72,10 +67,26 @@ export class ModuleCamera extends THREE.OrthographicCamera {
}

this.updateLevelConstraintFromLevel(firstLevel)

this.store.game.store.events.listen({
captured: ({ level }) => {
this.updateLevelConstraintFromLevel(level)

this.transitionAnimation = {
progress: 0,
startSize: this.size.clone(),
targetPosition: new THREE.Vector2(),
startPosition: this.position.clone(),
}

this.updateCameraSize()
this.updateCameraPosition()
},
})
}

onCanvasResize(width: number, height: number) {
this.runtime.factoryContext.renderer.setSize(width, height, false)
this.store.renderer.setSize(width, height, false)

const max = Math.max(width, height)

Expand All @@ -86,24 +97,6 @@ export class ModuleCamera extends THREE.OrthographicCamera {
this.updateCameraPosition()
}

onFixedUpdate() {
const lastCaptured = [...this.levelCapturedCollector].at(-1)

if (lastCaptured) {
this.updateLevelConstraintFromLevel(lastCaptured.level)

this.transitionAnimation = {
progress: 0,
startSize: this.size.clone(),
targetPosition: new THREE.Vector2(),
startPosition: this.position.clone(),
}

this.updateCameraSize()
this.updateCameraPosition()
}
}

onUpdate(delta: number) {
this.updateCameraPosition()

Expand Down Expand Up @@ -190,8 +183,8 @@ export class ModuleCamera extends THREE.OrthographicCamera {
}

updateViewport() {
const rendererWidth = this.runtime.factoryContext.renderer.domElement.clientWidth
const rendererHeight = this.runtime.factoryContext.renderer.domElement.clientHeight
const rendererWidth = this.store.renderer.domElement.clientWidth
const rendererHeight = this.store.renderer.domElement.clientHeight

let viewportSizeX = this.size.x / this.cameraTargetSize.x
let viewportSizeY = this.size.y / this.cameraTargetSize.y
Expand All @@ -204,7 +197,7 @@ export class ModuleCamera extends THREE.OrthographicCamera {
const viewportX = 0.5 * rendererWidth * (1 - viewportSizeX)
const viewportY = 0.5 * rendererHeight * (1 - viewportSizeY)

this.runtime.factoryContext.renderer.setViewport(
this.store.renderer.setViewport(
viewportX,
viewportY,
viewportSizeX * rendererWidth,
Expand Down Expand Up @@ -236,10 +229,15 @@ export class ModuleCamera extends THREE.OrthographicCamera {
}
}

const rocketPositionInViewX =
this.rocket.components.interpolation.position.x - this.levelConstraintPosition.x
const rocketPositionInViewY =
this.rocket.components.interpolation.position.y - this.levelConstraintPosition.y
const rocket = this.getRocket()
const interpolation = this.store.interpolation.get(rocket.id)

if (interpolation === undefined) {
throw new Error("Rocket interpolation not found")
}

const rocketPositionInViewX = interpolation.x - this.levelConstraintPosition.x
const rocketPositionInViewY = interpolation.y - this.levelConstraintPosition.y

const cameraPositionX =
this.levelConstraintPosition.x +
Expand All @@ -258,15 +256,17 @@ export class ModuleCamera extends THREE.OrthographicCamera {
}
}

private updateLevelConstraintFromLevel(entity: EntityWith<RuntimeComponents, "level">) {
private updateLevelConstraintFromLevel(level: LevelEntity) {
const levelComponent = level.get("level")

this.levelConstraintSize = new THREE.Vector2(
entity.components.level.camera.bottomRight.x - entity.components.level.camera.topLeft.x,
entity.components.level.camera.topLeft.y - entity.components.level.camera.bottomRight.y,
levelComponent.cameraRect.right - levelComponent.cameraRect.left,
levelComponent.cameraRect.top - levelComponent.cameraRect.bottom,
)

this.levelConstraintPosition = new THREE.Vector2(
entity.components.level.camera.topLeft.x,
entity.components.level.camera.bottomRight.y,
levelComponent.cameraRect.left,
levelComponent.cameraRect.bottom,
)
}

Expand Down
16 changes: 4 additions & 12 deletions packages/web-game/src/game/modules/module-hook-handler.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
import { ExtendedRuntime } from "../runtime-extension/new-extended-runtime"
import { WebGameStore } from "../model/store"

export class ModuleHookHandler {
constructor(private runtime: ExtendedRuntime) {
runtime.factoryContext.messageStore.listenTo("finished", () => {
/*
// protoc replaymodel to base64 bytes
const replaymodel = this.runtime.factoryContext.replayCapture.replay
const raw = ReplayModel.encode(replaymodel).finish()
const base64 = Buffer.from(raw).toString("base64")
console.log("ReplayModel: ", base64)
*/

this.runtime.factoryContext.settings.hooks?.onFinished?.(runtime)
constructor(private runtime: WebGameStore) {
runtime.game.store.events.listen({
death: () => {},
})
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ExtendedRuntime } from "../../runtime-extension/new-extended-runtime"
import { WebGameStore } from "../../model/store"
import { Keyboard } from "./keyboard"
import { Mouse } from "./mouse"
import { Touch } from "./touch"
Expand All @@ -20,7 +20,7 @@ export class ModuleInput {
// private precomputed: RuntimeSystemContext[]
// private iter = -1

constructor(runtime: ExtendedRuntime) {
constructor(runtime: WebGameStore) {
this.keyboard = new Keyboard()
this.mouse = new Mouse(runtime)
this.touch = new Touch(runtime)
Expand Down
17 changes: 6 additions & 11 deletions packages/web-game/src/game/modules/module-input/mouse.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ExtendedRuntime } from "../../runtime-extension/new-extended-runtime"
import { WebGameStore } from "../../model/store"

const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)

Expand All @@ -14,24 +14,19 @@ export class Mouse {

private ptrEvents = ["pointerdown", "pointerup", "pointermove", "pointercancel"] as const

constructor(private runtime: ExtendedRuntime) {
constructor(private runtime: WebGameStore) {
this.onPointerEvent = this.onPointerEvent.bind(this)

for (const ptrEvent of this.ptrEvents) {
runtime.factoryContext.renderer.domElement.addEventListener(
ptrEvent,
this.onPointerEvent,
{ capture: true },
)
runtime.renderer.domElement.addEventListener(ptrEvent, this.onPointerEvent, {
capture: true,
})
}
}

dispose() {
for (const ptrEvent of this.ptrEvents) {
this.runtime.factoryContext.renderer.domElement.removeEventListener(
ptrEvent,
this.onPointerEvent,
)
this.runtime.renderer.domElement.removeEventListener(ptrEvent, this.onPointerEvent)
}
}

Expand Down
Loading

0 comments on commit c5d72e8

Please sign in to comment.