feat: auto-save on scene change + resume from auto-save
- useGameEngine: auto-save to slot 0 on every sceneChange event - useGameEngine: add resumeAutoSave() for continuing from auto-save - useGameEngine: extract registerEvents() to share between start and resume - SaveLoadMenu: show slot 0 as '自动存档' with distinct styling and read-only button - App.vue: check auto-save on load, show '继续上次进度' button if available
This commit is contained in:
16
src/App.vue
16
src/App.vue
@@ -12,13 +12,15 @@ const videoElB = ref<HTMLVideoElement | null>(null)
|
|||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const started = ref(false)
|
const started = ref(false)
|
||||||
const showMenu = ref(false)
|
const showMenu = ref(false)
|
||||||
|
const hasAutoSave = ref(false)
|
||||||
|
|
||||||
const { loadGame, start, makeChoice, saveGame, loadGameFromSlot, refreshSaves } =
|
const { loadGame, start, resumeAutoSave, makeChoice, saveGame, loadGameFromSlot, refreshSaves, saveSystem } =
|
||||||
useGameEngine(() => [videoElA.value, videoElB.value])
|
useGameEngine(() => [videoElA.value, videoElB.value])
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
await loadGame('/scenes/demo.json')
|
await loadGame('/scenes/demo.json')
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
hasAutoSave.value = (await saveSystem.load(0)) !== null
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleStart() {
|
function handleStart() {
|
||||||
@@ -26,6 +28,11 @@ function handleStart() {
|
|||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleResume() {
|
||||||
|
started.value = true
|
||||||
|
await resumeAutoSave()
|
||||||
|
}
|
||||||
|
|
||||||
function onVideoReady(elA: HTMLVideoElement, elB: HTMLVideoElement) {
|
function onVideoReady(elA: HTMLVideoElement, elB: HTMLVideoElement) {
|
||||||
videoElA.value = elA
|
videoElA.value = elA
|
||||||
videoElB.value = elB
|
videoElB.value = elB
|
||||||
@@ -72,6 +79,7 @@ init()
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="!started" class="start-overlay">
|
<div v-if="!started" class="start-overlay">
|
||||||
<button class="start-btn" @click="handleStart">开始游戏</button>
|
<button class="start-btn" @click="handleStart">开始游戏</button>
|
||||||
|
<button v-if="hasAutoSave" class="start-btn resume-btn" @click="handleResume">继续上次进度</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="store.gameEnded" class="game-end-overlay">
|
<div v-if="store.gameEnded" class="game-end-overlay">
|
||||||
<div class="game-end-text">游戏结束</div>
|
<div class="game-end-text">游戏结束</div>
|
||||||
@@ -190,4 +198,10 @@ html, body {
|
|||||||
.start-btn:hover {
|
.start-btn:hover {
|
||||||
background: rgba(255, 255, 255, 0.25);
|
background: rgba(255, 255, 255, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.resume-btn {
|
||||||
|
margin-top: 16px;
|
||||||
|
border-color: rgba(100, 200, 255, 0.3);
|
||||||
|
color: #8cf;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -21,6 +21,23 @@ const maxSlots = 5
|
|||||||
<h2 class="save-title">存档 / 读档</h2>
|
<h2 class="save-title">存档 / 读档</h2>
|
||||||
|
|
||||||
<div class="slot-list">
|
<div class="slot-list">
|
||||||
|
<div class="save-slot auto-save-slot">
|
||||||
|
<div class="slot-label auto-save-label">自动存档</div>
|
||||||
|
<div class="slot-info" v-if="saves.find(s => s.slot === 0)">
|
||||||
|
{{ saves.find(s => s.slot === 0)!.sceneLabel }}
|
||||||
|
</div>
|
||||||
|
<div class="slot-info empty" v-else>暂无自动存档</div>
|
||||||
|
<div class="slot-actions">
|
||||||
|
<button
|
||||||
|
class="slot-btn load-btn"
|
||||||
|
:disabled="!saves.find(s => s.slot === 0)"
|
||||||
|
@click="emit('load', 0)"
|
||||||
|
>
|
||||||
|
读取
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-for="slot in maxSlots"
|
v-for="slot in maxSlots"
|
||||||
:key="slot"
|
:key="slot"
|
||||||
@@ -94,6 +111,15 @@ const maxSlots = 5
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.auto-save-slot {
|
||||||
|
border-color: rgba(100, 200, 255, 0.3);
|
||||||
|
background: rgba(100, 200, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-save-label {
|
||||||
|
color: #6cf;
|
||||||
|
}
|
||||||
|
|
||||||
.slot-label {
|
.slot-label {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
|
|||||||
@@ -20,14 +20,12 @@ export function useGameEngine(videoEls: () => [HTMLVideoElement | null, HTMLVide
|
|||||||
engine.stateManager.init(data.variables)
|
engine.stateManager.init(data.variables)
|
||||||
}
|
}
|
||||||
|
|
||||||
function start() {
|
function registerEvents() {
|
||||||
const [elA, elB] = videoEls()
|
|
||||||
engine.videoManager.attach(elA!, elB!)
|
|
||||||
|
|
||||||
engine.on('sceneChange', (scene) => {
|
engine.on('sceneChange', (scene) => {
|
||||||
store.setScene(scene)
|
store.setScene(scene)
|
||||||
store.clearChoices()
|
store.clearChoices()
|
||||||
store.clearTimer()
|
store.clearTimer()
|
||||||
|
saveGame(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
engine.on('choiceRequest', (choiceList) => {
|
engine.on('choiceRequest', (choiceList) => {
|
||||||
@@ -49,10 +47,22 @@ export function useGameEngine(videoEls: () => [HTMLVideoElement | null, HTMLVide
|
|||||||
store.setGameEnded(true)
|
store.setGameEnded(true)
|
||||||
engine.choiceSystem.stop()
|
engine.choiceSystem.stop()
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
const [elA, elB] = videoEls()
|
||||||
|
engine.videoManager.attach(elA!, elB!)
|
||||||
|
registerEvents()
|
||||||
engine.start()
|
engine.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function resumeAutoSave(): Promise<boolean> {
|
||||||
|
const [elA, elB] = videoEls()
|
||||||
|
engine.videoManager.attach(elA!, elB!)
|
||||||
|
registerEvents()
|
||||||
|
return await loadGameFromSlot(0)
|
||||||
|
}
|
||||||
|
|
||||||
function makeChoice(index: number) {
|
function makeChoice(index: number) {
|
||||||
const scene = store.currentScene
|
const scene = store.currentScene
|
||||||
if (!scene?.choices) return
|
if (!scene?.choices) return
|
||||||
@@ -99,5 +109,5 @@ export function useGameEngine(videoEls: () => [HTMLVideoElement | null, HTMLVide
|
|||||||
destroy()
|
destroy()
|
||||||
})
|
})
|
||||||
|
|
||||||
return { loadGame, start, makeChoice, saveGame, loadGameFromSlot, refreshSaves, engine, saveSystem }
|
return { loadGame, start, resumeAutoSave, makeChoice, saveGame, loadGameFromSlot, refreshSaves, engine, saveSystem }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user