首先,协程的准备工作
关键的代码段,写在了OnGUI中,在判断按钮的if语句块下
if (GUI.Button(new Rect(40, 50, 150, 50), "被击"))
{
StartCoroutine(WaitBehit());
}
然后创建新的结构,包含了协程需要执行的具体内容,固定写法 IEnumerator name()
IEnumerator WaitBehit()
{
// GetComponentInChildren会在该物体各个子物体的属性中查找对应的值(SkinnedMeshRenderer)
var smr = Monster.GetComponentInChildren<SkinnedMeshRenderer>();
//没有返回类型,所以不需要设置
smr.sharedMaterial.SetColor("_Color", Color.white);
yield return new WaitForSeconds(0.05f);
smr.sharedMaterial.SetColor("_Color", Color.black);
}
简单易懂,按下按钮后, 执行SetColor为白色,等待0.05秒后,执行SetColor为黑色
但是缺少了过渡效果,下面我们在中毒的效果中加入此项
if (GUI.Button(new Rect(40, 120, 150, 50), "中毒"))
{
StartCoroutine(WaitDu());
}
同样的,协程固定写法,然后是创建协程的结构
IEnumerator WaitDu()
{
// 声明一个_time为0的数值进入循环
float _time = 0;
// 始终为真,会一直循环,所以利用yield break打破循环,条件是当_time大于2时
while (true)
{
// Time.deltaTime表示距离上一帧所用的时间,该数值较小,会在每一帧都加上这个数值
_time += Time.deltaTime;
Debug.Log(_time);
// WaitForEndOfFrame表示在每一帧结束后执行一次,主要用来限制while循环的速率,避免一次性全部执行
// yield 表示让协程在此暂停并计算条件,满足后继续
yield return new WaitForEndOfFrame();
// 限制条件,避免进入死循环,_time值大于2时打破循环
if (_time > 2f)
{
yield break;
}
// else语句,_time不大于2时,执行以下颜色过渡计算
else
{
// 利用lerp函数,_time为0时,_color值为Color.red,_time为1时,_color值为Color.black ,这里为了取到1要除以2
Color _color = Color.Lerp(Color.red, Color.black, _time / 2f);
var smr = Monster.GetComponentInChildren<SkinnedMeshRenderer>();
smr.sharedMaterial.SetColor("_Color", _color);
}
}
}
看起来复杂,我们从关键的代码中梳理一下,关键代码其实在最后三段,利用lerp函数控制从白到黑的过渡,lerp(a,b,t) a是红色(中毒后掉血的效果,也可以是绿色),b是黑色,那么关键在于t
t是一个递增的数值,从a递增到b,我们使用while循环语句,并将其设定为true,为了避免死循环,我们设定if语句判断t的值是否大于某个数,只要成立那么就用 yield break 打破循环!
递增多少呢,我们使用了_time += Time.deltaTime; 即_time会加等于每一帧之间所需要的时间,我的理解,帧数越低,所需的实际时间应该会越长,所以玩家即便掉帧,也能收到反馈,我以为会引入实际时间计算,也许是考虑到了这一点。
还有一个问题,我的电脑性能强大,一瞬间就执行完了这个循环,那么我们应该引入一个时间的概念,即限制循环语句计算的速率
就是它了
yield return new WaitForEndOfFrame();
yield 这个语句会让我们的协程进入等待,完成计算或满足条件后解除等待,WaitForEndOfFrame() 就是在每一帧结束后再进行计算
过渡的效果已经有了,接下来我们进入优化代码的阶段
不难发现,我们每次需要对这个物体的材质进行操作时,都要输入一段长长的代码
var smr = Monster.GetComponentInChildren<SkinnedMeshRenderer>();
smr.sharedMaterial.SetColor("_Color", _color);
既然需要多次使用它,我们可以提前声明,并且在游戏开始时(void Start())就对它赋值
首先,我们要声明一个私有变量,给它命名为mat
#region [成员变量]
public GameObject Monster;
private Material mat;
#endregion
并在游戏开始时,就把需要用到的参数赋给mat
void Start()
{
mat = Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial;
}
这样就省了很多冗余的代码段了
哦对,还有2f值,即消散的时间,要更改它有些麻烦,我们也可以用同一个值控制它,在这里声明
IEnumerator WaitDu(float time)
那这个time的值到底是多少呀,这里仅仅是声明一个名字为time的浮点值,其实在GUI那里
if (GUI.Button(new Rect(40, 120, 150, 50), "中毒"))
{
StartCoroutine(WaitDu(2f));
}
在这里设置time具体的值,显而易见,这里的2f会传递给 float time,最后,我们把2f换成time就好了,如果需要,也可以将time值暴露出来进行设置
还有个问题
两个协程不能同时执行,点击中毒时,再点击被击是没有打断的效果的,那么简单粗暴的思路,我们设定在执行此协程时,停用所有其他协程,只需要一句
StopAllCoroutines();
写在GUI的if语句块里就好
if (GUI.Button(new Rect(40, 50, 150, 50), "被击"))
{
StopAllCoroutines();
StartCoroutine(WaitBehit());
}
最后就大功告成了!下面是完整代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MonsterShader : MonoBehaviour
{
#region [成员变量]
public GameObject Monster;
private Material mat;
#endregion
#region [Start/Update]
void Start()
{
mat = Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial;
}
void Update()
{
}
#endregion
#region [GUI]
void OnGUI()
{
if (GUI.Button(new Rect(40, 50, 150, 50), "被击"))
{
StopAllCoroutines();
StartCoroutine(WaitBehit());
}
if (GUI.Button(new Rect(40, 120, 150, 50), "中毒"))
{
StopAllCoroutines();
StartCoroutine(WaitDu(2f));
}
}
#endregion
#region [被击]
IEnumerator WaitBehit()
{
mat.SetColor("_Color", Color.white);
yield return new WaitForSeconds(0.05f);
mat.SetColor("_Color", Color.black);
}
#endregion
#region [中毒]
IEnumerator WaitDu(float time)
{
float _time = 0;
while (true)
{
_time += Time.deltaTime;
yield return new WaitForEndOfFrame();
if (_time > time)
{
yield break;
}
else
{
Color _color = Color.Lerp(Color.red, Color.black, _time / time);
mat.SetColor("_Color", _color);
}
}
}
#endregion
}