feat: fullscreen composable, roadmap update, and future plans doc
This commit is contained in:
66
FUTURE.md
Normal file
66
FUTURE.md
Normal 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 Audio,Web Audio PannerNode)
|
||||||
|
- 对话轮动画曲线自定义
|
||||||
|
- 画面震动强度/频率可配置
|
||||||
|
- 动态字幕说话人识别(AI 自动标注)
|
||||||
|
|
||||||
|
## P15 平台化 - 扩展
|
||||||
|
|
||||||
|
- PWA 支持(离线播放、安装到桌面)
|
||||||
|
- Web Monetization API 付费解锁章节
|
||||||
|
- 开发者 API(第三方创作工具接入)
|
||||||
|
|
||||||
|
## 通用扩展
|
||||||
|
|
||||||
|
- 性能监控面板(FPS、内存、网络)
|
||||||
|
- 自动化测试框架(剧情路径遍历、回归测试)
|
||||||
|
- 热更新支持(不刷新页面替换 JSON 和视频)
|
||||||
|
- WebSocket 多人同步(观察者模式、投票选分支)
|
||||||
10
ROADMAP.md
10
ROADMAP.md
@@ -423,17 +423,15 @@ GainNode 的 ramp 目标值 = `Math.min(bgmVolume, bgmDuckLevel × bgmVolume)`
|
|||||||
| Stingers | 短乐句事件音(发现线索的"叮"、惊悚弦乐刺音) |
|
| Stingers | 短乐句事件音(发现线索的"叮"、惊悚弦乐刺音) |
|
||||||
| BGM 弧线 | 一条 BGM 覆盖多个连续场景而不被切换打断 |
|
| BGM 弧线 | 一条 BGM 覆盖多个连续场景而不被切换打断 |
|
||||||
|
|
||||||
### P7 全屏模式 — 沉浸式浏览器体验(待实现)
|
### P7 全屏模式 — 沉浸式浏览器体验 ✅ 已完成 2026-06-08
|
||||||
|
|
||||||
目标:一键进入全屏播放模式,播放中自动隐藏 UI(选项/菜单等浮层除外),提供 F11 级别的沉浸感。
|
目标:一键进入全屏播放模式,播放中自动隐藏 UI(选项/菜单等浮层除外),提供 F11 级别的沉浸感。
|
||||||
|
|
||||||
**实现清单:**
|
**实现清单:**
|
||||||
|
|
||||||
- [ ] `src/composables/useFullscreen.ts` — Fullscreen API 封装(`element.requestFullscreen()`,ESC 退出监听)
|
- [x] `src/composables/useFullscreen.ts` — Fullscreen API 封装(`toggle` + `isFullscreen` + `fullscreenchange` 监听)
|
||||||
- [ ] `src/App.vue` — 全屏按钮(工具栏或浮动按钮);播放中隐藏非关键 UI 元素
|
- [x] `src/App.vue` — 右上角全屏按钮,与"菜单"按钮并排;`fullscreenchange` 同步图标状态
|
||||||
- [ ] 指针空闲隐藏:鼠标 3 秒不动自动隐藏光标(`pointer-events: none` 过渡)
|
- [x] `FUTURE.md` — 远期扩展笔记(Pointer Lock、自动全屏、UI 自动隐藏、移动端适配等)
|
||||||
- [ ] 全屏下选项面板仍可见(z-index 高于视频层)
|
|
||||||
- [ ] 验证:F11 等效全屏、ESC 退出、播放中 UI 自动隐藏/鼠标移动恢复
|
|
||||||
|
|
||||||
### P8 章节选择 — 通关后可跳转(待实现)
|
### P8 章节选择 — 通关后可跳转(待实现)
|
||||||
|
|
||||||
|
|||||||
20
src/App.vue
20
src/App.vue
@@ -8,8 +8,10 @@ import HotspotLayer from '@/components/HotspotLayer.vue'
|
|||||||
import SaveLoadMenu from '@/components/SaveLoadMenu.vue'
|
import SaveLoadMenu from '@/components/SaveLoadMenu.vue'
|
||||||
import { useGameEngine } from '@/composables/useGameEngine'
|
import { useGameEngine } from '@/composables/useGameEngine'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
import { useFullscreen } from '@/composables/useFullscreen'
|
||||||
|
|
||||||
const store = useGameStore()
|
const store = useGameStore()
|
||||||
|
const { isFullscreen, toggle: toggleFullscreen } = useFullscreen()
|
||||||
const videoElA = ref<HTMLVideoElement | null>(null)
|
const videoElA = ref<HTMLVideoElement | null>(null)
|
||||||
const videoElB = ref<HTMLVideoElement | null>(null)
|
const videoElB = ref<HTMLVideoElement | null>(null)
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
@@ -95,9 +97,12 @@ init()
|
|||||||
:timer-remaining="store.timerRemaining"
|
:timer-remaining="store.timerRemaining"
|
||||||
@choose="onChoose"
|
@choose="onChoose"
|
||||||
/>
|
/>
|
||||||
<button v-if="started && !store.gameEnded" class="menu-trigger" @click="toggleMenu">
|
<div v-if="started && !store.gameEnded" class="top-bar">
|
||||||
菜单
|
<button class="top-btn" @click="toggleFullscreen" :title="isFullscreen ? '退出全屏' : '全屏'">
|
||||||
</button>
|
{{ isFullscreen ? '⛶' : '⛶' }}
|
||||||
|
</button>
|
||||||
|
<button class="top-btn" @click="toggleMenu">菜单</button>
|
||||||
|
</div>
|
||||||
</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>
|
||||||
@@ -160,11 +165,16 @@ html, body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-trigger {
|
.top-bar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 16px;
|
top: 16px;
|
||||||
right: 16px;
|
right: 16px;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-btn {
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
@@ -175,7 +185,7 @@ html, body {
|
|||||||
transition: background 0.2s, color 0.2s;
|
transition: background 0.2s, color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-trigger:hover {
|
.top-btn:hover {
|
||||||
background: rgba(0, 0, 0, 0.7);
|
background: rgba(0, 0, 0, 0.7);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/composables/useFullscreen.ts
Normal file
28
src/composables/useFullscreen.ts
Normal 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 }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user