229 lines
8.1 KiB
C#
229 lines
8.1 KiB
C#
|
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 (left‑to‑right, top‑to‑bottom, maxColumns per row).
|
|||
|
/// 2) A JSON file with weapon‑anchor 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 bottom‑left)
|
|||
|
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 bottom‑left)
|
|||
|
public float y;
|
|||
|
}
|
|||
|
|
|||
|
[System.Serializable]
|
|||
|
private struct AnchorWrapper { public AnchorFrame[] anchors; }
|
|||
|
|
|||
|
#endregion -------------------------------------------------------------
|
|||
|
}
|
|||
|
}
|