Unity地屏效果的简单实现流程
前言
记得去年我在北京的时候,通过面试进了一家做展馆展示的公司。这家公司规模挺大的,老板也很有气场,做的项目也不小。我进来后觉得我应该能干挺长时间的,但是我干了差不多不到一个月就主动离职了。有的时候就是这样,希望越大,失望越大。至于我为什么离开这家公司,大致就是工作太累,公司内部内卷太厉害,公司里的那些做设计的人经常自己就将程序的功能改来改去的。他们觉得改改需求很容易,动动笔就可以,殊不知程序员改代码是有多费劲。还有就是加起班来没时没点的,公司里管项目的小领导只会逼人干活,但是干完活了也没有相应的加班费和补偿,甚至连一句感谢都没有,仿佛一切都是理所应当的。可能我在这家公司最不能忍受的就是让我干一些和程序不相干的事情吧,这家公司甚至会让程序员去安装投影机,调试硬件设备,甚至管理工人穿线。后来我干完上海这个项目我就打算离职了,后来公司小领导又让我做那个环拍的项目,只给了7天的时间,我上海项目完成了还没有调休呐,这又让我天天加班。我心寒了,第二天就和公司提请了辞职信,离开了这家公司。
不过我在这家公司做的上海那个项目,虽然天天加班到深夜,但是确实也学到了不少的东西,做的其中有一个项目就是地屏互动程序。大概逻辑就是利用雷达,在地面上安装长方形显示屏,然后人在显示屏上走,脚踩到哪里,哪里就会出现开花的图案,同时利用串口通信向硬件设备发送信号,触发旁边的等亮起来。等人走过之后,图案消失,恢复水面动画,同时将旁边的触控灯灭掉。
这个项目的难点就是调试雷达进行触控。我记得当时用的北洋雷达进行的触发,将雷达设置成为连续点击,人在上面走,模拟鼠标连续点击的效果。我调试了整整一个晚上,连修改程序效果,最终实现了比较好的效果。不过公司可是一点儿都没念着我的好,在我提离职的时候,那领导那张脸拉的和驴脸一样,我问她我需要移交什么吗,她说我在公司什么都没干,什么都不用移交。正好,我也懒得移交了,省事了。说了那么多,言归正传,我开始说一下这个项目的大致实现步骤。
实现流程
其实我一开始听公司那个搞设计的需求,我本来设计的是将程序屏幕平均分成5部分,然后每一个部分成为一个触控区,人通过雷达踩上去触发相应的逻辑,产生相应的效果。后来那个设计说不行,必须按照之前的程序员做过的效果进行开发。后来我看了下之前的程序员写的代码,实现的逻辑是是通过点击屏幕产生一片粒子效果,然后检测有没有其他人点击,如果没有,则粒子消失。在这个基础上,我又进一步改进了代码。具体实现步骤如下。
一、在场景中新建一个Image组件,在这个组件上新建一个水纹动画,如下图所示:
二、新建一个粒子,将粒子的tag设置成grass,粒子效果和设置如下所示:
三、新建CloseGrass.cs脚本,并将该脚本挂载到粒子物体上,将粒子物体设置为预制体,脚本代码如下图所示:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CloseGrass : MonoBehaviour
{
//粒子效果
public ParticleSystem[] thisParticleSystem;
private void Start()
{
ToCloseThis();
}
/// <summary>
/// 播放完动画后销毁自己
/// </summary>
internal void ToCloseThis()
{
StartCoroutine(ToDesThis());
}
IEnumerator ToDesThis()
{
yield return new WaitForSeconds(3.0f);
for (int i = 0; i < thisParticleSystem.Length; i++)
{
thisParticleSystem[i].Stop();
}
yield return new WaitForSeconds(1.0f);
Destroy(this.gameObject);
}
}
四、新建MouseLeft0.cs脚本,实现通过点击鼠标产生粒子的效果,并且检测场景中粒子效果的数量,如果场景中粒子的数量小于5个,则点击鼠标时新建一个粒子。脚本代码如下所示:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MouseLeft0 : MonoBehaviour
{
public GameObject thisImage;
// Update is called once per frame
void Update()
{
ToShowImage();
}
void ToShowImage()
{
if (Input.GetMouseButtonDown(0))
{
ToCheckImage();
//Debug.Log("按下鼠标!");
}
}
void ToCreateGrass()
{
Vector3 targetposition = Camera.main.WorldToScreenPoint(thisImage.transform.position);//将物体的世界坐标转换为屏幕坐标
Vector3 mouseposition = Input.mousePosition;
mouseposition.z = targetposition.z;
float x = Camera.main.ScreenToWorldPoint(mouseposition).x;
float y = Camera.main.ScreenToWorldPoint(mouseposition).y;
float z = thisImage.transform.position.z;
GameObject thisObject = GameObject.Instantiate(thisImage, new Vector3(x, y, z), Quaternion.identity);
}
void ToCheckImage()
{
GameObject[] thisGrass = GameObject.FindGameObjectsWithTag("grass");
Debug.Log(thisGrass.Length);
if (thisGrass.Length <= 5)
{
StartCoroutine(ToCreateThisGrass());
}
else
{
thisGrass[0].GetComponent<CloseGrass>().ToCloseThis();
StartCoroutine(ToCreateThisGrass());
}
}
IEnumerator ToCreateThisGrass()
{
yield return new WaitForSeconds(0.1f);
ToCreateGrass();
}
}
五、将MouseLeft0.cs脚本挂载到Canvas上,并将粒子预制体拖拽到该脚本上,如下图所示:
六、点击鼠标,可以看到屏幕已产生粒子效果,如下图所示:
七、接下来完成触发灯亮的逻辑,使用的是串口通信,将信号转成16进制的数据,触发灯亮或者等灭,在实际场景中人踩到什么位置,就触发相应的逻辑,代码如下所示:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO.Ports;
using System;
public class PortManager : MonoBehaviour
{
#region 参数
string getPortName;
int baudRate = 19200;
private Parity parity = Parity.None;
private int dataBits = 8;
private StopBits stopBits = StopBits.One;
SerialPort sp = null;
private string _data;
private string testString0, testString1, testString2, testString3, testString4, testString5,
closeString0, closeString1, closeString2, closeString3, closeString4, closeString5;
string reciveString;
//配置文件类
public ConfigTest thisConfigTest;
#endregion
#region 常规方法
// Use this for initialization
void Start()
{
getPortName = thisConfigTest.dic["端口号"]["portName"];
baudRate = int.Parse(thisConfigTest.dic["波特率"]["baudRate"]);
testString0 = thisConfigTest.dic["信号0"]["string0"];
testString1 = thisConfigTest.dic["信号1"]["string1"];
testString2 = thisConfigTest.dic["信号2"]["string2"];
testString3 = thisConfigTest.dic["信号3"]["string3"];
testString4 = thisConfigTest.dic["信号4"]["string4"];
testString5 = thisConfigTest.dic["信号5"]["string5"];
closeString0 = thisConfigTest.dic["关灯信号0"]["closeString0"];
closeString1 = thisConfigTest.dic["关灯信号1"]["closeString1"];
closeString2 = thisConfigTest.dic["关灯信号2"]["closeString2"];
closeString3 = thisConfigTest.dic["关灯信号3"]["closeString3"];
closeString4 = thisConfigTest.dic["关灯信号4"]["closeString4"];
closeString5 = thisConfigTest.dic["关灯信号5"]["closeString5"];
reciveString = thisConfigTest.dic["接收信号"]["receiveString"];
OpenPort(getPortName);
StartCoroutine(DataReceiveFunction());
}
#endregion
#region 串口通信控制
/// <summary>
/// 串口信号控制
/// </summary>
private void PortSignalControl()
{
if (_data == System.Text.Encoding.ASCII.GetBytes(reciveString)[0].ToString())
{
//Debug.Log("收到串口信号" + testString);
}
}
//打开串口
public void OpenPort(string DefaultPortName)
{
sp = new SerialPort(DefaultPortName, baudRate, parity, dataBits, stopBits);
sp.ReadTimeout = 10;
try
{
if (!sp.IsOpen)
{
sp.Open();
}
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
}
//关闭串口
public void ClosePort()
{
try
{
sp.Close();
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
}
IEnumerator DataReceiveFunction()
{
byte[] dataBytes = new byte[128];//存储长度
int bytesToRead = 0;//记录获取的数据长度
while (true)
{
if (sp != null && sp.IsOpen)
{
try
{
//通过read函数获取串口数据
bytesToRead = sp.Read(dataBytes, 0, dataBytes.Length);
_data = dataBytes[0].ToString();
print(_data);
PortSignalControl();
//串口数据已经被存入dataBytes中
}
catch (Exception ex)
{
}
}
yield return new WaitForSeconds(Time.deltaTime);
}
}
//发送一个字节
public void SendSerialPortData(string data)
{
if (sp.IsOpen)
{
sp.WriteLine(data);
}
}
//发送一个字节
public void SendSerialPortData0(byte[] data)
{
if (sp.IsOpen)
{
sp.Write(data,0,data.Length);
}
}
/// <summary>
/// 发送一个字符串0
/// </summary>
internal void SendString0()
{
//SendSerialPortData(testString0);
SendMsg(testString0);
}
/// <summary>
/// 发送字符串1
/// </summary>
internal void SendString1()
{
//SendSerialPortData(testString1);
SendMsg(testString1);
}
/// <summary>
/// 发送字符串2
/// </summary>
internal void SendString2()
{
//SendSerialPortData(testString2);
SendMsg(testString2);
}
/// <summary>
/// 发送字符串3
/// </summary>
internal void SendString3()
{
//SendSerialPortData(testString3);
SendMsg(testString3);
}
/// <summary>
/// 发送字符串4
/// </summary>
internal void SendString4()
{
//SendSerialPortData(testString4);
SendMsg(testString4);
}
/// <summary>
/// 发送字符串5
/// </summary>
internal void SendString5()
{
//SendSerialPortData(testString5);
SendMsg(testString5);
}
/// <summary>
/// 发生关灯信号0
/// </summary>
internal void SendCloseString0()
{
//SendSerialPortData(closeString0);
SendMsg(closeString0);
}
/// <summary>
/// 发生关灯信号1
/// </summary>
internal void SendCloseString1()
{
//SendSerialPortData(closeString1);
SendMsg(closeString1);
}
/// <summary>
/// 发生关灯信号2
/// </summary>
internal void SendCloseString2()
{
//SendSerialPortData(closeString2);
SendMsg(closeString2);
}
/// <summary>
/// 发生关灯信号3
/// </summary>
internal void SendCloseString3()
{
//SendSerialPortData(closeString3);
SendMsg(closeString3);
}
/// <summary>
/// 发生关灯信号4
/// </summary>
internal void SendCloseString4()
{
//SendSerialPortData(closeString4);
SendMsg(closeString4);
}
/// <summary>
/// 发生关灯信号5
/// </summary>
internal void SendCloseString5()
{
//SendSerialPortData(closeString5);
SendMsg(closeString5);
}
private void OnApplicationQuit()
{
ClosePort();
}
private void OnDisable()
{
ClosePort();
}
#endregion
#region 信号转16进制方法
public void SendMsg(string s)
{
string msg = s;
//byte[] cmd = new byte[1024 * 1024 * 3];
//cmd = Convert16(msg);
//SendSerialPortData0(cmd);
byte[] cmd = textWork16(s);
SendSerialPortData0(cmd);
}
private byte[] textWork16(string strText)
{
strText = strText.Replace(" ", "");
byte[] bText = new byte[strText.Length / 2];
for (int i = 0; i < strText.Length / 2; i++)
{
bText[i] = Convert.ToByte(Convert.ToInt32(strText.Substring(i * 2, 2), 16));
}
return bText;
}
#endregion
}
八、逻辑基本开发完成了,最难的就是调试雷达将软件效果调成最好,最后效果也不错,实现效果如下图所示:
小结
技术就是在一个一个项目中慢慢积淀的,有实际的项目练着练着也就成了大拿了。