改进
对话代码
之前的对白显示代码有一点小问题,需要稍作修改。
if (Time.time - instantiateTime >= continueTime && !spanning)
disappearing = true;
如图所示,之前是没有spanning的判定条件的,这会导致脚本不断把disappear值设为真,导致后续对话无法显示。
鼠标指针
在游戏中,我们用准星来代替鼠标指针,所以我们需要隐藏指针并将其锁定,在角色移动代码的Start函数中加入如下两行代码:
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
当然,为了方便测试,我们在测试过程中会暂时将这两行代码注释掉。
获得当前道具
有时我们需要获得当前道具编号,但是getItem函数存在一种特殊情况——当前没有任何道具,按照我们之前编写的代码,会造成indexOut类型的error,所以我们要稍加修改:
public int GetItem()
{
if (items.Count == 0)
return 0;
return items[num];
}
完成喷泉房间
置物架
上一次我们已经编写好了交互物体的脚本,这次我们直接将脚本拖动到需要互动的物体上即可。
首先是置物架,我们将脚本添加后修改参数如下:
值得注意的是,由于shelf本身是一个用来容纳各个组件的空物体,所以我们要手动添加Collider,才能让它被Raycast检测到,同时要修改其tag为Untouched。
之后我们要为架子上的部分物体添加玻璃材质,新建material,设置参数如下:
注意,一定要修改渲染模式为Fade,不然无法实现透明效果。
将材质赋予架子上的部分物体后,我们进入游戏检查一下:
我们如愿以偿得到了道具,点亮了灯光,并让特殊的玻璃杯消失,也出现了对白,置物架的实例摆放宣告成功。
喷泉
话不多说,先放上参数图:
如图,交互后出现的三个灯光分别是喷泉的有阴影灯光,无阴影灯光,以及隔壁房间保险柜的照明灯光。
另外因为喷泉下子物体的collider会影响射线检测,所以我们需要将子物体的collider移动到spring物体下。
喷泉灯光效果如下:
之后我们进入游戏看一看实际效果:
实际效果很成功,但是我们会发现一个问题——道具栏中的道具只有一个时,没有自动填充满,我们需要稍作修改。
道具添加代码修改
打开Item脚本的AddItem函数部分,修改部分代码如下:
if (items.Count == 1)
{
itemNowImg.sprite = img;
itemNextImg.sprite = img;
itemPrevImg.sprite = img;
}
这样道具个数变成一个时,就会填充满道具栏图标了。
密码解锁获得道具
用UI来实现输入密码
我们的保险柜为密码锁,所以我们需要实现密码解锁保险柜获得钥匙的功能,然而只使用我们之前所编写的Contact脚本无法完成这个功能,需要专门为它编写新的脚本。
首先我们还是把脚本添加给保险柜,但是由于我们点击保险柜不会得到任何道具,所以选择类型4,也不会有对话产生,我们唯一需要让交互做的就是生成UI界面。
因此我们需要制作一张新的画布命名为Password,将其拖动到保险柜的ShowObject数组下,剩下的就是设置好UI界面后编写脚本了!
设置UI界面
关于UI界面,我想要做出四个拨盘的密码锁效果,图片如下:
密码锁代码
图中有几个需要实现的功能,我将一一列举出来并附上代码。
首先是挂载在canvas画布上的脚本需要声明的变量:
[Header("Manu")]
public GameObject character;
public GameObject item;
public GameObject itemBox;
public GameObject dialogPrefeb;
[Header("ChangeBtnArea")]
public GameObject[] textObjNows;
public GameObject[] textObjNews;
public Transform[] up;
public Transform[] middle;
public Transform[] down;
[Header("SureBtnArea")]
public string password;
public Text wrongMsg;
private Text textNow;
private Text textNew;
private NumberMove moveNow;
private NumberMove moveNew;
private MoveController moveController;
private bool ifShowing = false;
其中ChangeBtnArea部分是利用提前放置好的位置用Lerp移动数字来实现数字拨盘滚动效果,我们还需要在数字Text对象上编写一个移动脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NumberMove : MonoBehaviour
{
[HideInInspector] public Transform targetPos;
[HideInInspector] public bool ifMoving = false;
private float moveSpeed = 0.05f;
void Update()
{
if (ifMoving)
{
transform.position = Vector3.Lerp(transform.position, targetPos.position, moveSpeed);
if (Vector3.Distance(transform.position, targetPos.position) < 0.05f)
ifMoving = false;
}
}
}
将这个脚本挂载之后,我们只需要修改targetPos和ifMoving就可以控制数字移动了,之后向上翻与向下翻的代码如下:
public void PageUp(int btnOrder)
{
GameObject objNow = textObjNows[btnOrder];
GameObject objNew = textObjNews[btnOrder];
textNow = objNow.GetComponent<Text>();
textNew = objNew.GetComponent<Text>();
moveNow = objNow.GetComponent<NumberMove>();
moveNew = objNew.GetComponent<NumberMove>();
textNew.text = ((int.Parse(textNow.text) + 1) % 10).ToString();
objNew.transform.position = down[btnOrder].position;
moveNow.targetPos = up[btnOrder];
moveNew.targetPos = middle[btnOrder];
moveNow.ifMoving = true;
moveNew.ifMoving = true;
RollNum(btnOrder);
}
public void PageDown(int btnOrder)
{
GameObject objNow = textObjNows[btnOrder];
GameObject objNew = textObjNews[btnOrder];
textNow = objNow.GetComponent<Text>();
textNew = objNew.GetComponent<Text>();
moveNow = objNow.GetComponent<NumberMove>();
moveNew = objNew.GetComponent<NumberMove>();
textNew.text = ((int.Parse(textNow.text) + 9) % 10).ToString();
objNew.transform.position = up[btnOrder].position;
moveNow.targetPos = down[btnOrder];
moveNew.targetPos = middle[btnOrder];
moveNow.ifMoving = true;
moveNew.ifMoving = true;
RollNum(btnOrder);
}
private void RollNum(int btnOrder)
{
GameObject tmpObj;
tmpObj = textObjNows[btnOrder];
textObjNows[btnOrder] = textObjNews[btnOrder];
textObjNews[btnOrder] = tmpObj;
}
遮罩与按钮响应挂载
首先,数字不能显示在仪表盘之外的地方,所以需要做一个遮罩层,而制作方法也很简单,先将数字设为遮罩图片的子物体,再为遮罩添加Mask组件,如下图:
而按钮响应则需要将挂载了脚本的canvas画布拖给按钮的Onclick列表,再选择刚刚编写的脚本中的对应点击触发函数:
返回与输入密码
因为玩家不一定能正确输入密码,所以我们还要给玩家退出按钮,也要对输入的密码作出相应,其脚本分别如下:
public void ExitBtn()
{
moveController.enabled = true;
itemBox.tag = "Untouched";
wrongMsg.color = new Color(1, 0, 0, 0);
dialogPrefeb.SendMessage("ShowDialog", "看上去你遇到了一些麻烦,为什么不看看置物架呢?");
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
ifShowing = true;
gameObject.SetActive(false);
}
在退出时要复原玩家的移动脚本,并重新锁定光标。
public void SureBtn()
{
string inputPass = "";
foreach (GameObject textObj in textObjNows)
inputPass += textObj.GetComponent<Text>().text;
if (inputPass.Equals(password))
{
item.GetComponent<Item>().AddItem(3);
moveController.enabled = true;
dialogPrefeb.SendMessage("ShowDialog", "哦!一把钥匙,你一定在想,保险柜用来装钥匙也真是太滑稽了。");
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
gameObject.SetActive(false);
}
else
{
wrongMsg.color = new Color(1, 0, 0, 1);
}
}
首先获取密码,之后进行比对,如果一致则不再重设保险箱的tag,防止第二次接触,并进行一系列响应。
如果不对,除了显示“密码错误!”的信息外不做任何处理。
每次打开密码锁都要锁定视角和打开鼠标,因此我们在脚本上添加如下代码:
private void Start()
{
moveController = character.GetComponent<MoveController>();
}
private void Update()
{
if (ifShowing)
{
Cursor.visible = true;
Cursor.lockState = CursorLockMode.None;
moveController.enabled = false;
ifShowing = false;
}
}
之后我们来实际进入游戏看看效果。
一切正常,下一次我们就可以开始最后的制作了!