enhnace(frontend): tweak game
This commit is contained in:
parent
14aedc17ae
commit
1063d39de8
7 changed files with 267 additions and 39 deletions
|
@ -61,7 +61,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="containerEl" :class="[$style.gameContainer, { [$style.gameOver]: gameOver }]" @contextmenu.stop.prevent @click.stop.prevent="onClick" @touchmove.stop.prevent="onTouchmove" @touchend="onTouchend" @mousemove="onMousemove">
|
||||
<div ref="containerEl" :class="[$style.gameContainer, { [$style.gameOver]: isGameOver && !replaying }]" @contextmenu.stop.prevent @click.stop.prevent="onClick" @touchmove.stop.prevent="onTouchmove" @touchend="onTouchend" @mousemove="onMousemove">
|
||||
<img v-if="defaultStore.state.darkMode" src="/client-assets/drop-and-fusion/frame-dark.svg" :class="$style.mainFrameImg"/>
|
||||
<img v-else src="/client-assets/drop-and-fusion/frame-light.svg" :class="$style.mainFrameImg"/>
|
||||
<canvas ref="canvasEl" :class="$style.canvas"/>
|
||||
|
@ -74,7 +74,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
>
|
||||
<div v-show="combo > 1" :class="$style.combo" :style="{ fontSize: `${100 + ((comboPrev - 2) * 15)}%` }">{{ comboPrev }} Chain!</div>
|
||||
</Transition>
|
||||
<div :class="$style.dropperContainer" :style="{ left: dropperX + 'px' }">
|
||||
<div v-if="!isGameOver && !replaying" :class="$style.dropperContainer" :style="{ left: dropperX + 'px' }">
|
||||
<!--<img v-if="currentPick" src="/client-assets/drop-and-fusion/dropper.png" :class="$style.dropper" :style="{ left: dropperX + 'px' }"/>-->
|
||||
<Transition
|
||||
:enterActiveClass="$style.transition_picked_enterActive"
|
||||
|
@ -91,15 +91,29 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div :class="$style.dropGuide"/>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="gameOver" :class="$style.gameOverLabel">
|
||||
<div v-if="isGameOver && !replaying" :class="$style.gameOverLabel">
|
||||
<div class="_gaps_s">
|
||||
<img src="/client-assets/drop-and-fusion/gameover.png" style="width: 200px; max-width: 100%; display: block; margin: auto; margin-bottom: -5px;"/>
|
||||
<div>SCORE: <MkNumber :value="score"/></div>
|
||||
<div>MAX CHAIN: <MkNumber :value="maxCombo"/></div>
|
||||
<div class="_buttonsCenter">
|
||||
<MkButton primary rounded @click="restart">Restart</MkButton>
|
||||
<MkButton primary rounded @click="share">Share</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="replaying" :class="$style.replayIndicator"><span :class="$style.replayIndicatorText"><i class="ti ti-player-play"></i> {{ i18n.ts.replaying }}</span></div>
|
||||
</div>
|
||||
<div v-if="replaying" style="display: flex;">
|
||||
<div :class="$style.frame" style="flex: 1; margin-right: 10px;">
|
||||
<div :class="$style.frameInner">
|
||||
<MkButton @click="endReplay"><i class="ti ti-player-stop"></i> END REPLAY</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isGameOver" :class="$style.frame">
|
||||
<div :class="$style.frameInner">
|
||||
<div class="_buttonsCenter">
|
||||
<MkButton primary rounded @click="end">{{ i18n.ts.done }}</MkButton>
|
||||
<MkButton primary rounded @click="replay">{{ i18n.ts.showReplay }}</MkButton>
|
||||
<MkButton primary rounded @click="share">{{ i18n.ts.share }}</MkButton>
|
||||
<MkButton rounded @click="exportLog">Copy replay data</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -139,7 +153,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
<div :class="$style.frame">
|
||||
<div :class="$style.frameInner">
|
||||
<MkButton @click="restart">Restart</MkButton>
|
||||
<MkButton danger @click="surrender">Retry</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -168,6 +182,7 @@ import { DropAndFusionGame, Mono } from '@/scripts/drop-and-fusion-engine.js';
|
|||
import * as sound from '@/scripts/sound.js';
|
||||
import MkRange from '@/components/MkRange.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
||||
|
||||
const NORMAL_BASE_SIZE = 30;
|
||||
const NORAML_MONOS: Mono[] = [{
|
||||
|
@ -401,6 +416,8 @@ const GAME_HEIGHT = 600;
|
|||
let viewScale = 1;
|
||||
let game: DropAndFusionGame;
|
||||
let containerElRect: DOMRect | null = null;
|
||||
let seed: string;
|
||||
let logs: ReturnType<DropAndFusionGame['getLogs']> | null = null;
|
||||
|
||||
const containerEl = shallowRef<HTMLElement>();
|
||||
const canvasEl = shallowRef<HTMLCanvasElement>();
|
||||
|
@ -414,22 +431,25 @@ const comboPrev = ref(0);
|
|||
const maxCombo = ref(0);
|
||||
const dropReady = ref(true);
|
||||
const gameMode = ref<'normal' | 'square'>('normal');
|
||||
const gameOver = ref(false);
|
||||
const isGameOver = ref(false);
|
||||
const gameStarted = ref(false);
|
||||
const highScore = ref<number | null>(null);
|
||||
const showConfig = ref(false);
|
||||
const replaying = ref(false);
|
||||
const mute = ref(false);
|
||||
const bgmVolume = ref(defaultStore.state.dropAndFusion.bgmVolume);
|
||||
const sfxVolume = ref(defaultStore.state.dropAndFusion.sfxVolume);
|
||||
|
||||
function onClick(ev: MouseEvent) {
|
||||
if (!containerElRect) return;
|
||||
if (replaying.value) return;
|
||||
const x = (ev.clientX - containerElRect.left) / viewScale;
|
||||
game.drop(x);
|
||||
}
|
||||
|
||||
function onTouchend(ev: TouchEvent) {
|
||||
if (!containerElRect) return;
|
||||
if (replaying.value) return;
|
||||
const x = (ev.changedTouches[0].clientX - containerElRect.left) / viewScale;
|
||||
game.drop(x);
|
||||
}
|
||||
|
@ -454,9 +474,18 @@ function hold() {
|
|||
game.hold();
|
||||
}
|
||||
|
||||
function restart() {
|
||||
async function surrender() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.ts.areYouSure,
|
||||
});
|
||||
if (canceled) return;
|
||||
game.surrender();
|
||||
}
|
||||
|
||||
function end() {
|
||||
game.dispose();
|
||||
gameOver.value = false;
|
||||
isGameOver.value = false;
|
||||
currentPick.value = null;
|
||||
dropReady.value = true;
|
||||
stock.value = [];
|
||||
|
@ -467,6 +496,45 @@ function restart() {
|
|||
gameStarted.value = false;
|
||||
}
|
||||
|
||||
function replay() {
|
||||
replaying.value = true;
|
||||
game.dispose();
|
||||
game = new DropAndFusionGame({
|
||||
width: GAME_WIDTH,
|
||||
height: GAME_HEIGHT,
|
||||
canvas: canvasEl.value!,
|
||||
seed: seed,
|
||||
sfxVolume: mute.value ? 0 : sfxVolume.value,
|
||||
...(
|
||||
gameMode.value === 'normal' ? {
|
||||
monoDefinitions: NORAML_MONOS,
|
||||
} : {
|
||||
monoDefinitions: SQUARE_MONOS,
|
||||
}
|
||||
),
|
||||
});
|
||||
attachGameEvents();
|
||||
os.promiseDialog(game.load(), async () => {
|
||||
game.start(logs!);
|
||||
});
|
||||
}
|
||||
|
||||
function endReplay() {
|
||||
replaying.value = false;
|
||||
game.dispose();
|
||||
}
|
||||
|
||||
function exportLog() {
|
||||
if (!logs) return;
|
||||
const data = JSON.stringify({
|
||||
seed: seed,
|
||||
date: new Date().toISOString(),
|
||||
logs: logs,
|
||||
});
|
||||
copyToClipboard(data);
|
||||
os.success();
|
||||
}
|
||||
|
||||
function attachGameEvents() {
|
||||
game.addListener('changeScore', value => {
|
||||
score.value = value;
|
||||
|
@ -492,9 +560,11 @@ function attachGameEvents() {
|
|||
});
|
||||
|
||||
game.addListener('dropped', () => {
|
||||
if (replaying.value) return;
|
||||
|
||||
dropReady.value = false;
|
||||
window.setTimeout(() => {
|
||||
if (!gameOver.value) {
|
||||
if (!isGameOver.value) {
|
||||
dropReady.value = true;
|
||||
}
|
||||
}, game.DROP_INTERVAL);
|
||||
|
@ -511,6 +581,8 @@ function attachGameEvents() {
|
|||
});
|
||||
|
||||
game.addListener('monoAdded', (mono) => {
|
||||
if (replaying.value) return;
|
||||
|
||||
// 実績関連
|
||||
if (mono.level === 10) {
|
||||
claimAchievement('bubbleGameExplodingHead');
|
||||
|
@ -523,9 +595,15 @@ function attachGameEvents() {
|
|||
});
|
||||
|
||||
game.addListener('gameOver', () => {
|
||||
if (replaying.value) {
|
||||
endReplay();
|
||||
return;
|
||||
}
|
||||
|
||||
logs = game.getLogs();
|
||||
currentPick.value = null;
|
||||
dropReady.value = false;
|
||||
gameOver.value = true;
|
||||
isGameOver.value = true;
|
||||
|
||||
if (score.value > (highScore.value ?? 0)) {
|
||||
highScore.value = score.value;
|
||||
|
@ -551,10 +629,13 @@ async function start() {
|
|||
highScore.value = null;
|
||||
}
|
||||
|
||||
seed = Date.now().toString();
|
||||
|
||||
game = new DropAndFusionGame({
|
||||
width: GAME_WIDTH,
|
||||
height: GAME_HEIGHT,
|
||||
canvas: canvasEl.value!,
|
||||
seed: seed,
|
||||
sfxVolume: mute.value ? 0 : sfxVolume.value,
|
||||
...(
|
||||
gameMode.value === 'normal' ? {
|
||||
|
@ -690,7 +771,7 @@ useInterval(() => {
|
|||
}, 1000, { immediate: false, afterMounted: true });
|
||||
|
||||
onDeactivated(() => {
|
||||
restart();
|
||||
end();
|
||||
});
|
||||
|
||||
definePageMetadata({
|
||||
|
@ -922,6 +1003,28 @@ definePageMetadata({
|
|||
}
|
||||
}
|
||||
|
||||
.replayIndicator {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
left: 10px;
|
||||
bottom: 10px;
|
||||
padding: 6px 8px;
|
||||
color: #f00;
|
||||
background: #0008;
|
||||
border-radius: 6px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.replayIndicatorText {
|
||||
animation: replayIndicator-blink 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes replayIndicator-blink {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes currentMonoArrow {
|
||||
0% { transform: translateY(0); }
|
||||
25% { transform: translateY(-8px); }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue