Unity刮刮乐效果实现及优化
前言
由于之前没有做过刮刮乐,所以对这个东西很感兴趣。最近研究了一下之后发现,刮刮乐的实现方式也没有想象得那么神奇。(内心真实感受:就这?)
为了保证该功能可以支持热更新,所以用的Lua实现的该功能。
这里做一个小的总结。
实现
Unity中实现刮刮卡的方式有很多种,但实现的基本原理都是通过计算改变目标点及周围的像素颜色的alpha为0。
监听事件
可以通过类继承IDragHandler、IPointerDownHandler、IPointerUpHandler等接口来实现。
也可以通过EventTrigger添加EventTriggerType.Drag、EventTriggerType.PointerDown、EventTriggerType.PointerUp等事件来实现。
// 添加EventTrigger事件的公共方法
public static void AddEntry(GameObject target, EventTriggerType eventTriggerType, UnityAction<BaseEventData> callback)
{
if (target == null)
{
return;
}
EventTrigger eventTrigger = target.GetComponent<EventTrigger>();
if (eventTrigger == null)
{
eventTrigger = target.AddComponent<EventTrigger>();
}
if (eventTrigger.triggers == null)
{
eventTrigger.triggers = new List<EventTrigger.Entry>();
}
EventTrigger.Entry entry = new EventTrigger.Entry();
entry.eventID = eventTriggerType;
entry.callback.AddListener(callback);
eventTrigger.triggers.Add(entry);
}
坐标映射
pointerEventData.position返回的是屏幕坐标。
camera.ScreenToWorldPoint(Vector3)返回的是屏幕坐标映射到相机对应Canvas的世界坐标。
transform.InverseTransformPoint(Vector3)返回的是世界坐标对应该transform的本地坐标。
Vector3 worldPos = Camera.main.ScreenToWorldPoint(pointerEventData.position);
Vector3 localPos = texture.gameObject.transform.InverseTransformPoint(worldPos);
修改像素
修改像素有很多种方式。
可以使用RenderTexture或Texture2D来修改alpha值,也可以使用shader来修改alpha值。
这里我用的是Texture2D。
主要就是用texture.SetPixel(int, int, Color)和texture.GetPixel(int, int)。
对于每个响应点,肯定不可能只改这一个像素的alpha值,要实现刮的效果就需要对相应点周围的像素都做同样的处理,也就是所谓的“刮痕”。刮痕可以是各种形状,可以是纯计算,可以是读取特定图片的像素。
这里考虑到性能和计算难度,我最终选择了最简单的圆形来形成刮痕。
简而言之,就是查找响应点周围一个圆内的所有像素,将它们的alpha全部设置为0。
优化
效果优化
当光标快速移动时,拖拽事件获取到的屏幕坐标很有可能会相差很远。这个时候如果我们只对这两个点周围圆形区域进行透明度剔除,那么从表现效果上来看,就像是抠出来两块,而不是刮出来的。效果很差。
因此,我们需要在两个之间的路径上补充更多的点,去除这一条路径上所有像素的alpha。
性能优化
由于每帧下都会对大量像素进行处理,所以说如何保证刮刮乐不卡是一个需要注意的问题。
1、减少Lua和C#交互
千万要注意不要在循环中有过多Lua和C#的交互,这里很有可能成为性能杀手。
举个例子:
for i = 1, 500 do
Dosometing(self.texture.width)
end
这里看似就只是获得table中的一个number,不会很费。但实际上,这里的texture.width存储的可不是一个number型的数据,而是一个C#的委托,最终会从Lua这边调到C#那边Texture2D的width属性的get()方法。整个流程乘上循环次数会导致开销非常大。
(具体xlua原理请自行学习,或者等我哪天有时间总结一下,发一份xlua的学习笔记)
如下改进后,运行速度绝对不只快亿点点:
local textureWidth = self.texture.width
for i = 1, 500 do
Dosometing(textureWidth)
end
当然,还有很多地方有类似的问题,这里不细说,具体在实践中就知道了。如果不考虑热更新,全部写在C#会更好。
2、减少临时变量
避免在循环中创建大量临时变量,尽可能复用变量,减少内存空间开辟消耗。
3、缓存数据
减少在循环中进行数据计算,能提前计算好的数据一定提前计算好。
如果使用Texture,可以提前缓存一个跟Texture大小一样的二维table,里面存储每个像素的颜色信息,之后所有计算全部用该缓存来做。当最后需要刷新显示的时候,才进行Lua和C#的交互。这样也可以提升不少计算速度。
4、注意
还有一点需要注意,很多情况下时间优化是需要空间代价来支持的。如何平衡时间和空间就需要自行斟酌。
后记
虽然刮刮卡这个功能并不难实现,但是在实现的过程中遇到的各种问题还是很有价值的。再者,在解决各种问题的时候还可以检验自身知识的掌握程度,将理论知识和实践串联起来,对提升很有帮助。