09存档,读档
本教程由水螅制作,教程内所有提供下载的练习素材皆为水螅制作,只可用于练习,不可用于其他用途。
终于可以写存档读档了呢……
首先我们给对话框上增加存档读档的按钮,起名叫savebt和loadbt
因为增加了按钮,所以最好将对话框淡入动画里边控制淡入的color替换成canvas group的alpha(重新录一下动画)
然后我们来做一个长成这样的save界面
大家对拉界面应该很熟悉了,可能一眼就有了把这个界面拉好的思路,不过这里我还是要顺便再介绍一种新的东西layout group的用法。
总之我们先拉一个image起名叫ui_save,把它设置为充满屏幕且透明度为0,作为我们save界面的容器
给他增加一个image的子object,使用ui_bg.png作为底图,拉放到合适的大小
再给ui_save加一个text的子object,设定一下文字的大小位置和颜色,写上SAVE
给这个text增加一个描边的component outline,设定一下描边的颜色等等
游戏里一般这种地方其实是用图片来做好看一点啦,为了尽量少让大家下载点素材我就直接用文字了……所以这里用了描边的豪华文字(并没有)
说到豪华,在text的配置栏里有一行是material
现在是none,就是使用了默认的样式,如果你之后对unity了解的更多,可以自己写shader或者找到了别人写的好看的shader的时候,就可以配置在这里,可以把文字搞出更多的样式来。目前我们要尽可能简单并且只使用现在就有的东西,那么就描个边算啦。
然后我们再增加一个返回用的button放在下边,我犯懒了底图和文字和下边的按钮就用默认名字不改了,因为这个界面里不会有和他们重名的物件。
然后我们在任意位置拉一个存档按钮,起名叫savebt1,设定一下各种状态的颜色
在上边放上no data的图片和--/--/---- --:--:-- --用来表示空时间的文字
暂定我们的屏幕比例是16:9,所以no data我设定为320:180,按钮大小也根据图片来进行一定的修正,总之拉到看起来舒服就行了。
然后我们看一下这个按钮最后拉好的尺寸,在我这里是365x276,先把它放在一边不管,我们回到ui_save
在ui_save上右键给他增加一个空的gameobject,起名叫content,用它来放置我们的六个存档按钮,
差不多拉到感觉合适就行了,等下放入按钮后还会再调整。
给content增加一个component叫做grid layout group。
layout group就是有着自动对齐排列所有子object的功能的配件,一共有3种,horizontal layout group(横向排列)vertical layout group(纵向排列)grid layout group(格子排列)
我们这次是六个方方的按钮分多排垒在一起,所以是用格子排列
padding是边缘缩进,我们都设为0,因为这个框是透明的没有缩进的必要。
cell size是一个格子有多大,放入content的子object会自动被拉伸到这里设置的大小。我们之前做好的按钮是365x276,所以这里就设置为365x276,如果你之前做好的按钮不是这个尺寸,那就在这里填写你的按钮的尺寸。
spacing是格子之间的间隔,我们姑且设定成横向间隔50像素,纵向间隔100像素
start corner是放进去的子物件从哪里开始排列,用默认的左上就好。
start axis是先横着排还是先竖着排,用默认的横着就好。
child alignment是对齐方式,我们依然用默认的左上
constrant是限定行数或者列数,我们用默认的不限定
然后我们可以把我们做好的savebt1拉到content的子物件里
可以看到savebt1自动被放在了content的左上
选中savebt1开始按ctrl c ctrl v,复制五个按钮,可以看到每复制一个按钮,他都自动被放在了合适的位置。
你的按钮可能未必有这么居中,调整content的大小以及适当的修改spacing的值直到你觉得按钮排列的比较舒服为止。
给这些按钮起名从savebt1到savebt6
现在save界面就做好了,结构应该是这样
我们给ui_save添加上canvas group,然后录制它的淡入淡出动画,写它的淡入淡出function,叫做savefadein和savefadeout
有人可能会疑惑为什么要给ui_save做一个透明的全屏幕的底图,其实是因为,这个界面会在有着其他界面比如对话框的时候出现,为了挡住这个界面下边的按钮,所以要有一个透明的底图遮住整个屏幕,不然当对话时唤出save界面时,玩家又重复点了一遍对话框上的save按钮……看起来就很难看了…………
说到遮挡的话题,稍微扩展一下,很多ui的元件比如image,text里边都会有一个叫做raycast target的参数
勾上的话就说明这个元件的方形内接受鼠标点击,不勾上就说明鼠标点击的时候不计算它。
为什么要叫raycast呢……是因为unity的鼠标点击的运算是将点击相当于一个从屏幕向着屏幕深出射出的射线,射线能接触到的元件就会被认为被鼠标点击了。
所以很多情况下这个其实是不需要勾上的,比如我们存档按钮上边的no data图片和时间文字,因为已经有下边的按钮底图接受鼠标了,上边的装饰就不需要计算有没有被点到也没关系。
而我们的透明底图为了能够遮住下边的按钮,所以需要勾上,同时配合canvas group的blocks raycasts(顾名思义就是把射线挡住),这样点击的射线碰到ui_save就停下了,不会再继续射到更深处的按钮上。
通常来说为了节省运算资源,没有鼠标判断必要的装饰用元件一般都是勾掉的,另外像是对话框底图和对话文字这种特意不需要他们有点击判断的地方也要勾掉比较好,不过我们范例为了简单,大部分都让大家采用默认配置,并没有特意提醒大家把这个勾掉。等到大家正经制作自己游戏,有了需要优化效率的时候,可以将这种地方都检查一下。
关于savefadein,我们希望只有在画面稳定也就是一句话说完等待点击的时候才能够有响应,其他时候点了也没用,所以在通常的fadein里边稍微加一个判断。
public void savefadein()
{
if(!onwaitclick)
{
return;
}
GameObject temp = GameObject.Find("Canvas").transform.Find("ui_save").gameObject;
temp.SetActive(true);
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 1);
}
public void savefadeout()
{
GameObject temp = GameObject.Find("Canvas").transform.Find("ui_save").gameObject;
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 2);
}
我们将savefadein挂载在对话框上的savebt上。将savefadeout挂载在ui_save的返回按钮上
然后跑一下游戏。
但是现在点击界面中的存档按钮,当然并不会真的能存档,因为我们还什么都没有写。
那么我们来理一理思路。我们需要保存的东西如下
背景放的是什么图
左中右立绘显示的是谁的立绘,用的是几号表情
对话框上是否显示了名字,显示的是什么名字
对话框上显示的是什么对话
当前是否可以重放语音,重放的是哪个语音文件
当前是否有背景音乐,背景音乐是什么文件
然后当然还有角色好感度自定义姓名那些本来就被放在我们的f里的东西。
首先背景放的是什么图,我们在bg1fadein和bg1fadeout里加上记录
public void bg1fadein(Dictionary<string, string> elm)
{
//记录当前显示的背景的文件名
f["bg"] = elm["storage"];
GameObject temp = GameObject.Find("bg1");
temp.GetComponent<SpriteRenderer>().sprite= (Sprite)Resources.Load("bg/"+elm["storage"], typeof(Sprite));
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 1);
StartCoroutine(waittime(0.8f));
}
public void bg1fadeout()
{
//当背景被消去时记录里也显示背景为空
f["bg"] = "";
GameObject temp = GameObject.Find("bg1");
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 2);
StartCoroutine(waittime(0.8f));
}
左中右立绘显示的是谁的立绘,是几号表情,我们通过在fg1fadein,fg1fadeout,changeface里边进行记录
public void fg1fadein(Dictionary<string, string> elm)
{
Destroy(GameObject.Find("fgroot" + elm["pos"]));
GameObject fgroot = (GameObject)Instantiate(Resources.Load("fg/"+elm["storage"], typeof(GameObject)));
fgroot.name = "fgroot"+elm["pos"];
fgroot.transform.SetParent(GameObject.Find("rtCamera"+elm["pos"]).transform);
fgroot.transform.localPosition = new Vector3(0, 0, 0);
fgroot.transform.localScale = new Vector3(1, 1, 1);
//我们用变量记录一下这个位置传入的文件名和表情号
f["fg" + elm["pos"]] = elm["storage"];
f["fg" + elm["pos"] + "face"] = elm["n"];
GameObject temp = GameObject.Find("Canvas").transform.Find("rfg"+elm["pos"]).gameObject;
temp.SetActive(true);
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 1);
StartCoroutine(waittime(0.8f));
//将传入的表情赋值给立绘
GameObject temp2 = (GameObject)Instantiate(Resources.Load("fg/" + f["fg" + elm["pos"]] + "_face" + elm["n"], typeof(GameObject)));
temp2.name = "face1";
temp2.transform.SetParent(fgroot.transform.Find("fg/face"));
temp2.transform.localPosition = new Vector3(0, 0, -0.1f);
temp2.transform.localScale = new Vector3(1, 1, 1);
temp2.GetComponent<Animator>().Play("facea1", -1, 1);
temp.GetComponent<RawImage>().color = new Color32(255, 255, 255, 255);
}
public void fg1fadeout(Dictionary<string, string> elm)
{
//记录这个位置没有立绘了
f["fg" + elm["pos"]] = "";
GameObject temp = GameObject.Find("rfg"+elm["pos"]);
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 2);
StartCoroutine(waittime(0.8f));
}
void changeface(Dictionary<string, string> elm)
{
//记录这个位置的立绘是几号表情
f["fg" + elm["pos"] + "face"] = elm["n"];
GameObject temp = GameObject.Find("fgroot" + elm["pos"]).transform.Find("fg/face").gameObject;
if (temp.transform.Find("face0"))
{
Destroy(temp.transform.Find("face0").gameObject);
}
GameObject temp1 = temp.transform.Find("face1").gameObject;
temp1.name = "face0";
temp1.transform.localPosition = new Vector3(0, 0, 0);
temp1.GetComponent<spriteconller>().activechildrenfalse();
GameObject temp2 = (GameObject)Instantiate(Resources.Load("fg/" + f["fg" + elm["pos"]] + "_face" + elm["n"], typeof(GameObject)));
temp2.name = "face1";
temp2.transform.SetParent(temp.transform);
temp2.transform.localPosition = new Vector3(0, 0, -0.1f);
temp2.transform.localScale = new Vector3(1, 1, 1);
startscript();
}
对话框上是否显示了名字,显示的是谁的名字,我们通过在namefadein和namefadeout里记录
void namefadein(Dictionary<string, string> elm)
{
//记录对话框上的名字和当前说话的人
f["dialog_name"] = elm["name"];
f["dialog_pos"] = elm["pos"];
GameObject temp = GameObject.Find("Canvas").transform.Find("ui_dialog/nameimage").gameObject;
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 1);
temp.transform.Find("Text").gameObject.GetComponent<Text>().text = elm["name"];
StartCoroutine(waittime(0.4f));
//将设定的位置的立绘变亮,其他位置的立绘变暗
GameObject.Find("Canvas").transform.Find("rfgr").gameObject.GetComponent<RawImage>().color = new Color32(140,140,140,255);
GameObject.Find("Canvas").transform.Find("rfgl").gameObject.GetComponent<RawImage>().color = new Color32(140, 140, 140, 255);
GameObject.Find("Canvas").transform.Find("rfgc").gameObject.GetComponent<RawImage>().color = new Color32(140, 140, 140, 255);
GameObject.Find("Canvas").transform.Find("rfg"+elm["pos"]).gameObject.GetComponent<RawImage>().color = new Color32(255, 255, 255, 255);
}
void namefadeout()
{
//记录对话框上没有名字
f["dialog_name"] = "";
f["dialog_pos"] = "";
GameObject temp = GameObject.Find("Canvas").transform.Find("ui_dialog/nameimage").gameObject;
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 2);
startscript();
}
对话框上显示的是什么对话,我们用textfadein来记录
public void textfadein()
{
//记录对话框上显示的是什么文字
f["dialog_text"] = dtext;
//原来文字赋值不要了,改成将文字清空
GameObject temp = GameObject.Find("ui_dialog_text");
temp.GetComponent<Text>().text = "";
//dtext = "";
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 1);
//开始等待点击也不要了,留在文字完全出现之后再执行这一句
//onwaitclick = true;
//因为蹦字是有等待时间的,所以我们需要将蹦字写成一个IEnumerator函数,用StartCoroutine来调用它
StartCoroutine(showtext());
}
当前播放的语音,voice里边本来就记录了,我们需要在textfadeout里做记录清空。
public void textfadeout()
{
//清空当前语音记录
f["voice"] = "";
GameObject temp = GameObject.Find("ui_dialog_text");
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 2);
StartCoroutine(waittime(0.5f));
GameObject.Find("ui_dialog").transform.Find("voicebt").gameObject.SetActive(false);
}
当前的背景音乐我们之前也已经在bgm和stopbgm里进行了记录,这里就不用改了。
现在我们需要的信息都已经放在了f里,只要将f作为字符串写入一个文本文件就可以了。
一般来说我会用一个第三方的json.net的库直接将字典转成一个json的字符串,但是这个教程的原则是尽量不使用第三方插件,所以就只能笨拙的进行手写了。好在范例里需要拼接的字符串并不多,不过实际在自己项目中的话,请务必使用插件进行转换。
在开始写具体存档写入之前,我们给savefadein里边加入一行截图的代码。毕竟等到save界面显示好之后再截图,就会把save界面一起截上去了…………所以不管玩家打不打算存档,只要他按了存档按钮,我们就先做一个临时的截图存着,如果他点了具体的存档位,我们再把截图文件copy给存档位就可以了。
public void savefadein()
{
if(!onwaitclick)
{
return;
}
GameObject temp = GameObject.Find("Canvas").transform.Find("ui_save").gameObject;
temp.SetActive(true);
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 1);
//在save界面显示之前临时截个图
ScreenCapture.CaptureScreenshot(Application.persistentDataPath + "/savepic");
}
ScreenCapture的用法见https://docs.unity3d.com/ScriptReference/ScreenCapture.html
接下来我们写一个叫做savegame的function用来做具体的存档行为
//我们建立一个新的字典用来放那些不能放在单个存档里的信息比如存档时间
Dictionary<string, string> sf = new Dictionary<string, string>();
void savegame(int num)
{
//将存档时间写入sf
sf["savetime" + num] = DateTime.Now.ToString();
//将当前的脚本行数写入f
f["scriptnum"] = scriptnum.ToString();
//我们将f转成一个字符串,建立一个用来拼接所有内容的stringbuilder,不像以前一样直接用+号来连接字符串是因为当要连接的字符串很多的时候用stringbuilder效率好一点,为了使用stringbuilder请在文件头加上using System.Text;
StringBuilder s1 = new StringBuilder();
//遍历f里边的所有内容,将它们用&连在一起
foreach(KeyValuePair<string, string> p in f)
{
s1.Append(p.Key);
s1.Append("&");
s1.Append(p.Value);
s1.Append("&");
}
string fs = s1.ToString();
//将转好f的字符串写入一个叫save数字.miao的文件里。为了使用File,需要在文件头加上using System.IO;
//Application.persistentDataPath是unity放置存档文件的常用路径,在windows下它在C:/Users/用户名/AppData/LocalLow/你在unity里设置的公司名/你起的游戏名
File.WriteAllText(Application.persistentDataPath + "/save" + num + ".miao", fs);
//将临时截图copy到存档位上
byte[] b1 = File.ReadAllBytes(Application.persistentDataPath + "/savepic");
File.WriteAllBytes(Application.persistentDataPath + "/savepic" + num ,b1);
//将截图更新到save界面上,
Texture2D texture = new Texture2D(Screen.width, Screen.height);
texture.LoadImage(b1);
GameObject.Find("ui_save").transform.Find("content/savebt"+num+"/Image").gameObject.GetComponent<Image>().sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
//将时间更新到save界面上
GameObject.Find("ui_save").transform.Find("content/savebt" + num + "/Text").gameObject.GetComponent<Text>().text = sf["savetime" + num];
}
然后我们要对每个存档按钮挂载上savegame这个function,一个一个手动挂载就太麻烦了,我们在savefadein里边直接用循环全部挂上
public void savefadein()
{
if(!onwaitclick)
{
return;
}
GameObject temp = GameObject.Find("Canvas").transform.Find("ui_save").gameObject;
temp.SetActive(true);
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 1);
for (int i = 1; i <= 6; i++)
{
Button b1 = temp.transform.Find("content/savebt"+i).gameObject.GetComponent<Button>();
b1.onClick.RemoveAllListeners();
int t1 = i;
b1.onClick.AddListener(delegate ()
{
savegame(t1);
});
}
//在save界面显示之前临时截个图
ScreenCapture.CaptureScreenshot(Application.persistentDataPath + "/savepic");
}
测试一下,显示的很好
但是但是,已经存过档的地方,再点存档就覆盖掉了呢,一般来说,我们为了避免玩家误操作,会在覆盖存档的地方弹一个“确定要覆盖存档吗?”的确认框。
那么我们来做这个确认框。
和之前一样,用一张透明的底图充满屏幕,加上canvas group,起名叫ui_ask,为它添加各种子物件拉成这样的一个界面
录制它的淡入淡出动画,写它的淡入淡出function,叫做askfadein,askfadeout
考虑到这种确认对话框用到的地方还挺多的,我们决定将显示的文字和点了是,否按钮分别执行什么函数作为参数传入askfadein,于是修改为
void askfadein(string text,string yesfunc,string nofunc)
{
GameObject temp = GameObject.Find("Canvas").transform.Find("ui_ask").gameObject;
temp.SetActive(true);
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 1);
//赋值显示的文字
temp.transform.Find("Text").gameObject.GetComponent<Text>().text = text;
//挂载是和否的按钮事件
Button b1 = temp.transform.Find("yesbt").gameObject.GetComponent<Button>();
b1.onClick.RemoveAllListeners();
b1.onClick.AddListener(delegate ()
{
//消去询问对话框
askfadeout();
//如果传入了点击是按钮执行的事件名就执行这个事件
if (yesfunc != "")
{
SendMessage(yesfunc);
}
});
b1 = temp.transform.Find("nobt").gameObject.GetComponent<Button>();
b1.onClick.RemoveAllListeners();
b1.onClick.AddListener(delegate ()
{
askfadeout();
if (nofunc != "")
{
SendMessage(nofunc);
}
});
}
然后我们修改savegame,在里边加入确定是否已经有存档的判断
//我们建立一个新的字典用来放不会保存在任何存档里的临时变量
Dictionary<string, string> tf = new Dictionary<string, string>();
void savegame(int num)
{
//将要储存的存档号存入临时变量
tf["tempnum"] = num.ToString();
if (sf.ContainsKey("savetime" + num))
{
askfadein("确定要覆盖存档吗?", "savegame1", "");
return;
}
else
{
savegame1();
}
}
void savegame1()
{
//将存档号取出来
string num = tf["tempnum"];
//将存档时间写入sf
sf["savetime" + num] = DateTime.Now.ToString();
…………下略………………
写好之后跑一下……可以正常的弹出确认对话框了。
接下来我们用相同的方法再做一下load界面,起名叫ui_load,其实将save界面copy一下改改文字就可以了呢。
写loadfadein和loadfadeout,将loadfadein挂载在对话框上的load按钮上。将loadfadeout挂载在ui_load的返回按钮上。
然后跑一下我们发现……之前存的档在load界面看不到(废话)
所以还是要修改一下loadfadein,将读档按钮的图片和文字都载入,而且,没有存档的位置,按钮应该也变成灰色不可点击才对。
public void loadfadein()
{
if (!onwaitclick)
{
return;
}
GameObject temp = GameObject.Find("Canvas").transform.Find("ui_load").gameObject;
temp.SetActive(true);
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 1);
//循环所有读档按钮来进行图片和文字的赋值以及挂载事件
for (int i = 1; i <= 6; i++)
{
Button b1 = temp.transform.Find("content/savebt" + i).gameObject.GetComponent<Button>();
//有存档的时候
if (sf.ContainsKey("savetime" + i))
{
//显示截图
byte[] tempb = File.ReadAllBytes(Application.persistentDataPath + "/savepic"+i);
Texture2D texture = new Texture2D(Screen.width, Screen.height);
texture.LoadImage(tempb);
temp.transform.Find("content/savebt" + i + "/Image").gameObject.GetComponent<Image>().sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
//显示存档时间
temp.transform.Find("content/savebt" + i + "/Text").gameObject.GetComponent<Text>().text = sf["savetime" + i];
//设定按钮事件
b1.interactable = true;
b1.onClick.RemoveAllListeners();
int t1 = i;
b1.onClick.AddListener(delegate ()
{
loadgame(t1);
});
}
else
{
//如果没有存档则这个按钮设为不可用
b1.interactable = false;
}
}
}
public void loadfadeout()
{
GameObject temp = GameObject.Find("Canvas").transform.Find("ui_load").gameObject;
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 2);
}
void loadgame(int num)
{
}
其中loadgame这个function我们还是空的,不过在这之前,你可能意识到了一件事情…………save界面也应该像load界面一样在显示前检测一下是否已经有存档来决定画面的显示。
所以我们将savefadein也做响应的修改
public void savefadein()
{
if(!onwaitclick)
{
return;
}
GameObject temp = GameObject.Find("Canvas").transform.Find("ui_save").gameObject;
temp.SetActive(true);
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 1);
for (int i = 1; i <= 6; i++)
{
if (sf.ContainsKey("savetime" + i))
{
//显示截图
byte[] tempb = File.ReadAllBytes(Application.persistentDataPath + "/savepic" + i);
Texture2D texture = new Texture2D(Screen.width, Screen.height);
texture.LoadImage(tempb);
temp.transform.Find("content/savebt" + i + "/Image").gameObject.GetComponent<Image>().sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
//显示存档时间
temp.transform.Find("content/savebt" + i + "/Text").gameObject.GetComponent<Text>().text = sf["savetime" + i];
}
Button b1 = temp.transform.Find("content/savebt"+i).gameObject.GetComponent<Button>();
b1.onClick.RemoveAllListeners();
int t1 = i;
b1.onClick.AddListener(delegate ()
{
savegame(t1);
});
}
//在save界面显示之前临时截个图
ScreenCapture.CaptureScreenshot(Application.persistentDataPath + "/savepic");
}
然后我们跑了一下,发现之前存入的存档不管save界面还是load界面都没有,原因是……我们本应该在游戏开始时候就载入的sf变量没有载入。
一路写下来,大家知道,我们把数据分为了f,sf,tf三个字典来装载。
其中f是单个存档的数据,在这个存档里小哥的好感度有多少啦,竖了多少个flag了,存档的位置的画面显示是什么样啦,这些数据并不会影响到其他存档,只存在这个档里。f的数据我们写入到save数字.miao文件里。在游戏运行中f的数据会根据我们读取了某个存档或是新开游戏而整个换掉。
sf是系统保留的数据,不管在任意游戏进度里,sf的值都是一致的,比如我们在第三个位置的存档时间,不管你在任何进度里打开save界面,这个位置的存档时间就是这个时间,不会随着你读取不同的档位而改变。目前我们只在里边放了存档时间的数据,以后还可以放置“完成了一周目”“开启了某张CG”之类的信息。sf的数据我们写入save.miao的文件里。在游戏运行中sf的数据从游戏开启就存在,不会换掉。
tf是游戏打开后的运行过程中用于临时记录的数据,关闭游戏就消失,重新打开游戏就清空。tf的数据我们并不写入硬盘。
所以,我们应该在储存游戏和关闭游戏的时候储存sf,在开始游戏的时候读取sf。
我们来写一下sf的存取,savesf和 loadsf
void savesf()
{
//将sf的值也连成一个字符串
StringBuilder s1 = new StringBuilder();
//遍历f里边的所有内容,将它们用&连在一起
foreach (KeyValuePair<string, string> p in sf)
{
s1.Append(p.Key);
s1.Append("&");
s1.Append(p.Value);
s1.Append("&");
}
string sfs = s1.ToString();
//将转好的sf的字符串写入一个叫做save.miao的文件里
File.WriteAllText(Application.persistentDataPath + "/save.miao", sfs);
}
void loadsf()
{
//判断save.miao是否存在
if (!File.Exists(Application.persistentDataPath + "/save.miao"))
{
return;
}
//读取save.miao
string sfs= File.ReadAllText(Application.persistentDataPath + "/save.miao");
//分割当初被连在一起的字符串
string[] temp = Regex.Split(sfs, "&", RegexOptions.IgnoreCase);
string key = "";
for (int i = 0; i < temp.Length; i++)
{
if (key =="")
{
key = temp[i];
}
else
{
sf[key] = temp[i];
key = "";
}
}
}
在游戏开始的function start里加入loadsf的调用。
void Start () {
loadsf();
readscript();
startscript();
}
在存档的savegame1里加入savesf的调用
void savegame1()
{
//将存档号取出来
string num = tf["tempnum"];
//将存档时间写入sf
sf["savetime" + num] = DateTime.Now.ToString();
//将当前的脚本行数写入f
f["scriptnum"] = scriptnum.ToString();
//我们将f转成一个字符串,建立一个用来拼接所有内容的stringbuilder
StringBuilder s1 = new StringBuilder();
//遍历f里边的所有内容,将它们用&连在一起
foreach(KeyValuePair<string, string> p in f)
{
s1.Append(p.Key);
s1.Append("&");
s1.Append(p.Value);
s1.Append("&");
}
string fs = s1.ToString();
//将转好f的字符串写入一个叫save数字.miao的文件里。Application.persistentDataPath是unity放置存档文件的常用路径,在windows下它在C:/Users/用户名/AppData/LocalLow/你在unity里设置的公司名/你起的游戏名
File.WriteAllText(Application.persistentDataPath + "/save" + num + ".miao", fs);
//将临时截图copy到存档位上
byte[] b1 = File.ReadAllBytes(Application.persistentDataPath + "/savepic");
File.WriteAllBytes(Application.persistentDataPath + "/savepic" + num ,b1);
//将截图更新到save界面上,
Texture2D texture = new Texture2D(Screen.width, Screen.height);
texture.LoadImage(b1);
GameObject.Find("ui_save").transform.Find("content/savebt"+num+"/Image").gameObject.GetComponent<Image>().sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
//将时间更新到save界面上
GameObject.Find("ui_save").transform.Find("content/savebt" + num + "/Text").gameObject.GetComponent<Text>().text = sf["savetime" + num];
//储存sf
savesf();
}
再play一下,第一次的时候,还是什么存档记录都没有,因为我们还没有储存过sf,随意的在一些档位上存档,然后停止测试,再次play打开游戏的时候,就可以看到之前的存档好好的显示了出来。
但是,虽然存了档,具体读档的部分我们还没有写。
那么来考虑一下思路。
点击读档按钮后,我们首先应该做的是把屏幕变黑,然后清空屏幕,把所有ui的active设为false,把摄影棚里的立绘都清掉,把背景设成透明。
然后我们清空f,读取save数字,miao文件,将读取的数据放入被清空的f里。
接下来我们根据f里边记录的数据一项一项的重建画面。
重建好的画面应该是在等待点击的状态。
屏幕变黑这里需要我们做一个全黑的充满屏幕的image,就叫他ui_black好了,为它增加canvas group,录制它的淡入动画,然后把它的active勾掉,等用到的时候再打开它,因为只要激活就可以开始播放淡入动画,所以我们不用给对他的animator做修改
接下来我们来写loadgame的function
void loadgame(int num)
{
//将读档位数存入tf
tf["tempnum"] = num.ToString();
askfadein("确定要读取存档吗?", "loadgame1", "");
}
void loadgame1()
{
//执行loadgame2
StartCoroutine(loadgame2());
}
IEnumerator loadgame2()
{
//开始播放黑色淡入动画
GameObject.Find("Canvas").transform.Find("ui_black").gameObject.SetActive(true);
//停止音乐音效语音
AudioSource temp = (AudioSource)GameObject.Find("bgm").GetComponent<AudioSource>();
temp.Stop();
temp = (AudioSource)GameObject.Find("se").GetComponent<AudioSource>();
temp.Stop();
temp = (AudioSource)GameObject.Find("voice").GetComponent<AudioSource>();
temp.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();
//读取save数字.miao里的数据
string fs = File.ReadAllText(Application.persistentDataPath + "/save" + tf["tempnum"] + ".miao");
//将数据放入f
string[] temps = Regex.Split(fs, "&", RegexOptions.IgnoreCase);
string key = "";
for (int i = 0; i < temps.Length; i++)
{
if (key == "")
{
key = temps[i];
}
else
{
f[key] = temps[i];
key = "";
}
}
//重现背景图
if (f.ContainsKey("bg") && f["bg"] != "")
{
SpriteRenderer sp1 = GameObject.Find("bg1").GetComponent<SpriteRenderer>();
sp1.color = new Color32(255, 255, 255, 255);
sp1.sprite = (Sprite)Resources.Load("bg/" + f["bg"], typeof(Sprite));
}
//重现左中右立绘
string[] list1 = { "c", "r", "l" };
foreach (string p in list1)
{
if (f.ContainsKey("fg" + p) && f["fg" + p] != "")
{
Destroy(GameObject.Find("fgroot" + p));
GameObject fgroot = (GameObject)Instantiate(Resources.Load("fg/" + f["fg" + p], typeof(GameObject)));
fgroot.name = "fgroot" + p;
fgroot.transform.SetParent(GameObject.Find("rtCamera" + p).transform);
fgroot.transform.localPosition = new Vector3(0, 0, 0);
fgroot.transform.localScale = new Vector3(1, 1, 1);
GameObject temp2 = (GameObject)Instantiate(Resources.Load("fg/" + f["fg" + p] + "_face" + f["fg"+p+"face"], typeof(GameObject)));
temp2.name = "face1";
temp2.transform.SetParent(fgroot.transform.Find("fg/face"));
temp2.transform.localPosition = new Vector3(0, 0, -0.1f);
temp2.transform.localScale = new Vector3(1, 1, 1);
temp2.GetComponent<Animator>().Play("facea1", -1, 1);
GameObject temp0 = GameObject.Find("Canvas").transform.Find("rfg" + p).gameObject;
temp0.SetActive(true);
Animator an = temp0.GetComponent<Animator>();
an.Play("rfga1", -1, 1);
temp0.GetComponent<RawImage>().color = new Color32(255, 255, 255, 255);
}
}
//显示对话框
temp = GameObject.Find("Canvas").transform.Find("ui_dialog").gameObject;
temp.SetActive(true);
Animator an1 = temp.GetComponent<Animator>();
an1.Play("dialoga1", -1, 1);
//显示水滴动画
temp.transform.Find("drop").gameObject.SetActive(true);
//显示对话框内文字
temp = temp.transform.Find("ui_dialog_text").gameObject;
an1 = temp.GetComponent<Animator>();
an1.SetInteger("a_num", 1);
temp.GetComponent<Text>().text = f["dialog_text"];
//显示姓名
temp = GameObject.Find("Canvas").transform.Find("ui_dialog/nameimage").gameObject;
if (f.ContainsKey("dialog_name") && f["dialog_name"] != "")
{
temp.SetActive(true);
an1 = temp.GetComponent<Animator>();
an1.Play("nameimagea1", -1, 1);
temp.transform.Find("Text").gameObject.GetComponent<Text>().text = f["dialog_name"];
GameObject.Find("Canvas").transform.Find("rfgr").gameObject.GetComponent<RawImage>().color = new Color32(140, 140, 140, 255);
GameObject.Find("Canvas").transform.Find("rfgl").gameObject.GetComponent<RawImage>().color = new Color32(140, 140, 140, 255);
GameObject.Find("Canvas").transform.Find("rfgc").gameObject.GetComponent<RawImage>().color = new Color32(140, 140, 140, 255);
GameObject.Find("Canvas").transform.Find("rfg" + f["dialog_pos"]).gameObject.GetComponent<RawImage>().color = new Color32(255, 255, 255, 255);
}
else
{
temp.SetActive(false);
}
//显示语音播放按钮
temp = GameObject.Find("Canvas").transform.Find("ui_dialog/voicebt").gameObject;
if (f.ContainsKey("voice") && f["voice"] != "")
{
temp.SetActive(true);
}
else
{
temp.SetActive(false);
}
//播放背景音乐
if (f.ContainsKey("bgm") && f["bgm"] != "")
{
//原来的bgm()里边有读取下一行脚本的内容所以我们另外写一个不带读取下一行脚本的播放音乐用function
bgm1(f["bgm"]);
}
//把脚本当前行数读回来
scriptnum = Convert.ToInt16(f["scriptnum"]);
//设定为等待点击
onwaitclick = true;
}
void bgm1(string storage)
{
AudioSource temp = (AudioSource)GameObject.Find("bgm").GetComponent<AudioSource>();
temp.clip = (AudioClip)Resources.Load("bgm/" + f["bgm"], typeof(AudioClip));
temp.Play();
}
主要放置读档代码的loadgame2是个非常长的function,不过没有用到任何我们之前没有写过的东西。
我们点play测试一下存档和读档,可以看到各种功能都执行的很好。
现在我们在开始界面也将读档按钮加上opbt3.png
在开始界面的按钮组ui_opbts里加上读档按钮bt3,如果有必要也修改淡入淡出动画
然后我们需要给loadfadein更改一下,将loadfadein的内容放到loadfadein1里边便于bt3调用
public void loadfadein()
{
if (!onwaitclick)
{
return;
}
loadfadein1();
}
public void loadfadein1()
{
GameObject temp = GameObject.Find("Canvas").transform.Find("ui_load").gameObject;
temp.SetActive(true);
Animator an = temp.GetComponent<Animator>();
an.SetInteger("a_num", 1);
//循环所有读档按钮来进行图片和文字的赋值以及挂载事件
for (int i = 1; i <= 6; i++)
{
Button b1 = temp.transform.Find("content/savebt" + i).gameObject.GetComponent<Button>();
//有存档的时候
if (sf.ContainsKey("savetime" + i))
{
//显示截图
byte[] tempb = File.ReadAllBytes(Application.persistentDataPath + "/savepic"+i);
Texture2D texture = new Texture2D(Screen.width, Screen.height);
texture.LoadImage(tempb);
temp.transform.Find("content/savebt" + i + "/Image").gameObject.GetComponent<Image>().sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
//显示存档时间
temp.transform.Find("content/savebt" + i + "/Text").gameObject.GetComponent<Text>().text = sf["savetime" + i];
//设定按钮事件
b1.interactable = true;
b1.onClick.RemoveAllListeners();
int t1 = i;
b1.onClick.AddListener(delegate ()
{
loadgame(t1);
});
}
else
{
//如果没有存档则这个按钮设为不可用
b1.interactable = false;
}
}
}
将loadfadein1挂载在bt3上,点击play,可以看到开始界面也很好的可以唤出load界面了。
那么下一次我们来做对话履历界面吧。
Created by Hydrozoa.2011
不支持IE7以下浏览器
凯恩插件程序:Hydrozoa 美术:Hydrozoa,红渊