E Story 故事编辑器开发笔记 #11 读取故事数据

读取故事数据

打开StoryEditorWindow.cs,新增以下方法:

C#
// 载入数据
private void LoadDatas(StoryDataSO storyData)
{
    UpdateFileName(storyData.FileName);
    Dictionary<string, BaseGroup> loadedGroups = LoadGroupDatas(storyData.GroupDatas);
    Dictionary<string, BaseNode> loadedNodes = LoadNodeDatas(storyData.NodeDatas, loadedGroups);
    LoadNodesConnections(loadedNodes);
}

// 载入分组数据
private Dictionary<string, BaseGroup> LoadGroupDatas(List<GroupData> groupDatas)
{
    Dictionary<string, BaseGroup> loadedGroups = new();

    // 遍历分组视图列表
    foreach (GroupData groupGraph in groupDatas)
    {
        BaseGroup group = graphView.CreateGroup(groupGraph.Title, groupGraph.Position);
        group.ID = groupGraph.GUID;

        loadedGroups.Add(group.ID, group);
    }

    return loadedGroups;
}

// 载入节点数据
private Dictionary<string, BaseNode> LoadNodeDatas(List<NodeData> nodeDatas, Dictionary<string, BaseGroup> loadedGroups)
{
    Dictionary<string, BaseNode> loadedNodes = new();

    // 遍历节点视图列表
    foreach (NodeData nodeData in nodeDatas)
    {
        // 创建节点
        BaseNode node = graphView.CreateNode(nodeData.Title, nodeData.Type, nodeData.Position, null, false);
        node.GUID = nodeData.GUID;
        node.Note = nodeData.Note;
        node.ChoiceDatas = DataUtility.CloneChoiceDatas(nodeData.ChoiceDatas);

        // 检测节点类型并获取特定变量值
        if (node.Type == NodeType.Dialogue)
        {
            DialogueNode dNode = node as DialogueNode;
            dNode.RoleName = nodeData.RoleName;
            dNode.SentenceDatas = DataUtility.CloneSentenceDatas(nodeData.SentenceDatas);
        }

        // 绘制节点
        node.Draw();

        // 获取所属分组分组
        BaseGroup group = null;
        if (!string.IsNullOrEmpty(nodeData.GroupID))
        {
            group = loadedGroups[nodeData.GroupID];
            group.AddElement(node);
        }

        // 记录该节点已被加载
        loadedNodes.Add(node.GUID, node);
    }

    return loadedNodes;
}

// 载入节点连线
private void LoadNodesConnections(Dictionary<string, BaseNode> loadedNodes)
{
    // 遍历所有已载入的节点
    foreach (KeyValuePair<string, BaseNode> loadedNode in loadedNodes)
    {
        // 遍历节点的所有输出端口
        foreach (Port outputPort in loadedNode.Value.outputContainer.Children())
        {
            ChoiceData choiceData = (ChoiceData)outputPort.userData;

            if (string.IsNullOrEmpty(choiceData.NextNodeID))
            {
                continue;
            }

            // 获取下个节点的输入端口
            Port nextNodeInputPort = loadedNodes[choiceData.NextNodeID].Input;

            // 创建连线
            graphView.CreateEdge(outputPort, nextNodeInputPort);
        }

        // 刷新端口样式
        loadedNode.Value.RefreshPorts();
    }
}

// 更新文件名
private void UpdateFileName(string newFileName)
{
    tfdFileName.value = newFileName;
}

打开故事文件

通过文件选择框打开

打开StoryEditorWindow.cs,新增以下方法:

C#
// 打开故事
private void OpenStory()
{
    // 获取文件路径
    string filePath = EditorUtility.OpenFilePanel("打开故事", storyDatasFolderPath, "asset");

    // 检测是否为空
    if (string.IsNullOrEmpty(filePath))
    {
        return;
    }

    // 绝对路径转为相对路径
    filePath = FileUtil.GetProjectRelativePath(filePath);

    // 载入文件
    StoryDataSO story = IOUtility.LoadAsset<StoryDataSO>(filePath);

    // 检测是否为空
    if (story == null)
    {
        string temp = "故事不存在:\n\n" +
            $"{filePath}\n\n" +
            "请确保你选择了正确的文件。";
        EditorUtility.DisplayDialog("警告", temp, "明白");

        return;
    }

    string str = "确认打开新故事并覆盖当前视图内容吗?未保存的数据将无法恢复。";
    if (EditorUtility.DisplayDialog("警告", str, "确认", "取消"))
    {
        storyData = story;

        graphView.ClearGraph();
        LoadDatas(storyData);

        // 提示消息
        string message = $"故事已打开";
        ShowNotification(new GUIContent(message));
    }
}

打开StoryGraphView.cs,新增ClearGraph方法:

C#
// 清除视图
public void ClearGraph()
{
    graphElements.ForEach(e => RemoveElement(e));
}

打开BaseNode.cs,新增以下属性:

C#
public Port Input { get => input; }
public Port Output { get => output; }

修改CreateNode方法:

C#
// 创建节点
public BaseNode CreateNode(string title, NodeType type, Vector2 position, Group group = null, bool shouldDraw = true)
{
    // 获取类型
    Type nodeType = Type.GetType($"E.Story.{type}Node");

    // 创建对应节点
    BaseNode node = Activator.CreateInstance(nodeType) as BaseNode;
    node.Init(this, title, position);

    // 是否绘制
    if (shouldDraw)
    {
        node.Draw();
    }

    if (group == null)
    {
        // 加入视图
        AddElement(node);
    }
    else
    {
        // 加入分组
        group.AddElement(node);
    }

    return node;
}

打开StoryEditorWindow.cs,修改AddToolbar方法,实现调用:

C#
// 添加工具栏
private void AddToolbar()
{
    /* ... 此处代码已省略 ... */

    btnOpen = ElementUtility.CreateButton("打开", () => OpenStory());
    
    /* ... 此处代码已省略 ... */
}

双击故事文件直接打开

打开StoryEditorWindow.cs,新增OnDoubleClick方法:

C#
// 当双击资产文件时,打开故事编辑器读取故事数据
[OnOpenAsset()]
public static bool OnDoubleClick(int instanceID)
{
    // 获取窗口实例并打开
    StoryEditorWindow wnd = (StoryEditorWindow)GetWindow(typeof(StoryEditorWindow));
    if (wnd == null)
    {
        Open();
    }
    wnd.RemoveNotification();

    // 获取资产路径
    string fullPath = AssetDatabase.GetAssetPath(instanceID);

    // 检测是否目标资产类型
    StoryDataSO storyData = IOUtility.LoadAsset<StoryDataSO>(fullPath);
    if (storyData == null)
    {
        return false;
    }

    string str = "确认打开新故事并覆盖当前视图内容吗?未保存的数据将无法恢复。";
    if (EditorUtility.DisplayDialog("警告", str, "确认", "取消"))
    {
        wnd.storyData = storyData;

        wnd.graphView.ClearGraph();
        wnd.LoadDatas(storyData);

        // 提示消息
        string message = $"故事已打开";
        wnd.ShowNotification(new GUIContent(message));
    }

    return true;
}

启动故事编辑器时自动打开

打开StoryEditorWindow.cs,新增以下方法:

C#
// 打开上次编辑的故事
private void OpenLastStory()
{
    // 获取文件路径
    string storyName = EditorPrefs.GetString(keyLastStoryName);

    // 检测是否为空
    if (string.IsNullOrEmpty(storyName))
    {
        return;
    }

    // 载入文件
    StoryDataSO story = IOUtility.LoadAsset<StoryDataSO>(storyDatasFolderPath, storyName);

    // 检测是否为空
    string message;
    if (story == null)
    {
        message = $"未找到上次编辑的故事";
        ShowNotification(new GUIContent(message));
        return;
    }

    storyData = story;
    graphView.ClearGraph();
    LoadDatas(storyData);

    // 提示消息
    message = $"已打开上次编辑的故事";
    ShowNotification(new GUIContent(message));
}


// 记录当前的故事
private void RecordCurrentStory()
{
    EditorPrefs.SetString(keyLastStoryName, storyData.FileName);
}

修改以下方法,实现调用:

C#
// 打开故事
private void OpenStory()
{
    /* ... 此处代码已省略 ... */

    string str = "确认打开新故事并覆盖当前视图内容吗?未保存的数据将无法恢复。";
    if (EditorUtility.DisplayDialog("警告", str, "确认", "取消"))
    {
        storyData = story;
        RecordCurrentStory();    /* 在此处新增调用 */

        /* ... 此处代码已省略 ... */
    }
}

// 当双击资产文件时,打开故事编辑器读取故事数据
[OnOpenAsset()]
public static bool OnDoubleClick(int instanceID)
{
    /* ... 此处代码已省略 ... */

    string str = "确认打开新故事并覆盖当前视图内容吗?未保存的数据将无法恢复。";
    if (EditorUtility.DisplayDialog("警告", str, "确认", "取消"))
    {
        wnd.storyData = storyData;
        wnd.RecordCurrentStory();   /* 在此处新增调用 */

        /* ... 此处代码已省略 ... */
    }

    return true;
}

private void CreateGUI()
{
    /* ... 此处代码已省略 ... */

    // 打开上个故事
    OpenLastStory();
}

测试效果

最终窗口效果如下:

相关链接

留下评论