Skip to content

Instantly share code, notes, and snippets.

@koras
Forked from ostryzhnyi/SpriteSlicer.cs
Created March 24, 2026 17:43
Show Gist options
  • Select an option

  • Save koras/da1dbd98de058ed7bd0172592307fca3 to your computer and use it in GitHub Desktop.

Select an option

Save koras/da1dbd98de058ed7bd0172592307fca3 to your computer and use it in GitHub Desktop.
SpriteSlicer
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Cysharp.Threading.Tasks;
using Editor.CMSEditor;
using Newtonsoft.Json;
using ProjectX.CodeBase.Core.Tags;
using ProjectX.CodeBase.Level.Characters;
using ProjectX.CodeBase.SpriteSlicer.Data;
using Runtime;
using UnityEditor;
using UnityEngine;
namespace ProjectX.CodeBase.SpriteSlicer.Editor
{
public class SpriteSlicerLogic
{
private Sprite _sprite;
private SpriteSliceData _sliceData;
private SpritePiece _currentPiece;
private int _gridRows = 2;
private int _gridCols = 2;
private List<Vector2> _horizontalLines = new List<Vector2>();
private List<Vector2> _verticalLines = new List<Vector2>();
private Rect _previewRect;
public LevelCardView CardView;
public Sprite Sprite => _sprite;
public SpriteSliceData SliceData => _sliceData;
public SpritePiece CurrentPiece => _currentPiece;
public int GridRows => _gridRows;
public int GridCols => _gridCols;
public List<Vector2> HorizontalLines => _horizontalLines;
public List<Vector2> VerticalLines => _verticalLines;
private string _defaultName;
private LevelCMSEntityPfb _entity;
public Rect PreviewRect
{
get => _previewRect;
set => _previewRect = value;
}
public void SetSprite(Sprite sprite)
{
_sprite = sprite;
ValidateAndInitializeSprite();
}
public void SetGridSize(int rows, int cols)
{
if (rows != _gridRows || cols != _gridCols)
{
_gridRows = Mathf.Max(1, rows);
_gridCols = Mathf.Max(1, cols);
GenerateGrid();
}
}
public void ValidateAndInitializeSprite()
{
if (_sprite == null)
{
_sliceData = null;
_currentPiece = null;
return;
}
InitializeSliceData();
}
public void InitializeSliceData()
{
if (_sprite == null) return;
string assetPath = AssetDatabase.GetAssetPath(_sprite);
_sliceData = new SpriteSliceData(assetPath, new Vector2(_sprite.rect.width, _sprite.rect.height));
_currentPiece = null;
GenerateGrid();
}
public void RestoreRootGrid()
{
if (_sliceData != null && _sliceData.Pieces.Count > 0)
{
LoadGridFromPieces(_sliceData.Pieces, GetCurrentRect());
}
else
{
GenerateGrid();
}
}
public void SetupSlicing(SpritePiece piece)
{
_currentPiece = piece;
GenerateGrid();
}
public void SelectPieceForEditing(SpritePiece piece)
{
_currentPiece = piece;
if (piece.Children.Count > 0)
{
LoadGridFromPieces(piece.Children, piece.Rect);
}
else
{
_gridRows = 1;
_gridCols = 1;
_horizontalLines.Clear();
_verticalLines.Clear();
}
}
public void SelectRootPiece()
{
_currentPiece = null;
RestoreRootGrid();
}
public void GenerateGrid()
{
_horizontalLines.Clear();
_verticalLines.Clear();
for (int i = 1; i < _gridRows; i++)
{
_horizontalLines.Add(new Vector2((float)i / _gridRows, 0));
}
for (int i = 1; i < _gridCols; i++)
{
_verticalLines.Add(new Vector2((float)i / _gridCols, 0));
}
}
public void ApplySlice()
{
if (_sprite == null) return;
if (_currentPiece == null)
{
_sliceData.Pieces.Clear();
CreatePiecesFromGrid(_sliceData.Pieces, _sprite.name, GetCurrentRect(), 0);
}
else
{
_currentPiece.Children.Clear();
CreatePiecesFromGrid(_currentPiece.Children, _currentPiece.Name, _currentPiece.Rect, ++_currentPiece.DepthIndex);
}
}
private void CreatePiecesFromGrid(List<SpritePiece> targetList, string baseName, Rect baseRect, byte depth)
{
List<float> hLines = new List<float> { 0f };
foreach (var line in _horizontalLines)
hLines.Add(line.x);
hLines.Add(1f);
hLines.Sort();
List<float> vLines = new List<float> { 0f };
foreach (var line in _verticalLines)
vLines.Add(line.x);
vLines.Add(1f);
vLines.Sort();
int pieceIndex = 1;
int totalRows = hLines.Count - 1;
int totalCols = vLines.Count - 1;
for (int row = 0; row < totalRows; row++)
{
for (int col = 0; col < totalCols; col++)
{
float x = baseRect.x + vLines[col] * baseRect.width;
float y = baseRect.y + hLines[row] * baseRect.height;
int width = Mathf.RoundToInt((vLines[col + 1] - vLines[col]) * baseRect.width);
int height = Mathf.RoundToInt((hLines[row + 1] - hLines[row]) * baseRect.height);
Rect pieceRect = new Rect(x, y, width, height);
string pieceName = $"{baseName}_{pieceIndex}";
bool isBorder = DetermineIfBorder(row, col, totalRows, totalCols, _currentPiece, baseRect);
SpritePiece piece = new SpritePiece(pieceName, pieceRect, depth, isBorder);
targetList.Add(piece);
pieceIndex++;
}
}
}
private bool DetermineIfBorder(int row, int col, int totalRows, int totalCols, SpritePiece parentPiece, Rect currentRect)
{
if (parentPiece == null)
{
return row == 0 || row == totalRows - 1 || col == 0 || col == totalCols - 1;
}
if (!parentPiece.IsBorder)
{
return false;
}
var parentBorderSides = GetParentBorderSides(parentPiece.Rect);
bool isBorder = false;
if (parentBorderSides.isTopBorder && row == 0)
isBorder = true;
if (parentBorderSides.isBottomBorder && row == totalRows - 1)
isBorder = true;
if (parentBorderSides.isLeftBorder && col == 0)
isBorder = true;
if (parentBorderSides.isRightBorder && col == totalCols - 1)
isBorder = true;
return isBorder;
}
private (bool isTopBorder, bool isBottomBorder, bool isLeftBorder, bool isRightBorder) GetParentBorderSides(Rect parentRect)
{
if (_sliceData == null || _sliceData.Pieces.Count == 0)
return (false, false, false, false);
var allMainPieces = _sliceData.Pieces;
if (allMainPieces.Count == 0)
return (false, false, false, false);
var minX = allMainPieces.Min(p => p.Rect.x);
var minY = allMainPieces.Min(p => p.Rect.y);
var maxX = allMainPieces.Max(p => p.Rect.x);
var maxY = allMainPieces.Max(p => p.Rect.y);
var tolerance = 1f;
bool isTopBorder = Mathf.Abs(parentRect.y - minY) < tolerance;
bool isBottomBorder = Mathf.Abs(parentRect.y - maxY) < tolerance;
bool isLeftBorder = Mathf.Abs(parentRect.x - minX) < tolerance;
bool isRightBorder = Mathf.Abs(parentRect.x - maxX) < tolerance;
return (isTopBorder, isBottomBorder, isLeftBorder, isRightBorder);
}
private void LoadGridFromPieces(List<SpritePiece> pieces, Rect parentRect)
{
_horizontalLines.Clear();
_verticalLines.Clear();
HashSet<float> hLines = new HashSet<float>();
HashSet<float> vLines = new HashSet<float>();
foreach (var piece in pieces)
{
float topNorm = (piece.Rect.y - parentRect.y) / parentRect.height;
float bottomNorm = (piece.Rect.y + piece.Rect.height - parentRect.y) / parentRect.height;
float leftNorm = (piece.Rect.x - parentRect.x) / parentRect.width;
float rightNorm = (piece.Rect.x + piece.Rect.width - parentRect.x) / parentRect.width;
if (topNorm > 0.001f && topNorm < 0.999f) hLines.Add(topNorm);
if (bottomNorm > 0.001f && bottomNorm < 0.999f) hLines.Add(bottomNorm);
if (leftNorm > 0.001f && leftNorm < 0.999f) vLines.Add(leftNorm);
if (rightNorm > 0.001f && rightNorm < 0.999f) vLines.Add(rightNorm);
}
var sortedHLines = hLines.OrderBy(x => x).ToList();
var sortedVLines = vLines.OrderBy(x => x).ToList();
foreach (float h in sortedHLines)
{
_horizontalLines.Add(new Vector2(h, 0));
}
foreach (float v in sortedVLines)
{
_verticalLines.Add(new Vector2(v, 0));
}
_gridRows = sortedHLines.Count + 1;
_gridCols = sortedVLines.Count + 1;
Debug.Log($"Loaded grid: {_gridRows}x{_gridCols} from {pieces.Count} pieces");
}
public Rect GetCurrentRect()
{
if (_currentPiece != null)
return _currentPiece.Rect;
if (_sprite != null)
return _sprite.rect;
return new Rect();
}
public Rect GetCurrentSpriteUV()
{
if (_sprite == null)
return new Rect();
Texture2D texture = _sprite.texture;
Rect spriteRect = GetCurrentRect();
return new Rect(
spriteRect.x / texture.width,
spriteRect.y / texture.height,
spriteRect.width / texture.width,
spriteRect.height / texture.height
);
}
public void HandleInput()
{
Event e = Event.current;
if (e == null) return;
if (e.type == EventType.MouseDown && e.button == 0)
{
if (_previewRect.Contains(e.mousePosition))
{
Vector2 localPos = e.mousePosition - new Vector2(_previewRect.x, _previewRect.y);
Vector2 normalizedPos = new Vector2(localPos.x / _previewRect.width, localPos.y / _previewRect.height);
SpritePiece clickedPiece = GetPieceAtPosition(normalizedPos);
if (clickedPiece != null)
{
SelectPieceForEditing(clickedPiece);
}
e.Use();
}
}
}
private SpritePiece GetPieceAtPosition(Vector2 normalizedPos)
{
List<SpritePiece> piecesToCheck = null;
Rect currentRect = GetCurrentRect();
if (_currentPiece == null)
{
piecesToCheck = _sliceData?.Pieces;
}
else
{
piecesToCheck = _currentPiece.Children;
}
if (piecesToCheck == null || piecesToCheck.Count == 0) return null;
normalizedPos.y = 1f - normalizedPos.y;
Vector2 worldPos = new Vector2(
currentRect.x + normalizedPos.x * currentRect.width,
currentRect.y + normalizedPos.y * currentRect.height
);
foreach (var piece in piecesToCheck)
{
if (piece.Rect.Contains(worldPos))
{
return piece;
}
}
return null;
}
public void ImportFromEntity()
{
string path = EditorUtility.OpenFilePanel("Import Sprite Slice Data", "", "prefab");
if (!string.IsNullOrEmpty(path))
{
var localPath = GetLocalPath(path);
var level = AssetDatabase.LoadAssetAtPath<LevelCMSEntityPfb>(localPath);
CardView = level.As<LevelCardTag>().CardView;
string json = level.As<JsonTag>().Json.text;
_sliceData = JsonConvert.DeserializeObject<SpriteSliceData>(json);
if (!string.IsNullOrEmpty(_sliceData.OriginalSpritePath))
{
_sprite = AssetDatabase.LoadAssetAtPath<Sprite>(_sliceData.OriginalSpritePath);
}
_currentPiece = null;
if (_sliceData.Pieces.Count > 0)
{
LoadGridFromPieces(_sliceData.Pieces, GetCurrentRect());
}
else
{
GenerateGrid();
}
Debug.Log($"Imported sprite slice data from: {path}");
}
}
public void ResetToOriginal()
{
if (_sprite == null) return;
InitializeSliceData();
SetupSlicing(null);
}
private string jsonPath = "";
public void ExportToJSON()
{
if (_sliceData == null || _sprite == null) return ;
var settings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore
};
string json = JsonConvert.SerializeObject(_sliceData, settings);
string defaultName = _sprite.name + "_slices";
string path = EditorUtility.SaveFilePanel("Export Sprite Slice Data", "", defaultName, "json");
if (!string.IsNullOrEmpty(path))
{
File.WriteAllText(path, json);
jsonPath = GetLocalPath(path);
Debug.Log($"Exported sprite slice data to: {path}");
AssetDatabase.Refresh();
}
}
public void ExportEntity()
{
string path = EditorUtility.SaveFilePanel("Export Level Entity", "", _defaultName, "prefab");
var localPath = GetLocalPath(path);
if (!string.IsNullOrEmpty(localPath))
{
if (AssetDatabase.CopyAsset("Assets/Resources/CMS/Prefabs/Level_Temaplate_Entity.prefab", localPath))
{
var newLevel = AssetDatabase.LoadAssetAtPath<LevelCMSEntityPfb>(localPath);
CMSEntityIdSetter.AutoFillCMSIds();
CMS.Unload();
CMS.Init();
newLevel.As<TagSprite>().sprite = _sprite;
var jsonAsset = AssetDatabase.LoadAssetAtPath<TextAsset>(jsonPath);
newLevel.As<JsonTag>().Json = jsonAsset;
newLevel.As<AnimationLocalUrl>().VideoClipUrl = SliceData.VideoUrl;
var types = new List<LevelType>();
if(SliceData.IsMirrored)
types.Add(LevelType.Mirrored);
if(SliceData.IsRotatable)
types.Add(LevelType.Rotatable);
newLevel.As<LevelTypeTag>().Types = types.ToArray();
newLevel.As<LevelCardTag>().CardView = CardView;
newLevel.As<DescribeTag>().DescriptionLoc = SliceData.DescriptionLoc;
newLevel.Parse();
UnityEngine.Debug.Log(newLevel.As<AnimationLocalUrl>().VideoClipUrl + " " + SliceData.VideoUrl);
Debug.Log($"Exported entity data to: {localPath}");
EditorUtility.SetDirty(newLevel);
EditorApplication.ExecuteMenuItem("File/Save Project");
}
}
}
private string GetLocalPath(string path)
{
return "Assets/" + path.Split("Assets/")[1];
}
}
}
using System.Collections.Generic;
using ProjectX.CodeBase.Level.Characters;
using UnityEditor;
using UnityEngine;
namespace ProjectX.CodeBase.SpriteSlicer.Editor
{
public class SpriteSlicerDrawer
{
private readonly SpriteSlicerLogic _logic;
private Vector2 _scrollPosition;
public SpriteSlicerDrawer(SpriteSlicerLogic logic)
{
_logic = logic;
}
public void DrawGUI()
{
DrawToolbar();
if (_logic.Sprite == null)
{
DrawEmptyState();
return;
}
EditorGUILayout.BeginHorizontal();
DrawSpritePreview();
EditorGUILayout.BeginVertical();
DrawHierarchy();
DrawSpriteSettings();
EditorGUILayout.EndVertical();
DrawPieceSettings();
EditorGUILayout.EndHorizontal();
DrawControls();
}
private void DrawToolbar()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar, GUILayout.Height(25));
GUILayout.Label("🎬", GUILayout.Width(20));
EditorGUI.BeginChangeCheck();
DrawSpriteSelection();
if (EditorGUI.EndChangeCheck())
{
_logic.ValidateAndInitializeSprite();
}
GUILayout.FlexibleSpace();
DrawToolbarButtons();
EditorGUILayout.EndHorizontal();
}
private void DrawSpriteSelection()
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Sprite:", GUILayout.Width(60));
EditorGUI.BeginChangeCheck();
Sprite selected = (Sprite)EditorGUILayout.ObjectField(_logic.Sprite, typeof(Sprite), false, GUILayout.Width(200));
if (EditorGUI.EndChangeCheck())
{
_logic.SetSprite(selected);
}
EditorGUILayout.EndHorizontal();
}
private void DrawToolbarButtons()
{
GUI.backgroundColor = new Color(0.7f, 1f, 0.7f);
if (GUILayout.Button("📁 Import", EditorStyles.toolbarButton, GUILayout.Width(70)))
{
_logic.ImportFromEntity();
}
GUI.backgroundColor = new Color(1f, 0.9f, 0.7f);
if (GUILayout.Button("🔄 Reset", EditorStyles.toolbarButton, GUILayout.Width(60)))
{
_logic.ResetToOriginal();
}
GUI.backgroundColor = new Color(0.7f, 0.9f, 1f);
if (GUILayout.Button("💾 Export Json", EditorStyles.toolbarButton, GUILayout.Width(120)))
{
_logic.ExportToJSON();
}
if (GUILayout.Button("💾 Export Entity", EditorStyles.toolbarButton, GUILayout.Width(120)))
{
_logic.ExportEntity();
}
GUI.backgroundColor = Color.white;
}
private void DrawEmptyState()
{
EditorGUILayout.Space(20);
EditorGUILayout.BeginVertical(GUI.skin.box);
EditorGUILayout.Space(10);
EditorGUILayout.LabelField("🎨 Sprite Slicer", EditorStyles.largeLabel);
EditorGUILayout.Space(5);
EditorGUILayout.LabelField("Select a sprite to slice!", EditorStyles.helpBox);
EditorGUILayout.Space(10);
EditorGUILayout.EndVertical();
}
private void DrawSpritePreview()
{
EditorGUILayout.BeginVertical(GUI.skin.box, GUILayout.Width(420));
EditorGUILayout.LabelField("🎯 Preview", EditorStyles.boldLabel);
if (_logic.Sprite != null)
{
Sprite currentSprite = _logic.Sprite;
Rect spriteArea = GUILayoutUtility.GetRect(800, 800);
Rect previewRect = CalculatePreviewRect(currentSprite, spriteArea);
_logic.PreviewRect = previewRect;
DrawSpriteBackground(previewRect);
DrawSpriteTexture(currentSprite, previewRect);
DrawPreviewOverlays(previewRect);
GUILayout.Space(10);
DrawPreviewInfo();
}
EditorGUILayout.EndVertical();
}
private Rect CalculatePreviewRect(Sprite sprite, Rect area)
{
float spriteAspect = sprite.rect.width / sprite.rect.height;
float areaAspect = area.width / area.height;
Rect previewRect = new Rect();
if (spriteAspect > areaAspect)
{
previewRect.width = area.width * 0.95f;
previewRect.height = (area.width * 0.95f) / spriteAspect;
}
else
{
previewRect.height = area.height * 0.95f;
previewRect.width = (area.height * 0.95f) * spriteAspect;
}
previewRect.x = area.x + (area.width - previewRect.width) * 0.5f;
previewRect.y = area.y + (area.height - previewRect.height) * 0.5f;
return previewRect;
}
private void DrawSpriteBackground(Rect previewRect)
{
EditorGUI.DrawRect(
new Rect(previewRect.x - 2, previewRect.y - 2, previewRect.width + 4, previewRect.height + 4),
new Color(0.3f, 0.3f, 0.3f));
EditorGUI.DrawRect(
new Rect(previewRect.x - 1, previewRect.y - 1, previewRect.width + 2, previewRect.height + 2),
Color.white);
}
private void DrawSpriteTexture(Sprite sprite, Rect previewRect)
{
Rect uvRect = _logic.GetCurrentSpriteUV();
GUI.DrawTextureWithTexCoords(previewRect, sprite.texture, uvRect);
}
private void DrawPreviewOverlays(Rect previewRect)
{
DrawExistingPieces(previewRect);
DrawGrid(previewRect);
DrawSliceLines(previewRect);
}
private void DrawPreviewInfo()
{
string info = _logic.CurrentPiece != null ? $"Editing: {_logic.CurrentPiece.Name}" : "Editing: Original Sprite";
EditorGUILayout.LabelField(info, EditorStyles.centeredGreyMiniLabel);
}
private void DrawHierarchy()
{
EditorGUILayout.BeginVertical(GUI.skin.box, GUILayout.Width(480));
DrawHierarchyHeader();
DrawCurrentPieceInfo();
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition, GUILayout.Height(350));
if (_logic.SliceData != null && _logic.SliceData.Pieces.Count > 0)
{
DrawPieceHierarchy(_logic.SliceData.Pieces, 0);
}
else
{
EditorGUILayout.LabelField("No slices yet. Create some!", EditorStyles.centeredGreyMiniLabel);
}
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
private void DrawHierarchyHeader()
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("🌳 Sprite Hierarchy", EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
Color originalBg = GUI.backgroundColor;
bool isRootSelected = _logic.CurrentPiece == null;
if (isRootSelected)
{
GUI.backgroundColor = new Color(0.3f, 0.8f, 0.3f);
}
else
{
GUI.backgroundColor = new Color(0.8f, 1f, 0.8f);
}
if (GUILayout.Button("🏠 Root", GUILayout.Width(60), GUILayout.Height(20)))
{
_logic.SelectRootPiece();
}
GUI.backgroundColor = originalBg;
EditorGUILayout.EndHorizontal();
}
private void DrawCurrentPieceInfo()
{
EditorGUILayout.Space(5);
if (_logic.CurrentPiece != null)
{
EditorGUILayout.LabelField($"📍 Current: {TruncateText(_logic.CurrentPiece.Name, 50)}", EditorStyles.miniLabel);
}
else
{
EditorGUILayout.LabelField("📍 Current: Original Sprite", EditorStyles.miniLabel);
}
EditorGUILayout.Space(5);
}
private void DrawPieceHierarchy(List<SpritePiece> pieces, int indent)
{
foreach (var piece in pieces)
{
bool hasChildren = piece.Children.Count > 0;
bool isSelected = _logic.CurrentPiece == piece;
Color originalColor = GUI.backgroundColor;
if (isSelected)
{
GUI.backgroundColor = new Color(0.3f, 0.6f, 1f, 0.6f);
}
EditorGUILayout.BeginHorizontal(GUI.skin.box);
GUILayout.Space(indent * 15);
if (hasChildren)
{
piece.IsExpanded = EditorGUILayout.Foldout(piece.IsExpanded, "");
if (GUILayout.Button($"📁 {piece.Name}", isSelected ? EditorStyles.whiteBoldLabel : EditorStyles.label, GUILayout.ExpandWidth(true)))
{
_logic.SelectPieceForEditing(piece);
}
}
else
{
GUILayout.Space(12);
if (GUILayout.Button($"📄 {piece.Name}", isSelected ? EditorStyles.whiteBoldLabel : EditorStyles.label, GUILayout.ExpandWidth(true)))
{
_logic.SelectPieceForEditing(piece);
}
}
GUI.backgroundColor = new Color(0.9f, 0.9f, 1f, 0.8f);
if (GUILayout.Button("✏️", GUILayout.Width(25), GUILayout.Height(18)))
{
_logic.SelectPieceForEditing(piece);
}
GUI.backgroundColor = originalColor;
EditorGUILayout.EndHorizontal();
if (piece.IsExpanded && hasChildren)
{
DrawPieceHierarchy(piece.Children, indent + 1);
}
}
}
private void DrawControls()
{
EditorGUILayout.Space(10);
if (_logic.Sprite == null) return;
EditorGUILayout.BeginVertical(GUI.skin.box);
EditorGUILayout.LabelField("⚙️ Slice Settings", EditorStyles.boldLabel);
DrawGridControls();
DrawSliceInfo();
EditorGUILayout.EndVertical();
}
private void DrawGridControls()
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Grid Size:", GUILayout.Width(70));
GUI.backgroundColor = new Color(0.9f, 1f, 0.9f);
EditorGUILayout.BeginVertical(GUILayout.Width(80));
EditorGUI.BeginChangeCheck();
int newRows = EditorGUILayout.IntField("Rows", _logic.GridRows);
int newCols = EditorGUILayout.IntField("Cols", _logic.GridCols);
if (EditorGUI.EndChangeCheck())
{
_logic.SetGridSize(newRows, newCols);
}
EditorGUILayout.EndVertical();
GUI.backgroundColor = Color.white;
GUILayout.FlexibleSpace();
GUI.backgroundColor = new Color(0.7f, 1f, 0.7f);
if (GUILayout.Button("🔥 Apply Slice", GUILayout.Height(25), GUILayout.Width(100)))
{
_logic.ApplySlice();
}
GUI.backgroundColor = Color.white;
EditorGUILayout.EndHorizontal();
}
private void DrawSliceInfo()
{
EditorGUILayout.Space(5);
int totalPieces = _logic.GridRows * _logic.GridCols;
EditorGUILayout.LabelField($"📊 Will create {totalPieces} pieces", EditorStyles.helpBox);
EditorGUILayout.LabelField("💡 Tip: Drag red lines to adjust slice positions", EditorStyles.helpBox);
}
private void DrawGrid(Rect previewRect)
{
if (_logic.Sprite == null) return;
Handles.BeginGUI();
Handles.color = new Color(1, 1, 1, 0.4f);
foreach (var line in _logic.HorizontalLines)
{
float y = Mathf.Lerp(previewRect.y, previewRect.y + previewRect.height, line.x);
Handles.DrawLine(new Vector3(previewRect.x, y), new Vector3(previewRect.x + previewRect.width, y));
}
foreach (var line in _logic.VerticalLines)
{
float x = Mathf.Lerp(previewRect.x, previewRect.x + previewRect.width, line.x);
Handles.DrawLine(new Vector3(x, previewRect.y), new Vector3(x, previewRect.y + previewRect.height));
}
Handles.EndGUI();
}
private void DrawSliceLines(Rect previewRect)
{
if (_logic.Sprite == null) return;
Handles.BeginGUI();
Handles.color = new Color(1f, 0.3f, 0.3f, 0.9f);
for (int i = 0; i < _logic.HorizontalLines.Count; i++)
{
float y = Mathf.Lerp(previewRect.y, previewRect.y + previewRect.height, _logic.HorizontalLines[i].x);
Vector3 start = new Vector3(previewRect.x, y);
Vector3 end = new Vector3(previewRect.x + previewRect.width, y);
Handles.DrawAAPolyLine(3f, start, end);
}
for (int i = 0; i < _logic.VerticalLines.Count; i++)
{
float x = Mathf.Lerp(previewRect.x, previewRect.x + previewRect.width, _logic.VerticalLines[i].x);
Vector3 start = new Vector3(x, previewRect.y);
Vector3 end = new Vector3(x, previewRect.y + previewRect.height);
Handles.DrawAAPolyLine(3f, start, end);
}
Handles.EndGUI();
}
private void DrawExistingPieces(Rect previewRect)
{
if (_logic.CurrentPiece == null) return;
List<SpritePiece> piecesToShow = _logic.CurrentPiece.Children;
if (piecesToShow == null || piecesToShow.Count == 0) return;
Handles.BeginGUI();
Rect currentRect = _logic.GetCurrentRect();
foreach (var piece in piecesToShow)
{
float normalizedX = (piece.Rect.x - currentRect.x) / currentRect.width;
float normalizedY = (piece.Rect.y - currentRect.y) / currentRect.height;
float normalizedWidth = piece.Rect.width / currentRect.width;
float normalizedHeight = piece.Rect.height / currentRect.height;
float x = previewRect.x + normalizedX * previewRect.width;
float y = previewRect.y + normalizedY * previewRect.height;
float width = normalizedWidth * previewRect.width;
float height = normalizedHeight * previewRect.height;
Rect pieceRect = new Rect(x, y, width, height);
Handles.color = new Color(0, 1, 0, 0.3f);
Handles.DrawSolidRectangleWithOutline(pieceRect, new Color(0, 1, 0, 0.1f), Color.green);
GUI.Label(new Rect(x + 2, y + 2, width - 4, 16), TruncateText(piece.Name, 15), EditorStyles.miniLabel);
}
Handles.EndGUI();
}
private string TruncateText(string text, int maxLength)
{
if (string.IsNullOrEmpty(text) || text.Length <= maxLength)
return text;
return text.Substring(0, maxLength - 3) + "...";
}
private void DrawSpriteSettings()
{
if (_logic.SliceData == null) return;
EditorGUILayout.BeginVertical(GUI.skin.box, GUILayout.Width(480));
EditorGUILayout.LabelField("🖼 Sprite Settings", EditorStyles.boldLabel);
EditorGUI.BeginChangeCheck();
_logic.SliceData.VideoUrl = EditorGUILayout.TextField("Video URL", _logic.SliceData.VideoUrl);
_logic.SliceData.DescriptionLoc = EditorGUILayout.TextField("Describe loc key", _logic.SliceData.DescriptionLoc);
_logic.SliceData.IsRotatable = EditorGUILayout.Toggle("Is Rotatable", _logic.SliceData.IsRotatable);
_logic.SliceData.IsMirrored = EditorGUILayout.Toggle("Is Mirrored", _logic.SliceData.IsMirrored);
_logic.CardView = (LevelCardView)EditorGUILayout.ObjectField("Card view",_logic.CardView, typeof(LevelCardView), false);
EditorGUILayout.EndVertical();
}
private void DrawPieceSettings()
{
if (_logic.CurrentPiece == null) return;
EditorGUILayout.BeginVertical(GUI.skin.box, GUILayout.Width(480));
EditorGUILayout.LabelField("🔒 Piece Settings", EditorStyles.boldLabel);
EditorGUI.BeginChangeCheck();
_logic.CurrentPiece.IsLock = EditorGUILayout.Toggle("Is Locked", _logic.CurrentPiece.IsLock);
EditorGUILayout.EndVertical();
}
}
}
using UnityEditor;
namespace ProjectX.CodeBase.SpriteSlicer.Editor
{
public class SpriteSlicer : EditorWindow
{
private SpriteSlicerDrawer _drawer;
private SpriteSlicerLogic _logic;
[MenuItem("Tools/Sprite Slicer")]
public static void ShowWindow()
{
GetWindow<SpriteSlicer>("Sprite Slicer");
}
private void OnEnable()
{
_logic = new SpriteSlicerLogic();
_drawer = new SpriteSlicerDrawer(_logic);
}
private void OnGUI()
{
_drawer.DrawGUI();
_logic.HandleInput();
}
}
}
using System;
using DG.Tweening;
using Sirenix.OdinInspector;
using UnityEngine;
namespace ProjectX.CodeBase.SpriteSlicer
{
public class SpritePieceComponent : MonoBehaviour
{
public AnimatedPieceMonoBeh AnimatedPieceMonoBeh;
[SerializeField, ReadOnly] private SpritePiece _pieceData;
private RenderTexture _videoTexture;
private Material _material;
private MaterialPropertyBlock _materialPropertyBlock;
private Renderer _renderer;
private Vector2 _uvMin;
private Vector2 _uvMax;
private float _rotationZ = 0f;
public SpritePiece PieceData => _pieceData;
[ShowInInspector] public float RotationZ
{
get => _rotationZ * Mathf.Rad2Deg;
set
{
_rotationZ = Quaternion.Euler(0f, 0f, value).eulerAngles.z * Mathf.Deg2Rad;
UpdateMaterialProperties();
}
}
private Vector3 _initScale = Vector2.one;
private void Awake()
{
_renderer = GetComponent<Renderer>();
if (_renderer == null)
{
Debug.LogError($"No Renderer found on {gameObject.name}");
}
_materialPropertyBlock = new MaterialPropertyBlock();
}
public void Initialize(SpritePiece pieceData, RenderTexture videoTexture, Material material,
Vector2 uvMin, Vector2 uvMax, Vector3 worldPosition, Vector3 worldScale)
{
_pieceData = pieceData;
_videoTexture = videoTexture;
_material = material;
_uvMin = uvMin;
_uvMax = uvMax;
transform.localPosition = worldPosition;
_initScale = new Vector3(CeilToDigits(worldScale.x, 2), CeilToDigits(worldScale.x, 2), worldPosition.z);
transform.localScale = _initScale;
if (_renderer != null)
{
_renderer.material = _material;
}
if(videoTexture == null || material == null)
return;
UpdateMaterialProperties();
}
public void ResetScale()
{
transform.DOScale(_initScale, 0.2f);
}
public static float CeilToDigits(float value, int digits)
{
float scale = (float)Math.Pow(10, digits);
return (float)Math.Ceiling(value * scale) / scale;
}
private void UpdateMaterialProperties()
{
if (_materialPropertyBlock == null || _renderer == null)
return;
_materialPropertyBlock.SetTexture("_MainTex", _videoTexture);
_materialPropertyBlock.SetVector("_UVMin", _uvMin);
_materialPropertyBlock.SetVector("_UVMax", _uvMax);
_materialPropertyBlock.SetFloat("_RotationZ", _rotationZ);
Vector2 uvCenter = new Vector2(0.5f, 0.5f);
_materialPropertyBlock.SetVector("_UVCenter", uvCenter);
_renderer.SetPropertyBlock(_materialPropertyBlock);
}
public void SetColor(Color color)
{
_materialPropertyBlock.SetColor("_Color", color);
_renderer.SetPropertyBlock(_materialPropertyBlock);
}
public Color GetColor()
{
return _materialPropertyBlock.GetColor("_Color");
}
[Button]
public void SetRotation(float rotationZ)
{
RotationZ = rotationZ;
}
public void AddRotation(float deltaRotation)
{
RotationZ += deltaRotation;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Cysharp.Threading.Tasks;
using DG.Tweening;
using ProjectX.CodeBase.Core;
using ProjectX.CodeBase.Feel;
using ProjectX.CodeBase.Level.InteractableObjects;
using Sirenix.OdinInspector;
using UnityEngine;
namespace ProjectX.CodeBase.SpriteSlicer
{
[RequireComponent(typeof(Renderer), typeof(Collider))]
public class AnimatedPieceMonoBeh : ManagedMonoBehaviour
{
[ReadOnly, ShowInInspector] public bool WasCompleted = false;
[ReadOnly, ShowInInspector] public bool IsBordered => SpritePiece.PieceData.IsBorder;
[HideInInspector] public List<InteractableImage> InteractableImages = new List<InteractableImage>();
public SpriteRenderer Outline;
public SpriteRenderer Lock;
public SpriteRenderer LockParent;
public BoxCollider Collider;
public SpritePieceComponent SpritePiece;
public SpriteRenderer Hover;
public bool LockHover = false;
private CancellationTokenSource _cancellationTokenSource;
private Color32 fullColor = new Color32(207, 104, 188, 255 / 2);
private Color32 halfColor = new Color32(207, 104, 188, 255 / 4);
private Color32 clear = new Color32(207, 104, 188, 0);
private void Awake()
{
_cancellationTokenSource = new CancellationTokenSource();
}
public void EnableLock()
{
LockParent.gameObject.SetActive(SpritePiece.PieceData.IsLock);
}
public void PlayLockAnim()
{
LockParent.transform.PunchScaleOnce(.5f,.2f);
}
public bool IsCompleted()
{
var isCompleted = InteractableImages.All(i => i.IsCompleted());
return isCompleted;
}
public int? GetCurrentRotationIndex()
{
var rotatable = InteractableImages
.FirstOrDefault(i => i is RotatableImageView) as RotatableImageView;
return rotatable?.CurrentRotationIndex;
}
public bool GetCurrentMirror()
{
return transform.localScale.x > 0;
}
public void SetStateInteractables(bool state)
{
foreach (var interactableImage in InteractableImages)
{
interactableImage.enabled = false;
}
}
private void OnDestroy()
{
_cancellationTokenSource.Cancel();
}
private void OnMouseEnter()
{
if(LockParent.gameObject.activeSelf)
return;
_cancellationTokenSource.Cancel();
_cancellationTokenSource = new CancellationTokenSource();
if(!LockHover)
OnMouseEnterAsync(_cancellationTokenSource.Token).Forget();
}
private void OnMouseExit()
{
if(LockParent.gameObject.activeSelf)
return;
_cancellationTokenSource.Cancel();
_cancellationTokenSource = new CancellationTokenSource();
if(!LockHover)
OnMouseExitAsync(_cancellationTokenSource.Token).Forget();
}
private async UniTask OnMouseEnterAsync(CancellationToken token)
{
await UniTask.WaitForSeconds(.2f, cancellationToken: token);
await Hover.DOColor(fullColor, .5f).ToUniTask(TweenCancelBehaviour.Kill, token);
while (!token.IsCancellationRequested)
{
await Hover.DOColor(halfColor, .5f).ToUniTask(TweenCancelBehaviour.Kill, token);
await Hover.DOColor(fullColor, .5f).ToUniTask(TweenCancelBehaviour.Kill, token);
}
}
private async UniTask OnMouseExitAsync(CancellationToken token)
{
await UniTask.WaitForSeconds(.2f, cancellationToken: token);
await Hover.DOColor(clear, .5f).ToUniTask(TweenCancelBehaviour.Kill, token);
}
public void Resolve()
{
foreach (var interactableImage in InteractableImages)
{
interactableImage.Resolve();
}
}
public void SetRandom(bool canResolve = true)
{
foreach (var interactableImage in InteractableImages)
{
interactableImage.SetRandom(canResolve);
}
}
public void ResetSize()
{
SpritePiece.ResetScale();
}
}
}
using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using Newtonsoft.Json;
using ProjectX.CodeBase.Core;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Video;
namespace ProjectX.CodeBase.SpriteSlicer
{
public class VideoSlicer : MonoBehaviour
{
public IReadOnlyList<SpritePieceComponent> Pieces => _pieces;
[SerializeField] private VideoPlayer _videoPlayer;
[SerializeField] private RenderTexture _renderTexture;
[SerializeField] private Material _spriteMaterial;
[SerializeField] private SpritePieceComponent _planePrefab;
private readonly List<SpritePieceComponent> _pieces = new List<SpritePieceComponent>();
private string _jsonData;
private SpriteSliceData _sliceData;
private int _tryVideoLoading;
private bool _handlersBound;
private bool _prepared;
private bool _error;
public async UniTask Init(string jsonData, string clipName)
{
_jsonData = jsonData;
_tryVideoLoading = 0;
BindHandlersOnce();
var loaded = await TryLoadAndPlay(clipName);
if (!loaded)
{
G.OpenMenu().Forget();
return;
}
ParseJsonAndCreatePieces();
}
private void BindHandlersOnce()
{
if (_handlersBound) return;
_videoPlayer.errorReceived += OnVideoError;
_videoPlayer.prepareCompleted += OnVideoPrepared;
_videoPlayer.playOnAwake = false;
_videoPlayer.waitForFirstFrame = true;
_handlersBound = true;
}
private void OnVideoPrepared(VideoPlayer source)
{
_prepared = true;
}
private void OnVideoError(VideoPlayer vp, string message)
{
Debug.LogError($"Video error: {message}");
_error = true;
}
private async UniTask<bool> TryLoadAndPlay(string clipName)
{
var url = System.IO.Path.Combine(Application.streamingAssetsPath, clipName);
Debug.Log($"Video URL: {url}");
while (_tryVideoLoading < 3)
{
_tryVideoLoading++;
_prepared = false;
_error = false;
#if UNITY_WEBGL && !UNITY_EDITOR
var accessible = await CheckUrlAccessible(url);
if (!accessible)
{
Debug.LogError($"Video not accessible at url: {url}");
continue;
}
#endif
HardResetPlayer();
_videoPlayer.url = url;
_videoPlayer.Prepare();
var timeoutSeconds = 20f;
var start = Time.realtimeSinceStartup;
while (!_prepared && !_error && Time.realtimeSinceStartup - start < timeoutSeconds)
await UniTask.Yield(PlayerLoopTiming.Update);
if (_error)
{
Debug.LogError($"Video prepare error on try #{_tryVideoLoading}");
continue;
}
if (!_prepared)
{
Debug.LogError($"Video prepare timeout on try #{_tryVideoLoading}");
continue;
}
Debug.Log($"Video prepared successfully on try #{_tryVideoLoading}");
_videoPlayer.Play();
return true;
}
return false;
}
#if UNITY_WEBGL && !UNITY_EDITOR
private async UniTask<bool> CheckUrlAccessible(string url)
{
using var req = UnityWebRequest.Get(url);
req.downloadHandler = new DownloadHandlerBuffer();
await req.SendWebRequest();
if (req.result != UnityWebRequest.Result.Success)
{
Debug.LogError($"Video file not accessible: {req.error}");
return false;
}
return true;
}
#endif
private void HardResetPlayer()
{
if (_videoPlayer.isPlaying)
_videoPlayer.Stop();
_videoPlayer.url = null;
_videoPlayer.targetTexture = _renderTexture;
}
private void ParseJsonAndCreatePieces()
{
if (string.IsNullOrEmpty(_jsonData))
{
Debug.LogError("JSON data is empty");
return;
}
try
{
var settings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore
};
_sliceData = JsonConvert.DeserializeObject<SpriteSliceData>(_jsonData, settings);
if (_sliceData == null || _sliceData.Pieces == null)
{
Debug.LogError("Failed to parse JSON or no pieces found");
return;
}
CreateRootPieces();
}
catch (Exception e)
{
Debug.LogError($"Error parsing JSON: {e.Message}");
}
}
private void CreateRootPieces()
{
foreach (var piece in _sliceData.Pieces)
CreatePieceComponent(piece, null);
}
private SpritePieceComponent CreatePieceComponent(SpritePiece pieceData, Transform parent)
{
if (_planePrefab == null)
{
Debug.LogError("Plane prefab is not assigned");
return null;
}
var pieceComponent = Instantiate(_planePrefab, parent != null ? parent : transform);
pieceComponent.name = pieceData.Name;
var hasChildren = pieceData.Children != null && pieceData.Children.Count > 0;
if (!hasChildren)
{
Vector2 uvMin = new Vector2(
pieceData.Rect.x / _sliceData.OriginalSpriteSize.x,
pieceData.Rect.y / _sliceData.OriginalSpriteSize.y
);
Vector2 uvMax = new Vector2(
(pieceData.Rect.x + pieceData.Rect.width) / _sliceData.OriginalSpriteSize.x,
(pieceData.Rect.y + pieceData.Rect.height) / _sliceData.OriginalSpriteSize.y
);
float baseScale = 10f;
Vector3 worldScale = new Vector3(
(pieceData.Rect.width / _sliceData.OriginalSpriteSize.x) * baseScale,
(pieceData.Rect.height / _sliceData.OriginalSpriteSize.y) * baseScale,
1f
);
Vector3 worldPosition;
if (parent == null)
{
worldPosition = new Vector3(
((pieceData.Rect.x + pieceData.Rect.width * 0.5f) / _sliceData.OriginalSpriteSize.x - 0.5f) *
baseScale,
((pieceData.Rect.y + pieceData.Rect.height * 0.5f) / _sliceData.OriginalSpriteSize.y - 0.5f) *
baseScale,
0f
);
}
else
{
Rect parentRect = GetParentRect(parent);
float relativeX =
(pieceData.Rect.x + pieceData.Rect.width * 0.5f - parentRect.x - parentRect.width * 0.5f) /
_sliceData.OriginalSpriteSize.x * baseScale;
float relativeY =
(pieceData.Rect.y + pieceData.Rect.height * 0.5f - parentRect.y - parentRect.height * 0.5f) /
_sliceData.OriginalSpriteSize.y * baseScale;
worldPosition = new Vector3(relativeX, relativeY, 0f);
}
pieceComponent.Initialize(pieceData, _renderTexture, _spriteMaterial, uvMin, uvMax, worldPosition,
worldScale);
_pieces.Add(pieceComponent);
}
else
{
float baseScale = 10f;
Vector3 worldPosition;
if (parent == null)
{
worldPosition = new Vector3(
((pieceData.Rect.x + pieceData.Rect.width * 0.5f) / _sliceData.OriginalSpriteSize.x - 0.5f) *
baseScale,
((pieceData.Rect.y + pieceData.Rect.height * 0.5f) / _sliceData.OriginalSpriteSize.y - 0.5f) *
baseScale,
0f
);
}
else
{
Rect parentRect = GetParentRect(parent);
float relativeX =
(pieceData.Rect.x + pieceData.Rect.width * 0.5f - parentRect.x - parentRect.width * 0.5f) /
_sliceData.OriginalSpriteSize.x * baseScale;
float relativeY =
(pieceData.Rect.y + pieceData.Rect.height * 0.5f - parentRect.y - parentRect.height * 0.5f) /
_sliceData.OriginalSpriteSize.y * baseScale;
worldPosition = new Vector3(relativeX, relativeY, 0f);
}
pieceComponent.Initialize(pieceData, null, null, Vector2.zero, Vector2.zero, worldPosition,
Vector3.one);
var renderer = pieceComponent.GetComponent<Renderer>();
if (renderer != null) renderer.enabled = false;
foreach (var childPiece in pieceData.Children)
CreatePieceComponent(childPiece, pieceComponent.transform);
pieceComponent.AnimatedPieceMonoBeh.Outline.enabled = false;
pieceComponent.AnimatedPieceMonoBeh.Collider.enabled = false;
pieceComponent.AnimatedPieceMonoBeh.Lock.enabled = false;
pieceComponent.AnimatedPieceMonoBeh.LockParent.enabled = false;
}
return pieceComponent;
}
private Rect GetParentRect(Transform parent)
{
var parentComponent = parent.GetComponent<SpritePieceComponent>();
if (parentComponent != null && parentComponent.PieceData != null)
return parentComponent.PieceData.Rect;
return new Rect(0, 0, _sliceData.OriginalSpriteSize.x, _sliceData.OriginalSpriteSize.y);
}
private void OnDestroy()
{
if (_handlersBound)
{
_videoPlayer.errorReceived -= OnVideoError;
_videoPlayer.prepareCompleted -= OnVideoPrepared;
}
if (_renderTexture != null)
_renderTexture.Release();
}
}
}
using System;
using System.Collections.Generic;
using UnityEngine;
using Newtonsoft.Json;
[Serializable]
public class SpritePiece
{
[JsonProperty("Name")]
public string Name;
[JsonProperty("Rect")]
public Rect Rect;
[JsonProperty("DepthIndex")]
public byte DepthIndex;
[JsonProperty("IsBorder")]
public bool IsBorder;
[JsonProperty("Children")]
public List<SpritePiece> Children = new List<SpritePiece>();
[JsonProperty("IsExpanded")]
public bool IsExpanded = true;
[JsonProperty("IsLock")]
public bool IsLock = false;
public SpritePiece() { }
public SpritePiece(string name, Rect rect, byte depthIndex = 0, bool isBorder = false)
{
this.Name = name;
this.Rect = rect;
this.DepthIndex = depthIndex;
IsBorder = isBorder;
}
}
[Serializable]
public class SpriteSliceData
{
[JsonProperty("OriginalSpritePath")]
public string OriginalSpritePath;
[JsonProperty("OriginalSpriteSize")]
public Vector2 OriginalSpriteSize;
[JsonProperty("VideoUrl")]
public string VideoUrl = "";
[JsonProperty("Description")]
public string DescriptionLoc = "";
[JsonProperty("IsRotatable")]
public bool IsRotatable = false;
[JsonProperty("IsMirrored")]
public bool IsMirrored = false;
[JsonProperty("Pieces")]
public List<SpritePiece> Pieces = new List<SpritePiece>();
public SpriteSliceData() { }
public SpriteSliceData(string spritePath, Vector2 spriteSize)
{
OriginalSpritePath = spritePath;
OriginalSpriteSize = spriteSize;
}
}
Shader "Custom/SpriteClipShader"
{
Properties
{
_MainTex ("Video Texture", 2D) = "white" {}
_UVMin ("UV Min", Vector) = (0, 0, 0, 0)
_UVMax ("UV Max", Vector) = (1, 1, 0, 0)
_UVCenter ("UV Center", Vector) = (0.5, 0.5, 0, 0)
_RotationZ ("Rotation Z", Float) = 0
_Color ("Tint Color", Color) = (1, 1, 1, 1)
_ColorBlendMode ("Color Blend Mode", Range(0, 2)) = 0
}
SubShader
{
Tags
{
"RenderType"="Transparent"
"Queue"="Transparent"
}
Blend SrcAlpha OneMinusSrcAlpha
Cull Off
ZWrite Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float2 _UVMin;
float2 _UVMax;
float2 _UVCenter;
float _RotationZ;
fixed4 _Color;
float _ColorBlendMode;
float2 RotateUV(float2 uv, float2 center, float angle)
{
float2 dir = uv - center;
float cosAngle = cos(angle);
float sinAngle = sin(angle);
float2 rotated;
rotated.x = dir.x * cosAngle - dir.y * sinAngle;
rotated.y = dir.x * sinAngle + dir.y * cosAngle;
return rotated + center;
}
fixed4 BlendColor(fixed4 base, fixed4 blend, float mode)
{
if (mode < 0.5)
{
return base * blend;
}
else if (mode < 1.5)
{
return lerp(base, blend, blend.a);
}
else
{
return base + blend;
}
}
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float2 localUV = i.uv;
if (_RotationZ != 0)
{
localUV = RotateUV(localUV, _UVCenter, _RotationZ);
}
float2 clippedUV = lerp(_UVMin, _UVMax, localUV);
if (clippedUV.x < 0 || clippedUV.x > 1 || clippedUV.y < 0 || clippedUV.y > 1)
{
return fixed4(0, 0, 0, 0);
}
fixed4 col = tex2D(_MainTex, clippedUV);
col = BlendColor(col, _Color, _ColorBlendMode);
return col;
}
ENDCG
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment