图片破碎 算法(附Unity项目)C#代码

 效果图:

 Demo下载(Unity 2018.4.8f1)
百度网盘:https://pan.baidu.com/s/17QxwnHUuC0ZXPOs2CS8-bg 密码:1234

注意需要把sprite的属性勾选可读,算法才能运行:

基本思路和原理:

首先获取鼠标点击的位置在图像中的对应位置(行数和列数),然后从这个点开始向外辐射线条,线条的角度是随机数摇出来的,为了避免有两条线条太近,可以设置阈值角度差值判断是否重新摇, 在这里,为了让破碎的效果更加逼真,可以在使线条从中心辐射出来的时候并不是直线,而是直线段,比如辐射每隔100像素就在原本的角度上加一个随机小角度,使得最后辐射的轨迹是一个弯弯曲曲的轨迹。数据结构的实现上,另开一个bool数组,用来存储之前辐射出的那些线条上的每个像素点的坐标,有点则为true,做好这一步之后就可以像扫雷那样,用栈存储点集,上下左右遍历扩展每个分裂块,遍历到true像素即为边缘,这样就可以得出每块碎片的像素坐标集了,然后求出点集的上下左右边界,开辟出对应大小的新图像,并根据位置获取原图的颜色数据,映射到新图像中,到此,完成了碎裂图片的获取,之后将其初始化,并显示在世界坐标空间中的对应位置,加上各种组件即可。

项目一共只有一个脚本,把这个脚本直接挂在一个有sprite的脚本上就能运行啦:

(注意:物体还需要挂上Polygon Collider 2D组件,不然无法识别点击)

//注意,图片尺寸过大可能会运行卡顿或者直接数组越界
using System.Collections.Generic;
using UnityEngine;
public struct Pos {
	public int i;
	public int j;
	public Pos(int i, int j) : this() {
		this.i = i;
		this.j = j;
	}

	public Pos(float i, float j) : this() {
		this.i = Mathf.RoundToInt(i);
		this.j = Mathf.RoundToInt(j);
	}
}
[RequireComponent(typeof(PolygonCollider2D))]
public class Split : MonoBehaviour {

	[Range(1, 20)]
	public int radiateNum = 5;		//裂痕的数量
	[Range(0,45)]
	public int changeAngle = 15;	//随机裂痕的弯曲程度

	private Color[] color;
	private int w, h;

	private void Start() {
		Texture2D texture = GetComponent<SpriteRenderer>().sprite.texture;
		if (!texture.isReadable) {
			Debug.LogError("图片不可读!请勾选图片属性中的Read/Write Enabled选项.");
			return;
		}
		color = texture.GetPixels();
		w = texture.width;
		h = texture.height;
	}

	private void OnMouseDown() {
		Vector3 clickPos = new Vector3(w, h) * 0.5f;
		Vector3 pos1 = (Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position) * 100;
		pos1.x /= transform.lossyScale.x;
		pos1.y /= transform.lossyScale.y;
		Vector3 pos2 = new Vector3();
		float angle = -transform.eulerAngles.z * Mathf.Deg2Rad;
		pos2.x = pos1.x * Mathf.Cos(angle) - pos1.y * Mathf.Sin(angle);
		pos2.y = pos1.x * Mathf.Sin(angle) + pos1.y * Mathf.Cos(angle);
		clickPos += pos2;
		CreateSprites((int)clickPos.x, (int)clickPos.y);
	}

	//创建分裂图
	private void CreateSprites(int center_j, int center_i) {
		//随机分裂角度
		float[] angle = new float[radiateNum];
		int num = 0;
		bool retry; //随机角度接近时要重新生成
		float delt;
		while (num != radiateNum) {
			angle[num] = Random.Range(0, 2 * Mathf.PI);
			retry = false;
			for (int i = 0; i < num; i++) {
				delt = Mathf.Abs(angle[i] - angle[num]);
				if (delt < Mathf.PI / radiateNum / 2 || delt > 2 * Mathf.PI - Mathf.PI / radiateNum / 2) {
					retry = true;
					break;
				}
			}
			if (!retry) {
				num++;
			}
		}

		//绘制随机裂痕
		bool[] line = new bool[w * h];
		line[center_i * w + center_j] = true;
		int pixelL = 20;        //分段长度
		for (int i = 0; i < radiateNum; i++) {
			Vector2 curVec = new Vector2(center_i, center_j);
			Pos curPos;
			Vector2 step = new Vector2(Mathf.Cos(angle[i]), Mathf.Sin(angle[i])) * 0.98f;
			int stepNum = 1;
			while (true) {
				if (stepNum % pixelL == 0) {
					angle[i] += Random.Range(-changeAngle * Mathf.Deg2Rad, changeAngle * Mathf.Deg2Rad);
					step = new Vector2(Mathf.Cos(angle[i]), Mathf.Sin(angle[i])) * 0.98f;
				}
				curVec += step;
				curPos = new Pos(curVec.x, curVec.y);
				if (curPos.i >= 0 && curPos.j >= 0 && curPos.i < h && curPos.j < w) {
					line[curPos.i * w + curPos.j] = true;
				}
				else {
					break;
				}
				stepNum++;
			}
		}

		//向四周扩展从而获取每块图像
		GameObject g1 = new GameObject("sprites");
		int splitNum = 0;
		for (int n = 0; n < w * h; n++) {
			if (!line[n]) {
				splitNum++;
				List<Pos> list = new List<Pos>();
				List<Pos> list1 = new List<Pos>();
				list.Add(new Pos(n / w, n % w));
				list1.Add(new Pos(n / w, n % w));
				line[n] = true;
				int left, right, down, up;
				left = right = n % w;
				down = up = n / w;
				while (list.Count != 0) {
					Pos p = list[0];
					list.RemoveAt(0);
					if (p.i > 0 && !line[(p.i - 1) * w + p.j]) {
						list.Add(new Pos(p.i - 1, p.j));
						list1.Add(new Pos(p.i - 1, p.j));
						line[(p.i - 1) * w + p.j] = true;
						if (p.i - 1 < down) {
							down = p.i - 1;
						}
					}
					if (p.j > 0 && !line[p.i * w + p.j - 1]) {
						list.Add(new Pos(p.i, p.j - 1));
						list1.Add(new Pos(p.i, p.j - 1));
						line[p.i * w + p.j - 1] = true;
						if (p.j - 1 < left) {
							left = p.j - 1;
						}
					}
					if (p.i < h - 1 && !line[(p.i + 1) * w + p.j]) {
						list.Add(new Pos(p.i + 1, p.j));
						list1.Add(new Pos(p.i + 1, p.j));
						line[(p.i + 1) * w + p.j] = true;
						if (p.i + 1 > up) {
							up = p.i + 1;
						}
					}
					if (p.j < w - 1 && !line[p.i * w + p.j + 1]) {
						list.Add(new Pos(p.i, p.j + 1));
						list1.Add(new Pos(p.i, p.j + 1));
						line[p.i * w + p.j + 1] = true;
						if (p.j + 1 > right) {
							right = p.j + 1;
						}
					}
				}

				//创建sprite
				PhysicsMaterial2D m;
				m = new PhysicsMaterial2D("m");
				m.bounciness = 0.3f;    //物体边缘的弹性和摩擦系数
				m.friction = 0.6f;
				int w1 = right - left + 1;
				int h1 = up - down + 1;
				int pos_x = (left + right - w) / 2;
				int pos_y = (down + up - h) / 2;
				Vector3 pos1 = new Vector3(pos_x, pos_y, 0) / 100;
				Vector3 pos2 = new Vector3();
				float angle1 = transform.eulerAngles.z * Mathf.Deg2Rad;
				pos1.x *= transform.lossyScale.x;
				pos1.y *= transform.lossyScale.y;
				pos2.x = pos1.x * Mathf.Cos(angle1) - pos1.y * Mathf.Sin(angle1);
				pos2.y = pos1.x * Mathf.Sin(angle1) + pos1.y * Mathf.Cos(angle1);
				if (list1.Count > 20) { //大于20像素才会创建
					Texture2D t1 = new Texture2D(w1, h1);
					Color[] c1 = new Color[w1 * h1];
					foreach (Pos p in list1) {
						c1[(p.i - down) * w1 + p.j - left] = color[p.i * w + p.j];
					}
					t1.SetPixels(c1);
					t1.Apply();
					GameObject g = new GameObject("sprite");
					g.transform.SetParent(g1.transform);
					g.transform.position = transform.position + pos2;
					g.transform.eulerAngles = new Vector3(0, 0, angle1 * Mathf.Rad2Deg);
					g.transform.localScale = transform.lossyScale;
					g.AddComponent<SpriteRenderer>().sprite = Sprite.Create(t1, new Rect(0, 0, w1, h1), new Vector2(0.5f, 0.5f));
					g.AddComponent<PolygonCollider2D>().sharedMaterial = m;
					g.AddComponent<Rigidbody2D>();
					float mass;
					if (GetComponent<Rigidbody2D>()) {
						g.GetComponent<Rigidbody2D>().velocity = GetComponent<Rigidbody2D>().velocity;
						mass = list1.Count / (float)(w * h) * GetComponent<Rigidbody2D>().mass*2;
					}
					else {
						mass = list1.Count / (float)(w * h) * 20;    //重新分配每块的质量
					}
					g.GetComponent<Rigidbody2D>().mass = mass;
					g.AddComponent<Split>().radiateNum = radiateNum;
				}
			}
		}
		//销毁原物体
		Destroy(gameObject);
	}

}

脚本开放两个参数,一个是每次点击后碎裂的个数,另一个是碎裂的裂痕的不规则程度(角度),其实别的还有很多参数可以调整,做出不同效果,这个可以自己研究上面的脚本进行修改,就不再赘述啦。
 

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值