229 lines
8.1 KiB
C#
Raw Permalink Normal View History

2025-09-19 19:43:49 +05:00
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;
namespace SpriteCaptureTool
{
/// <summary>
/// Captures an animation (optionally from four angles) and produces:
/// 1) A single ordered sprite sheet (lefttoright, toptobottom, maxColumns per row).
/// 2) A JSON file with weaponanchor pixel coordinates for each frame (inside its own cell).
/// </summary>
public class SpriteSheetRenderer : MonoBehaviour
{
#region Inspector -------------------------------------------------------
[Header("Render Settings")]
[Tooltip("Camera used to render individual frames.")]
public Camera renderCamera;
[Tooltip("Square size, in pixels, of each frame.")]
public int frameSize = 512;
[Tooltip("Desired output framerate.")]
public float frameRate = 12f;
[Header("Animation Settings")]
[Tooltip("Animator holding the clip to capture.")]
public Animator animator;
[Tooltip("Name of the clip/state to play.")]
public string animationName = "Idle";
[Header("Capture Angles")]
[Tooltip("Capture four directions: Front, Right, Back, Left.")]
public bool captureMultiAngles = true;
[Header("Sprite Sheet Settings")]
[Tooltip("Maximum number of columns (frames per row).")]
[Range(1, 20)]
public int maxColumns = 10;
[Header("Anchor Settings")]
[Tooltip("Transform positioned on the hand (or desired weapon anchor).")]
public Transform anchorTransform;
#endregion --------------------------------------------------------------
private readonly List<Texture2D> capturedFrames = new();
private readonly List<Vector2> anchorPixels = new(); // pixel coords per frame
private int totalFrameCount;
private void Start() => StartCoroutine(CaptureAllAngles());
#region Capture ---------------------------------------------------------
private IEnumerator CaptureAllAngles()
{
Vector3 originalRotation = transform.eulerAngles;
List<Vector3> angles = new()
{
new Vector3(0, 0, 0), // Front
new Vector3(0, 90, 0), // Right
new Vector3(0, 180, 0), // Back
new Vector3(0, 270, 0) // Left
};
int angleCount = captureMultiAngles ? angles.Count : 1;
for (int i = 0; i < angleCount; i++)
{
transform.eulerAngles = angles[i];
yield return StartCoroutine(CaptureAnimation());
}
transform.eulerAngles = originalRotation;
SaveSpriteSheetAndJson();
}
private IEnumerator CaptureAnimation()
{
AnimationClip clip = GetAnimationClip(animationName);
if (clip == null)
{
Debug.LogError($"[SpriteSheetRenderer] Clip '{animationName}' not found.");
yield break;
}
totalFrameCount = Mathf.CeilToInt(clip.length * frameRate);
animator.Play(animationName, 0, 0f);
yield return null; // wait 1 frame for proper pose
for (int i = 0; i < totalFrameCount; i++)
{
yield return new WaitForEndOfFrame();
// --- Render texture --------------------------------------------------
RenderTexture rt = new RenderTexture(frameSize, frameSize, 24);
renderCamera.targetTexture = rt;
Texture2D frame = new Texture2D(frameSize, frameSize, TextureFormat.RGBA32, false)
{
filterMode = FilterMode.Point
};
renderCamera.Render();
RenderTexture.active = rt;
frame.ReadPixels(new Rect(0, 0, frameSize, frameSize), 0, 0);
frame.Apply();
renderCamera.targetTexture = null;
RenderTexture.active = null;
Destroy(rt);
capturedFrames.Add(frame);
// --- Anchor position -------------------------------------------------
if (anchorTransform != null)
{
Vector3 vp = renderCamera.WorldToViewportPoint(anchorTransform.position);
var pixel = new Vector2(vp.x * frameSize, vp.y * frameSize);
anchorPixels.Add(pixel);
}
else
{
anchorPixels.Add(Vector2.zero);
}
yield return new WaitForSeconds(1f / frameRate);
}
Debug.Log($"[SpriteSheetRenderer] Captured {totalFrameCount} frames.");
}
#endregion --------------------------------------------------------------
#region Output ----------------------------------------------------------
private void SaveSpriteSheetAndJson()
{
if (capturedFrames.Count == 0)
{
Debug.LogWarning("[SpriteSheetRenderer] No frames to save.");
return;
}
int totalFrames = capturedFrames.Count;
int columns = Mathf.Min(maxColumns, totalFrameCount);
int rows = Mathf.CeilToInt((float)totalFrames / columns);
int sheetWidth = columns * frameSize;
int sheetHeight = rows * frameSize;
Texture2D sheet = new Texture2D(sheetWidth, sheetHeight, TextureFormat.RGBA32, false);
for (int i = 0; i < totalFrames; i++)
{
int col = i % columns;
int row = i / columns;
// sprite placement (origin bottomleft)
int xPos = col * frameSize;
int yPos = (rows - row - 1) * frameSize; // flip Y for texture coords
sheet.SetPixels(xPos, yPos, frameSize, frameSize, capturedFrames[i].GetPixels());
}
sheet.Apply();
string dir = "Assets/SpriteFrames";
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
File.WriteAllBytes($"{dir}/SpriteSheet.png", sheet.EncodeToPNG());
File.WriteAllText($"{dir}/AnchorPoints.json", BuildAnchorJson(columns, rows));
Debug.Log($"[SpriteSheetRenderer] ✔ SpriteSheet.png & AnchorPoints.json gerados ({columns}x{rows}).");
}
/// <summary>Returns the requested clip from the AnimatorController.</summary>
private AnimationClip GetAnimationClip(string clipName)
{
foreach (var clip in animator.runtimeAnimatorController.animationClips)
if (clip.name == clipName) return clip;
return null;
}
/// <summary>
/// Builds a JSON string with anchor pixel coordinates relative to each frame cell.
/// </summary>
private string BuildAnchorJson(int columns, int rows)
{
List<AnchorFrame> list = new(capturedFrames.Count);
for (int i = 0; i < anchorPixels.Count; i++)
{
int col = i % columns;
int row = i / columns;
list.Add(new AnchorFrame
{
frame = i,
column = col,
row = row,
x = anchorPixels[i].x,
y = anchorPixels[i].y
});
}
AnchorWrapper wrapper = new() { anchors = list.ToArray() };
return JsonUtility.ToJson(wrapper, true);
}
[System.Serializable]
private struct AnchorFrame
{
public int frame; // absolute index in spritesheet order
public int column; // col 0..N
public int row; // row 0..M
public float x; // pixel inside the frame (origin bottomleft)
public float y;
}
[System.Serializable]
private struct AnchorWrapper { public AnchorFrame[] anchors; }
#endregion -------------------------------------------------------------
}
}