搞过嵌入式开发的人应该都知道最简单的入门级程序就是流水灯,如果没见过的话,你可以看一下地铁门上方的站点LED灯,大概就是下图这个效果:
没错,不是“Hello world”,可能对于应用层程序而言,最简单的程序应该是“Hello World”,但是如果在单片机开发中要实现显示“Hello World”的效果,如果你用LED显示,你需要懂字库;如果用LCD显示,你可能需要了解LCD驱动程序,这都不是一个初学者所能掌握的,反而是这种只需要置0或1的电平操作要更简单一些。
然而在Unity中要实现流水灯,大概对于初学者有一定的难度,反正笔者在刚接触的Unity的时候试着写了一个流水灯类似的Unity程序,自然是Bug百出,然后百度搜也搜不到,一搜全是单片机的流水灯。所以写下这篇文章,为那些和我遇到同样问题,但又无处寻找答案的初学者提供思路,涉及的东西不太深,大神勿喷。
实现效果及思路
我这里用材质颜色代替灯光,为了效果更明显些,而且这种方式对新手也比较友好,你也可以用灯光组件强度来实现,效果都差不多,最终效果如下图:
这里分成两步:
- 循环定时回调
- 更改材质
对于定时回调提供五种思路:
- InvokeRepeating实现
- 协程加while(true)实现
- Plane大神的PETimer插件实现
- 声明定时变量实现
- 修改Time面板中的Fixed Timestep的值,并在FixedUpdate中实现
对于更改材质提供三种思路(参考嵌入式开发思路):
- 把所有情况都写出来,逐个更改材质
- 先把所有材质置白,指定的材质置红
- 使用变量记录上一次索引,发现这次的变量和上次不一样时更改材质,然后将上次索引改为这次的索引
下面对以上8个思路一一实现如下:
更改材质函数实现一(穷举)
代码实现如下:
private int index = 0;
public List<MeshRenderer> sphereList;
public void ChangeMat01(int tid){
switch (index){
case 0:
sphereList[4].material.color = Color.white;
sphereList[0].material.color = Color.red;
break;
case 1:
sphereList[0].material.color = Color.white;
sphereList[1].material.color = Color.red;
break;
case 2:
sphereList[1].material.color = Color.white;
sphereList[2].material.color = Color.red;
break;
case 3:
sphereList[2].material.color = Color.white;
sphereList[3].material.color = Color.red;
break;
case 4:
sphereList[3].material.color = Color.white;
sphereList[4].material.color = Color.red;
break;
}
index++;
index %= 5;
}
本例以五个灯点为例,如果,灯点数不同请修改 index %= 5;
中的5为灯点数,sphereList需要在面板上拖拽赋值,否则回报空指针。在需要定时回调的地方回调该函数即可。如果要增加或者减少灯点,就增加或减少相应的case语句即可。
显然这种方法是用空间换时间的方法,如果灯点数比较多,比如成百上千个,那就需要写成百上千个case语句……那代码量就相当恐怖了,但是不会产生多余的操作,时间复杂度较低。
另外如果使用Invoke方式调用,需要把函数改为无参数,tid参数使用来当Plane大神的插件的委托时使用。
更改材质函数实现二(类比嵌入式移位法)
代码实现如下:
private int index = 0;
public List<MeshRenderer> sphereList;
public void ChangeMat02(int tid){
foreach (MeshRenderer temp in sphereList){
temp.material.color = Color.white;
}
sphereList[index].material.color = Color.red;
index++;
index %= sphereList.Count;
}
这种方法无需考虑灯点数量,同样需要在面板上给sphereList拖拽赋值,tid用法同前,也需在Invoke中去掉参数。这种操作是用时间换空间,代码虽短,但时间复杂度为n,多了很多无用代码,因为上次被置为红的材质只有一个,却遍历了列表所有元素置为白,显然这里多了n-1个多余操作,但在列表元素变多时无需变更代码
更改材质函数实现三(推荐但不好理解)
代码实现如下:
private int index = 0;
public List<MeshRenderer> sphereList;
private int lastIndex = -1;
public void ChangeMat03(int tid){
if (lastIndex != index && lastIndex != -1){
sphereList[lastIndex].material.color = Color.white;
sphereList[index].material.color = Color.red;
lastIndex = index;
}
index++;
index %= sphereList.Count;
}
这种方法无论是时间上还是空间上都达到最优,但是如果是刚接触Unity的人可能不太好理解,总体思路就是再声明一个变量用来记录上次变为红色的物体索引,另一个变量记录当前即将变为红色物体索引,发现两个值不一样,则把上个变为红色物体材质变为白色,把这次要变成红色物体的材质变为红色,然后更改上次的记录索引为这次的值,直到当前索引改变再次循环。
同样需要在面板上给sphereList拖拽赋值,tid用法同前,也需在Invoke中去掉参数。
定时回调实现一(InvokeRepeating)
代码实现如下:
public float delay = .5f;
private void Start(){
InvokeRepeating("ChangeMat01", delay, delay);
}
使用Unity自带InvokeRepeating方法,脚本需要继承MonoBehaviour,第一个参数的函数名根据更改材质方法而定,但一定要无参,将延迟时间公开到面板上方便修改。
定时回调实现二(协程)
代码实现如下:
public float delay = .5f;
private void Start(){
StartCoroutine("ChangeMatContour");
}
IEnumerator ChangeMatContour(){
while (true){
yield return new WaitForSeconds(delay);
ChangeMat01(1);
}
}
利用协程的yield返回加死循环的方式实现,同样公开循环时间到面板,更改材质函数ChangeMat01如果无参则不填参数
定时回调实现三(PETimer工具类)
代码实现如下:
private PETimer pt = new PETimer();
public float delay = .5f;
private void Start(){
pt.AddTimeTask(ChangeMat03, delay, PETimeUnit.Second, 0);
}
private void Update(){
pt.Update();
}
此方法使用的PETimer类为Plane大神所写,具体源码及使用方法,大家可以到其Github地址寻找:
使用时传入的函数需要带一个整型参数,需要在Update中开启驱动,AddTimeTask第二个参数为延迟时间,第三个单位是时间单位枚举,第四个参数为执行次数,如果为0则循环。一个非常实用的定时回调插件。
定时回调实现四(维护定时用变量)
代码实现如下:
private float timer = 0;
public float delay = .5f;
private void Update(){
SetTimer();
}
private void SetTimer(){
timer += Time.deltaTime;
if (timer >= delay){
ChangeMat02(1);
timer = 0;
}
}
这种没什么可说的,无须太多Unity基础,懂C#应该就能看懂,Time.deltaTime为一帧的时间,其中时间单位都为秒
定时回调实现五(修改工程Fixed Timestep值)
代码实现如下:
private void FixedUpdate(){
ChangeMat01(1);
}
实现最简单但不推荐使用,因为需要修改工程设置,如果后续需要用到FixedUpdate的地方会出问题i
,尤其是要在FixedUpdate中执行物理运动时,具体修改Fixed Timestep值的方法如下,在Edit–ProjectSetting–Time面板上修改为你需要的延迟时间即可:
最后提供完整代码如下,随便找个空物体挂上,然后在面板上给sphereList
赋值即可:
/****************************************************
文件:GameRoot.cs
作者:JZHacker
邮箱: 2280787540@qq.com
日期:2019/9/29 12:55:32
功能:游戏根物体
*****************************************************/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameRoot : MonoBehaviour
{
private PETimer pt = new PETimer();
public float delay = .5f;
public List<MeshRenderer> sphereList;
private int index = 0;
private int lastIndex = -1;
private float timer = 0;
private void Start(){
//pt.AddTimeTask(ChangeMat03, delay, PETimeUnit.Second, 0);
//InvokeRepeating("ChangeMat01", delay, delay);
StartCoroutine("ChangeMatContour");
}
IEnumerator ChangeMatContour(){
while (true){
yield return new WaitForSeconds(delay);
ChangeMat01(1);
}
}
//private void FixedUpdate()
//{
// ChangeMat01(1);
//}
private void Update(){
pt.Update();
// SetTimer();
}
private void SetTimer(){
timer += Time.deltaTime;
if (timer >= delay){
ChangeMat02(1);
timer = 0;
}
}
private void ChangeMat01(int tid){
foreach (MeshRenderer temp in sphereList){
temp.material.color = Color.white;
}
sphereList[index].material.color = Color.red;
index++;
index %= sphereList.Count;
}
private void ChangeMat02(int tid){
switch (index){
case 0:
sphereList[4].material.color = Color.white;
sphereList[0].material.color = Color.red;
break;
case 1:
sphereList[0].material.color = Color.white;
sphereList[1].material.color = Color.red;
break;
case 2:
sphereList[1].material.color = Color.white;
sphereList[2].material.color = Color.red;
break;
case 3:
sphereList[2].material.color = Color.white;
sphereList[3].material.color = Color.red;
break;
case 4:
sphereList[3].material.color = Color.white;
sphereList[4].material.color = Color.red;
break;
}
index++;
index %= 5;
}
private void ChangeMat03(int tid){
if (lastIndex != index && lastIndex != -1){
sphereList[lastIndex].material.color = Color.white;
sphereList[index].material.color = Color.red;
lastIndex = index;
}
index++;
index %= sphereList.Count;
}
}
转载:https://blog.csdn.net/HackerJZ/article/details/101696865