Développement de l'outil de flux de dialogue Unity

question:

Lors du développement d'un module de guidage, vous rencontrerez toujours le processus de dialogue entre personnages. Dans la plupart des cas, le produit envoie le document du processus de dialogue au programme, puis le programme le traite secondairement dans ses propres données textuelles lisibles. De cette façon, le programme et le produit sont dans un certain sens. On dit que cela a causé une perte de temps. Chaque fois qu'un changement est apporté plus tard, le temps de développement de l'équipe sera perdu.

attendre

Afin de simplifier le processus de développement, j'espère développer un outil de visualisation des produits. Les fichiers de données enregistrés par le produit après l'écriture des données peuvent être lus de manière transparente par le programme, puis cet outil sera utilisé pour générer de nouveaux fichiers à remplacer. les anciens fichiers pour chaque itération du processus de dialogue.

Capture d'écran de l'outil

Insérer la description de l'image ici
Tout d'abord, si nous développons cet outil, nous devons d'abord définir un type de données pour stocker les informations dont nous avons besoin.
Ensuite, commencer par la plus petite unité et travailler en arrière pour la définir. Nous avons besoin d'une instruction d'information. Cette instruction doit contenir ceci. phrase dans l'ensemble du processus de parole. L'identifiant, le type de locuteur, le contenu de la parole, la durée de la parole, etc. sont les suivants :

/// <summary>
/// 说话信息
/// </summary>
[Serializable]
public class SpeakInfo
{
    public int id;// 在所属对话流中的id
    public SpeakerType type;//说话者类型
    public string info;//说话内容
    public float time=3;
}

Définir l'énumération des locuteurs. Afin de faciliter la configuration du produit, j'ai également réalisé un outil pour générer ce fichier d'énumération. L'effet est le suivant :

public enum SpeakerType
{
	Teacher,
	Wang,
	Li,
	Zhang,
}

Insérer la description de l'image ici

Nous savons alors que chaque conversation est composée de plusieurs phrases. Afin de faciliter l'indexation ultérieure du flux de conversation, nous définissons un indicateur unique pour le flux de conversation. Ensuite, les données du flux de conversation sont définies comme suit :

/// <summary>
/// 对话流
/// </summary>
[Serializable]
public class DialogFlow:ISerializationCallbackReceiver
{
    /// <summary>
    /// 对话流flag (唯一标签)
    /// </summary>
    public string flag;
    /// <summary>
    /// 所有说话内容
    /// </summary>
    public List<SpeakInfo> speakInfoList =new List<SpeakInfo>();
    
    public void OnBeforeSerialize()
    {
    }

    public void OnAfterDeserialize()
    {
        if (speakInfoList==null||speakInfoList.Count==0)return;
        for (int i = 0; i < speakInfoList.Count; i++)speakInfoList[i].id = i;
    }
}

Maintenant que les données stockées ont été clairement définies, nous avons besoin d'un objet pour contrôler la lecture et la sauvegarde des données. Je l'ai nommé DialogFlowBook :

/// <summary>
/// 对话流课本(包含所有对话流,可以检索目标流)
/// </summary>
public class DialogFlowBook
{
    private static DialogFlowBook _instance;

    public static DialogFlowBook instance
    {
        get
        {
            if (_instance==null)_instance=new DialogFlowBook();
            return _instance;
        }
    }

    public static string filePath = Application.dataPath+"/Dialog/DialogInfo.txt";
    
    [Serializable]
    private class DialogFlowGroup:ISerializationCallbackReceiver
    {
        public List<DialogFlow> list = new List<DialogFlow>();
        
        private Dictionary<string,DialogFlow> dict= new Dictionary<string, DialogFlow>();
        
        public DialogFlow GetDialogFlow(string flag)
        {
            if (dict == null || dict.Count == 0) return null;
            if (dict.ContainsKey(flag)) return dict[flag];
            return null;
        }
        
        private void OnInitDict()
        {
            if (list == null || list.Count == 0) return;
            for (int i = 0; i < list.Count; i++)
            {
                DialogFlow df = list[i];
                if (dict.ContainsKey(df.flag))
                {
                    Debug.LogErrorFormat("【Error: 对话流程中有Flag:【{0}】重复】",df.flag);
                    return;
                }
                dict.Add(df.flag,df);
            }
        }

        public void OnBeforeSerialize()
        {
            
        }

        public void OnAfterDeserialize()
        {
            OnInitDict();
        }
    }

    private static DialogFlowGroup dialogFlowGroup;

    private DialogFlowBook()
    {
        dialogFlowGroup = LoadDialogFlowGroup();
    }

    private DialogFlowGroup LoadDialogFlowGroup()
    {
        DialogFlowGroup group = null;
        string json = ReadLocal(filePath);
        if (string.IsNullOrEmpty(json))
        {
            group=new DialogFlowGroup();
            Debug.Log("本地读取对话流程信息为空");
            return group;
        }
        group=JsonUtility.FromJson<DialogFlowGroup>(json);
        return group;
    }

    /// <summary>
    /// 获取对话流
    /// </summary>
    /// <param name="flag">唯一标识</param>
    /// <returns></returns>
    public DialogFlow GetDialogFlow(string flag)
    {
        return dialogFlowGroup.GetDialogFlow(flag);
    }

#if UNITY_EDITOR
    public static List<DialogFlow> Load()
    {
        DialogFlowGroup group = null;
        string json = ReadLocal(filePath);
        if (string.IsNullOrEmpty(json))
        {
            group=new DialogFlowGroup();
            Debug.Log("本地读取对话流程信息为空");
            return group.list;
        }
        group=JsonUtility.FromJson<DialogFlowGroup>(json);
        return group?.list;
    }

    public static void Save(List<DialogFlow> list)
    {
        if (dialogFlowGroup==null)dialogFlowGroup=new DialogFlowGroup();
        dialogFlowGroup.list = list;
        string json=JsonUtility.ToJson(dialogFlowGroup);
        WriteToLocal(filePath,json);
    }
    #endif

    public static void WriteToLocal(string path,string info)
    {
        if (File.Exists(path))File.Delete(path);
        string dirPath = Path.GetDirectoryName(path);
        if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath);
        using (FileStream fs= new FileStream(path,FileMode.CreateNew))
        {
            StreamWriter sw = new StreamWriter(fs);
            sw.Write(info);
            sw.Close();
            sw.Dispose();
        }
    }

    public static string ReadLocal(string path)
    {
        if (!File.Exists(path))return null;
        string info = "";
        using (FileStream fs = new FileStream(path,FileMode.Open,FileAccess.Read))
        {
            StreamReader sr = new StreamReader(fs);
            info = sr.ReadToEnd();
            sr.Close();
            sr.Dispose();
        }
        return info;
    }

}

Commençons par développer l'éditeur. Le code source est le suivant :

using System;
using System.Collections.Generic;
using System.Text;
using UnityEditor;
using UnityEngine;

public class SayTypeTools : EditorWindow
{
    private List<string> speakerTypeList;
    private Vector2 scrollPos;
    private string filePath;

    [MenuItem("Tools/对话/类型配置")]
    static void OpenWindow()
    {
        SayTypeTools window = GetWindow<SayTypeTools>("类型配置");
        window.OnInit();
        window.Show();
    }
    void OnInit()
    {  
        filePath=Application.dataPath + "/Dialog/SpeakerType.cs";
        speakerTypeList=new List<string>();
        speakerTypeList.AddRange(Enum.GetNames(typeof(SpeakerType)));
    }
    
    private void OnGUI()
    {
        DrawSayTypes();
    }
    
    void DrawSayTypes()
    {
        if (GUILayout.Button("增加对话类型(用英文字符命名)"))speakerTypeList.Add("");
        GUILayout.Space(10);
        scrollPos=EditorGUILayout.BeginScrollView(scrollPos);
        for (int i = 0; i < speakerTypeList.Count; i++)
        {
            EditorGUILayout.BeginHorizontal();
            speakerTypeList[i] = EditorGUILayout.TextField(speakerTypeList[i]);
            if (GUILayout.Button("删除",GUILayout.Width(50)))
            {
                speakerTypeList.RemoveAt(i);
                i--;
            }
            EditorGUILayout.EndHorizontal();
        }
        EditorGUILayout.EndScrollView();
        if (GUILayout.Button("保存"))SaveSayTypeFile();
    }
    
    void SaveSayTypeFile()
    {
        StringBuilder sayTypeStr=new StringBuilder();
        sayTypeStr.AppendLine("public enum SpeakerType");
        sayTypeStr.AppendLine("{");
        for (int i = 0; i < speakerTypeList.Count; i++)
        {
            sayTypeStr.AppendLine("\t"+speakerTypeList[i]+",");
        }
        sayTypeStr.AppendLine("}");
        DialogFlowBook.WriteToLocal(filePath,sayTypeStr.ToString());
        AssetDatabase.Refresh();
    }
}

public class DialogTools : EditorWindow
{
    private List<DialogFlow> dialogFlowList;
    private List<bool> bigSwitchList;
    private GUIStyle smallPageStyle;
    private Vector2 scrollPos;

    [MenuItem("Tools/对话/内容编辑")]
    static void OpenWindow()
    {
        DialogTools window = GetWindowWithRect<DialogTools>(new Rect(0,0,800,900),false,"内容编辑");
        window.OnInit();
        window.Show();
    }

    void OnInit()
    {
        smallPageStyle=new GUIStyle();
        smallPageStyle.normal.textColor = Color.green;
        smallPageStyle.fontSize = 50;
        smallPageStyle.alignment = TextAnchor.MiddleCenter;
        bigSwitchList=new List<bool>();
        dialogFlowList = DialogFlowBook.Load();
        if (dialogFlowList!=null&&dialogFlowList.Count>0)for (int i = 0; i < dialogFlowList.Count; i++)bigSwitchList.Add(false);
    }

    private void OnGUI()
    {
        if(GUILayout.Button("增加一段对话"))
        {
            GUI.FocusControl(null);
            DialogFlow big = new DialogFlow();
            dialogFlowList.Add(big);
            bigSwitchList.Add(true);
        }

        EditorGUILayout.BeginHorizontal();
        if (GUILayout.Button("展开"))
        {
            GUI.FocusControl(null);
            for (int i = 0; i < bigSwitchList.Count; i++) bigSwitchList[i] = true;
        }
        
        if (GUILayout.Button("收缩"))
        {
            GUI.FocusControl(null);
            for (int i = 0; i < bigSwitchList.Count; i++) bigSwitchList[i] = false;
        }
        EditorGUILayout.EndHorizontal();
        scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
        DrawMain();
        EditorGUILayout.EndScrollView();
        if (GUILayout.Button("保存"))
        {
            GUI.FocusControl(null);
            Save();
        }
    }

    void DrawMain()
    {
        void DrawBigTitle(int id,out bool isDelete)
        {
            isDelete = false;
            EditorGUILayout.BeginHorizontal();
            
            bigSwitchList[id]=EditorGUILayout.Foldout(bigSwitchList[id],"段"+(id+1)+"、");
            EditorGUILayout.LabelField("标签",GUILayout.Width(30));
            DialogFlow flow = dialogFlowList[id];
            flow.flag=EditorGUILayout.TextField(flow.flag);
            
            if (GUILayout.Button("增加对话"))
            {
                GUI.FocusControl(null);
                flow.speakInfoList.Add(new SpeakInfo());
                bigSwitchList[id] = true;
            }

            if (GUILayout.Button("删除"))
            {
                GUI.FocusControl(null);
                dialogFlowList.RemoveAt(id);
                bigSwitchList.RemoveAt(id);
                isDelete = true;
            }
            EditorGUILayout.EndHorizontal();
        }

        if (dialogFlowList == null || dialogFlowList.Count == 0) return;
        for (int i = dialogFlowList.Count-1; i >=0 ; i--)
        {
            bool isDelete;
            DrawBigTitle(i,out isDelete);
            if (isDelete)continue;
            if (bigSwitchList[i])DrawDialogFlow(dialogFlowList[i]);
            EditorGUILayout.LabelField("---------------------------------------------------------------------------------------------------------------------");
        }
    }

    void DrawDialogFlow(DialogFlow dialogFlow)
    {
        if (dialogFlow == null) return;
        if (dialogFlow.speakInfoList == null || dialogFlow.speakInfoList.Count == 0) return;
        for (int i = dialogFlow.speakInfoList.Count-1; i >=0 ; i--)
        {
            SpeakInfo speakInfo = dialogFlow.speakInfoList[i];
            bool isDelete;
            DrawSayInfo(speakInfo,i,out isDelete);
            if (isDelete)dialogFlow.speakInfoList.RemoveAt(i);
        }
    }

    void DrawSayInfo(SpeakInfo speakInfo,int id,out bool isDelete)
    {
        isDelete = false;
        if (speakInfo == null) return;
        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.BeginVertical(GUILayout.Width(20));
        speakInfo.type = (SpeakerType)EditorGUILayout.EnumPopup(speakInfo.type,GUILayout.Width(100));
        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.LabelField("时长",GUILayout.Width(40));
        speakInfo.time = EditorGUILayout.FloatField(speakInfo.time,GUILayout.Width(60));
        EditorGUILayout.EndHorizontal();
        GUILayout.Space(14);
        EditorGUILayout.LabelField((id+1).ToString(),smallPageStyle,GUILayout.Width(100));
        EditorGUILayout.EndVertical();
        speakInfo.info = EditorGUILayout.TextArea(speakInfo.info,GUILayout.Height(EditorGUIUtility.singleLineHeight*5));
        if (GUILayout.Button("删除",GUILayout.Width(35),GUILayout.Height(EditorGUIUtility.singleLineHeight*5)))
        {
            GUI.FocusControl(null);
            isDelete = true;
        }
        EditorGUILayout.EndHorizontal();
    }

    void Save()
    {
        int bigCount = dialogFlowList == null ? 0 : dialogFlowList.Count;
        if (dialogFlowList.Count==0)
        {
            if (!EditorUtility.DisplayDialog("提示:", "\n数据为空,确定要替换本地数据?", "是", "否"))
            {
                AssetDatabase.Refresh();
                return;
            }
        }

        for (int i = 0; i < bigCount; i++)
        {
            if (string.IsNullOrEmpty(dialogFlowList[i].flag))
            {
                if (EditorUtility.DisplayDialog("提示:", "\n有语段标签为空,不可保存", "是"))
                {
                    AssetDatabase.Refresh();
                }
                return;
            }
        }
        DialogFlowBook.Save(dialogFlowList);
        AssetDatabase.Refresh();
        Debug.Log("数据保存成功!");
    }
}

ok Le développement de l'outil est terminé.
Vous trouverez ci-dessous un cadre de processus de dialogue simple que j'ai écrit pour l'adapter à cet outil, comprenant la lecture du dialogue, l'arrêt du dialogue, les rappels pour le début et la fin de chaque dialogue, et les rappels pour la fin du dialogue du processus, etc. Ces modules fonctionnels couramment utilisés ont été développés. Si vous êtes intéressé, vous pouvez télécharger le code source et y jeter un œil.
Adresse de démonstration

Je suppose que tu aimes

Origine blog.csdn.net/weixin_42498461/article/details/123335034
conseillé
Classement