在制作 RPG 甚至 AVG 的时候,我们要涉及大量的文本编辑,这个时候不可视化的界面会大大提升项目的维护成本(非常好脚本,使我 AVG 项目崩溃),所以我们需要自己创建一个文本编辑界面。
而在涉及对话的时候,这个叫做对话树的结构具有不错的性质,对话树是一个简单的多叉树结构,但节点被分为 CP 对话和 PL 对话,这样我们就能比较方便的依靠节点来推出 UI,下面我们就来细说一下如何实现可视化编辑对话树。
首先对于一个对话树,我们采用两个脚本来实现,一个是节点脚本,存放了每个对话节点的信息,比如:
· 文本:此时对话中显示的文本
· 对话者:这个节点中对话的发起者
· 出度:这个节点连接的下一个节点
而为了实现可视化编辑,我们还另外储存这些信息:
· 节点的 Rect :记录节点在编辑界面中的位置和长宽
· 节点的 Texture2D :记录节点的背景样式,这样对眼睛比较友好,而且易于维护节点中的对话者
· isChange :节点信息是否被改变,这样我们只在节点大小改变的时候重新绘制一次 Texture2D
那么节点的代码如下
namespace Dia.Dialogue
{
public enum Speaker
{
Player,
Npc
}
public class DialogueNode : ScriptableObject
{
[TextArea(5,50)]
public string text;
public Texture2D texture;
public Speaker speaker = Speaker.Npc;
[HideInInspector] public bool isChange = false;
[HideInInspector] public Rect pos = new Rect(0, 0, 180, 90);
[HideInInspector] public List<string> nxt = new List<string>();
private void OnValidate() => isChange = true;
}
}
另一个则是文本库,用来返回任一文本节点的信息,以及维护文本节点的创建与删除,在文本库中我们需要实现以下功能:
· 查询:利用文本节点 .name 来获取文本节点
· 获得根节点:便于后续对话的运作
· 获得所有节点:获取文本库中的所有节点
· 获取所有后代:获取一个节点的所有后代,方便对话选项的建立和 CP 随机对话
· 创建与删除节点:在可视化编辑界面中创建和删除节点,在创建或删除之后要重载查询字典和处理节点的后代
那么代码如下
namespace Dia.Dialogue
{
[CreateAssetMenu(fileName = "New Dialogue", menuName = "New Dialogue", order = 0)]
public class Dialogue : ScriptableObject
{
[SerializeField] private List<DialogueNode> nodes = new List<DialogueNode>();
private Dictionary<string, DialogueNode> NodeLookUp =new Dictionary<string, DialogueNode>();
#if UNITY_EDITOR
private void Awake()
{
Initialize();
}
#endif
public void Initialize()
{
if (nodes.Count != 0) return;
CreateNode(null);
}
private void OnValidate()
{
NodeLookUp.Clear();
foreach(DialogueNode node in GetNodes())
NodeLookUp[node.name] = node;
}
public IEnumerable<DialogueNode> GetNodes()
{
return nodes;
}
public DialogueNode GetRootNode()
{
return nodes[0];
}
public IEnumerable<DialogueNode> GetAllChildren(DialogueNode parentNode)
{
foreach(string childID in parentNode.nxt)
{
if (!NodeLookUp.ContainsKey(childID)) continue;
yield return NodeLookUp[childID];
}
}
public IEnumerable<DialogueNode> GetPlayerChildren(DialogueNode currentNode)
{
foreach(DialogueNode node in GetAllChildren(currentNode))
{
if (node.speaker == Speaker.Player) yield return node;
}
}
public IEnumerable<DialogueNode> GetAIChildren(DialogueNode currentNode)
{
foreach (DialogueNode node in GetAllChildren(currentNode))
{
if (node.speaker == Speaker.Npc) yield return node;
}
}
public void CreateNode(DialogueNode parent)
{
DialogueNode newNode = CreateInstance<DialogueNode>();
if (parent != null) newNode.name = System.Guid.NewGuid().ToString();
else newNode.name = "Root";
//Undo.RecordObject(this, "A");
//Undo.RegisterCreatedObjectUndo(newNode, "Creating Dialogue Node");
if (parent != null)
{
parent.nxt.Add(newNode.name);
newNode.pos.x = parent.pos.x + 10f;
newNode.pos.y = parent.pos.y + 10f;
if (parent.speaker == Speaker.Npc) newNode.speaker = Speaker.Player;
if (parent.speaker == Speaker.Player) newNode.speaker = Speaker.Npc;
}
nodes.Add(newNode);
AssetDatabase.AddObjectToAsset(newNode, this);
OnValidate();
}
public void DeleteNode(DialogueNode nodeToRemove)
{
Undo.RecordObject(this, "Delete Dialogue Node");
nodes.Remove(nodeToRemove);
OnValidate();
CleanChildren(nodeToRemove);
Undo.DestroyObjectImmediate(nodeToRemove);
}
private void CleanChildren(DialogueNode nodeToRemove)
{
foreach (DialogueNode node in GetNodes())
node.nxt.Remove(nodeToRemove.name);
}
}
}
接下来就是可视化界面的创建, GetWindow(typeof(this.name),false,"editor name")
函数可以创建一个自定义编辑器窗口,这个函数必须在一个拥有 [MenuItem()]
属性的函数中执行,代码如下。
[MenuItem("Window/Dialogue Editor")] //Create a option in tools
public static void ShowEditorWindow()
{
GetWindow(typeof(DialogueEditor), false, "Dialogue System");
}
而我们需要实现选中一个文本库时,自动弹出编辑窗口,这个时候我们就要使用 Unity 提供的一个回调属性 OnOpenAsset(x)
,其中 x
是函数被调用的次序,加入了这个属性的静态函数将会在一个资产被选中的时候被调用,这个静态函数必须拥有以下两种特征之一。(这是官网对该属性的介绍)
· static bool OnOpenAsset(int instanceID, int line)
· static bool OnOpenAsset(int instanceID, int line, int column)
其中, instanceID 是选中资产在 Unity 中的编号,利用 EditorUtility.InstanceIDToObject
函数就能获取这个资产,接着我们使用投射 as
来将这个资产转换为 dialogue ,如果转换成功的话,我们就选中这个 dialogue 作为当前编辑的文本库,并且打开编辑器窗口。以下是代码。
[OnOpenAsset(1)] //Open the window
public static bool OpenDialogue(int instanceID,int line)
{
Dialogue tmp = EditorUtility.InstanceIDToObject(instanceID) as Dialogue;
if (tmp == null) return false;
ShowEditorWindow();
return true;
}
标签:node,DialogueNode,Dialogue,笔记,public,对话,newNode,可视化,节点
From: https://www.cnblogs.com/caijiLYC/p/18437057