enhance(drop-and-fusion): リプレイの倍速再生対応
This commit is contained in:
parent
138a248a6c
commit
3d9e42efca
|
@ -103,7 +103,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div v-if="replaying" style="display: flex;">
|
<div v-if="replaying" style="display: flex;">
|
||||||
<div :class="$style.frame" style="flex: 1; margin-right: 10px;">
|
<div :class="$style.frame" style="flex: 1; margin-right: 10px;">
|
||||||
<div :class="$style.frameInner">
|
<div :class="$style.frameInner">
|
||||||
<MkButton @click="endReplay"><i class="ti ti-player-stop"></i> END REPLAY</MkButton>
|
<div class="_buttonsCenter">
|
||||||
|
<MkButton @click="endReplay"><i class="ti ti-player-stop"></i> END REPLAY</MkButton>
|
||||||
|
<MkButton :primary="replayPlaybackRate === 2" @click="replayPlaybackRate = replayPlaybackRate === 2 ? 1 : 2"><i class="ti ti-player-track-next"></i> x2</MkButton>
|
||||||
|
<MkButton :primary="replayPlaybackRate === 4" @click="replayPlaybackRate = replayPlaybackRate === 4 ? 1 : 4"><i class="ti ti-player-track-next"></i> x4</MkButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -437,10 +441,15 @@ const gameStarted = ref(false);
|
||||||
const highScore = ref<number | null>(null);
|
const highScore = ref<number | null>(null);
|
||||||
const showConfig = ref(false);
|
const showConfig = ref(false);
|
||||||
const replaying = ref(false);
|
const replaying = ref(false);
|
||||||
|
const replayPlaybackRate = ref(1);
|
||||||
const mute = ref(false);
|
const mute = ref(false);
|
||||||
const bgmVolume = ref(defaultStore.state.dropAndFusion.bgmVolume);
|
const bgmVolume = ref(defaultStore.state.dropAndFusion.bgmVolume);
|
||||||
const sfxVolume = ref(defaultStore.state.dropAndFusion.sfxVolume);
|
const sfxVolume = ref(defaultStore.state.dropAndFusion.sfxVolume);
|
||||||
|
|
||||||
|
watch(replayPlaybackRate, (newValue) => {
|
||||||
|
game.replayPlaybackRate = newValue;
|
||||||
|
});
|
||||||
|
|
||||||
function onClick(ev: MouseEvent) {
|
function onClick(ev: MouseEvent) {
|
||||||
if (!containerElRect) return;
|
if (!containerElRect) return;
|
||||||
if (replaying.value) return;
|
if (replaying.value) return;
|
||||||
|
@ -493,6 +502,7 @@ function end() {
|
||||||
game.dispose();
|
game.dispose();
|
||||||
isGameOver.value = false;
|
isGameOver.value = false;
|
||||||
replaying.value = false;
|
replaying.value = false;
|
||||||
|
replayPlaybackRate.value = 1;
|
||||||
currentPick.value = null;
|
currentPick.value = null;
|
||||||
dropReady.value = true;
|
dropReady.value = true;
|
||||||
stock.value = [];
|
stock.value = [];
|
||||||
|
|
|
@ -44,7 +44,7 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
gameOver: () => void;
|
gameOver: () => void;
|
||||||
}> {
|
}> {
|
||||||
private PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高いがガタガタして安定しなくなる、逆に高すぎても何故か不安定になる
|
private PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高いがガタガタして安定しなくなる、逆に高すぎても何故か不安定になる
|
||||||
private COMBO_INTERVAL = 1000;
|
private COMBO_INTERVAL = 60; // frame
|
||||||
public readonly DROP_INTERVAL = 500;
|
public readonly DROP_INTERVAL = 500;
|
||||||
public readonly PLAYAREA_MARGIN = 25;
|
public readonly PLAYAREA_MARGIN = 25;
|
||||||
private STOCK_MAX = 4;
|
private STOCK_MAX = 4;
|
||||||
|
@ -76,7 +76,7 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
private latestDroppedBodyId: Matter.Body['id'] | null = null;
|
private latestDroppedBodyId: Matter.Body['id'] | null = null;
|
||||||
|
|
||||||
private latestDroppedAt = 0;
|
private latestDroppedAt = 0;
|
||||||
private latestFusionedAt = 0;
|
private latestFusionedAt = 0; // frame
|
||||||
private stock: { id: string; mono: Mono }[] = [];
|
private stock: { id: string; mono: Mono }[] = [];
|
||||||
private holding: { id: string; mono: Mono } | null = null;
|
private holding: { id: string; mono: Mono } | null = null;
|
||||||
|
|
||||||
|
@ -100,6 +100,8 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
|
|
||||||
private comboIntervalId: number | null = null;
|
private comboIntervalId: number | null = null;
|
||||||
|
|
||||||
|
public replayPlaybackRate = 1;
|
||||||
|
|
||||||
constructor(opts: {
|
constructor(opts: {
|
||||||
canvas: HTMLCanvasElement;
|
canvas: HTMLCanvasElement;
|
||||||
width: number;
|
width: number;
|
||||||
|
@ -219,13 +221,12 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
}
|
}
|
||||||
|
|
||||||
private fusion(bodyA: Matter.Body, bodyB: Matter.Body) {
|
private fusion(bodyA: Matter.Body, bodyB: Matter.Body) {
|
||||||
const now = Date.now();
|
if (this.latestFusionedAt > this.frame - this.COMBO_INTERVAL) {
|
||||||
if (this.latestFusionedAt > now - this.COMBO_INTERVAL) {
|
|
||||||
this.combo++;
|
this.combo++;
|
||||||
} else {
|
} else {
|
||||||
this.combo = 1;
|
this.combo = 1;
|
||||||
}
|
}
|
||||||
this.latestFusionedAt = now;
|
this.latestFusionedAt = this.frame;
|
||||||
|
|
||||||
// TODO: 単に位置だけでなくそれぞれの動きベクトルも融合する?
|
// TODO: 単に位置だけでなくそれぞれの動きベクトルも融合する?
|
||||||
const newX = (bodyA.position.x + bodyB.position.x) / 2;
|
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) {
|
if (logs) {
|
||||||
const playTick = () => {
|
const playTick = () => {
|
||||||
this.frame++;
|
for (let i = 0; i < this.replayPlaybackRate; i++) {
|
||||||
const log = logs.find(x => x.frame === this.frame - 1);
|
this.frame++;
|
||||||
if (log) {
|
if (this.latestFusionedAt < this.frame - this.COMBO_INTERVAL) {
|
||||||
switch (log.operation) {
|
this.combo = 0;
|
||||||
case 'drop': {
|
|
||||||
this.drop(log.x);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'hold': {
|
|
||||||
this.hold();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'surrender': {
|
|
||||||
this.surrender();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
const log = logs.find(x => x.frame === this.frame - 1);
|
||||||
this.tickCallbackQueue = this.tickCallbackQueue.filter(x => {
|
if (log) {
|
||||||
if (x.frame === this.frame) {
|
switch (log.operation) {
|
||||||
x.callback();
|
case 'drop': {
|
||||||
return false;
|
this.drop(log.x);
|
||||||
} else {
|
break;
|
||||||
return true;
|
}
|
||||||
|
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) {
|
if (!this.isGameOver) {
|
||||||
this.tickRaf = window.requestAnimationFrame(playTick);
|
this.tickRaf = window.requestAnimationFrame(playTick);
|
||||||
|
@ -446,6 +446,9 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
|
|
||||||
private tick() {
|
private tick() {
|
||||||
this.frame++;
|
this.frame++;
|
||||||
|
if (this.latestFusionedAt < this.frame - this.COMBO_INTERVAL) {
|
||||||
|
this.combo = 0;
|
||||||
|
}
|
||||||
this.tickCallbackQueue = this.tickCallbackQueue.filter(x => {
|
this.tickCallbackQueue = this.tickCallbackQueue.filter(x => {
|
||||||
if (x.frame === this.frame) {
|
if (x.frame === this.frame) {
|
||||||
x.callback();
|
x.callback();
|
||||||
|
|
Loading…
Reference in New Issue