using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Rendering; namespace AibisDream.Framework { /// /// 合并结果,包含合并后的Sprite和世界位置信息 /// public struct MergeResult { public Sprite sprite; public Vector3 worldPosition; // 世界空间中心位置 public Bounds worldBounds; // 世界空间包围盒 } /// /// SpriteRenderer合并工具类 /// public static class SpriteMerger { /// /// 合并多个SpriteRenderer为一个Sprite /// /// 要合并的SpriteRenderer数组 /// 输出纹理宽度(像素),0则自动计算 /// 输出纹理高度(像素),0则自动计算 /// Sprite的pixelsPerUnit /// 合并结果,包含Sprite和世界位置信息 public static MergeResult MergeSpriteRenderers( SpriteRenderer[] renderers, int textureWidth = 0, int textureHeight = 0, float pixelsPerUnit = 100f) { if (renderers == null || renderers.Length == 0) { Debug.LogError("SpriteRenderer数组为空"); return new MergeResult(); } // 过滤掉无效的renderer var validRenderers = renderers.Where(r => r != null && r.sprite != null).ToArray(); if (validRenderers.Length == 0) { Debug.LogError("没有有效的SpriteRenderer"); return new MergeResult(); } // 1. 计算世界空间包围盒 Bounds worldBounds = CalculateWorldBounds(validRenderers); // 2. 确定纹理尺寸 if (textureWidth <= 0 || textureHeight <= 0) { // 根据包围盒自动计算尺寸,保持宽高比 float aspect = worldBounds.size.x / worldBounds.size.y; if (aspect > 1f) { textureWidth = Mathf.NextPowerOfTwo(Mathf.CeilToInt(worldBounds.size.x * pixelsPerUnit)); textureHeight = Mathf.NextPowerOfTwo(Mathf.CeilToInt(textureWidth / aspect)); } else { textureHeight = Mathf.NextPowerOfTwo(Mathf.CeilToInt(worldBounds.size.y * pixelsPerUnit)); textureWidth = Mathf.NextPowerOfTwo(Mathf.CeilToInt(textureHeight * aspect)); } } // 3. 创建RenderTexture和临时Camera RenderTexture renderTexture = null; Camera tempCamera = null; try { renderTexture = CreateRenderTexture(textureWidth, textureHeight); tempCamera = CreateTempCamera(worldBounds, renderTexture); // 4. 渲染SpriteRenderer到RenderTexture RenderSpritesToTexture(validRenderers, tempCamera); // 5. 读取为Texture2D Texture2D texture2D = ReadRenderTexture(renderTexture); // 6. 转换为Sprite Sprite sprite = TextureToSprite(texture2D, pixelsPerUnit); // 7. 返回结果 return new MergeResult { sprite = sprite, worldPosition = worldBounds.center, worldBounds = worldBounds }; } finally { // 清理资源 CleanupResources(tempCamera, renderTexture); } } /// /// 计算世界空间包围盒 /// private static Bounds CalculateWorldBounds(SpriteRenderer[] renderers) { if (renderers.Length == 0) return new Bounds(); bool first = true; Bounds bounds = new Bounds(); foreach (var renderer in renderers) { if (renderer.sprite == null) continue; // 获取Sprite的本地边界 Bounds spriteBounds = renderer.sprite.bounds; // 获取世界位置(只考虑位置,不考虑旋转缩放) Vector3 worldPos = renderer.transform.position; // 计算世界空间中的四个角点 Vector3 center = worldPos + spriteBounds.center; Vector3 size = spriteBounds.size; Vector3[] corners = new Vector3[] { center + new Vector3(-size.x / 2, -size.y / 2, 0), center + new Vector3(size.x / 2, -size.y / 2, 0), center + new Vector3(-size.x / 2, size.y / 2, 0), center + new Vector3(size.x / 2, size.y / 2, 0) }; if (first) { bounds = new Bounds(corners[0], Vector3.zero); first = false; } foreach (var corner in corners) { bounds.Encapsulate(corner); } } return bounds; } /// /// 创建RenderTexture /// private static RenderTexture CreateRenderTexture(int width, int height) { RenderTexture rt = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32); rt.antiAliasing = 1; rt.filterMode = FilterMode.Bilinear; rt.anisoLevel = 0; rt.Create(); return rt; } /// /// 创建临时Camera /// private static Camera CreateTempCamera(Bounds worldBounds, RenderTexture targetTexture) { GameObject cameraObj = new GameObject("TempSpriteMergerCamera"); Camera cam = cameraObj.AddComponent(); cam.orthographic = true; cam.orthographicSize = worldBounds.size.y / 2f; cam.nearClipPlane = 0.1f; cam.farClipPlane = 1000f; cam.clearFlags = CameraClearFlags.SolidColor; cam.backgroundColor = new Color(0, 0, 0, 0); // 透明背景 cam.cullingMask = 0; // 不渲染任何Layer,我们手动绘制 cam.targetTexture = targetTexture; // 设置相机位置在包围盒中心,面向Z轴正方向 cam.transform.position = new Vector3( worldBounds.center.x, worldBounds.center.y, worldBounds.center.z - 100f ); cam.transform.rotation = Quaternion.identity; return cam; } /// /// 渲染SpriteRenderer到RenderTexture /// private static void RenderSpritesToTexture( SpriteRenderer[] renderers, Camera camera) { // 按sortingOrder排序 var sortedRenderers = renderers .OrderBy(r => r.sortingLayerID) .ThenBy(r => r.sortingOrder) .ToArray(); // 使用CommandBuffer来确保立即渲染 CommandBuffer cmd = new CommandBuffer(); cmd.name = "SpriteMerger"; // 设置RenderTexture cmd.SetRenderTarget(camera.targetTexture); cmd.ClearRenderTarget(true, true, new Color(0, 0, 0, 0)); // 为每个SpriteRenderer创建Mesh并绘制 foreach (var renderer in sortedRenderers) { if (renderer.sprite == null) continue; Mesh mesh = CreateSpriteMesh(renderer); if (mesh == null) continue; Material material = renderer.sharedMaterial != null ? renderer.sharedMaterial : GetDefaultSpriteMaterial(); if (material == null) continue; MaterialPropertyBlock mpb = new MaterialPropertyBlock(); renderer.GetPropertyBlock(mpb); // 应用颜色 Color color = renderer.color; mpb.SetColor("_Color", color); // 使用单位矩阵(顶点已经是世界空间坐标) Matrix4x4 matrix = Matrix4x4.identity; // 绘制Mesh到CommandBuffer cmd.DrawMesh(mesh, matrix, material, 0, 0, mpb); } // 执行CommandBuffer Graphics.ExecuteCommandBuffer(cmd); cmd.Dispose(); } /// /// 为SpriteRenderer创建Mesh(使用世界空间坐标) /// private static Mesh CreateSpriteMesh(SpriteRenderer renderer) { Sprite sprite = renderer.sprite; if (sprite == null) return null; // 获取Sprite的UV和三角形信息 Vector2[] uvs = sprite.uv; ushort[] triangles = sprite.triangles; // 计算世界位置 Vector3 worldPos = renderer.transform.position; Vector3 spriteBoundsCenter = sprite.bounds.center; Vector3 spriteBoundsSize = sprite.bounds.size; // Sprite的世界中心位置 Vector3 spriteWorldCenter = worldPos + spriteBoundsCenter; // 计算Sprite的四个角点(相对于sprite中心,单位:世界单位) float halfWidth = spriteBoundsSize.x / 2f; float halfHeight = spriteBoundsSize.y / 2f; // 构建四个角的世界坐标 Vector3[] worldCorners = new Vector3[] { spriteWorldCenter + new Vector3(-halfWidth, -halfHeight, 0), // 左下 spriteWorldCenter + new Vector3(-halfWidth, halfHeight, 0), // 左上 spriteWorldCenter + new Vector3(halfWidth, halfHeight, 0), // 右上 spriteWorldCenter + new Vector3(halfWidth, -halfHeight, 0) // 右下 }; // 创建顶点数组(使用UV数量,根据UV映射到正确的角点) Vector3[] vertices = new Vector3[uvs.Length]; // 将UV坐标映射到世界坐标的角点 for (int i = 0; i < uvs.Length; i++) { Vector2 uv = uvs[i]; // 找到最接近的角点UV int cornerIndex = 0; float minDist = float.MaxValue; Vector2[] cornerUVs = new Vector2[] { new Vector2(0, 0), // 左下 new Vector2(0, 1), // 左上 new Vector2(1, 1), // 右上 new Vector2(1, 0) // 右下 }; for (int j = 0; j < cornerUVs.Length; j++) { float dist = Vector2.Distance(uv, cornerUVs[j]); if (dist < minDist) { minDist = dist; cornerIndex = j; } } // 使用世界空间坐标 vertices[i] = worldCorners[cornerIndex]; } // 创建Mesh Mesh mesh = new Mesh(); mesh.vertices = vertices; mesh.uv = uvs; mesh.triangles = System.Array.ConvertAll(triangles, x => (int)x); mesh.RecalculateNormals(); mesh.RecalculateBounds(); return mesh; } /// /// 获取默认Sprite材质 /// private static Material GetDefaultSpriteMaterial() { // 使用Unity内置的Sprites/Default材质 return new Material(Shader.Find("Sprites/Default")); } /// /// 从RenderTexture读取像素数据 /// private static Texture2D ReadRenderTexture(RenderTexture renderTexture) { RenderTexture previousActive = RenderTexture.active; RenderTexture.active = renderTexture; Texture2D texture2D = new Texture2D( renderTexture.width, renderTexture.height, TextureFormat.RGBA32, false ); texture2D.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0); texture2D.Apply(); RenderTexture.active = previousActive; return texture2D; } /// /// 将Texture2D转换为Sprite /// private static Sprite TextureToSprite(Texture2D texture, float pixelsPerUnit) { Sprite sprite = Sprite.Create( texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), // pivot在中心 pixelsPerUnit ); return sprite; } /// /// 清理临时资源 /// private static void CleanupResources(Camera camera, RenderTexture renderTexture) { if (camera != null) { if (camera.gameObject != null) { UnityEngine.Object.DestroyImmediate(camera.gameObject); } } if (renderTexture != null) { renderTexture.Release(); UnityEngine.Object.DestroyImmediate(renderTexture); } } #region 异步版本 /// /// 异步合并多个SpriteRenderer为一个Sprite(协程版本) /// /// 要合并的SpriteRenderer数组 /// 完成回调 /// 输出纹理宽度(像素),0则自动计算 /// 输出纹理高度(像素),0则自动计算 /// Sprite的pixelsPerUnit /// 每帧处理的Sprite数量,用于控制性能 /// 协程 public static IEnumerator MergeSpriteRenderersAsync( SpriteRenderer[] renderers, Action onComplete, int textureWidth = 0, int textureHeight = 0, float pixelsPerUnit = 100f, int spritesPerFrame = 5) { if (renderers == null || renderers.Length == 0) { Debug.LogError("SpriteRenderer数组为空"); onComplete?.Invoke(new MergeResult()); yield break; } // 过滤掉无效的renderer var validRenderers = renderers.Where(r => r != null && r.sprite != null).ToArray(); if (validRenderers.Length == 0) { Debug.LogError("没有有效的SpriteRenderer"); onComplete?.Invoke(new MergeResult()); yield break; } // 1. 计算世界空间包围盒(分帧执行) Bounds worldBounds = new Bounds(); bool boundsCalculated = false; yield return CalculateWorldBoundsAsync(validRenderers, result => { worldBounds = result; boundsCalculated = true; }); while (!boundsCalculated) { yield return null; } // 2. 确定纹理尺寸 if (textureWidth <= 0 || textureHeight <= 0) { float aspect = worldBounds.size.x / worldBounds.size.y; if (aspect > 1f) { textureWidth = Mathf.NextPowerOfTwo(Mathf.CeilToInt(worldBounds.size.x * pixelsPerUnit)); textureHeight = Mathf.NextPowerOfTwo(Mathf.CeilToInt(textureWidth / aspect)); } else { textureHeight = Mathf.NextPowerOfTwo(Mathf.CeilToInt(worldBounds.size.y * pixelsPerUnit)); textureWidth = Mathf.NextPowerOfTwo(Mathf.CeilToInt(textureHeight * aspect)); } } // 3. 创建RenderTexture和临时Camera RenderTexture renderTexture = null; Camera tempCamera = null; try { renderTexture = CreateRenderTexture(textureWidth, textureHeight); tempCamera = CreateTempCamera(worldBounds, renderTexture); // 4. 异步渲染SpriteRenderer到RenderTexture(分帧执行) yield return RenderSpritesToTextureAsync(validRenderers, tempCamera, spritesPerFrame); // 5. 读取为Texture2D(分帧执行) Texture2D texture2D = null; bool textureRead = false; yield return ReadRenderTextureAsync(renderTexture, result => { texture2D = result; textureRead = true; }); while (!textureRead) { yield return null; } // 6. 转换为Sprite Sprite sprite = TextureToSprite(texture2D, pixelsPerUnit); // 7. 返回结果 onComplete?.Invoke(new MergeResult { sprite = sprite, worldPosition = worldBounds.center, worldBounds = worldBounds }); } finally { // 清理资源 CleanupResources(tempCamera, renderTexture); } } /// /// 异步计算世界空间包围盒 /// private static IEnumerator CalculateWorldBoundsAsync(SpriteRenderer[] renderers, Action onComplete) { if (renderers.Length == 0) { onComplete?.Invoke(new Bounds()); yield break; } bool first = true; Bounds bounds = new Bounds(); // 分帧计算,每帧处理一部分 int processed = 0; int batchSize = 10; // 每帧处理的renderer数量 while (processed < renderers.Length) { int endIndex = Mathf.Min(processed + batchSize, renderers.Length); for (int i = processed; i < endIndex; i++) { var renderer = renderers[i]; if (renderer.sprite == null) continue; Bounds spriteBounds = renderer.sprite.bounds; Vector3 worldPos = renderer.transform.position; Vector3 center = worldPos + spriteBounds.center; Vector3 size = spriteBounds.size; Vector3[] corners = new Vector3[] { center + new Vector3(-size.x / 2, -size.y / 2, 0), center + new Vector3(size.x / 2, -size.y / 2, 0), center + new Vector3(-size.x / 2, size.y / 2, 0), center + new Vector3(size.x / 2, size.y / 2, 0) }; if (first) { bounds = new Bounds(corners[0], Vector3.zero); first = false; } foreach (var corner in corners) { bounds.Encapsulate(corner); } } processed = endIndex; yield return null; // 每批处理完后让出一帧 } onComplete?.Invoke(bounds); } /// /// 异步渲染SpriteRenderer到RenderTexture(分帧执行) /// private static IEnumerator RenderSpritesToTextureAsync( SpriteRenderer[] renderers, Camera camera, int spritesPerFrame) { // 按sortingOrder排序 var sortedRenderers = renderers .OrderBy(r => r.sortingLayerID) .ThenBy(r => r.sortingOrder) .ToArray(); // 使用CommandBuffer来确保立即渲染 CommandBuffer cmd = new CommandBuffer(); cmd.name = "SpriteMerger"; // 设置RenderTexture cmd.SetRenderTarget(camera.targetTexture); cmd.ClearRenderTarget(true, true, new Color(0, 0, 0, 0)); // 预先创建所有Mesh(分帧执行) List meshes = new List(); List materials = new List(); List mpbs = new List(); int processed = 0; while (processed < sortedRenderers.Length) { int endIndex = Mathf.Min(processed + spritesPerFrame, sortedRenderers.Length); for (int i = processed; i < endIndex; i++) { var renderer = sortedRenderers[i]; if (renderer.sprite == null) continue; Mesh mesh = CreateSpriteMesh(renderer); if (mesh == null) continue; Material material = renderer.sharedMaterial != null ? renderer.sharedMaterial : GetDefaultSpriteMaterial(); if (material == null) continue; MaterialPropertyBlock mpb = new MaterialPropertyBlock(); renderer.GetPropertyBlock(mpb); Color color = renderer.color; mpb.SetColor("_Color", color); meshes.Add(mesh); materials.Add(material); mpbs.Add(mpb); } processed = endIndex; yield return null; // 每批处理完后让出一帧 } // 一次性绘制所有Mesh for (int i = 0; i < meshes.Count; i++) { cmd.DrawMesh(meshes[i], Matrix4x4.identity, materials[i], 0, 0, mpbs[i]); } // 执行CommandBuffer Graphics.ExecuteCommandBuffer(cmd); cmd.Dispose(); // 清理临时创建的Mesh(避免内存泄漏) foreach (var mesh in meshes) { if (mesh != null) { UnityEngine.Object.DestroyImmediate(mesh); } } } /// /// 异步从RenderTexture读取像素数据 /// private static IEnumerator ReadRenderTextureAsync(RenderTexture renderTexture, Action onComplete) { // ReadPixels是同步操作,但我们可以分块读取来减少卡顿 RenderTexture previousActive = RenderTexture.active; RenderTexture.active = renderTexture; Texture2D texture2D = new Texture2D( renderTexture.width, renderTexture.height, TextureFormat.RGBA32, false ); // 读取像素(这个操作可能比较耗时) texture2D.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0); yield return null; // 让出一帧 texture2D.Apply(); RenderTexture.active = previousActive; onComplete?.Invoke(texture2D); } /// /// 启动异步合并协程(需要MonoBehaviour实例) /// /// 用于启动协程的MonoBehaviour实例 /// 要合并的SpriteRenderer数组 /// 完成回调 /// 输出纹理宽度(像素),0则自动计算 /// 输出纹理高度(像素),0则自动计算 /// Sprite的pixelsPerUnit /// 每帧处理的Sprite数量 public static void StartMergeAsync( MonoBehaviour monoBehaviour, SpriteRenderer[] renderers, Action onComplete, int textureWidth = 0, int textureHeight = 0, float pixelsPerUnit = 100f, int spritesPerFrame = 5) { if (monoBehaviour == null) { Debug.LogError("MonoBehaviour实例为空,无法启动协程"); onComplete?.Invoke(new MergeResult()); return; } monoBehaviour.StartCoroutine(MergeSpriteRenderersAsync( renderers, onComplete, textureWidth, textureHeight, pixelsPerUnit, spritesPerFrame )); } /// /// 使用MonoCenter启动异步合并协程(推荐) /// public static void StartMergeAsyncWithMonoCenter( SpriteRenderer[] renderers, Action onComplete, int textureWidth = 0, int textureHeight = 0, float pixelsPerUnit = 100f, int spritesPerFrame = 5) { if (MonoCenter.Mono == null) { Debug.LogError("MonoCenter未初始化,无法启动协程"); onComplete?.Invoke(new MergeResult()); return; } StartMergeAsync( MonoCenter.Mono, renderers, onComplete, textureWidth, textureHeight, pixelsPerUnit, spritesPerFrame ); } #endregion } }