Skip to content

Instantly share code, notes, and snippets.

@codorizzi
Created June 21, 2023 10:04
Show Gist options
  • Save codorizzi/bf544229f76266c9df0b723a1e83a152 to your computer and use it in GitHub Desktop.
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
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