Created
June 21, 2023 10:04
-
-
Save codorizzi/bf544229f76266c9df0b723a1e83a152 to your computer and use it in GitHub Desktop.
Unity Sprite Merging - Takes children Sprite Renderers, Merges them into a single parent
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using System.Collections.Generic; | |
using Sirenix.OdinInspector; | |
using UnityEngine; | |
[RequireComponent(typeof(SpriteRenderer))] | |
public class SpriteMerger : MonoBehaviour | |
{ | |
private const int PixelsPerUnit = 256; | |
[Button] | |
public void Combine() | |
{ | |
// Calculate the bounds of all child sprites | |
Rect totalBounds = GetTotalBounds(); | |
// Create a combined texture | |
Texture2D combinedTexture = CreateCombinedTexture(totalBounds); | |
// Trim the combined texture | |
combinedTexture = TrimTexture(combinedTexture); | |
// Create the final sprite | |
CreateFinalSprite(combinedTexture); | |
} | |
private Rect GetTotalBounds() | |
{ | |
SpriteRenderer[] childSprites = GetComponentsInChildren<SpriteRenderer>(); | |
float minX = float.MaxValue, minY = float.MaxValue, maxX = float.MinValue, maxY = float.MinValue; | |
foreach (SpriteRenderer childSprite in childSprites) | |
{ | |
if (childSprite.gameObject != this.gameObject) | |
{ | |
Vector2 spriteSize = childSprite.sprite.bounds.size; | |
Vector2 localPosition = childSprite.transform.localPosition; | |
minX = Mathf.Min(minX, localPosition.x); | |
minY = Mathf.Min(minY, localPosition.y); | |
maxX = Mathf.Max(maxX, localPosition.x + spriteSize.x); | |
maxY = Mathf.Max(maxY, localPosition.y + spriteSize.y); | |
} | |
} | |
Vector2 min = new Vector2(minX, minY); | |
Vector2 size = new Vector2(maxX - minX, maxY - minY); | |
return new Rect(min, size); | |
} | |
private Texture2D CreateCombinedTexture(Rect totalBounds) | |
{ | |
// Convert bounds size to pixel size | |
Vector2Int totalSize = new Vector2Int( | |
Mathf.CeilToInt(totalBounds.width * PixelsPerUnit), | |
Mathf.CeilToInt(totalBounds.height * PixelsPerUnit) | |
); | |
// Create a new texture with the calculated size | |
Texture2D combinedTexture = new Texture2D(totalSize.x, totalSize.y, TextureFormat.RGBA32, false); | |
combinedTexture.filterMode = FilterMode.Point; | |
// Initialize all pixels to be fully transparent | |
for (int x = 0; x < combinedTexture.width; x++) | |
{ | |
for (int y = 0; y < combinedTexture.height; y++) | |
{ | |
combinedTexture.SetPixel(x, y, new Color(0, 0, 0, 0)); | |
} | |
} | |
// Go through all child sprites and draw them onto the new texture | |
SpriteRenderer[] childSprites = GetComponentsInChildren<SpriteRenderer>(); | |
foreach (SpriteRenderer childSprite in childSprites) | |
{ | |
if (childSprite.gameObject != this.gameObject) | |
{ | |
// Position is local to the parent transform | |
Vector2 localPosition = childSprite.transform.localPosition; | |
AddSpriteToTexture(localPosition, childSprite.sprite, totalBounds, combinedTexture); | |
childSprite.gameObject.SetActive(false); | |
} | |
} | |
combinedTexture.Apply(); | |
return combinedTexture; | |
} | |
private void AddSpriteToTexture(Vector2 position, Sprite sprite, Rect totalBounds, Texture2D combinedTexture) | |
{ | |
Texture2D spriteTexture = sprite.texture; | |
Vector2Int pixelPos = WorldToPixelCoordinates(position, totalBounds); | |
for (int x = 0; x < spriteTexture.width; x++) | |
{ | |
for (int y = 0; y < spriteTexture.height; y++) | |
{ | |
Color pixelColor = spriteTexture.GetPixel(x, y); | |
if (pixelColor.a > 0) // Don't overwrite pixels if the current pixel is transparent | |
{ | |
combinedTexture.SetPixel(pixelPos.x + x, pixelPos.y + y, pixelColor); | |
} | |
} | |
} | |
} | |
private Vector2Int WorldToPixelCoordinates(Vector2 worldPos, Rect totalBounds) | |
{ | |
return new Vector2Int( | |
Mathf.FloorToInt((worldPos.x - totalBounds.xMin) * PixelsPerUnit), | |
Mathf.FloorToInt((worldPos.y - totalBounds.yMin) * PixelsPerUnit) | |
); | |
} | |
private Texture2D TrimTexture(Texture2D original) | |
{ | |
int xMin = int.MaxValue, xMax = int.MinValue, yMin = int.MaxValue, yMax = int.MinValue; | |
for (int x = 0; x < original.width; ++x) | |
{ | |
for (int y = 0; y < original.height; ++y) | |
{ | |
Color pixelColor = original.GetPixel(x, y); | |
if (pixelColor.a > 0) | |
{ | |
if (x < xMin) xMin = x; | |
if (x > xMax) xMax = x; | |
if (y < yMin) yMin = y; | |
if (y > yMax) yMax = y; | |
} | |
} | |
} | |
int width = xMax - xMin + 1; | |
int height = yMax - yMin + 1; | |
Color[] pixels = original.GetPixels(xMin, yMin, width, height); | |
Texture2D trimmedTexture = new Texture2D(width, height); | |
trimmedTexture.SetPixels(pixels); | |
trimmedTexture.Apply(); | |
return trimmedTexture; | |
} | |
private void CreateFinalSprite(Texture2D combinedTexture) | |
{ | |
Sprite finalSprite = Sprite.Create(combinedTexture, new Rect(0.0f, 0.0f, combinedTexture.width, combinedTexture.height), new Vector2(0.5f, 0.5f), PixelsPerUnit); | |
SpriteRenderer renderer = GetComponent<SpriteRenderer>(); | |
renderer.sprite = finalSprite; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment