You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity视觉小说存档脚本更新无效问题求助及代码咨询

视觉小说存档数据异常与脚本热更新实现问题

问题背景

开发视觉小说时遇到存档数据异常:存档后修改脚本逻辑,加载旧存档时旧逻辑依然生效。比如存档时角色向左移动,修改脚本为向右移动后加载存档,角色仍停在左侧。

计划用FileSystemWatcher监听脚本文件变更,手动重新加载存档关联的脚本,但不确定具体实现细节。另外在菜单界面修改脚本时会触发游戏引擎管理器空引用异常,考虑用Coroutine等待管理器实例初始化完成后再执行操作,附上当前实现代码,寻求技术优化建议。

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using VISUALNOVEL;

public class ScriptUpdater : MonoBehaviour
{
    [SerializeField]
    private TextAsset file;
    private const string ASSET_FORMAT = "txt";
    private string filePath => $"{Application.dataPath}/{FilePaths.main_path}{FilePaths.dialogue_path}{file.name}.{ASSET_FORMAT}";
    private FileSystemWatcher watcher;
    private bool is_updating => co_updating != null;
    private Coroutine co_updating = null;
    // Start is called before the first frame update
    void Start()
    {
        watcher = new FileSystemWatcher();
        watcher.Path = Path.GetDirectoryName(filePath);
        watcher.Filter = Path.GetFileName(filePath);
        watcher.Changed += OnFileChanged;
        watcher.EnableRaisingEvents = true;
    }

    private void OnFileChanged(object sender, FileSystemEventArgs e)
    {
        ScriptUpdate();
        Debug.Log("changed!");
    }
    private void ScriptUpdate()
    {
        if (is_updating)
        {
            StopCoroutine(co_updating);
        }

        co_updating = StartCoroutine(Updating());
    }

    private IEnumerator Updating()
    {
        Debug.Log("Started Updating Coroutine");

        while (DIALOGUE.DialogueSystem.instance == null)
        {
            Debug.Log("Waiting for DialogueSystem instance...");
            yield return null;
        }

        Debug.Log("DialogueSystem instance found");

        if (DIALOGUE.DialogueSystem.instance != null)
        {
            Debug.Log("Setting Conversation Data");
            VNGameSave.activeFile.SetConversationData();
        }
        Debug.Log("Finished Updating Coroutine");
        co_updating = null;
        yield return null;
    }
}

问题分析与优化方案

1. Coroutine等待实例的思路是可行的

你用Coroutine循环等待DialogueSystem.instance初始化的核心逻辑没问题,但可以补充超时机制避免无限等待:

private IEnumerator Updating()
{
    Debug.Log("Started Updating Coroutine");

    float waitTimeout = 10f;
    float elapsedTime = 0f;
    while (DIALOGUE.DialogueSystem.instance == null)
    {
        elapsedTime += Time.deltaTime;
        if (elapsedTime >= waitTimeout)
        {
            Debug.LogError("等待DialogueSystem实例超时,终止更新");
            co_updating = null;
            yield break;
        }
        Debug.Log("Waiting for DialogueSystem instance...");
        yield return null;
    }

    // 后续逻辑...
}

2. 修复FileSystemWatcher重复触发问题

Changed事件会因文件保存时的临时写入触发多次,需要加防抖处理:

private float debounceDelay = 0.5f;
private float lastTriggerTime;

private void OnFileChanged(object sender, FileSystemEventArgs e)
{
    lastTriggerTime = Time.time;
    StartCoroutine(DebounceUpdate());
    Debug.Log("文件已变更");
}

private IEnumerator DebounceUpdate()
{
    yield return new WaitForSeconds(debounceDelay);
    if (Time.time - lastTriggerTime >= debounceDelay)
    {
        ScriptUpdate();
    }
}

3. 完善存档脚本的重新加载逻辑

当前代码仅调用SetConversationData(),可能未覆盖角色移动这类逻辑的更新,需要补充脚本重解析与存档数据同步:

private IEnumerator Updating()
{
    // 等待实例逻辑...

    Debug.Log("DialogueSystem instance found");

    // 重新读取并解析脚本文件
    if (File.Exists(filePath))
    {
        string newScriptContent = File.ReadAllText(filePath);
        // 调用对话系统的脚本解析方法(需根据你的引擎实现调整)
        DIALOGUE.DialogueSystem.instance.ParseScript(newScriptContent);
        
        // 更新存档中的脚本数据
        VNGameSave.activeFile.UpdateScriptContent(newScriptContent);
        
        // 如果当前存档处于加载状态,立即应用新逻辑
        if (VNGameSave.activeFile.IsLoaded)
        {
            DIALOGUE.DialogueSystem.instance.ReApplySaveLogic();
        }
    }
    else
    {
        Debug.LogError($"未找到脚本文件:{filePath}");
    }

    Debug.Log("更新协程执行完成");
    co_updating = null;
    yield return null;
}

4. 菜单场景的待更新标记处理

在菜单界面时DialogueSystem未初始化,可添加全局标记延迟执行更新:

private bool isPendingUpdate = false;

private IEnumerator DebounceUpdate()
{
    yield return new WaitForSeconds(debounceDelay);
    if (Time.time - lastTriggerTime >= debounceDelay)
    {
        if (DIALOGUE.DialogueSystem.instance == null)
        {
            isPendingUpdate = true;
            Debug.Log("脚本更新已挂起,将在DialogueSystem就绪后执行");
        }
        else
        {
            ScriptUpdate();
        }
    }
}

void Update()
{
    if (isPendingUpdate && DIALOGUE.DialogueSystem.instance != null)
    {
        isPendingUpdate = false;
        ScriptUpdate();
    }
}

内容的提问来源于stack exchange,提问作者mary jane

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 10:30:02