Skip to content

Commit 3054ece

Browse files
committed
feat: support fast travel
1 parent 02dae0b commit 3054ece

3 files changed

Lines changed: 77 additions & 36 deletions

File tree

game/field.ts

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -418,34 +418,53 @@ class BlockUnloadScope extends RectScope {
418418
}
419419

420420
export class Field implements IField {
421-
#el: HTMLElement
422-
#blocks: Record<string, FieldBlock> = {}
423-
#blockElements: Record<string, HTMLCanvasElement> = {}
424-
#loadScope = new BlockLoadScope()
425-
#unloadScope = new BlockUnloadScope()
426-
#activateScope: RectScope
427-
#mapLoader = new BlockMapLoader(new URL("map/", location.href).href)
428-
#initialBlocksLoaded = false
429-
#initialActivateReady = false
430-
421+
readonly #el: HTMLElement
422+
readonly #blocks: Record<string, FieldBlock> = {}
423+
readonly #blockElements: Record<string, HTMLCanvasElement> = {}
424+
readonly #loadScope = new BlockLoadScope()
425+
readonly #unloadScope = new BlockUnloadScope()
426+
readonly #activateScope: RectScope
427+
readonly #deactivateScope: RectScope
428+
readonly #mapLoader = new BlockMapLoader(new URL("map/", location.href).href)
429+
readonly me: Actor
430+
431+
// mutables
431432
#actors: FieldActors
432433
#items: FieldItems
433434
#props: FieldProps
434435
#effects: FieldEffects
435436

436-
#time = 0
437+
#time: number
438+
#initialBlocksLoaded: boolean
439+
#initialActivateReady: boolean
437440

438441
constructor(
439442
el: HTMLElement,
443+
me: Actor,
440444
activateScope: RectScope,
441445
deactivateScope: RectScope,
442446
) {
443447
this.#el = el
448+
this.me = me
444449
this.#activateScope = activateScope
445-
this.#actors = new FieldActors([], deactivateScope)
446-
this.#items = new FieldItems(deactivateScope)
447-
this.#props = new FieldProps(deactivateScope)
450+
this.#deactivateScope = deactivateScope
451+
this.#actors = new FieldActors([me], this.#deactivateScope)
452+
this.#items = new FieldItems(this.#deactivateScope)
453+
this.#props = new FieldProps(this.#deactivateScope)
454+
this.#effects = new FieldEffects()
455+
this.#time = 0
456+
this.#initialBlocksLoaded = false
457+
this.#initialActivateReady = false
458+
}
459+
460+
reset() {
461+
this.#actors = new FieldActors([this.me], this.#deactivateScope)
462+
this.#items = new FieldItems(this.#deactivateScope)
463+
this.#props = new FieldProps(this.#deactivateScope)
448464
this.#effects = new FieldEffects()
465+
this.#time = 0
466+
this.#initialBlocksLoaded = false
467+
this.#initialActivateReady = false
449468
}
450469

451470
get actors() {

game/game-screen.ts

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ import { RectScope } from "../util/rect-scope.ts"
99
import { Field } from "./field.ts"
1010
import { Actor } from "../model/actor.ts"
1111

12-
const parseStart = (hash: string) => {
12+
const parseGridPosition = (hash: string) => {
1313
const m = hash.match(/#(-?\d+),(-?\d+)/)
1414
if (m) {
1515
return { i: +m[1], j: +m[2] }
1616
}
1717
return null
1818
}
1919

20-
const start = parseStart(globalThis.location.hash)
20+
const start = parseGridPosition(globalThis.location.hash)
2121

2222
// The starting position of the main character
2323
const I = start?.i ?? -131
@@ -78,21 +78,17 @@ export function GameScreen({ el, query }: Context) {
7878
new MoveEndMainActor(),
7979
new IdleMainActor(),
8080
)
81-
signal.centerPixel.update({ x: me.centerX, y: me.centerY })
82-
8381
const viewScope = new ViewScope(screenSize, screenSize)
84-
8582
const entityLayer = new DrawLayer(entityCanvas, viewScope)
86-
8783
const activateScope = new ActivateScope(screenSize)
8884
const deactivateScope = new DeactivateScope(screenSize)
8985

90-
const field = new Field(query(".field")!, activateScope, deactivateScope)
91-
field.actors.add(me)
86+
const field = new Field(query(".field")!, me, activateScope, deactivateScope)
87+
signal.centerPixel.update({ x: field.me.centerX, y: field.me.centerY })
9288

9389
signal.centerGrid10.subscribe(() => {
94-
field.checkBlockLoad(me.i, me.j, viewScope)
95-
field.checkBlockUnload(me.i, me.j)
90+
field.checkBlockLoad(field.me.i, field.me.j, viewScope)
91+
field.checkBlockUnload(field.me.i, field.me.j)
9692
})
9793

9894
signal.centerPixel.subscribe(({ x, y }) => {
@@ -106,18 +102,15 @@ export function GameScreen({ el, query }: Context) {
106102
}
107103
})
108104

109-
let i = 0
110-
111105
const loop = new Gameloop(60, () => {
112-
i++
113106
if (!field.assetsReady) {
114107
signal.isGameLoading.update(true)
115108
return
116109
}
117110
signal.isGameLoading.update(false)
118111

119112
field.step()
120-
signal.centerPixel.update({ x: me.centerX, y: me.centerY })
113+
signal.centerPixel.update({ x: field.me.centerX, y: field.me.centerY })
121114

122115
entityLayer.clear()
123116
entityLayer.drawIterableEntity(field.props.iter())
@@ -126,17 +119,19 @@ export function GameScreen({ el, query }: Context) {
126119
entityLayer.drawIterableColorBox(field.effects.iter())
127120
entityLayer.drawWhiteNoise()
128121

129-
if (i % 300 === 299) {
130-
field.actors.checkDeactivate(me.i, me.j)
122+
const time = field.time
123+
124+
if (time % 300 === 299) {
125+
field.actors.checkDeactivate(field.me.i, field.me.j)
131126
}
132-
if (i % 300 === 199) {
133-
field.items.checkDeactivate(me.i, me.j)
127+
if (time % 300 === 199) {
128+
field.items.checkDeactivate(field.me.i, field.me.j)
134129
}
135-
if (i % 300 === 99) {
136-
field.props.checkDeactivate(me.i, me.j)
130+
if (time % 300 === 99) {
131+
field.props.checkDeactivate(field.me.i, field.me.j)
137132
}
138-
if (i % 60 === 59) {
139-
field.checkActivate(me.i, me.j, { viewScope })
133+
if (time % 60 === 59) {
134+
field.checkActivate(field.me.i, field.me.j, { viewScope })
140135
}
141136
})
142137
loop.onStep((fps, v) => {
@@ -146,4 +141,22 @@ export function GameScreen({ el, query }: Context) {
146141
}
147142
})
148143
loop.start()
144+
145+
const reset = (i: number, j: number) => {
146+
loop.stop()
147+
query(".curtain")!.style.opacity = "1"
148+
setTimeout(() => {
149+
field.reset()
150+
field.me.fastTravel(i, j)
151+
signal.centerPixel.update({ x: field.me.centerX, y: field.me.centerY })
152+
loop.start()
153+
}, 2000)
154+
}
155+
156+
globalThis.onhashchange = () => {
157+
const pos = parseGridPosition(globalThis.location.hash)
158+
if (pos) {
159+
reset(pos.i, pos.j)
160+
}
161+
}
149162
}

model/actor.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,15 @@ export class Actor implements IActor {
240240
this.#idle = idle
241241
}
242242

243+
fastTravel(i: number, j: number) {
244+
this.#i = i
245+
this.#j = j
246+
this.#dir = DOWN
247+
this.#physicalGridKey = this.#calcPhysicalGridKey()
248+
this.#move = null
249+
this.#idleCounter = 0
250+
}
251+
243252
tryMove(
244253
type: "go" | "slide",
245254
dir: Dir,

0 commit comments

Comments
 (0)