10关闭游戏提示,对话履历,隐藏对话框
本教程由水螅制作,教程内所有提供下载的练习素材皆为水螅制作,只可用于练习,不可用于其他用途。
在开始做对话履历之前我想起来我们之前说好要在关闭游戏的时候储存sf的事情还没有做。其实理论上来说sf开游戏的时候读一次,关游戏的时候存一次就可以了,在储存存档的同时额外还要多存一次主要原因是防止玩家玩着游戏的时候程序崩溃然后sf一点都没存下来这种惨事……
我们写一下关闭游戏的function
public void quit()
{
savesf();
Application.Quit();
}
把它挂在我们的ui_opbts的关闭按钮上
void opbtfadein()
{
GameObject temp = GameObject.Find("Canvas").transform.Find("ui_opbts").gameObject;
temp.SetActive(true);
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 1);
//给开始游戏按钮挂载事件
Button b1 = temp.transform.Find("bt1").gameObject.GetComponent<Button>();
b1.onClick.RemoveAllListeners();
b1.onClick.AddListener(delegate ()
{
//把开始界面按钮组淡出
opbtfadeout();
//把背景淡出,执行下一行
bg1fadeout();
});
//给结束游戏按钮挂载事件
b1 = temp.transform.Find("bt2").gameObject.GetComponent<Button>();
b1.onClick.RemoveAllListeners();
b1.onClick.AddListener(delegate ()
{
//结束游戏
quit();
});
}
这样就完成了(并没有)
事实上……一般来说……点了关闭游戏,也还会再问一遍玩家是不是确定要关闭游戏呢…………我是觉得挺啰嗦的,但是误操作的玩家真的很多呢……
那么就改成这样……
public void quit()
{
askfadein("确定要退出游戏吗?", "quit1", "");
}
void quit1()
{
savesf();
Application.Quit();
}
然后既然都做了,也得把游戏窗口化的时候的右上角的x也一起附上事件。
这里需要用到的是一个叫做Application.wantsToQuit的东西,把函数附在这个东西上就可以在任意关闭窗口的时候调用我们的函数,但是需要函数返回bool值,如果返回false就停止关闭游戏,返回true就继续关闭游戏。所以我们将quit修改一下。
public bool quit()
{
askfadein("确定要退出游戏吗?", "quit1", "");
return false;
}
然后把quit这个事件在游戏开始的时候附在Application.wantsToQuit上
void Start () {
loadsf();
readscript();
startscript();
Application.wantsToQuit += quit;
}
那么在点了是要关闭游戏的时候我们需要再把quit事件从Application.wantsToQuit上拿下来
void quit1()
{
savesf();
Application.wantsToQuit -= quit;
Application.Quit();
}
这样就好了,虽然说不打包成exe也看不出来效果吧……不过打包的部分我们到教程最后一篇再说吧……
终于可以来做对话履历界面了。
我们先给对话框上增加一个对话履历的按钮logbt
然后我们来拉一个对话履历界面ui_log出来
首先,和之前的save load界面一样拉出一个底图
然后给ui_log添加一个image的子object,起名叫mask,用它来放对话文字,把它拉一个合适的大小。
给它增加一个叫做mask的component,把show mask graphic勾掉,可以看到它变成了透明的
添加了mask的组件的image上的子object的图像就不会超出mask自己的范围。像这样
为它添加一个空的子GameObject,叫做content,用来放对话文字,把它拉到和mask一样大,然后把它的对齐设置成上中对齐,把它的锚点的y设置成1
这样可以保证我们任意的增加它的高度不会影响它的坐标。毕竟我们要往上一行一行的放对话履历,所以需要动态的把这个高度不停增加。
为content增加vertical layout group的组件,把间隔设为40好了
我们来做一下对话履历的单元图。起名叫logimage1
具体怎么拉就不多说了,大家都很熟悉。因为会根据对话行数改变高度,所以把它的privot的y也设为1,内部的子object的对齐全部设为上中对齐
然后再做一个没有姓名框的版本logimage2用来放独白之类没有姓名的文字
同样设置它的privot的y也设为1,内部的子object的对齐全部设为上中对齐
把这两个按钮拉到content下边看看效果,然后任意复制多个,可以看到mask起到的作用。
把content一口气拉长一点,拉到能够合适的放下所有的按钮(虽然现在也看不到mask以外的内容吧……)
接下来我们来给界面加上滚动条。
在ui_log上右键ui>scrollbar,可以看到生成了一个小小的横向的滚动条。我们把它设成纵向的
其他的设置和button一样,我这次就直接用默认的没有修改。
将滚动条拉到合适的位置和大小
我们需要把滚动条和我们的滚动区域关联起来。
我们为mask添加一个叫做scroll rect的组件,把content拖到它的content设置上,把我们的滚动条Scrollbar拖到它的vertical csrollbar设置上,把可以横向滚动的horizontal勾掉,visblility设置为auto hide,这样在对话没有满一屏的时候不会出现滚动条。
现在我们左右拖动scrollbar的value
可以看到对话履历列被上下滚动
把logimage1和logimage2拖到resources文件夹里作为prefab,然后就可以把content里边的这些logimage都删掉了,毕竟对话履历我们是要用代码来动态添加文字的。
这样log界面就做好了。给ui_log录制淡入淡出动画,写淡入淡出function logfadein和logfadeout,将logfadein挂在对话框的logbt上,将logfadeout挂在ui_log的返回按钮上
现在我们可以开始写记录对话履历的部分。我们将记录的部分放置在textfadeout里边,每当消去一句对话,就把这句对话放入履历的记录里。
//定义用来放置对话履历人物姓名的列表
List<string> logname = new List<string>();
//定义用来放置对话履历语音的列表
List<string> logvoice = new List<string>();
//定义用来放置对话履历文字的列表
List<string> logtext = new List<string>();
public void textfadeout()
{
GameObject temp = GameObject.Find("ui_dialog_text");
//将当前对话加入履历
logtext.Add( temp.GetComponent<Text>().text);
//将当前姓名加入履历
GameObject temp1 = GameObject.Find("ui_dialog").transform.Find("nameimage").gameObject;
if (temp1.active)
{
logname.Add(temp1.transform.Find("Text").gameObject.GetComponent<Text>().text);
}
else
{
logname.Add("");
}
//将当前语音加入履历
logvoice.Add(f["voice"]);
//判断履历长于20句就删除旧的履历
if (logtext.Count > 20)
{
logtext.RemoveAt(0);
logname.RemoveAt(0);
logvoice.RemoveAt(0);
}
//清空当前语音记录
f["voice"] = "";
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 2);
StartCoroutine(waittime(0.5f));
GameObject.Find("ui_dialog").transform.Find("voicebt").gameObject.SetActive(false);
}
这段代码里用到了一种新的叫做List的类型,使用方法见https://docs.microsoft.com/zh-cn/dotnet/api/system.collections.generic.list-1?view=netframework-4.7.2
然后我们在logfadein里边将对话履历放到ui_log的content里
public void logfadein()
{
if (!onwaitclick)
{
return;
}
GameObject temp = GameObject.Find("Canvas").transform.Find("ui_log").gameObject;
temp.SetActive(true);
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 1);
//将界面上已有的对话履历清空
GameObject temp0 = temp.transform.Find("mask/content").gameObject;
for (int n = 0; n < temp0.transform.childCount; n++)
{
Destroy(temp0.transform.GetChild(n).gameObject);
}
//定义用来计算content高度的变量
float height = 0;
//循环将每句话放到界面上
for(int i=0;i<logtext.Count;i++)
{
GameObject temp1;
//有姓名框的情况下
if (logname[i]!="")
{
//复制一个logimage1的克隆放到content上
temp1 = (GameObject)Instantiate(Resources.Load("logimage1", typeof(GameObject)));
temp1.name = "logimage" + i;
temp1.transform.SetParent(temp0.transform);
temp1.transform.localScale = new Vector3(1, 1, 1);
//将姓名框里的姓名写入
temp1.transform.Find("nameimage/Text").gameObject.GetComponent<Text>().text = logname[i];
//判断是否有语音,如果有的话给语音按钮挂上事件
if(logvoice[i]!="")
{
Button b1 = temp1.transform.Find("voicebt").gameObject.GetComponent<Button>();
b1.onClick.RemoveAllListeners();
string s1 = logvoice[i];
b1.onClick.AddListener(delegate ()
{
voice1(s1);
});
}
else
{
temp1.transform.Find("voicebt").gameObject.SetActive(false);
}
}
//没有姓名框的情况下
else
{
//复制一个logimage2的克隆放到content上
temp1 = (GameObject)Instantiate(Resources.Load("logimage2", typeof(GameObject)));
temp1.name = "logimage" + i;
temp1.transform.SetParent(temp0.transform);
temp1.transform.localScale = new Vector3(1, 1, 1);
}
//将履历文字赋值给logimage
temp1.transform.Find("Text").gameObject.GetComponent<Text>().text = logtext[i];
//计算底框高度,按照每行22个字,每行51像素高来算的话
int linenum = (int)Math.Ceiling((double)logtext[i].Length / 22);
RectTransform r1 = temp1.GetComponent<RectTransform>();
r1.sizeDelta = new Vector2(r1.sizeDelta.x, r1.sizeDelta.y - (3 - linenum) * 51);
height = height + r1.sizeDelta.y + 40;
}
//设定content的高度
temp0.GetComponent<RectTransform>().sizeDelta = new Vector2(temp.GetComponent<RectTransform>().sizeDelta.x,height);
//将滚动条拉到最下边
temp.transform.Find("Scrollbar").gameObject.GetComponent<Scrollbar>().value = 0;
}
为了测试稍微把脚本的对话多写几句
@bg1fadein storage=op1
@bgm storage=bgm1
@opbtfadein
@bgm storage=bgm2
@editnamefadein
@initf
@bg1fadein storage=bg1
@fg1fadein storage=fg1 pos=l n=0
@dialogfadein
@se storage=se1
@namefadein name=宿彦 pos=l
@voice storage=fg1voice1
今天天气不错,
@ch text=name
,你要吃包子吗?
@textfadein
@namefadeout
@textfadeout
@stopbgm
@fg1fadein storage=fg2 pos=r n=0
@namefadein name=费斌 pos=r
你能不能不要一直都是包子包子。
@textfadein
@namefadeout
@textfadeout
@changeface pos=l n=1
@namefadein name=宿彦 pos=l
@voice storage=fg1voice2
你这种人哪里懂得包子的好。
@textfadein
@namefadeout
@textfadeout
@namefadein name=费斌 pos=r
我本来也不想懂……
@textfadein
@textfadeout
@ch text=name
,你不用勉强自己配合这个笨蛋也没关系的。
@textfadein
@textfadeout
@changeface pos=r n=1
比起包子,当然还是饺子比较好吃。
@textfadein
@namefadeout
@textfadeout
说到笨蛋的程度你也没有差好嘛!
@textfadein
@textfadeout
少许有点感到疲惫……
@textfadein
@textfadeout
@dialogfadeout
@fg1fadeout pos=l
@fg1fadeout pos=r
@bg1fadeout
测试一下,嗯没有问题了。
接下来我们来做隐藏对话框的按钮hidebt
在对话中的时候点击一下这个按钮就隐藏对话框,再点击屏幕的任意地方恢复对话框。
那么我们就写一个hidedialog的function
//定义一个用来记录对话框有没有被隐藏的变量
bool onhidedialog = false;
public void hidedialog()
{
if(!onwaitclick)
{
return;
}
onhidedialog = true;
GameObject.Find("ui_dialog").SetActive(false);
}
把这个function绑到hidebt上
然后我们修改update来使对话框被隐藏的时候点击一下屏幕就恢复对话框
void Update () {
//当正在等待点击的时候鼠标点击了就继续读下一行脚本
if (onwaitclick && Input.GetMouseButtonUp(0)&&!EventSystem.current.IsPointerOverGameObject())
{
//如果对话框被隐藏就显示对话框
if (onhidedialog)
{
onhidedialog = false;
GameObject.Find("Canvas").transform.Find("ui_dialog").gameObject.SetActive(true);
return;
}
onwaitclick = false;
GameObject.Find("ui_dialog").transform.Find("drop").gameObject.SetActive(false);
startscript();
}
}
我们将GetMouseButton改成了GetMouseButtonUp是因为我们每点一下的时间其实已经经历了好几帧,之前只要在第一帧判断的地方获取了鼠标左键点击就会更改onwaitclick,所以用GetMouseButton是没问题的,但是当条件变复杂了之后,我们更改onhidedialog的值后一帧再次运行update的时候,依然有点击判断,就会执行下一句脚本,这不是我们想要的效果,所以这里将会持续多帧的GetMouseButton改成了瞬间事件GetMouseButtonUp。
这样这一块就写好了,大家可以测试一下。
接下来我们来做回到游戏开始按钮quitbt。
现在理一下思路,我们点了exit按钮之后希望的是
出现确认对话框,点了是之后
画面变黑
清除画面里的元素
清理f
读取脚本第一行
这个需求的有大部分和我们的loadgame完全是一样的嘛。那我们就开心的把loadgame里边这部分分出来一个function叫做clearscreen
void loadgame1()
{
StartCoroutine(clearscreen("loadgame2"));
}
IEnumerator clearscreen(string func)
{
//开始播放黑色淡入动画
GameObject.Find("Canvas").transform.Find("ui_black").gameObject.SetActive(true);
//停止音乐音效语音
AudioSource temp0 = (AudioSource)GameObject.Find("bgm").GetComponent<AudioSource>();
temp0.Stop();
temp0 = (AudioSource)GameObject.Find("se").GetComponent<AudioSource>();
temp0.Stop();
temp0 = (AudioSource)GameObject.Find("voice").GetComponent<AudioSource>();
temp0.Stop();
//等待0.5秒等屏幕全黑
yield return new WaitForSeconds(0.5f);
//把所有ui设为active false
GameObject temp = GameObject.Find("Canvas");
for (int n = 0; n < temp.transform.childCount; n++)
{
temp.transform.GetChild(n).gameObject.SetActive(false);
}
//把三个摄影棚里的立绘都清掉
temp = GameObject.Find("rtCamerac");
for (int n = 0; n < temp.transform.childCount; n++)
{
Destroy(temp.transform.GetChild(n).gameObject);
}
temp = GameObject.Find("rtCamerar");
for (int n = 0; n < temp.transform.childCount; n++)
{
Destroy(temp.transform.GetChild(n).gameObject);
}
temp = GameObject.Find("rtCameral");
for (int n = 0; n < temp.transform.childCount; n++)
{
Destroy(temp.transform.GetChild(n).gameObject);
}
//把背景设为透明
GameObject.Find("bg1").GetComponent<SpriteRenderer>().color = new Color32(255, 255, 255, 0);
//清空f
f.Clear();
SendMessage(func);
}
void loadgame2()
{
//读取save数字.miao里的数据
string fs = File.ReadAllText(Application.persistentDataPath + "/save" + tf["tempnum"] + ".miao");
………………以下省略………………
然后我们用同样的方式来写我们的返回开始function returntotitle
public void returntotitle()
{
if (!onwaitclick)
{
return;
}
askfadein("确定要返回开始界面吗?", "returntotitle1", "");
}
void returntotitle1()
{
StartCoroutine(clearscreen("returntotitle2"));
}
void returntotitle2()
{
scriptnum = 0;
startscript();
}
把returntotitle挂在exitbt上,然后跑一下测试。
然后我们还多了一句可以用的脚本@returntotitle1,用于在游戏结束的时候返回开始界面。
今天真是做了好多功能按钮……下篇教程会做剩下的功能按钮:自动前进,快进和系统设定
Created by Hydrozoa.2011
不支持IE7以下浏览器
凯恩插件程序:Hydrozoa 美术:Hydrozoa,红渊