UI update 02

This commit is contained in:
FloatGaming
2026-06-30 21:21:30 +08:00
parent b2a1e307a4
commit f62f643cfc
1666 changed files with 211081 additions and 22799 deletions
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2af252442079b524e97d60543408a92f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,30 @@
using System;
using UnityEngine;
namespace KD.Destro2D{
[Serializable]
public class CircleDestruction : Destruction
{
public float radius = 0.3f;
EllipseDestruction e;
public override void Setup(GameObject gobj)
{
e.Setup(gobj);
}
public override void SetupWithColor(GameObject gobj, Color color)
{
Setup(gobj);
burnColor = color;
}
public override void ApplyDestruction(int cx, int cy, bool[,] mask, float stepX, float stepY,int maskW,int maskH,Color32[]burnage, float burntime)
{
e.rx = radius;
e.ry = radius;
e.ApplyDestruction(cx,cy,mask,stepX,stepY,maskW,maskH,burnage,burntime);
}
public CircleDestruction()
{
e = new EllipseDestruction();
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: c38b0a6170f7b7b419c3c2c0ad2acf89
AssetOrigin:
serializedVersion: 1
productId: 358408
packageName: Destro 2D - Dynamic Sprite Destruction
packageVersion: 1.0.3
assetPath: Assets/Destro2DMain/Core/Destruction Types/CircleDestruction.cs
uploadId: 925032
@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace KD.Destro2D{
[Serializable]
public class EllipseDestruction : Destruction
{
public float rx = 0.3f;
public float ry = 0.3f;
Sprite sprite;
float normalX;
float normalY;
List<int> destroyed = new();
public override void Setup(GameObject gobj)
{
SpriteRenderer sprend = gobj.GetComponent<SpriteRenderer>();
sprite = sprend.sprite;
normalX=gobj.transform.localScale.x;
normalY=gobj.transform.localScale.y;
}
public override void SetupWithColor(GameObject gobj, Color color)
{
Setup(gobj);
burnColor = color;
}
public override void ApplyDestruction(int cx, int cy, bool[,] mask, float stepX, float stepY,int maskW,int maskH,Color32[] burnage, float burntime)
{
destroyed.Clear();
float rpx = rx * sprite.pixelsPerUnit/normalX;
float rpy = ry * sprite.pixelsPerUnit/normalY;
int cmx = Mathf.FloorToInt(cx/stepX);
int cmy = Mathf.FloorToInt(cy/stepY);
int rmx = (int)(rpx/stepX);
int rmy = (int)(rpy/stepY);
for (int i = cmx - rmx; i <= cmx + rmx; i++){
if(i >= maskW || i < 0) continue;
float dx = i - cmx;
float nx = dx/rmx;
for (int j = cmy - rmy; j <= cmy + rmy; j++)
{
if (j < 0 ||j >= maskH) continue;
float dy = j - cmy;
float ny = dy/rmy;
if (nx * nx + ny * ny <= 1f){
mask[i, j] = false;
destroyed.Add(j * maskW + i);
}
}
}
BurnEdge(destroyed,maskW,maskH,mask,burnage,burntime);
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 85cc06dd67d78f847b096890f2223790
AssetOrigin:
serializedVersion: 1
productId: 358408
packageName: Destro 2D - Dynamic Sprite Destruction
packageVersion: 1.0.3
assetPath: Assets/Destro2DMain/Core/Destruction Types/EllipseDestruction.cs
uploadId: 925032
@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace KD.Destro2D{
[Serializable]
public class RectDestruction : Destruction
{
public float l = 0.3f;
public float b = 0.3f;
Sprite sprite;
List<int> destroyed = new();
float normalX;
float normalY;
public override void Setup(GameObject gobj)
{
SpriteRenderer sprend = gobj.GetComponent<SpriteRenderer>();
sprite = sprend.sprite;
normalX=gobj.transform.localScale.x;
normalY=gobj.transform.localScale.y;
}
public override void SetupWithColor(GameObject gobj, Color color)
{
Setup(gobj);
burnColor = color;
}
public override void ApplyDestruction(int cx, int cy, bool[,] mask, float stepX, float stepY,int maskW,int maskH,Color32[] burnage, float burntime)
{
destroyed.Clear();
float lp = l * sprite.pixelsPerUnit/normalX;
float bp = b * sprite.pixelsPerUnit/normalY;
int cmx = Mathf.FloorToInt(cx/stepX);
int cmy = Mathf.FloorToInt(cy/stepY);
int lm = (int)(lp/stepX);
int bm = (int)(bp/stepY);
for (int i = cmx - lm; i <= cmx + lm; i++){
if(i >= maskW || i < 0) continue;
for (int j = cmy - bm; j <= cmy + bm; j++)
{
if (j < 0 ||j >= maskH) continue;
mask[i, j] = false;
destroyed.Add(j * maskW + i);
}
}
BurnEdge(destroyed,maskW,maskH,mask,burnage,burntime);
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 59ed6c62a15ec3141998da30fc21c102
AssetOrigin:
serializedVersion: 1
productId: 358408
packageName: Destro 2D - Dynamic Sprite Destruction
packageVersion: 1.0.3
assetPath: Assets/Destro2DMain/Core/Destruction Types/RectDestruction.cs
uploadId: 925032
@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace KD.Destro2D{
[Serializable]
public class StampDestruction : Destruction
{
public Texture2D stampTexture;
public float scaleFactor;
public float rotationAngle;
private int stampWidth;
private int stampHeight;
private Color[] stampPixels;
float normalX;
float normalY;
List<int> destroyed = new();
public override void Setup(GameObject gobj)
{
stampWidth = stampTexture.width;
stampHeight = stampTexture.height;
stampPixels = stampTexture.GetPixels();
normalX = gobj.transform.localScale.x;
normalY = gobj.transform.localScale.y;
}
public override void SetupWithColor(GameObject gobj, Color color)
{
Setup(gobj);
burnColor = color;
}
public override void ApplyDestruction(int cx, int cy, bool [,] mask, float stepX, float stepY,int maskW,int maskH,Color32[] burnage, float burntime)
{
destroyed.Clear();
float rotRad = rotationAngle * Mathf.Deg2Rad;
int cmx = Mathf.FloorToInt(cx / stepX);
int cmy = Mathf.FloorToInt(cy / stepY);
int sw = Mathf.FloorToInt(stampWidth /(scaleFactor*normalX * stepX));
int sh = Mathf.FloorToInt(stampHeight /(scaleFactor*normalY * stepY));
float c = Mathf.Cos(rotRad);
float s = Mathf.Sin(rotRad);
for(int i = 0; i< sw; i++)
{
int x = cmx + i - sw/2; //offsetting to centre (-sw/2)
if(x < 0 || x >= maskW) continue;
for(int j = 0; j < sh; j++)
{
int y = cmy + j - sh/2;
if(y < 0 || y>=maskH) continue;
float dx = i - 0.5f * sw;//make x and y centred(-ve and +ve around midpoint)
float dy = j - 0.5f * sh;
float ux = dx * stepX * scaleFactor * normalX;
float uy = dy * stepY * scaleFactor * normalY;
float rx = ux * c - uy * s;
float ry = ux * s + uy * c;
int px = Mathf.FloorToInt(rx + stampWidth * 0.5f);
int py = Mathf.FloorToInt(ry + stampHeight * 0.5f);
if(px < 0|| py < 0 || px >= stampWidth|| py >= stampHeight) continue;
int indx = py * stampWidth + px;
if(stampPixels[indx].a > 0.1f)
{
mask[x,y] = false;
destroyed.Add(y * maskW + x);
}
}
}
BurnEdge(destroyed,maskW,maskH,mask,burnage,burntime);
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 623659f1a080fc54a97298c5b8e07174
AssetOrigin:
serializedVersion: 1
productId: 358408
packageName: Destro 2D - Dynamic Sprite Destruction
packageVersion: 1.0.3
assetPath: Assets/Destro2DMain/Core/Destruction Types/StampDestruction.cs
uploadId: 925032
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: caeb7972101235344a85482afc9d5ae1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,361 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace KD.Destro2D{
[RequireComponent(typeof(PolygonCollider2D))]
[RequireComponent(typeof(PixelHandler))]
public class CollisionHandler : MonoBehaviour
{
public int maskWidth = 64;
public int maskHeight = 64;
public bool HasInit()=>init;
Color[] rectPixels;
float stepX;
float stepY;
bool[,] collisionMask;
PolygonCollider2D polyCollider;
PixelHandler pHandler;
SpriteRenderer sprend;
public Vector2[,] posGrid{get;private set;}
List<(Vector2,Vector2)> segments = new();
float cellWidth,cellHeight;
bool init = false;
public bool collisionDebug = false;
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void InitialiseCollision()//initialise only after pixel handler initialises
{
if(HasInit()) return;
pHandler = GetComponent<PixelHandler>();
sprend = GetComponent<SpriteRenderer>();
polyCollider = GetComponent<PolygonCollider2D>();
if (!pHandler.HasInit())
{
Debug.LogWarning("Has not initialised visuals before collisions! Quitting.");
return;
}
rectPixels = pHandler.rectPixels;
int texWidth = sprend.sprite.texture.width;
int texHeight = sprend.sprite.texture.height;
stepX = (float)texWidth/maskWidth;
stepY = (float)texHeight/maskHeight;
BuildCollisionMask(texWidth,texHeight);
Make2DLocalGrid();
segments.Clear();
pHandler.SyncToVisual(collisionMask,maskWidth,maskHeight);
MarchingSquares();
pHandler.DoStuffOnVisualUpdate+=CollisionUpdater;
init = true;
}
void BuildCollisionMask(int texwidth, int texheight)
{
collisionMask = new bool[maskWidth, maskHeight];
posGrid = new Vector2[maskWidth,maskHeight];
for(int i = 0; i < maskWidth; i++)
{
for(int j = 0; j < maskHeight; j++)
{
int px = (int)(i * stepX);
int py = (int)(j * stepY);
if(px < 0||py<0||px>=texwidth||py>=texheight) continue;
collisionMask[i,j] = rectPixels[py * texwidth + px].a > 0.1f;
}
}
}
void Make2DLocalGrid()
{
Bounds b = sprend.sprite.bounds;
Vector2 origin = b.min;
cellWidth = b.size.x/maskWidth;
cellHeight = b.size.y/maskHeight;
for(int i = 0; i < maskWidth; i++)
{
for(int j = 0; j < maskHeight; j++)
{
Vector2 pos = origin + new Vector2(i * cellWidth, j * cellHeight);
posGrid[i,j] = pos;
}
}
}
void CollisionUpdater()
{
segments.Clear();
pHandler.SyncToVisual(collisionMask,maskWidth,maskHeight);
MarchingSquares();
}
void MarchingSquares() //determines collider edges and calls function to build collider
{
for(int i = -1; i <= maskWidth; i++)
{
for(int j = -1; j <= maskHeight; j++)
{
//anti clockwise
bool c1 = GetMask(i,j);
bool c2 = GetMask(i+1,j);
bool c3 = GetMask(i+1,j+1);
bool c4 = GetMask(i,j+1);
int val = (c1 ? 1:0)+(c2 ? 2:0)+(c3 ? 4:0)+(c4 ? 8:0);
if(val == 0 || val == 15) continue;
Vector2 p00 = GetPos(i, j);
Vector2 p10 = GetPos(i+1, j);
Vector2 p11 = GetPos(i+1, j+1);
Vector2 p01 = GetPos(i, j+1);
Vector2 M(Vector2 a, Vector2 b) => (a + b) * 0.5f;
switch (val)
{
case 1:
segments.Add((M(p00, p01), M(p00, p10)));
break;
case 2:
segments.Add((M(p00, p10), M(p10, p11)));
break;
case 3:
segments.Add((M(p00, p01), M(p10, p11)));
break;
case 4:
segments.Add((M(p10, p11), M(p01, p11)));
break;
case 5:
segments.Add((M(p00, p10), M(p01, p11)));
break;
case 6:
segments.Add((M(p00, p10), M(p01, p11)));
break;
case 7:
segments.Add((M(p00, p01), M(p01, p11)));
break;
case 8:
segments.Add((M(p01, p11), M(p00, p01)));
break;
case 9:
segments.Add((M(p01, p11), M(p00, p10)));
break;
case 10:
segments.Add((M(p10, p11), M(p00, p01)));
break;
case 11:
segments.Add((M(p01, p11), M(p11, p10)));
break;
case 12:
segments.Add((M(p00, p01), M(p10, p11)));
break;
case 13:
segments.Add((M(p10, p11), M(p00, p10)));
break;
case 14:
segments.Add((M(p00, p10), M(p01, p00)));
break;
}
}
}
BuildCollider();
}
bool GetMask(int x, int y)
{
if(x < 0 || y < 0 || x >= maskWidth || y >= maskHeight)
return false;
return collisionMask[x,y];
}
Vector2 GetPos(int x, int y)
{
int cx = Mathf.Clamp(x,0,maskWidth-1);
int cy = Mathf.Clamp(y,0,maskHeight-1);
Vector2 pos = posGrid[cx,cy];
if(x < 0)
{
pos.x -= cellWidth;
}else if(x >= maskWidth)
{
pos.x += cellWidth;
}
if(y < 0)
{
pos.y -= cellHeight;
}else if(y >= maskHeight)
{
pos.y += cellHeight;
}
return pos;
}
void BuildCollider()
{
float snap = 0.001f;
Vector2 Snap(Vector2 v)
{
return new Vector2(
Mathf.Round(v.x / snap) * snap,
Mathf.Round(v.y / snap) * snap
);
}
Dictionary<Vector2, List<Vector2>> graph = new();
foreach (var seg in segments)
{
Vector2 a = Snap(seg.Item1);
Vector2 b = Snap(seg.Item2);
if (!graph.ContainsKey(a)) graph[a] = new();
if (!graph.ContainsKey(b)) graph[b] = new();
graph[a].Add(b);
graph[b].Add(a);
}
if (graph.Count == 0)
{
polyCollider.pathCount = 0;
return;
}
HashSet<Vector2> vis = new();
List<List<Vector2>> loops = new();
foreach(var start in graph.Keys)
{
if(vis.Contains(start)) continue;
var l = Looper(start,graph,vis);
if(l.Count >= 3)
loops.Add(l);
}
polyCollider.pathCount = loops.Count;
for (int i = 0; i < loops.Count; i++)
{
Vector2[] local = new Vector2[loops[i].Count];
for (int j = 0; j < loops[i].Count; j++)
{
local[j] = loops[i][j];
}
polyCollider.SetPath(i, local);
}
}
List<Vector2> Looper(Vector2 start, Dictionary<Vector2, List<Vector2>> graph, HashSet<Vector2> visited)
{
List<Vector2> loop = new();
Vector2 current = start;
Vector2 prev = new(float.NaN, float.NaN);
loop.Add(current);
int count = 0;
while (true)
{
visited.Add(current);
bool moved = false;
foreach(var n in graph[current])
{
if (prev != n && !visited.Contains(n))
{
prev = current;
current = n;
loop.Add(current);
moved = true;
break;
}
}
if(!moved)
{
break;
}
count ++;
if(count > 1000) break;
}
return loop;
}
public Vector2 GetPosOnDifferent(int x, int y, int mWidth, int mHeight)
{
float stepX = maskWidth/mWidth;
float stepY = maskHeight/mHeight;
int mx = Mathf.FloorToInt(x * stepX);
int my = Mathf.FloorToInt(y * stepY);
if(mx < 0||my<0||mx>=maskWidth||my>=maskHeight){
throw new IndexOutOfRangeException("Position out of bounds");
}
return posGrid[mx,my];
}
//only for visualising collision mask and edges
void OnDrawGizmosSelected()
{
if(!sprend || !collisionDebug || !HasInit()) return;
Bounds b = sprend.sprite.bounds;
Vector2 origin = b.min;
for (int x = 0; x < maskWidth; x++)
{
for (int y = 0; y < maskHeight; y++)
{
if (!collisionMask[x, y]) continue;
Vector2 center = origin + new Vector2(
x * cellWidth + cellWidth * 0.5f,
y * cellHeight + cellHeight * 0.5f
);
Vector3 centerWorld = transform.TransformPoint(center);
Gizmos.color = Color.green;
Gizmos.DrawCube(centerWorld, new Vector3(cellWidth * 0.9f * transform.localScale.x, cellHeight * 0.9f * transform.localScale.y, 0.01f));
}
}
if (segments == null)
return;
Gizmos.color = Color.blue;
foreach (var seg in segments)
{
Vector3 A = transform.TransformPoint(seg.Item1);
Vector3 B = transform.TransformPoint(seg.Item2);
Gizmos.DrawLine(A, B);
Gizmos.DrawSphere(A, 0.01f);
Gizmos.DrawSphere(B, 0.01f);
}
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: a6483c7bdbae77c4186cbd3b87097918
AssetOrigin:
serializedVersion: 1
productId: 358408
packageName: Destro 2D - Dynamic Sprite Destruction
packageVersion: 1.0.3
assetPath: Assets/Destro2DMain/Core/Main/CollisionHandler.cs
uploadId: 925032
@@ -0,0 +1,175 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace KD.Destro2D{
[RequireComponent(typeof(PixelHandler))]
[RequireComponent(typeof(CollisionHandler))]
[RequireComponent(typeof(SplitHandler))]
public class Destro2DMain : MonoBehaviour
{
[SerializeReference]
public List<Destruction> destructionList = new();
PixelHandler pHandler;
SpriteRenderer sprend;
CollisionHandler cHandler;
SplitHandler sHandler;
CircleDestruction fracture;
public Action<Vector2> OnDestruction;
// Start is called once before the first execution of Update after the MonoBehaviour is created
void OnEnable()
{
Initialise();
fracture = new();
fracture.Setup(this.gameObject);
}
// Update is called once per frame
void Update()
{
}
public void DynamicDestroyWorld(Vector2 world)
{
Vector2 loc = transform.InverseTransformPoint(world);
(int cx, int cy) = Destruction.LocToPixel(loc,sprend);
foreach(Destruction d in destructionList)
{
pHandler.DestroyMask(d,cx,cy);
}
OnDestruction?.Invoke(world);
}
public void DynamicDestroyLocal(Vector2 loc)
{
(int cx, int cy) = Destruction.LocToPixel(loc,sprend);
foreach(Destruction d in destructionList)
{
pHandler.DestroyMask(d,cx,cy);
}
OnDestruction?.Invoke(loc);
}
public void DynamicDestroyLocal(Vector2 loc, List<Destruction> dlist)
{
(int cx, int cy) = Destruction.LocToPixel(loc,sprend);
foreach(Destruction d in dlist)
{
pHandler.DestroyMask(d,cx,cy);
}
OnDestruction?.Invoke(loc);
}
public void DynamicDestroyWorld(Vector2 world, List<Destruction> dlist)
{
Vector2 loc = transform.InverseTransformPoint(world);
(int cx, int cy) = Destruction.LocToPixel(loc,sprend);
foreach(Destruction d in dlist)
{
pHandler.DestroyMask(d,cx,cy);
}
OnDestruction?.Invoke(world);
}
public void DynamicFracture(Vector2 world, float Rcore, float Router, float noiseScale,float thickness, int lines)
{
Vector2 loc = transform.InverseTransformPoint(world);
(int cx, int cy) = Destruction.LocToPixel(loc,sprend);
fracture.radius = thickness;
float seed = UnityEngine.Random.Range(10,100);
float step = 0.1f;
float[] angles = new float[lines];
for (int i = 0; i < lines; i++)
{
angles[i] = i * Mathf.PI * 2f / lines
+ UnityEngine.Random.Range(-20, 20) * Mathf.Deg2Rad;
}
float bendStrength = 1.5f * Mathf.Deg2Rad;
foreach (float baseAngle in angles)
{
float angle = baseAngle;
for (float r = Rcore; r <= Router; r += step)
{
Vector2 dir = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));
Vector2 p = world + dir * r;
float wobble = Mathf.PerlinNoise(
(p.x * noiseScale) + seed,
(p.y * noiseScale) + seed
) * 2f - 1f;
angle += wobble * bendStrength;
FractureHelper(p,thickness);
}
}
OnDestruction?.Invoke(world);
}
public void Initialise()
{
pHandler = GetComponent<PixelHandler>();
sprend = GetComponent<SpriteRenderer>();
cHandler = GetComponent<CollisionHandler>();
sHandler = GetComponent<SplitHandler>();
pHandler.Initialise();
cHandler.InitialiseCollision();
sHandler.InitialiseSplit();
foreach(Destruction d in destructionList)
{
d.Setup(this.gameObject);
}
}
public void AddStampDestruction(Texture2D stampTex, float scale, float rot)
{
StampDestruction stmp = new StampDestruction();
stmp.stampTexture = stampTex;
stmp.scaleFactor = scale;
stmp.rotationAngle = rot;
stmp.Setup(this.gameObject);
destructionList.Add(stmp);
}
public void AddCircleDestruction(float radius)
{
CircleDestruction cd = new CircleDestruction();
cd.radius = radius;
cd.Setup(this.gameObject);
destructionList.Add(cd);
}
public void AddEllipseDestruction(float radiusX, float radiusY)
{
EllipseDestruction ed = new EllipseDestruction();
ed.rx = radiusX;
ed.ry = radiusY;
ed.Setup(this.gameObject);
destructionList.Add(ed);
}
public void AddRectDestruction(float l, float b)
{
RectDestruction rd = new RectDestruction();
rd.l = l;
rd.b = b;
rd.Setup(this.gameObject);
destructionList.Add(rd);
}
public void InheritDestruction(GameObject src)
{
if(src.TryGetComponent<Destro2DMain>(out var d))
{
destructionList = d.destructionList;
}
}
void FractureHelper(Vector2 world, float radius)
{
Vector2 loc = transform.InverseTransformPoint(world);
(int cx, int cy) = Destruction.LocToPixel(loc,sprend);
pHandler.DestroyMask(fracture,cx,cy);
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 3ead6d33feb996340b43f15ce524b4e7
AssetOrigin:
serializedVersion: 1
productId: 358408
packageName: Destro 2D - Dynamic Sprite Destruction
packageVersion: 1.0.3
assetPath: Assets/Destro2DMain/Core/Main/Destro2DMain.cs
uploadId: 925032
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace KD.Destro2D{
[Serializable]
public abstract class Destruction
{
protected Color burnColor = Color.white;
public static List<GameObject> chunks = new();
public abstract void Setup(GameObject gobj);
public abstract void SetupWithColor(GameObject gobj, Color color);
public abstract void ApplyDestruction(int cx, int cy, bool[,] mask, float stepX, float stepY,int maskW, int mmaskH,Color32[] burnage, float burntime);
public static (int cx, int cy) LocToPixel(Vector2 loc, SpriteRenderer sprend)
{
Sprite s = sprend.sprite;
Bounds b = s.bounds;
float spw = s.rect.width;
float sph = s.rect.height;
float uvx = (loc.x - b.min.x)/b.size.x;
float uvy = (loc.y - b.min.y)/b.size.y;
int cx = Mathf.FloorToInt(uvx * spw);
int cy = Mathf.FloorToInt(uvy * sph);
return (cx,cy);
}
protected void BurnEdge(List<int> destroyed, int width, int height, bool[,] mask, Color32[] burnage, float burntime)
{
foreach (int i in destroyed)
{
int x = i % width;
int y = i / width;
TryBurn(x + 1, y);
TryBurn(x - 1, y);
TryBurn(x, y + 1);
TryBurn(x, y - 1);
}
void TryBurn(int nx, int ny)
{
if (nx < 0 || ny < 0 || nx >= width || ny >= height)
return;
if (mask[nx, ny])
{
int nidx = ny * width + nx;
burnage[nidx] = burnColor;
}
}
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 1836b5c3ec02ee04394ff03d708ed6d4
AssetOrigin:
serializedVersion: 1
productId: 358408
packageName: Destro 2D - Dynamic Sprite Destruction
packageVersion: 1.0.3
assetPath: Assets/Destro2DMain/Core/Main/Destruction.cs
uploadId: 925032
@@ -0,0 +1,228 @@
using System;
using System.Collections;
using System.Linq;
using UnityEngine;
namespace KD.Destro2D{
public class PixelHandler : MonoBehaviour
{
public Material maskerMaterial;
public int maskWidth = 128, maskHeight = 128;
public bool HasInit() => init;
public bool IsDirty() => isDirty;
public int burnDecay = 1;
public float burnTime = 5f;
SpriteRenderer sprend;
public Texture2D runtimeTex{get; private set;}
bool[,] visualMask;
int width, height;
float stepX, stepY;
public Color[] rectPixels{get; private set;}
Texture2D maskTex;
bool isDirty = false;
bool init = false;
public Action DoStuffOnVisualUpdate;
Color32[] burnAge;
Color32[] maskPixels;
void Update()
{
}
public void Initialise(Color[] cachedRectPixels = null, Texture2D cachedRunTimeTex = null,bool isChunk = false)
{
if(HasInit()) return;
sprend = GetComponent<SpriteRenderer>();
Texture2D src = sprend.sprite.texture;
Rect srcRect = sprend.sprite.rect;
width = (int)srcRect.width;
height = (int)srcRect.height;
if(cachedRectPixels == null)
rectPixels = src.GetPixels((int)srcRect.x, (int)srcRect.y,width,height);
else
rectPixels = cachedRectPixels;
if(cachedRunTimeTex == null){
runtimeTex = new Texture2D(width,height,TextureFormat.RGBA32,false);
runtimeTex.SetPixels(rectPixels);
runtimeTex.Apply();
}
else
{
runtimeTex = cachedRunTimeTex;
}
if(!isChunk)
sprend.sprite = Sprite.Create(runtimeTex, new Rect(0,0,width,height),new Vector2(0.5f,0.5f),sprend.sprite.pixelsPerUnit);
stepX = (float)width/maskWidth;
stepY = (float)height/maskHeight;
maskTex = new Texture2D(maskWidth, maskHeight, TextureFormat.RGBA32,false);
maskTex.filterMode = FilterMode.Point;
sprend.sharedMaterial = maskerMaterial;
var block = new MaterialPropertyBlock();
sprend.GetPropertyBlock(block);
block.SetTexture("_MainTex", runtimeTex);
block.SetTexture("_MaskTex", maskTex);
block.SetVector("_MaskTexelSize",new Vector2(1f / maskTex.width, 1f / maskTex.height));
sprend.SetPropertyBlock(block);
InitialiseMask();
ApplyMaskToTexture();
init = true;
}
private void InitialiseMask()
{
maskPixels = new Color32[maskWidth * maskHeight];
burnAge = new Color32[maskWidth * maskHeight];
visualMask = new bool[maskWidth,maskHeight];
for(int i = 0; i< maskWidth; i++)
{
for(int j = 0; j< maskHeight; j++)
{
int px = (int)(i * stepX);
int py = (int)(j * stepY);
if(px < 0||py<0||px>=width||py>=height) continue;
visualMask[i,j] = rectPixels[py * width + px].a > 0.1f;
}
}
}
public void ImmediateMaskUpdate(SplitHandler split)
{
if(split)
ApplyMaskToTexture();
}
void OnEnable()
{
StartCoroutine(CheckDirty());
}
private void SetDirty()
{
isDirty = true;
}
IEnumerator CheckDirty()
{
while (true)
{
if(isDirty) {
ApplyMaskToTexture();
isDirty = false;
}
yield return new WaitForSeconds(0.01f);
}
}
private void ApplyMaskToTexture()
{
for(int i = 0; i < maskWidth; i++)
{
for(int j = 0; j < maskHeight; j++)
{
int idx = j * maskWidth + i;
byte solid = visualMask[i,j] ? (byte)255: (byte)0;
maskPixels[idx].r = burnAge[idx].r;
maskPixels[idx].g = burnAge[idx].g;
maskPixels[idx].b = burnAge[idx].b;
maskPixels[idx].a = solid;
float step = burnDecay * Time.deltaTime;
burnAge[idx].r = (byte)(burnAge[idx].r > step ? burnAge[idx].r - step : 0);
burnAge[idx].g = (byte)(burnAge[idx].g > step ? burnAge[idx].g - step : 0);
burnAge[idx].b = (byte)(burnAge[idx].b > step ? burnAge[idx].b - step : 0);
}
}
maskTex.SetPixels32(maskPixels);
maskTex.Apply(false);
DoStuffOnVisualUpdate?.Invoke();
}
//fucntions for changing mask:
public void DestroyMask(Destruction d, int cx, int cy)
{
d.ApplyDestruction(cx,cy,visualMask,stepX,stepY,maskWidth,maskHeight,burnAge,burnTime);
SetDirty();
}
public void SyncToVisual(bool[,] mask, int mWidth, int mHeight)
{
float stepX = (float)maskWidth/mWidth;
float stepY = (float)maskHeight/mHeight;
for(int i = 0; i < mWidth; i++)
{
for(int j = 0; j< mHeight; j++)
{
int x = (int)(i * stepX);
int y = (int)(j * stepY);
if(x < 0||y<0||x>=maskWidth||y>=maskHeight) continue;
mask[i,j] = visualMask[x,y];
}
}
}
public void Split(bool[,]mask, int mWidth, int mHeight, bool chunk = false)
{
float stepX = (float)maskWidth/mWidth;
float stepY = (float)maskHeight/mHeight;
for(int i = 0; i< maskWidth; i++)
{
for(int j = 0; j < maskHeight; j++)
{
int x = (int)(i / stepX);
int y = (int)(j / stepY);
if(x < 0||y<0||x>=mWidth||y>=mHeight) continue;
if(!chunk){
if (!visualMask[i, j])
continue;
bool keep = false;
for (int dy = -1; dy <= 1 && !keep; dy++)
{
for (int dx = -1; dx <= 1 && !keep; dx++)
{
int nx = x + dx;
int ny = y + dy;
if (nx < 0 || ny < 0 || nx >= mWidth || ny >= mHeight)
continue;
if (mask[nx, ny])
keep = true;
}
}
visualMask[i, j] = keep;
}
else
{
visualMask[i,j] = mask[x,y];
}
}
}
SetDirty();
ApplyMaskToTexture();
}
public void InheritBurn(PixelHandler pother)
{
pother.burnAge = burnAge.ToArray();
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 0cd2b8cdc7d724d4fa15c58096ef8cc3
AssetOrigin:
serializedVersion: 1
productId: 358408
packageName: Destro 2D - Dynamic Sprite Destruction
packageVersion: 1.0.3
assetPath: Assets/Destro2DMain/Core/Main/PixelHandler.cs
uploadId: 925032
@@ -0,0 +1,245 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace KD.Destro2D{
[RequireComponent(typeof(PixelHandler))]
public class SplitHandler : MonoBehaviour
{
public int maskWidth = 64;
public int maskHeight = 64;
public int minCount = 8;
public float splitMass = 1;
public GameObject anchor;
public Vector2 chunkCentre{get; private set;}
public bool HasInit()=>init;
public int MaxChunkCount = 4;
bool[,] splitMask;
PixelHandler pHandler;
SpriteRenderer sprend;
Color[] rectPixels;
float stepX;
float stepY;
List<List<(int,int)>> regions = new();
bool init = false;
bool generatingChunks = false;
public Action OnAnchorBroken;
bool flag = true;
Texture2D runtimeTex;
public Queue<GameObject> splitChunks{get; private set;} = new();
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void InitialiseSplit()//initialise only after pixel handler initialises
{
if(HasInit()) return;
pHandler = GetComponent<PixelHandler>();
sprend = GetComponent<SpriteRenderer>();
if (!pHandler.HasInit())
{
Debug.LogWarning("Has not initialised visuals before splitter! Quitting.");
return;
}
rectPixels = pHandler.rectPixels;
runtimeTex = pHandler.runtimeTex;
int texWidth = sprend.sprite.texture.width;
int texHeight = sprend.sprite.texture.height;
stepX = (float)texWidth/maskWidth;
stepY = (float)texHeight/maskHeight;
BuildSplitMask(texWidth,texHeight);
pHandler.SyncToVisual(splitMask,maskWidth,maskHeight);
GetRegions();
pHandler.DoStuffOnVisualUpdate+=CheckDisconnected;
init = true;
}
void CheckDisconnected()
{
if(generatingChunks) return;
pHandler.SyncToVisual(splitMask,maskWidth,maskHeight);
GetRegions();
}
void GetRegions()
{
regions.Clear();
bool[,] visited = new bool[maskWidth,maskHeight];
var (ax, ay) = PlaceAnchor();
FloodFill(ax,ay,visited);//get anchored base to get relative split chunks(object doesn't have to be anchored, though)
for(int i = 0; i < maskWidth; i++)
{
for(int j = 0; j < maskHeight; j++)
{
if(!splitMask[i,j] || visited[i,j])
continue;
List<(int,int)> splitRegion = new();
FloodFill(i, j, visited, splitRegion);
if(splitRegion.Count >= minCount)
regions.Add(splitRegion);
}
}
GenerateChunks();
}
void BuildSplitMask(int texwidth, int texheight)
{
splitMask = new bool[maskWidth, maskHeight];
for(int i = 0; i < maskWidth; i++)
{
for(int j = 0; j < maskHeight; j++)
{
int px = (int)(i * stepX);
int py = (int)(j * stepY);
if(px < 0||py<0||px>=texwidth||py>=texheight) continue;
splitMask[i,j] = rectPixels[py * texwidth + px].a > 0.1f;
}
}
}
void FloodFill(int startX, int startY, bool[,] visited, List<(int,int)> region = null)
{
Stack<(int x, int y)> stack = new();
stack.Push((startX, startY));
while (stack.Count > 0)
{
var (x,y) = stack.Pop();
if(x < 0 || y < 0|| x >= maskWidth || y >= maskHeight)
continue;
if(!splitMask[x,y]) continue;
if(visited[x,y]) continue;
visited[x,y] = true;
region?.Add((x,y));
stack.Push((x+1,y));
stack.Push((x-1,y));
stack.Push((x,y+1));
stack.Push((x,y-1));
}
}
(int,int) PlaceAnchor()
{
if (anchor)
{
Vector2 locPos = transform.InverseTransformPoint(anchor.transform.position);
var (ax, ay) = Destruction.LocToPixel(locPos,sprend);
ax = Mathf.FloorToInt(ax/stepX);
ay = Mathf.FloorToInt(ay/stepY);
if(ax >= 0 && ay >= 0 && ax < maskWidth && ay < maskHeight)
{
if(splitMask[ax,ay])
return (ax,ay);
}
}
//if no anchor, call event ONCE, then default to first visible pixel
if (flag)
{
OnAnchorBroken?.Invoke();
flag = false;
}
for (int i = 0; i < maskWidth; i++)
{
for (int j = 0; j < maskHeight; j++)
{
if (splitMask[i, j])
{
return (i,j);
}
}
}
//if no visible pixel, Destroy and remove from list
if (Destruction.chunks.Contains(this.gameObject))
{
Destruction.chunks.Remove(this.gameObject);
}
Destroy(this.gameObject);
return (-1,-1);
}
void GenerateChunks()
{
generatingChunks = true;
foreach(var region in regions)
{
int minLX = int.MaxValue, minLY = int.MaxValue;
int maxLX = int.MinValue, maxLY = int.MinValue;
bool[,] regMask = new bool[maskWidth,maskHeight];
foreach(var r in region)
{
int x = r.Item1;
int y = r.Item2;
regMask[x,y] = true;
splitMask[x,y] = false;
minLX = Mathf.Min(minLX, x);
minLY = Mathf.Min(minLY, y);
maxLX = Mathf.Max(maxLX, x);
maxLY = Mathf.Max(maxLY, y);
}
//Updating Parent visual
pHandler.Split(splitMask,maskWidth,maskHeight);
//Chunk stuff, here u can make it inherit stuff as u want (by default it doesnt inherit mask details)
GameObject chunk = new GameObject("Split Chunk");
//if queue count is greater than max, cycle
if(splitChunks.Count >= MaxChunkCount)
{
if(splitChunks.TryDequeue(out GameObject g)){
Destruction.chunks.Remove(g);
Destroy(g);
}
}
splitChunks.Enqueue(chunk);
Destruction.chunks.Add(chunk);
SpriteRenderer sr = chunk.AddComponent<SpriteRenderer>();
sr.sprite = sprend.sprite;
sr.color = sprend.color;
chunk.transform.position = transform.position;
chunk.transform.localScale = transform.localScale;
chunk.transform.rotation = transform.rotation;
Rigidbody2D rb = chunk.AddComponent<Rigidbody2D>();
rb.mass = splitMass;
chunk.AddComponent<PolygonCollider2D>();
PixelHandler p = chunk.AddComponent<PixelHandler>();
p.maskerMaterial = pHandler.maskerMaterial;
p.Initialise(rectPixels,runtimeTex,true);
p.Split(regMask,maskWidth,maskHeight,true);
pHandler.InheritBurn(p);
var c = chunk.AddComponent<CollisionHandler>();
c.InitialiseCollision();
Vector2 minPos = c.GetPosOnDifferent(minLX,minLY,maskWidth,maskHeight);
Vector2 maxPos = c.GetPosOnDifferent(maxLX,maxLY,maskWidth,maskHeight);
var s = chunk.AddComponent<SplitHandler>();
s.splitMass = splitMass;
s.InitialiseSplit();
s.chunkCentre = (minPos + maxPos)/2f;
Destro2DMain d = chunk.AddComponent<Destro2DMain>();
//This part decides what destruction the chunk is susceptible to
d.InheritDestruction(this.gameObject);
}
generatingChunks = false;
}
}
}
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 6bbfd1860c521b44294e75917251fdba
AssetOrigin:
serializedVersion: 1
productId: 358408
packageName: Destro 2D - Dynamic Sprite Destruction
packageVersion: 1.0.3
assetPath: Assets/Destro2DMain/Core/Main/SplitHandler.cs
uploadId: 925032