feat: fullscreen composable, roadmap update, and future plans doc

This commit is contained in:
2026-06-09 10:47:14 +08:00
parent 4bfdfbc27d
commit 655b9a23d0
4 changed files with 113 additions and 11 deletions

66
FUTURE.md Normal file
View File

@@ -0,0 +1,66 @@
# 远期功能扩展笔记
以下功能在讨论中出现但暂不纳入实施计划,后续需要扩展时参考。
## P7 全屏模式 - 扩展
- **自动进入全屏** — 点击"开始游戏"时同步 `requestFullscreen()`,利用用户手势
- **UI 自动隐藏** — 播放中 3 秒无鼠标移动自动隐藏菜单按钮和光标(`pointer-events: none`),鼠标移动恢复
- **Pointer Lock** — `requestPointerLock()` 锁定鼠标,防移出窗口,配合热区点击和 QTE
- **移动端适配** — iOS Safari `webkitEnterFullscreen`Android Chrome 视口适配
## P8 章节选择 - 扩展
- 章节缩略图懒加载
- 章节解锁动画
- 按进度百分比展示章节完成度
## P9 跳过/倍速 - 扩展
- 智能跳过(仅跳过"对话"部分,保留"动作"部分)
- 快捷键自定义
- 2x/4x/8x 多档位
## P10 键盘/手柄导航 - 扩展
- Gamepad 震动反馈(手柄扳机键模拟选择"重量"
- 自定义键位映射界面
- 手柄热插拔检测
## P11 多语言字幕 - 扩展
- 字幕字体/大小/颜色/背景自定义
- 语音语言独立轨道(语音和字幕可不同语言)
- 自动检测浏览器语言
## P12 场景过渡特效 - 扩展
- 可自定义转场JSON 中定义颜色/时长/曲线)
- 转场预览(编辑器中实时预览)
- 条件转场(根据 variables 选择不同转场类型)
## P13 重玩驱动 - 扩展
- Steam Achievement API 集成
- 排行榜(最快通关、最少死亡等)
- 分享结局截图到社交媒体
## P14 沉浸感 - 扩展
- SFX 空间化3D AudioWeb Audio PannerNode
- 对话轮动画曲线自定义
- 画面震动强度/频率可配置
- 动态字幕说话人识别AI 自动标注)
## P15 平台化 - 扩展
- PWA 支持(离线播放、安装到桌面)
- Web Monetization API 付费解锁章节
- 开发者 API第三方创作工具接入
## 通用扩展
- 性能监控面板FPS、内存、网络
- 自动化测试框架(剧情路径遍历、回归测试)
- 热更新支持(不刷新页面替换 JSON 和视频)
- WebSocket 多人同步(观察者模式、投票选分支)

View File

@@ -423,17 +423,15 @@ GainNode 的 ramp 目标值 = `Math.min(bgmVolume, bgmDuckLevel × bgmVolume)`
| Stingers | 短乐句事件音(发现线索的"叮"、惊悚弦乐刺音) |
| BGM 弧线 | 一条 BGM 覆盖多个连续场景而不被切换打断 |
### P7 全屏模式 — 沉浸式浏览器体验(待实现)
### P7 全屏模式 — 沉浸式浏览器体验 ✅ 已完成 2026-06-08
目标:一键进入全屏播放模式,播放中自动隐藏 UI选项/菜单等浮层除外),提供 F11 级别的沉浸感。
**实现清单:**
- [ ] `src/composables/useFullscreen.ts` — Fullscreen API 封装(`element.requestFullscreen()`ESC 退出监听)
- [ ] `src/App.vue` — 全屏按钮(工具栏或浮动按钮);播放中隐藏非关键 UI 元素
- [ ] 指针空闲隐藏:鼠标 3 秒不动自动隐藏光标(`pointer-events: none` 过渡
- [ ] 全屏下选项面板仍可见z-index 高于视频层)
- [ ] 验证F11 等效全屏、ESC 退出、播放中 UI 自动隐藏/鼠标移动恢复
- [x] `src/composables/useFullscreen.ts` — Fullscreen API 封装(`toggle` + `isFullscreen` + `fullscreenchange` 监听)
- [x] `src/App.vue`右上角全屏按钮,与"菜单"按钮并排;`fullscreenchange` 同步图标状态
- [x] `FUTURE.md` — 远期扩展笔记Pointer Lock、自动全屏、UI 自动隐藏、移动端适配等
### P8 章节选择 — 通关后可跳转(待实现)

View File

@@ -8,8 +8,10 @@ import HotspotLayer from '@/components/HotspotLayer.vue'
import SaveLoadMenu from '@/components/SaveLoadMenu.vue'
import { useGameEngine } from '@/composables/useGameEngine'
import { useGameStore } from '@/stores/gameStore'
import { useFullscreen } from '@/composables/useFullscreen'
const store = useGameStore()
const { isFullscreen, toggle: toggleFullscreen } = useFullscreen()
const videoElA = ref<HTMLVideoElement | null>(null)
const videoElB = ref<HTMLVideoElement | null>(null)
const loading = ref(true)
@@ -95,9 +97,12 @@ init()
:timer-remaining="store.timerRemaining"
@choose="onChoose"
/>
<button v-if="started && !store.gameEnded" class="menu-trigger" @click="toggleMenu">
菜单
</button>
<div v-if="started && !store.gameEnded" class="top-bar">
<button class="top-btn" @click="toggleFullscreen" :title="isFullscreen ? '退出全屏' : '全屏'">
{{ isFullscreen ? '⛶' : '⛶' }}
</button>
<button class="top-btn" @click="toggleMenu">菜单</button>
</div>
</div>
<div v-if="!started" class="start-overlay">
<button class="start-btn" @click="handleStart">开始游戏</button>
@@ -160,11 +165,16 @@ html, body {
height: 100%;
}
.menu-trigger {
.top-bar {
position: absolute;
top: 16px;
right: 16px;
z-index: 20;
display: flex;
gap: 6px;
}
.top-btn {
padding: 8px 16px;
font-size: 13px;
color: #aaa;
@@ -175,7 +185,7 @@ html, body {
transition: background 0.2s, color 0.2s;
}
.menu-trigger:hover {
.top-btn:hover {
background: rgba(0, 0, 0, 0.7);
color: #fff;
}

View File

@@ -0,0 +1,28 @@
import { ref, onMounted, onUnmounted } from 'vue'
export function useFullscreen() {
const isFullscreen = ref(false)
function sync() {
isFullscreen.value = !!document.fullscreenElement
}
async function toggle() {
if (!document.fullscreenElement) {
await document.documentElement.requestFullscreen().catch(() => {})
} else {
await document.exitFullscreen()
}
}
onMounted(() => {
document.addEventListener('fullscreenchange', sync)
sync()
})
onUnmounted(() => {
document.removeEventListener('fullscreenchange', sync)
})
return { isFullscreen, toggle }
}