feat: editor services, stores, and graph improvements

This commit is contained in:
2026-06-14 17:46:34 +08:00
parent 271c909398
commit 82bfae0e1b
5 changed files with 291 additions and 371 deletions

View File

@@ -1,192 +1,41 @@
import { ref, computed, shallowRef, triggerRef } from 'vue'
import type { GameData, SceneNode, Choice } from '@engine/types'
import { computed } from 'vue'
import { useEditorStore } from '../stores/editorStore'
import { computeEdges, computeSceneNodes } from '../services/GraphService'
export function useGraphEditor() {
const gameData = shallowRef<GameData>({ scenes: {}, startScene: '', variables: {} })
const selectedNodeId = ref<string | null>(null)
const startSceneId = ref('')
const store = useEditorStore()
const sceneList = computed(() =>
Object.values(gameData.value.scenes).map((s) => ({ id: s.id, label: s.id })),
)
const sceneNodes = computed(() => computeSceneNodes(store.gameData))
const sceneEdges = computed(() => computeEdges(store.gameData))
const sceneList = computed(() => computeSceneNodes(store.gameData))
const sceneNodes = computed(() =>
Object.values(gameData.value.scenes).map((s) => ({ id: s.id, label: s.id })),
)
const sceneEdges = computed(() => {
const result: { id: string; source: string; target: string; label?: string }[] = []
for (const [id, scene] of Object.entries(gameData.value.scenes)) {
if (scene.choices) {
let ci = 0
for (const c of scene.choices) {
if (c.targetScene) {
result.push({ id: `${id}_choice_${ci}`, source: id, target: c.targetScene, label: c.text.slice(0, 10) })
}
ci++
}
}
if (scene.nextScene) {
if (Array.isArray(scene.nextScene)) {
for (let ri = 0; ri < scene.nextScene.length; ri++) {
const r = scene.nextScene[ri]
if (r.targetScene) {
const condLabel = r.conditions?.length ? '→ 条件' : '→ 默认'
result.push({ id: `${id}_next_${ri}`, source: id, target: r.targetScene, label: condLabel })
}
}
} else {
result.push({ id: `${id}_next`, source: id, target: scene.nextScene, label: '\u2192 \u9ed8\u8ba4' })
}
}
if (scene.qte) {
if (scene.qte.successScene)
result.push({ id: `${id}_qte_s`, source: id, target: scene.qte.successScene, label: 'QTE\u6210\u529f' })
if (scene.qte.failScene)
result.push({ id: `${id}_qte_f`, source: id, target: scene.qte.failScene, label: 'QTE\u5931\u8d25' })
}
}
return result
})
const selectedScene = computed(() => {
if (!selectedNodeId.value) return null
return gameData.value.scenes[selectedNodeId.value] ?? null
})
function trigger() {
triggerRef(gameData)
}
function loadJSON(json: GameData) {
gameData.value = JSON.parse(JSON.stringify(json))
trigger()
startSceneId.value = json.startScene || ''
selectedNodeId.value = null
}
function exportJSON(): GameData {
return JSON.parse(JSON.stringify({ ...gameData.value, startScene: startSceneId.value }))
}
function generateId(): string {
let i = Object.keys(gameData.value.scenes).length + 1
while (gameData.value.scenes[`scene_${i}`]) i++
return `scene_${i}`
}
function addScene(): string {
const id = generateId()
gameData.value = {
...gameData.value,
scenes: {
...gameData.value.scenes,
[id]: {
id,
videoUrl: '',
choices: [],
nextScene: '',
subtitleUrl: '',
onEnter: [],
},
},
}
trigger()
return id
}
function deleteScene(id: string) {
if (startSceneId.value === id) return
const nextScenes = { ...gameData.value.scenes }
delete nextScenes[id]
for (const key of Object.keys(nextScenes)) {
const s = nextScenes[key]
if (s.choices)
nextScenes[key] = { ...s, choices: s.choices.filter((c) => c.targetScene !== id) }
if (Array.isArray(s.nextScene)) {
nextScenes[key] = { ...s, nextScene: s.nextScene.filter((r) => r.targetScene !== id) }
} else if (s.nextScene === id) {
nextScenes[key] = { ...nextScenes[key], nextScene: '' }
}
if (s.qte) {
const qte = { ...s.qte }
let changed = false
if (qte.successScene === id) { qte.successScene = ''; changed = true }
if (qte.failScene === id) { qte.failScene = ''; changed = true }
if (changed) nextScenes[key] = { ...nextScenes[key], qte }
}
}
gameData.value = { ...gameData.value, scenes: nextScenes }
trigger()
if (selectedNodeId.value === id) selectedNodeId.value = null
}
function updateScene(id: string, partial: Partial<SceneNode>) {
const scene = gameData.value.scenes[id]
function onAddEdge(source: string, target: string) {
const scene = store.gameData.scenes[source]
if (!scene) return
gameData.value = {
...gameData.value,
scenes: { ...gameData.value.scenes, [id]: { ...scene, ...partial } },
const newChoices = [...(scene.choices || []), { text: `${source}${target}`, targetScene: target }]
store.gameData = {
...store.gameData,
scenes: { ...store.gameData.scenes, [source]: { ...scene, choices: newChoices } },
}
trigger()
}
function addChoice(sourceId: string) {
const scene = gameData.value.scenes[sourceId]
if (!scene) return
gameData.value = {
...gameData.value,
scenes: {
...gameData.value.scenes,
[sourceId]: {
...scene,
choices: [...(scene.choices || []), { text: '\u65b0\u9009\u9879', targetScene: '' }],
},
},
}
trigger()
}
function updateChoice(sourceId: string, index: number, partial: Partial<Choice>) {
const scene = gameData.value.scenes[sourceId]
if (!scene?.choices) return
const newChoices = scene.choices.map((c, i) => (i === index ? { ...c, ...partial } : c))
gameData.value = {
...gameData.value,
scenes: { ...gameData.value.scenes, [sourceId]: { ...scene, choices: newChoices } },
}
trigger()
}
function deleteChoice(sourceId: string, index: number) {
const scene = gameData.value.scenes[sourceId]
if (!scene?.choices) return
gameData.value = {
...gameData.value,
scenes: {
...gameData.value.scenes,
[sourceId]: { ...scene, choices: scene.choices.filter((_, i) => i !== index) },
},
}
trigger()
store.markDirty()
}
return {
gameData,
selectedNodeId,
selectedScene,
sceneList,
gameData: store.gameData,
selectedNodeId: store.selectedNodeId,
selectedScene: store.selectedScene,
startSceneId: store.startSceneId,
sceneNodes,
sceneEdges,
startSceneId,
loadJSON,
exportJSON,
addScene,
deleteScene,
updateScene,
addChoice,
updateChoice,
deleteChoice,
generateId,
sceneList,
loadJSON: store.loadJSON,
exportJSON: store.exportJSON,
updateScene: store.updateScene,
addChoice: store.addChoice,
updateChoice: store.updateChoice,
deleteChoice: store.deleteChoice,
addScene: store.addScene,
deleteScene: store.deleteScene,
onAddEdge,
}
}