Unity游戏开发之手把手教你实战剧情对话
作者:
宏哥1995
来源
: https://blog.csdn.net/lyh916/article/details/45126317
尝试
在网上看到了一篇不错的文章,讲的是游戏中的剧情动画,感觉去做一做也是挺好玩的事,于是就有了这篇文章。游戏中的剧情(非CG动画)主要有两种,一种是自动播放的,另一种是含有对话的。可以把剧情中的一个个动画(这里的动画不仅仅包含角色动画,还包含位移,旋转,时间等待等等)当成一种种状态(有点像状态机),放到一个链表或者队列中,每当一个状态完成时便跳到下一个状态,于是就会形成连环的剧情动画了。
要注意的是,在普通状态下,一个动作完成会跳到下一个动作,但是在对话状态下,一个动作完成时不会自动跳到下一个动作,而是每出现一个新的对话,就会触发动作。
在这里给出核心代码(一个动作基类以及一个管理动作的类):
using UnityEngine;
using System.Collections;
public class Command {
public virtual void Execute() { }
public void Next()
{
CommandManager.instance.Next2();
}
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class CommandManager : MonoBehaviour {
public static CommandManager instance;
private List<Command> commands = new List<Command>();
private int nowIndex = 0;
public bool isDialogueMode = false;
//缓存Transform
[SerializeField]
public List<string> startObj = new List<string>();//一开始就存在的物体
public Dictionary<string,Transform> allObj = new Dictionary<string,Transform>();
void Awake()
{
instance = this;
}
void Start()
{
for (int i = 0; i < startObj.Capacity;i++ )
allObj.Add(startObj[i], GameObject.Find(startObj[i]).transform);
commands.Add(new CommandMusic("Taylor Swift - Red"));
//场景一 对话驱动画面
commands.Add(new CommandDialogueModeEnter());
commands.Add(new CommandDialogue("Story1", 0, 0, 1));
commands.Add(new CommandMove("Main Camera", new Vector3(-3.5f, 3.4f, -5.5f), 6f));
commands.Add(new CommandDialogue("Story1", 1, 1, 2));
commands.Add(new CommandMove("Main Camera", new Vector3(-4.8f, 3.4f, -4.8f), 0f));
commands.Add(new CommandMove("Main Camera", new Vector3(-2.5f, 3.4f, -4.8f), 5f));
commands.Add(new CommandDialogue("Story1", 2, 2, 3));
commands.Add(new CommandRotate("Main Camera", new Vector3(10f, 180f, 0f), 0f));
commands.Add(new CommandMove("Main Camera", new Vector3(-2.5f, 3.4f, -1f), 0f));
commands.Add(new CommandMove("Main Camera", new Vector3(-4.6f, 3.4f, -1f), 5f));
commands.Add(new CommandDialogueModeExit());
//场景二 自动
commands.Add(new CommandMove("Main Camera", new Vector3(-20.5f, 5.8f, -7.9f), 0f));
commands.Add(new CommandRotate("Main Camera", new Vector3(12f, 180f, 0f), 0f));
commands.Add(new CommandWait(2f));
commands.Add(new CommandPlayAnim("skeleton_warrior1", AnimatorManager.instance.run));
commands.Add(new CommandPlayAnim("skeleton_warrior2", AnimatorManager.instance.run));
commands.Add(new CommandPlayAnim("skeleton_warrior3", AnimatorManager.instance.run));
commands.Add(new CommandPlayAnim("skeleton_warrior4", AnimatorManager.instance.run));
commands.Add(new CommandPlayAnim("skeleton_warrior5", AnimatorManager.instance.run));
commands.Add(new CommandWait(2f));
commands.Add(new CommandPlayAnim("magic", AnimatorManager.instance.attack1));
commands.Add(new CommandWait(1f));
commands.Add(new CommandPlayAnim("skeleton_warrior1", AnimatorManager.instance.dead));
commands.Add(new CommandPlayAnim("skeleton_warrior2", AnimatorManager.instance.dead));
commands.Add(new CommandPlayAnim("skeleton_warrior3", AnimatorManager.instance.dead));
commands.Add(new CommandPlayAnim("skeleton_warrior4", AnimatorManager.instance.dead));
commands.Add(new CommandPlayAnim("skeleton_warrior5", AnimatorManager.instance.dead));
commands.Add(new CommandPlayAnim("magic", AnimatorManager.instance.stand));
commands.Add(new CommandDialogue("Story1", 3, 5, 0));
commands.Add(new CommandWait(1f));
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Q))
commands[nowIndex].Execute();
}
//对话模式下直接进入下一状态
public void Next()
{
nowIndex++;
if (nowIndex < commands.Count)
commands[nowIndex].Execute();
}
//自动模式下直接进入下一状态
public void Next2()
{
if (isDialogueMode)
return;
else
Next();
}
}
只需要根据自身的需要,写一些继承Command的类即可达到想要的效果了。以下是本人自己扩展的类:
效果图:
完整视频链接:http://v.youku.com/v_show/id_XOTMxMjIyMDU2.html
大约两分钟,建议全屏模式下播放(因为不太清晰),自认为剧情还是挺好的。。。
一个更好的方法是写一个编辑器类,将配置保存下来(保存在xml或者json文件),每次运行时读取。不过这里先放一放,最近好忙啊(因为课程的关系,大二下比之前都要忙啊),也不知道下一次写博客是什么时候!哎!
优化
上面讲到我们在CommandManager中直接写入一个个的指令,实际上这样是很不好的,因为一个游戏可能会出现很多的剧情动画,那么不就是要写n个类吗?而且修改起来也不是很好的。所以一个比较好的方法就是写一个编辑器类。
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
public enum Option
{
CommandDestory,
CommandDialogue,
CommandDialogueModeEnter,
CommandDialogueModeExit,
CommandGenerate,
CommandMove,
CommandMusic,
CommandPlayAnim,
CommandRotate,
CommandWait
}
public class Window : EditorWindow
{
List<Option> options = new List<Option>();
List<string[]> strings = new List<string[]>();
List<int[]> ints = new List<int[]>();
List<float[]> floats = new List<float[]>();
List<Vector3[]> vector3s = new List<Vector3[]>();
Vector2 v2 = new Vector2(0, 0);//滚动条参数
[MenuItem("Window/My Window")]
static void Init()
{
EditorWindow.GetWindow(typeof(Window));
}
void OnGUI()
{
if (GUILayout.Button("保存命令"))
{
}
if (GUILayout.Button("添加命令"))
{
Option x = Option.CommandDialogueModeEnter;
options.Add(x);
string[] a = new string[5];
strings.Add(a);
int[] b = new int[5];
ints.Add(b);
float[] c = new float[5];
floats.Add(c);
Vector3[] d = new Vector3[5];
vector3s.Add(d);
}
if (GUILayout.Button("删除命令"))
{
options.RemoveAt(options.Count - 1);
strings.RemoveAt(options.Count - 1);
ints.RemoveAt(options.Count - 1);
floats.RemoveAt(options.Count - 1);
vector3s.RemoveAt(options.Count - 1);
}
v2 = EditorGUILayout.BeginScrollView(v2,false,true,null);
for (int i = 0; i < options.Count; i++)
{
options[i] = (Option)EditorGUILayout.EnumPopup("选项" + i, options[i]);
switch (options[i])
{
case Option.CommandDestory:
strings[i][0] = EditorGUILayout.TextField("物体名字", strings[i][0]);
break;
case Option.CommandDialogue:
strings[i][0] = EditorGUILayout.TextField("xml文本名字", strings[i][0]);
ints[i][0] = EditorGUILayout.IntField("开始序号", ints[i][0]);
ints[i][1] = EditorGUILayout.IntField("结束序号", ints[i][1]);
ints[i][2] = EditorGUILayout.IntField("点击后执行的步骤数", ints[i][2]);
break;
case Option.CommandDialogueModeEnter:
break;
case Option.CommandDialogueModeExit:
break;
case Option.CommandGenerate:
strings[i][0] = EditorGUILayout.TextField("物体名字", strings[i][0]);
vector3s[i][0] = EditorGUILayout.Vector3Field("生成位置", vector3s[i][0]);
vector3s[i][1] = EditorGUILayout.Vector3Field("生成角度", vector3s[i][1]);
break;
case Option.CommandMove:
strings[i][0] = EditorGUILayout.TextField("物体名字", strings[i][0]);
vector3s[i][0] = EditorGUILayout.Vector3Field("终点位置", vector3s[i][0]);
floats[i][0] = EditorGUILayout.FloatField("所用时间", floats[i][0]);
break;
case Option.CommandMusic:
strings[i][0] = EditorGUILayout.TextField("音乐名字", strings[i][0]);
break;
case Option.CommandPlayAnim:
strings[i][0] = EditorGUILayout.TextField("物体名字", strings[i][0]);
ints[i][0] = EditorGUILayout.IntField("动画状态", ints[i][0]);
break;
case Option.CommandRotate:
strings[i][0] = EditorGUILayout.TextField("物体名字", strings[i][0]);
vector3s[i][0] = EditorGUILayout.Vector3Field("终点角度", vector3s[i][0]);
floats[i][0] = EditorGUILayout.FloatField("所用时间", floats[i][0]);
break;
case Option.CommandWait:
floats[i][0] = EditorGUILayout.FloatField("等待时间", floats[i][0]);
break;
default:
break;
}
}
EditorGUILayout.EndScrollView();
}
}
最后
在这里,我们使用xml来实现保存与读取功能。首先,是头文件的引用。微信搜索公众号 [爱上游戏开发],回复 “资料”,免费领取 200G 学习资料!
using System.Xml;
保存到xml文件:
void Save()
{
string filePath = Application.dataPath + @"/my.xml";
XmlDocument xmlDoc = new XmlDocument();
XmlElement root = xmlDoc.CreateElement("root");
for (int i = 0; i < options.Count; i++)
{
XmlElement child = xmlDoc.CreateElement("child");
XmlElement x = xmlDoc.CreateElement("type");
x.InnerText = options[i].ToString();
child.AppendChild(x);
int count = 1;
for (int q = 0; q < strings[q].Length; q++)
{
XmlElement q1 = xmlDoc.CreateElement("string_" + count);
q1.InnerText = strings[i][q];
child.AppendChild(q1);
count++;
}
for (int q = 0; q < ints[q].Length; q++)
{
XmlElement q1 = xmlDoc.CreateElement("int_" + count);
q1.InnerText = ints[i][q].ToString();
child.AppendChild(q1);
count++;
}
for (int q = 0; q < floats[q].Length; q++)
{
XmlElement q1 = xmlDoc.CreateElement("float_" + count);
q1.InnerText = floats[i][q].ToString();
child.AppendChild(q1);
count++;
}
for (int q = 0; q < vector3s[q].Length; q++)
{
XmlElement q1 = xmlDoc.CreateElement("vector3_" + count);
q1.InnerText = vector3s[i][q].ToString();
child.AppendChild(q1);
count++;
}
root.AppendChild(child);
}
xmlDoc.AppendChild(root);
xmlDoc.Save(filePath);
AssetDatabase.Refresh();
Debug.Log("Ok!!");
}
读取xml文件:
void Load(string xmlPath)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(xmlPath);
XmlNodeList childs = xmlDoc.SelectSingleNode("root").ChildNodes;//所有child
for (int i = 0; i < childs.Count; i++)
{
XmlNodeList nodes = childs[i].ChildNodes;//单个child的所有节点
string a = nodes[0].InnerText;
switch (a)
{
case "CommandDestory":
commands.Add(new CommandDestory(nodes[1].InnerText));
break;
case "CommandDialogue":
int int1 = int.Parse(nodes[6].InnerText);
int int2 = int.Parse(nodes[7].InnerText);
int int3 = int.Parse(nodes[8].InnerText);
commands.Add(new CommandDialogue(nodes[1].InnerText, int1, int2, int3));
break;
case "CommandDialogueModeEnter":
commands.Add(new CommandDialogueModeEnter());
break;
case "CommandDialogueModeExit":
commands.Add(new CommandDialogueModeExit());
break;
case "CommandGenerate":
Vector3 v1 = StringToVector3(nodes[16].InnerText);
Vector3 v2 = StringToVector3(nodes[17].InnerText);
commands.Add(new CommandGenerate(nodes[1].InnerText,v1,v2));
break;
case "CommandMove":
Vector3 v = StringToVector3(nodes[16].InnerText);
commands.Add(new CommandMove(nodes[1].InnerText, v, float.Parse(nodes[11].InnerText)));
break;
case "CommandMusic":
commands.Add(new CommandMusic(nodes[1].InnerText));
break;
case "CommandPlayAnim":
commands.Add(new CommandPlayAnim(nodes[1].InnerText, int.Parse(nodes[6].InnerText)));
break;
case "CommandRotate":
Vector3 v3 = StringToVector3(nodes[16].InnerText);
commands.Add(new CommandRotate(nodes[1].InnerText, v3, float.Parse(nodes[11].InnerText)));
break;
case "CommandWait":
commands.Add(new CommandWait(float.Parse(nodes[11].InnerText)));
break;
default:
break;
}
}
}
Vector3 StringToVector3(string s)
{
s = s.Substring(1,s.Length - 2);
string[] a = s.Split(',');
Vector3 b = new Vector3(float.Parse(a[0]), float.Parse(a[1]), float.Parse(a[2]));
return b;
}
可以看到,节点名称都被标号了,这样读取的时候就很容易拿到我们想要的数据了。
其实做编辑器工具是一种挺好的加快效率的方式,因为生活中,程序主要负责的是程序,策划主要就是弄表格等等的,这样就可以把程序的一部分工作交给策划来弄,策划只需在编辑器上使用这些工具,就可以实现同等的功能,可谓是双赢啊哈哈。。
-- END --
公众号后台回复「资料」获取超多学习福利
>>> 点击进入技术讨论群 <<< ▽想深入了解么?
长按/扫码关注我吧↑↑↑
觉得不错就点个在看吧!