diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue index d041a675f..f58551945 100644 --- a/packages/frontend/src/pages/drop-and-fusion.vue +++ b/packages/frontend/src/pages/drop-and-fusion.vue @@ -103,7 +103,11 @@ SPDX-License-Identifier: AGPL-3.0-only
- END REPLAY +
+ END REPLAY + x2 + x4 +
@@ -437,10 +441,15 @@ const gameStarted = ref(false); const highScore = ref(null); const showConfig = ref(false); const replaying = ref(false); +const replayPlaybackRate = ref(1); const mute = ref(false); const bgmVolume = ref(defaultStore.state.dropAndFusion.bgmVolume); const sfxVolume = ref(defaultStore.state.dropAndFusion.sfxVolume); +watch(replayPlaybackRate, (newValue) => { + game.replayPlaybackRate = newValue; +}); + function onClick(ev: MouseEvent) { if (!containerElRect) return; if (replaying.value) return; @@ -493,6 +502,7 @@ function end() { game.dispose(); isGameOver.value = false; replaying.value = false; + replayPlaybackRate.value = 1; currentPick.value = null; dropReady.value = true; stock.value = []; diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/frontend/src/scripts/drop-and-fusion-engine.ts index 16fe87d97..a59eb271e 100644 --- a/packages/frontend/src/scripts/drop-and-fusion-engine.ts +++ b/packages/frontend/src/scripts/drop-and-fusion-engine.ts @@ -44,7 +44,7 @@ export class DropAndFusionGame extends EventEmitter<{ gameOver: () => void; }> { private PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高いがガタガタして安定しなくなる、逆に高すぎても何故か不安定になる - private COMBO_INTERVAL = 1000; + private COMBO_INTERVAL = 60; // frame public readonly DROP_INTERVAL = 500; public readonly PLAYAREA_MARGIN = 25; private STOCK_MAX = 4; @@ -76,7 +76,7 @@ export class DropAndFusionGame extends EventEmitter<{ private latestDroppedBodyId: Matter.Body['id'] | null = null; private latestDroppedAt = 0; - private latestFusionedAt = 0; + private latestFusionedAt = 0; // frame private stock: { id: string; mono: Mono }[] = []; private holding: { id: string; mono: Mono } | null = null; @@ -100,6 +100,8 @@ export class DropAndFusionGame extends EventEmitter<{ private comboIntervalId: number | null = null; + public replayPlaybackRate = 1; + constructor(opts: { canvas: HTMLCanvasElement; width: number; @@ -219,13 +221,12 @@ export class DropAndFusionGame extends EventEmitter<{ } private fusion(bodyA: Matter.Body, bodyB: Matter.Body) { - const now = Date.now(); - if (this.latestFusionedAt > now - this.COMBO_INTERVAL) { + if (this.latestFusionedAt > this.frame - this.COMBO_INTERVAL) { this.combo++; } else { this.combo = 1; } - this.latestFusionedAt = now; + this.latestFusionedAt = this.frame; // TODO: 単に位置だけでなくそれぞれの動きベクトルも融合する? const newX = (bodyA.position.x + bodyB.position.x) / 2; @@ -390,44 +391,43 @@ export class DropAndFusionGame extends EventEmitter<{ } }); - this.comboIntervalId = window.setInterval(() => { - if (this.latestFusionedAt < Date.now() - this.COMBO_INTERVAL) { - this.combo = 0; - } - }, 500); - if (logs) { const playTick = () => { - this.frame++; - const log = logs.find(x => x.frame === this.frame - 1); - if (log) { - switch (log.operation) { - case 'drop': { - this.drop(log.x); - break; - } - case 'hold': { - this.hold(); - break; - } - case 'surrender': { - this.surrender(); - break; - } - default: - break; + for (let i = 0; i < this.replayPlaybackRate; i++) { + this.frame++; + if (this.latestFusionedAt < this.frame - this.COMBO_INTERVAL) { + this.combo = 0; } - } - this.tickCallbackQueue = this.tickCallbackQueue.filter(x => { - if (x.frame === this.frame) { - x.callback(); - return false; - } else { - return true; + const log = logs.find(x => x.frame === this.frame - 1); + if (log) { + switch (log.operation) { + case 'drop': { + this.drop(log.x); + break; + } + case 'hold': { + this.hold(); + break; + } + case 'surrender': { + this.surrender(); + break; + } + default: + break; + } } - }); + this.tickCallbackQueue = this.tickCallbackQueue.filter(x => { + if (x.frame === this.frame) { + x.callback(); + return false; + } else { + return true; + } + }); - Matter.Engine.update(this.engine, this.TICK_DELTA); + Matter.Engine.update(this.engine, this.TICK_DELTA); + } if (!this.isGameOver) { this.tickRaf = window.requestAnimationFrame(playTick); @@ -446,6 +446,9 @@ export class DropAndFusionGame extends EventEmitter<{ private tick() { this.frame++; + if (this.latestFusionedAt < this.frame - this.COMBO_INTERVAL) { + this.combo = 0; + } this.tickCallbackQueue = this.tickCallbackQueue.filter(x => { if (x.frame === this.frame) { x.callback();