目录
若想进一步提升游戏的互动性,可引入变量系统。简单来说就是,当玩家在剧情分支做出不同选择时,能够修改预先设定好的变量数值,后续系统读取改动后的数值,判断条件并展示不同的后续剧情。
我们可以设计好感度、生命值、货币等这类变量,让玩家的每一次选择都产生长远影响,这样便能极大地丰富游戏的互动层次。
因为内容较多,故我分成了三篇文章进行详解,以下内容是第一部分:
在编辑器中实现变量面板
显示与隐藏变量面板
打开StoryEditorWindow.cs,新增若干字段与方法,并进行调用:
/*** 新增字段 ***/
private Button btnVars;
/*** 新增方法 ***/
// 切换变量面板
private void ToggleVarPanel()
{
graphView.ToggleVarPanel();
// 切换按钮样式
btnVars.ToggleInClassList("button__selected");
}
/*** 设置调用 ***/
// 添加工具栏
private void AddToolbar()
{
/* ... 此处代码已省略 ... */
btnVars = ElementUtility.CreateButton("变量面板", () => ToggleVarPanel());
/* ... 此处代码已省略 ... */
toolbar.Add(btnVars);
/* ... 此处代码已省略 ... */
}打开StoryGraphView.cs,新增若干字段与方法,并进行调用:
/*** 新增字段 ***/
private VarPanel varPanel;
public VarPanel VarPanel { get => varPanel; }
/*** 新增方法 ***/
// 添加变量面板
private void AddVarPanel()
{
varPanel = new();
varPanel.Init(this);
varPanel.Draw();
Add(varPanel);
}
// 切换变量面板
public void ToggleVarPanel()
{
varPanel.visible = !varPanel.visible;
}
/*** 设置调用 ***/
// 构造器
public StoryGraphView(StoryEditorWindow window)
{
/* ... 此处代码已省略 ... */
AddVarPanel();
/* ... 此处代码已省略 ... */
}打开UI文件夹,创建C#文件,命名为VarPanel,打开VarPanel.cs,将内容修改如下:
using UnityEditor.Experimental.GraphView;
namespace E.Story
{
public class VarPanel : Blackboard
{
// 初始化
public void Init(StoryGraphView graphView)
{
this.graphView = graphView;
title = "变量";
subTitle = "";
scrollable = true;
visible = false;
}
// 绘制
public void Draw()
{}
}
}新增、删除与编辑变量
打开Data文件夹,创建C#文件,命名为VarData,打开VarData.cs,将内容修改如下:
using System;
using UnityEngine;
namespace E.Story
{
// 变量数据
[Serializable]
public class VarData
{
[SerializeField] private string key;
[SerializeField] private int value;
// 变量名
public string Key { get => key; set => key = value; }
// 变量值
public int Value { get => value; set => this.value = value; }
public VarData(string key, int value = 0)
{
this.key = key;
this.value = value;
}
}
}打开VarPanel.cs,将内容修改如下:
using System.Collections.Generic;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;
namespace E.Story
{
public class VarPanel : Blackboard
{
public List<VarData> VarDatas { get; set; }
public List<string> VarDataKeys
{
get
{
List<string> keys = new();
foreach (VarData varData in VarDatas)
{
string key = varData.Key;
keys.Add(key);
}
return keys;
}
}
// 初始化
public void Init(StoryGraphView graphView)
{
this.graphView = graphView;
title = "变量";
subTitle = "";
scrollable = true;
visible = false;
VarDatas = new();
addItemRequested = (bb) =>
{
VarData varData = new("新变量");
VarDatas.Add(varData);
VisualElement lineContainer = CreateVarData(varData);
Add(lineContainer);
CheckDuplicateVarKey();
};
moveItemRequested = (bb, index, ve) =>
{
Debug.Log("moveItemRequested");
};
editTextRequested = (bb, ve, text) =>
{
Debug.Log("editTextRequested");
};
}
// 绘制
public void Draw()
{
Clear();
// 遍历
foreach (VarData varData in VarDatas)
{
VisualElement lineContainer = CreateVarData(varData);
Add(lineContainer);
}
}
// 创建变量数据UI
private VisualElement CreateVarData(VarData varData)
{
// 创建行容器
VisualElement lineContainer = new();
lineContainer.userData = userData;
lineContainer.AddClasses("row-container");
// 创建变量名输入框
TextField tfdVarKey = ElementUtility.CreateTextArea(varData.Key, null, callback =>
{
varData.Key = callback.newValue;
CheckDuplicateVarKey();
});
tfdVarKey.AddClasses
(
"textfield",
"textfield__quote"
);
lineContainer.Add(tfdVarKey);
// 创建变量值输入框
IntegerField tfdVarValue = ElementUtility.CreateIntField(varData.Value, null, callback =>
{
varData.Value = callback.newValue;
});
tfdVarValue.AddClasses
(
"textfield",
"textfield__quote"
);
lineContainer.Add(tfdVarValue);
// 创建删除按钮
Button btnDelete = ElementUtility.CreateButton("X", () =>
{
VarDatas.Remove(varData);
Remove(lineContainer);
CheckDuplicateVarKey();
});
btnDelete.AddClasses();
lineContainer.Add(btnDelete);
return lineContainer;
}
// 检测重复变量名
private void CheckDuplicateVarKey()
{
List<string> keys = new();
List<string> duplicateKeys = new();
// 记录重复的变量名
foreach (VisualElement lineContainer in Children())
{
TextField tfdKey = lineContainer.Q<TextField>();
if (keys.Contains(tfdKey.value))
{
duplicateKeys.Add(tfdKey.value);
}
else
{
keys.Add(tfdKey.value);
}
}
// 如果有重复,高亮所有相关条目的背景色
foreach (VisualElement lineContainer in Children())
{
TextField tfdKey = lineContainer.Q<TextField>();
if (duplicateKeys.Contains(tfdKey.value))
{
lineContainer.AddClasses("error-bg");
}
else
{
lineContainer.RemoveFromClassList("error-bg");
}
}
// 如果有重复,显示副标题
if (duplicateKeys.Count > 0)
{
this.Q<Label>("subTitleLabel").style.color = new StyleColor(new Color(1, 0.133f, 0.133f, 1));
subTitle = "请勿设置同名变量";
}
else
{
this.Q<Label>("subTitleLabel").style.color = new StyleColor(new Color(0.133f, 0.133f, 0.133f, 1));
subTitle = "";
}
}
}
}打开ElementUtility.cs,新增方法:
// 创建整数字段
public static IntegerField CreateIntField(int value = 0, string label = null, EventCallback<ChangeEvent<int>> onValueChanged = null)
{
IntegerField field = new()
{
value = value,
label = label,
};
if (onValueChanged != null)
{
field.RegisterValueChangedCallback(onValueChanged);
}
return field;
}新增默认变量
打开StoryGraphView.cs,新增方法并进行调用:
/*** 新增方法 ***/
// 添加默认变量
public void AddDefaultVarData()
{
VarData varData1 = new("变量一");
VarData varData2 = new("变量二");
VarData varData3 = new("变量三");
varPanel.VarDatas.Add(varData1);
varPanel.VarDatas.Add(varData2);
varPanel.VarDatas.Add(varData3);
varPanel.Draw();
}
/*** 设置调用 ***/
// 构造器
public StoryGraphView(StoryEditorWindow window)
{
/* ... 此处代码已省略 ... */
AddDefaultVarData();
/* ... 此处代码已省略 ... */
}打开StoryEditorWindow.cs进行调用:
// 新建故事
private void NewStory()
{
/* ... 此处代码已省略 ... */
if (EditorUtility.DisplayDialog("警告", str, "确认", "取消"))
{
/* ... 此处代码已省略 ... */
graphView.AddDefaultVarData();
/* ... 此处代码已省略 ... */
}
}
// 清空视图并添加默认数据
private void ClearGraphAndCreateDefaultDatas()
{
/* ... 此处代码已省略 ... */
if (EditorUtility.DisplayDialog("警告", str, "确认", "取消"))
{
/* ... 此处代码已省略 ... */
graphView.AddDefaultVarData();
/* ... 此处代码已省略 ... */
}
}保存与读取变量
打开StoryDataSO.cs,新增字段并进行初始化:
/*** 新增字段 ***/
[SerializeField] private List<VarData> varDatas;
public List<VarData> VarDatas { get => varDatas; set => varDatas = value; }
/*** 修改方法 ***/
// 初始化
public void Init(string fileName)
{
/* ... 此处代码已省略 ... */
varDatas = new(); //初始化字段
}打开StoryEditorWindow.cs,新增若干方法并进行调用:
/*** 新增方法 ***/
// 保存变量数据
private void SaveVarDatas(List<VarData> vars)
{
storyData.VarDatas = DataUtility.CloneVarDatas(vars);
}
// 载入变量数据
private void LoadVarDatas(List<VarData> oldVarDatas)
{
List<VarData> newVarDatas = DataUtility.CloneVarDatas(oldVarDatas);
graphView.VarPanel.SetVarDatas(newVarDatas);
}
/*** 设置调用 ***/
// 保存数据
private void SaveDatas()
{
/* ... 此处代码已省略 ... */
SaveVarDatas(graphView.Vars);
/* ... 此处代码已省略 ... */
}
// 载入数据
private void LoadDatas(StoryDataSO storyData)
{
/* ... 此处代码已省略 ... */
LoadVarDatas(storyData.VarDatas);
/* ... 此处代码已省略 ... */
}打开VarPanel.cs,新增若干方法:
// 设置变量数据
public void SetVarDatas(List<VarData> varDatas)
{
VarDatas = varDatas;
Draw();
}
// 清除面板
public void ClearPanel()
{
VarDatas.Clear();
Draw();
}打开StoryGraphView.cs,新增属性并修改方法:
/*** 新增属性 ***/
public List<VarData> Vars
{
get => varPanel.VarDatas;
}
/*** 修改方法 ***/
// 清除视图
public void ClearGraph()
{
graphElements.ForEach(e => RemoveElement(e));
varPanel.ClearPanel();
}打开DataUtility.cs,新增方法:
// 克隆变量数据列表
public static List<VarData> CloneVarDatas(List<VarData> oldDatas)
{
if (oldDatas == null)
{
return null;
}
List<VarData> newDatas = new();
foreach (VarData data in oldDatas)
{
VarData newData = new(data.Key, data.Value);
newDatas.Add(newData);
}
return newDatas;
}在游戏界面显示变量信息
添加基础交互UI
- 打开Example场景,在
Canvas下创建变量信息面板,命名为Pan Vars - 在
Pan Vars下创建一个空对象,命名为Item Var - 在
Item Var下创建两个文本对象,分别用于显示变量名和变量值

添加组件脚本
打开Component文件夹,创建C#文件,命名为UIVarList,打开UIVarList.cs,将内容修改如下:
namespace E.Story
{
// UI变量列表
public class UIVarList : UIList<VarData>
{
}
}继续创建C#文件,命名为UIVarItem,打开UIVarItem.cs,将内容修改如下:
using UnityEngine;
using UnityEngine.UI;
namespace E.Story
{
public class UIVarItem : UIItem<VarData>
{
[Header("UI组件")]
[SerializeField] private Text txtKey;
[SerializeField] private Text txtValue;
public override void Refresh()
{
// 更新选项文本
txtKey.text = Data.Key;
txtValue.text = Data.Value.ToString();
}
}
}设置显示逻辑
打开StoryUI.cs,新增若干字段与方法:
/* 变量面板 */
[SerializeField] private GameObject panVars;
[SerializeField] private UIVarList uiVarList;
// 显示变量面板
public void ShowVars(List<VarData> varDatas)
{
panVars.SetActive(true);
uiVarList.SetDatas(varDatas);
}
// 隐藏变量面板
public void HideVars()
{
panVars.SetActive(false);
}打开StoryRunner.cs,新增字段并调用方法:
/*** 新增字段 ***/
[SerializeField] private List<VarData> varDatas;
/*** 设置调用 ***/
private void Start()
{
/* ... 此处代码已省略 ... */
storyUI.HideVars();
}
// 开始故事
public void StartStory()
{
/* ... 此处代码已省略 ... */
isStoryPlaying = true;
varDatas = DataUtility.CloneVarDatas(currentStory.VarDatas); //此处调用
/* ... 此处代码已省略 ... */
storyUI.ShowVars(varDatas); //此处调用
DoNode(nodeData);
}
// 结束故事
public void EndStory()
{
// 重置数据
/* ... 此处代码已省略 ... */
varDatas = null;
// 重置UI
/* ... 此处代码已省略 ... */
storyUI.HideVars();
}
// 执行节点
public void DoNode(NodeData nodeData)
{
/* ... 此处代码已省略 ... */
// 根据当前节点类型执行对应的操作
switch (nodeData.Type)
{
/* ... 此处代码已省略 ... */
case NodeType.End:
/* ... 此处代码已省略 ... */
storyUI.HideVars();
break;
/* ... 此处代码已省略 ... */
}
}挂载组件并赋值
将UIVarItem.cs挂载在
Item Var对象上,并对红框中的两个变量赋值:
将UIVarList.cs挂载在
Pan Vars对象上,并对红框中的两个变量赋值:
将
Story Manager对象的StoryUI.cs组件红框中的两个变量赋值:
测试效果
如图所示配置好变量:

最终运行效果如下:

相关链接
- 完整代码:https://gitee.com/helloestar/e-story/wikis/DevelopNotes/18
- 视频版本:制作中……


