先上实际效果


如上图所示,效果很直观,即原始的Sprite图像的破碎组件触发后,会将原图碎裂成无数小块,且使之炸裂。那么,要实现这个功能就有如下几点需求:

  • 对于任意大小形状的Sprite,仅通过一个方法就能使其破碎;
  • 尽可能的保证碎片的形状大小具有随机性,但是碎片不能太大,否则不美观;
  • 触发完之后要让碎片炸开来;
  • 考虑到复用性,要封装成一个组件,以便与工程解耦。

一、Sprite随机分割点生成

我们依然拿原图举例子。随机裁切的本质,即是在Sprite的矩形框内,随机找分割点,并对分割的轴做垂线,重新生成四个新的矩形区域,如下图所示:

只不过,用完全随机的方式来生成多个切割点的时候,如果两个点的坐标比较相近,那么实际效果就会很不美观(有些地方特别大,有些地方特别小),如下图:

所以,我们的目的是要分割点均匀的随机(没错,就是有前提的随机);
举个例子:如果我们希望生成9张碎裂后的子图片,则需要有2个分割点来分割图像,即N个分割点,生成(N+1)(N+1)张子图,可以采取如下图的区域划分方法。

  1. 先将区域划分为5X5的区域(即(2N+1)*(2N+1));
  2. 设左上方的子区域为S00,则于主对角线上,所有i和j坐标均为奇数的区域Sij内生成随机数(黄点所在区域);
  3. 对随机数生成的坐标进行升序排序;
  4. 从左上角的点依次分割至左下角。

    算法如上所述那样,通过区域划分法,在对应子区域内生成分割点,即可以保证随机性,又不会使分割效果太丑。那么,生成了随机分割点之后,我们应该如果去分割Sprite呢?

二、Sprite的裁切

U3D的Sprite类内并没有直接提供裁切的API,但是Sprite.Create()方法却可以以Sprite对象的一部分来生成一个新的Sprite,它的API是这样的:

Sprite.Create(Texture texture, Rect rect, Vector2 vector);

第二个参数就是新Sprite对应原始Sprite的矩形区域。

三、碎片弹射

碎片弹射效果,我们采用的是给碎片加力的方式来实现的。这样一来,就要求碎片必须是一个刚体,于是,在碎片对象生成的部分,我们使用如下代码来执行。

 /// <summary>/// 弹射一个碎片对象。/// </summary>/// <param name="fragment">碎片对象。</param>private void Ejection(GameObject fragment){Vector2 start = fragment.transform.position;Vector2 end = gameObject.transform.position;Vector2 direction = end - start;fragment.GetComponent<Rigidbody2D>().AddForce(direction * forceMultiply, ForceMode2D.Impulse);}/// <summary>/// 创造一个碎片对象。/// </summary>/// <param name="sprite">碎片贴图。</param>/// <param name="position">碎片贴图位置。</param>/// <returns>碎片对象。</returns>private GameObject CreateFragment(Sprite sprite, Vector2 position){GameObject fragment = new GameObject("Fragment");fragment.layer = LayerMask.NameToLayer(layerName);fragment.transform.position = position;fragment.AddComponent<SpriteRenderer>().sprite = sprite;// 可以将碎片视作刚体,这样会有与地形的碰撞效果fragment.AddComponent<Rigidbody2D>();fragment.AddComponent<BoxCollider2D>();fragment.AddComponent<FadeOut>().delaySecond = delaySecond;     // 添加淡出效果return fragment;}

在上述代码中,我们默认了碎片是刚体,我们还添加了一个碰撞盒,以便它在与地形碰撞时有真实的物理效果,而不是穿模。但是,能碰撞的刚体碎片存在一个问题,就是它也会与玩家/敌人对象碰撞,可能会因为XX碎了一地而把角色卡在墙角,所以,我们传入一个LayerName参数,去标定碎片生成的Layer,并在Project Setting–>Physics2D中修改碰撞列表,使它只与特地Layer的物体碰撞。这种方式会让Unity直接过滤两个Layer之间的碰撞(不会被OnTrigger/OnCollision检测到),效率较高。

四、碎片回收

碎片本质上是一种垃圾对象,它在效果执行完毕之后便应当被立即回收掉,我们可以单独构造一个FadeOut组件类,让它持续一段时间后能够自动消失。 (注:此组件会逐渐使物体的alpha值减小,减为0时Destroy掉物体。如果不需要改变alpha值而直接Destroy的话,就使用协程来做) FadeOut类的代码如下:

using System.Collections;
using UnityEngine;/// <summary>
/// 淡出效果组件类。
/// </summary>
public class FadeOut : MonoBehaviour
{#region 可视变量[HideInInspector] [Tooltip("消失时延。")] public float delaySecond = 5F;#endregion#region 成员变量private SpriteRenderer spriteRenderer = null;private float fadeSpeed = 0;    // 消逝速度#endregion#region 功能方法/// <summary>/// 第一帧调用之前触发。/// </summary>private void Start(){if (TryGetComponent(out SpriteRenderer spriteRenderer))this.spriteRenderer = spriteRenderer;fadeSpeed = this.spriteRenderer.color.a * Time.fixedDeltaTime / delaySecond;//StartCoroutine(DestroyNow());}/*/// <summary>/// 定时自杀。/// </summary>/// <returns></returns>private IEnumerator DestroyNow(){yield return new WaitForSeconds(delaySecond);Destroy(gameObject);}*//// <summary>/// 降低对象透明度,为0后摧毁对象。/// 在固定物理帧刷新时触发。/// </summary>private void FixedUpdate(){float alpha = spriteRenderer.color.a - fadeSpeed;spriteRenderer.color = new Color(spriteRenderer.color.r, spriteRenderer.color.r, spriteRenderer.color.r, alpha);if (alpha <= 0)Destroy(gameObject);}#endregion
}

完整代码

  1. 排序算法Sort类:
using System;/// <summary>
/// 排序算法类。
/// </summary>
public class Sort<T> where T : IComparable
{#region 基础公有方法/// <summary>/// 数组快速排序。/// </summary>/// <param name="array">待排序数组。</param>/// <param name="low">排序起点。</param>/// <param name="high">排序终点。</param>public void QuickSort(T[] array, int low, int high){if (low >= high)return;int first = low;int last = high;T key = array[low];while (first < last){while (first < last && CompareGeneric(array[last], key) >= 0)last--;array[first] = array[last];while (first < last && CompareGeneric(array[first], key) <= 0)first++;array[last] = array[first];}array[first] = key;QuickSort(array, low, first - 1);QuickSort(array, first + 1, high);}#endregion#region 静态私有方法/// <summary>/// 泛型对象比较大小。/// </summary>/// <param name="t1">待比较对象。</param>/// <param name="t2">待比较对象。</param>/// <returns>大于0则前者的值更大,小于0则反之,等于0则二者的值相等。</returns>private static int CompareGeneric(T t1, T t2){if (t1.CompareTo(t2) > 0)return 1;else if (t1.CompareTo(t2) == 0)return 0;elsereturn -1;}#endregion
}
  1. 核心代码Crasher组件类:
using System.Collections.Generic;
using UnityEngine;/// <summary>
/// 物体分裂效果组件类。
/// </summary>
public class Crasher : MonoBehaviour
{#region 可视变量[SerializeField][Tooltip("Sprite对象。")]private Sprite sprite = null;[SerializeField][Tooltip("碎片的层次名称,用于避碰。")]private string layerName = "Fragment";[SerializeField] [Tooltip("分割点的数量。")]private int splitPoint = 3;[SerializeField] [Tooltip("爆破力乘数。")]private float forceMultiply = 50F;[SerializeField] [Tooltip("碎片消失时延。")]private float delaySecond = 5F;#endregion#region 成员变量private int seed = 0;               // 随机数种子private float spriteWidth = 0;      // 贴图实际宽度private float spriteHeight = 0;     // 贴图实际高度private List<GameObject> fragments = new List<GameObject>();    // 碎片对象列表#endregion#region 功能方法/// <summary>/// 对对象执行粉碎特效。/// </summary>public void Crash(){// 属性初始化spriteWidth = sprite.texture.width;spriteHeight = sprite.texture.height;// 获取所有碎片对象GetFragments(sprite.texture, RandomSplits());// 弹射碎片对象for (int i = 0; i < fragments.Count; i++)Ejection(fragments[i]);}/// <summary>/// 根据割点获取所有碎片对象。/// </summary>/// <param name="texture2D">原始对象的纹理。</param>/// <param name="splits">割点列表。</param>private void GetFragments(Texture2D texture2D, Vector2[] splits){// 分别获取x,y两个数组float[] splitXs = new float[splits.Length + 2];float[] splitYs = new float[splits.Length + 2];splitXs[0] = 0;splitXs[splitXs.Length - 1] = spriteWidth;splitYs[0] = 0;splitYs[splitYs.Length - 1] = spriteHeight;for (int i = 0; i < splits.Length; i++){splitXs[i + 1] = splits[i].x;splitYs[i + 1] = spriteHeight - splits[i].y;    // y轴坐标系倒转}// 对数组进行升序排序Sort<float> sort = new Sort<float>();sort.QuickSort(splitXs, 0, splits.Length);sort.QuickSort(splitYs, 0, splits.Length);// 分割物体for (int i = 0; i < splitXs.Length - 1; i++){for (int j = 0; j < splitYs.Length - 1; j++){float x1 = splitXs[i];float y1 = splitYs[j];float x2 = splitXs[i + 1];float y2 = splitYs[j + 1];float centerX = gameObject.transform.position.x - gameObject.transform.localScale.x / 2 + (x1 + x2) / (2 * spriteWidth);float centerY = gameObject.transform.position.y - gameObject.transform.localScale.y / 2 + (y1 + y2) / (2 * spriteHeight);Rect rect = new Rect(x1, y1, x2 - x1, y2 - y1);Sprite sprite = Sprite.Create(texture2D, rect, Vector2.zero);Vector2 position = new Vector2(centerX, centerY);fragments.Add(CreateFragment(sprite, position));}}}/// <summary>/// 在spriteRenderer区域内获取随机分割点。/// </summary>/// <returns>分割点数组。</returns>private Vector2[] RandomSplits(){System.Random random;Vector2[] splits = new Vector2[splitPoint];// 为了避免割点聚集,先分割区域,再于对应区域随机取点float spanX = spriteWidth / (2 * splitPoint + 1);float spanY = spriteHeight / (2 * splitPoint + 1);for (int i = 0; i < splitPoint; i++){random = new System.Random(unchecked((int)System.DateTime.Now.Ticks) + seed);seed++;double x = random.NextDouble() * spanX + 2 * (i + 1) * spanX;random = new System.Random(unchecked((int)System.DateTime.Now.Ticks) + seed);seed++;double y = random.NextDouble() * spanY + 2 * (i + 1) * spanY;splits[i] = new Vector2((float)x, (float)y);}return splits;}/// <summary>/// 弹射一个碎片对象。/// </summary>/// <param name="fragment">碎片对象。</param>private void Ejection(GameObject fragment){Vector2 start = fragment.transform.position;Vector2 end = gameObject.transform.position;Vector2 direction = end - start;fragment.GetComponent<Rigidbody2D>().AddForce(direction * forceMultiply, ForceMode2D.Impulse);}/// <summary>/// 创造一个碎片对象。/// </summary>/// <param name="sprite">碎片贴图。</param>/// <param name="position">碎片贴图位置。</param>/// <returns>碎片对象。</returns>private GameObject CreateFragment(Sprite sprite, Vector2 position){GameObject fragment = new GameObject("Fragment");fragment.layer = LayerMask.NameToLayer(layerName);fragment.transform.position = position;fragment.AddComponent<SpriteRenderer>().sprite = sprite;// 可以将碎片视作刚体,这样会有与地形的碰撞效果fragment.AddComponent<Rigidbody2D>();fragment.AddComponent<BoxCollider2D>();fragment.AddComponent<FadeOut>().delaySecond = delaySecond;     // 添加淡出效果return fragment;}#endregion
}
  1. FadeOut类的代码已在上一节给出;
  2. 测试类ClickImage:
using UnityEngine;public class ClickImage : MonoBehaviour
{public GameObject sprite = null;private void Update(){if (Input.GetMouseButtonUp(0)){sprite.GetComponent<Crasher>().Crash();sprite.SetActive(false);}}
}

附加内容

  • 为了单独封装,本项目内的碎片对象没有用对象池来回收,实际上,反复生成的垃圾对象,用对象池的效率比较高;
  • 不规则的Sprite也是可以破碎的,如下图所示:

Unity2D游戏开发——Sprite碎裂特效的实现(独立解耦的组件,附详细流程和代码)相关推荐

  1. unity2d游戏开发系列教程:四、一个2D游戏所需要的主要功能(游戏框架)

    目录 unity2d游戏开发系列教程:一.环境安装 unity2d游戏开发系列教程:二.新建工程并熟悉Unity编辑器常用功能 unity2d游戏开发系列教程:三.场景布置,增加怪物和机关 原文下载 ...

  2. unity2d游戏开发系列教程:二、新建工程并熟悉Unity编辑器常用功能

    目录 unity2d游戏开发系列教程:一.环境安装 第一步.打开项目 耐心等待一小会 工程界面 第二步.创建第一个场景(第一关)进行试玩 点击图中标号1的运行按钮,即可简单试玩感受,操作如下 移动A, ...

  3. 【Unity2D游戏开发入门第一卷】✨Unity入门总结Sunnyland示例(上卷)

    部分功能例如目录跳转,回到顶部功能在这里有问题 追求阅读体验可以转到 ✨本人主战场!✨ ✨✨目录 一.入门卷 二.杂项卷 三.最后 一.入门卷 回到顶部 前言 准备资源 Tilemap 地图布置,刚体 ...

  4. 视频教程-丑小鸭历险记——趣味玩转unity2d游戏开发(下)-Unity3D

    丑小鸭历险记--趣味玩转unity2d游戏开发(下) 从业8年以上,学过一点知识,写过一点代码,擅长计算机图形学,擅长unity3d,擅长将抽象的东西讲明白,写看得懂的代码,讲听得懂的课程,不闲聊,不 ...

  5. Unity2D游戏开发和C#编程大师班

    本课程采用现代游戏开发的最新内容和最新技术(Unity 2D 2022) 学习任何东西的最好方法是以一种真正有趣的方式去做,这就是这门课程的来源.如果你想了解你看到的这些不可思议的游戏是如何制作的,没 ...

  6. Unity2D游戏开发基础教程1.2项目、资源和场景

    Unity2D游戏开发基础教程1.2项目.资源和场景 如果使用Unity制作游戏,就一定会接触到项目(Project.资源(Asset)和场景(Scene).本节将依次介绍它们. 1.2.1  项目 ...

  7. Unity2D游戏开发基础教程1.2 项目、资源和场景

    Unity2D游戏开发基础教程1.2 项目.资源和场景 如果使用Unity制作游戏,就一定会接触到项目(Project.资源(Asset)和场景(Scene).本节将依次介绍它们. 1.2.1  项目 ...

  8. unity2d游戏开发系列教程:一、环境安装

    从这篇文章开始,一步一步教大家从0开始通过2DGameKit项目进行2D游戏开发 第一步.环境安装 1.先使用手机下载Unity Connect并注册登陆 2.进入unity官网https://uni ...

  9. unity2d游戏开发系列教程:三、场景布置,增加怪物和机关

    目录 unity2d游戏开发系列教程:一.环境安装 unity2d游戏开发系列教程:二.新建工程并熟悉Unity编辑器常用功能 第一节.场景草地布置 先查看一下资源文件里都有什么,一会就要用到的 打开 ...

最新文章

  1. SAP UI5 CreateBindingContext 方法的实现逻辑
  2. 送给程序员:IT大神们的编程名言
  3. 不属于python标准库的是_python标准库和扩展库
  4. 武汉理工大学java,武汉理工大学 web技术基础
  5. 怎样改变计算机桌面的特效主题,电脑桌面主题、图片怎么设置的技巧大全
  6. 无心剑随感《最完美的图形——圆》
  7. mysql数据库死锁的产生原因及解决办法
  8. linux下shell程序(一)
  9. 干货----003----乱码解决方法
  10. 计算机学数字电子基础知识,什么是数字电路?新手如何快速学习数字电路基础?...
  11. WeChat for Linux
  12. cass等距离等分线段的命令键_CAD定距等分与定数等分使用技巧 - CAD自学网
  13. matlab 直流-直流变换器毕业论文,基于MATLAB直流-直流变换器的研究毕业论文.docx-资源下载在线文库www.lddoc.cn...
  14. Java练习10:输入两个正整数m和n,求其最大公约数和最小公倍数
  15. Mybatis| Bug合集
  16. ShowDown.js MD 转HTML 时的问题
  17. python中的关系运算符可以连续室友_在Python中,关系运算符可以连续使用,例如135等价于13 and 35。...
  18. 四十岁想跳槽,年龄是最大的障碍吗?
  19. android开发中中按钮 变成红边白底,PS人像换红底为白底等的处理
  20. keras val_categorical_accuracy: 0.0000e+00问题

热门文章

  1. 计算机1级word试题,计算机一级MSOffice强化试题及答案
  2. 推测30以内具体数字
  3. php播放wmv代码,asp 网页视频播放器程序代码(通用代码),支持avi,wmv,asf,mov,rm,ra,ram等...
  4. 1.Unity协程、进程、线程的区别
  5. DOS中的dir命令的参数
  6. mount_apfs: volume could not be mounted: Operation not permitted,mount: / failed with 77
  7. 文件夹监听FileListener
  8. 在外远程查看家里内网监控
  9. 木马下载器盗号数十种 破天木马剑指网游
  10. python采集高德地图上商家信息代码(亲测OK)