signed

QiShunwang

“诚信为本、客户至上”

Unity刮刮乐效果实现及优化

2021/4/26 22:03:25   来源:

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、注意

还有一点需要注意,很多情况下时间优化是需要空间代价来支持的。如何平衡时间和空间就需要自行斟酌。


后记

虽然刮刮卡这个功能并不难实现,但是在实现的过程中遇到的各种问题还是很有价值的。再者,在解决各种问题的时候还可以检验自身知识的掌握程度,将理论知识和实践串联起来,对提升很有帮助。