小言_互联网的博客

Unity实现流水灯的几种思路(类比嵌入式)

316人阅读  评论(0)

搞过嵌入式开发的人应该都知道最简单的入门级程序就是流水灯,如果没见过的话,你可以看一下地铁门上方的站点LED灯,大概就是下图这个效果:

没错,不是“Hello world”,可能对于应用层程序而言,最简单的程序应该是“Hello World”,但是如果在单片机开发中要实现显示“Hello World”的效果,如果你用LED显示,你需要懂字库;如果用LCD显示,你可能需要了解LCD驱动程序,这都不是一个初学者所能掌握的,反而是这种只需要置0或1的电平操作要更简单一些。
然而在Unity中要实现流水灯,大概对于初学者有一定的难度,反正笔者在刚接触的Unity的时候试着写了一个流水灯类似的Unity程序,自然是Bug百出,然后百度搜也搜不到,一搜全是单片机的流水灯。所以写下这篇文章,为那些和我遇到同样问题,但又无处寻找答案的初学者提供思路,涉及的东西不太深,大神勿喷。

实现效果及思路

我这里用材质颜色代替灯光,为了效果更明显些,而且这种方式对新手也比较友好,你也可以用灯光组件强度来实现,效果都差不多,最终效果如下图:

这里分成两步:

  1. 循环定时回调
  2. 更改材质

对于定时回调提供五种思路:

  1. InvokeRepeating实现
  2. 协程加while(true)实现
  3. Plane大神的PETimer插件实现
  4. 声明定时变量实现
  5. 修改Time面板中的Fixed Timestep的值,并在FixedUpdate中实现

对于更改材质提供三种思路(参考嵌入式开发思路):

  1. 把所有情况都写出来,逐个更改材质
  2. 先把所有材质置白,指定的材质置红
  3. 使用变量记录上一次索引,发现这次的变量和上次不一样时更改材质,然后将上次索引改为这次的索引

下面对以上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地址寻找:

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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场