feat: intro video, asset updates, roadmap and engine improvements
This commit is contained in:
78
src/App.vue
78
src/App.vue
@@ -37,6 +37,10 @@ const canSkip = ref(false)
|
||||
const paused = ref(false)
|
||||
const promptToast = ref('')
|
||||
const showPromptToast = ref(false)
|
||||
const showIntro = ref(false)
|
||||
const introWatched = ref(false)
|
||||
const introVideoRef = ref<HTMLVideoElement | null>(null)
|
||||
const menuVideoRef = ref<HTMLVideoElement | null>(null)
|
||||
|
||||
const { loadGame, start, resumeAutoSave, makeChoice, clickHotspot, startChapter,
|
||||
skipScene, setSpeed, getSpeed, isSceneWatched,
|
||||
@@ -68,6 +72,21 @@ async function init() {
|
||||
}
|
||||
loading.value = false
|
||||
hasAutoSave.value = (await saveSystem.load(0)) !== null
|
||||
|
||||
if (store.introVideo) {
|
||||
introWatched.value = await isSceneWatched('__intro__')
|
||||
showIntro.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function onIntroEnded() {
|
||||
saveSystem.markWatched('__intro__')
|
||||
showIntro.value = false
|
||||
}
|
||||
|
||||
function skipIntro() {
|
||||
saveSystem.markWatched('__intro__')
|
||||
showIntro.value = false
|
||||
}
|
||||
|
||||
function handleStart() {
|
||||
@@ -212,7 +231,16 @@ init()
|
||||
<div class="app-container">
|
||||
<div v-if="loading" class="loading">{{ t('ui.loading') }}</div>
|
||||
<template v-else>
|
||||
<div class="game-screen">
|
||||
<div v-if="showIntro" class="intro-overlay" @click="skipIntro">
|
||||
<video ref="introVideoRef" :src="store.introVideo" class="intro-video" autoplay @ended="onIntroEnded"></video>
|
||||
<button v-if="introWatched" class="intro-skip-btn" @click.stop="skipIntro">{{ t('ui.skip') }}</button>
|
||||
</div>
|
||||
|
||||
<div v-if="store.menuVideo && (!started || store.gameEnded)" class="menu-bg">
|
||||
<video ref="menuVideoRef" :src="store.menuVideo" class="menu-bg-video" autoplay loop muted></video>
|
||||
</div>
|
||||
|
||||
<div class="game-screen" v-show="started && !store.gameEnded">
|
||||
<GamePlayer v-show="!store.isImageScene" @video-ready="onVideoReady" />
|
||||
<HotspotLayer
|
||||
:hotspots="store.hotspots"
|
||||
@@ -418,6 +446,54 @@ html, body {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.intro-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: #000;
|
||||
z-index: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.intro-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.intro-skip-btn {
|
||||
position: absolute;
|
||||
bottom: 40px;
|
||||
right: 40px;
|
||||
padding: 10px 24px;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
letter-spacing: 2px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.intro-skip-btn:hover {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.menu-bg {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.menu-bg-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.prompt-toast {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
|
||||
@@ -142,6 +142,8 @@ export function useGameEngine(videoEls: () => [HTMLVideoElement | null, HTMLVide
|
||||
if (c.thumbnail) c.thumbnail = resolveAsset(base, c.thumbnail)
|
||||
}
|
||||
}
|
||||
if (data.introVideo) data.introVideo = resolveAsset(base, data.introVideo)
|
||||
if (data.menuVideo) data.menuVideo = resolveAsset(base, data.menuVideo)
|
||||
}
|
||||
|
||||
async function loadGame(dataUrl: string) {
|
||||
@@ -165,6 +167,9 @@ export function useGameEngine(videoEls: () => [HTMLVideoElement | null, HTMLVide
|
||||
|
||||
const visitedIds = await saveSystem.getVisitedSceneIds()
|
||||
store.setVisitedSceneIds(visitedIds)
|
||||
|
||||
store.setIntroVideo(data.introVideo || '')
|
||||
store.setMenuVideo(data.menuVideo || '')
|
||||
}
|
||||
|
||||
function ensureVideo() {
|
||||
|
||||
@@ -45,6 +45,8 @@ export const useGameStore = defineStore('game', () => {
|
||||
const antiMistap = ref(localStorage.getItem('antiMistap') !== 'false')
|
||||
const pauseEnabled = ref(localStorage.getItem('pauseEnabled') !== 'false')
|
||||
const showSettings = ref(false)
|
||||
const introVideo = ref('')
|
||||
const menuVideo = ref('')
|
||||
|
||||
function setScene(scene: SceneNode) {
|
||||
currentScene.value = scene
|
||||
@@ -197,6 +199,9 @@ export const useGameStore = defineStore('game', () => {
|
||||
function setPauseEnabled(v: boolean) { pauseEnabled.value = v; localStorage.setItem('pauseEnabled', String(v)) }
|
||||
function setShowSettings(v: boolean) { showSettings.value = v }
|
||||
|
||||
function setIntroVideo(url: string) { introVideo.value = url }
|
||||
function setMenuVideo(url: string) { menuVideo.value = url }
|
||||
|
||||
function dump() {
|
||||
console.group('GameStore')
|
||||
console.log('currentScene:', currentScene.value?.id)
|
||||
@@ -217,7 +222,7 @@ export const useGameStore = defineStore('game', () => {
|
||||
toastAchievementId, showEndingGallery, endings, visitedSceneIds,
|
||||
storyLocales,
|
||||
subFontSize, subBgAlpha, qteTimeRelax, qteSingleKey, antiMistap, pauseEnabled,
|
||||
showSettings,
|
||||
showSettings, introVideo, menuVideo,
|
||||
setScene, setChoices, clearChoices, setGameEnded,
|
||||
setTimer, clearTimer, setSaves,
|
||||
showQTE, updateQTE, resolveQTE, clearQTE, setVideoTime,
|
||||
@@ -229,7 +234,7 @@ export const useGameStore = defineStore('game', () => {
|
||||
setEndings, setShowEndingGallery, setVisitedSceneIds, addVisitedSceneId,
|
||||
setStoryLocales,
|
||||
setSubFontSize, setSubBgAlpha, setQteTimeRelax, setQteSingleKey, setAntiMistap, setPauseEnabled,
|
||||
setShowSettings,
|
||||
setShowSettings, setIntroVideo, setMenuVideo,
|
||||
dump,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user