Skip to content

Instantly share code, notes, and snippets.

@arash-hacker
Created April 1, 2019 14:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save arash-hacker/aefb53f249773f76d99ca39cc65c03d1 to your computer and use it in GitHub Desktop.
Save arash-hacker/aefb53f249773f76d99ca39cc65c03d1 to your computer and use it in GitHub Desktop.
sprit2dslicer
//#define TK2D_SLICING_ENABLED
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
/// <summary>
/// Helper class to pass information about sliced sprites to the calling method
/// </summary>
public class SpriteSlicer2DSliceInfo
{
public GameObject SlicedObject { get; set; }
public Vector2 SliceEnterWorldPosition { get; set; }
public Vector2 SliceExitWorldPosition { get; set; }
public List<GameObject> ChildObjects { get { return m_ChildObjects; } set { m_ChildObjects = value; } }
List<GameObject> m_ChildObjects = new List<GameObject>();
}
/// <summary>
/// Main sprite slicer class, provides static functions to slice sprites
/// </summary>
public static class SpriteSlicer2D
{
public static bool DebugLoggingEnabled { get { return s_DebugLoggingEnabled; } set { s_DebugLoggingEnabled = value; } }
// Enable or disable debug logging
static bool s_DebugLoggingEnabled = true;
// Use at your own risk! Likely to produce odd results, depending on the shape
static bool s_AllowConvexSlicing = false;
// Helper to allow ExplodeSprite to slice sub parts that violate the same sprite id or game object rules
static List<SpriteSlicer2DSliceInfo> s_SubSlicesCont = new List<SpriteSlicer2DSliceInfo>();
#region SLICING_METHODS
/// <summary>
/// Slices any sprites that are intersected by the given vector
/// </summary>
/// <param name="worldStartPoint">Slice start point in world coordinates.</param>
/// <param name="worldEndPoint">Slice end point in world coordinates.</param>
public static void SliceAllSprites(Vector3 worldStartPoint, Vector3 worldEndPoint)
{
LayerMask layerMask = -1;
List<SpriteSlicer2DSliceInfo> slicedObjectInfo = null;
SliceSpritesInternal(worldStartPoint, worldEndPoint, null, 0, true, -1, ref slicedObjectInfo, layerMask, null);
}
/// <summary>
/// Slices any sprites that are intersected by the given vector
/// </summary>
/// <param name="worldStartPoint">Slice start point in world coordinates.</param>
/// <param name="worldEndPoint">Slice end point in world coordinates.</param>
/// <param name="layerMask">Layermask to use in raycast operations.</param>
public static void SliceAllSprites(Vector3 worldStartPoint, Vector3 worldEndPoint, LayerMask layerMask)
{
List<SpriteSlicer2DSliceInfo> slicedObjectInfo = null;
SliceSpritesInternal(worldStartPoint, worldEndPoint, null, 0, true, -1, ref slicedObjectInfo, layerMask, null);
}
/// <summary>
/// Slices any sprites that are intersected by the given vector
/// </summary>
/// <param name="worldStartPoint">Slice start point in world coordinates.</param>
/// <param name="worldEndPoint">Slice end point in world coordinates.</param>
/// <param name="layerMask">Layermask to use in raycast operations.</param>
/// <param name="tag">Only sprites with the given tag can be cut.</param>
public static void SliceAllSprites(Vector3 worldStartPoint, Vector3 worldEndPoint, string tag)
{
LayerMask layerMask = -1;
List<SpriteSlicer2DSliceInfo> slicedObjectInfo = null;
SliceSpritesInternal(worldStartPoint, worldEndPoint, null, 0, true, -1, ref slicedObjectInfo, layerMask, tag);
}
/// <summary>
/// Slices any sprites that are intersected by the given vector
/// </summary>
/// <param name="worldStartPoint">Slice start point in world coordinates.</param>
/// <param name="worldEndPoint">Slice end point in world coordinates.</param>
/// <param name="destroySlicedObjects">Controls whether the parent objects are automatically destroyed. Set to false if you need to perform additional processing on them after slicing</param>
/// <param name="slicedObjectInfo">A list of SpriteSlicer2DSliceInfo that will be fill out with details about slice locations, slcied objects, and created child objects.</param>
public static void SliceAllSprites(Vector3 worldStartPoint, Vector3 worldEndPoint, bool destroySlicedObjects, ref List<SpriteSlicer2DSliceInfo> slicedObjectInfo)
{
LayerMask layerMask = -1;
SliceSpritesInternal(worldStartPoint, worldEndPoint, null, 0, destroySlicedObjects, -1, ref slicedObjectInfo, layerMask, null);
}
/// <summary>
/// Slices any sprites that are intersected by the given vector
/// </summary>
/// <param name="worldStartPoint">Slice start point in world coordinates.</param>
/// <param name="worldEndPoint">Slice end point in world coordinates.</param>
/// <param name="destroySlicedObjects">Controls whether the parent objects are automatically destroyed. Set to false if you need to perform additional processing on them after slicing</param>
/// <param name="slicedObjectInfo">A list of SpriteSlicer2DSliceInfo that will be fill out with details about slice locations, slcied objects, and created child objects.</param>
/// <param name="tag">Only sprites with the given tag can be cut.</param>
public static void SliceAllSprites(Vector3 worldStartPoint, Vector3 worldEndPoint, bool destroySlicedObjects, ref List<SpriteSlicer2DSliceInfo> slicedObjectInfo, string tag)
{
LayerMask layerMask = -1;
SliceSpritesInternal(worldStartPoint, worldEndPoint, null, 0, destroySlicedObjects, -1, ref slicedObjectInfo, layerMask, tag);
}
/// <summary>
/// Slices any sprites that are intersected by the given vector
/// </summary>
/// <param name="worldStartPoint">Slice start point in world coordinates.</param>
/// <param name="worldEndPoint">Slice end point in world coordinates.</param>
/// <param name="destroySlicedObjects">Controls whether the parent objects are automatically destroyed. Set to false if you need to perform additional processing on them after slicing</param>
/// <param name="slicedObjectInfo">A list of SpriteSlicer2DSliceInfo that will be fill out with details about slice locations, slcied objects, and created child objects.</param>
/// <param name="layerMask">Layermask to use in raycast operations.</param>
public static void SliceAllSprites(Vector3 worldStartPoint, Vector3 worldEndPoint, bool destroySlicedObjects, ref List<SpriteSlicer2DSliceInfo> slicedObjectInfo, LayerMask layerMask)
{
SliceSpritesInternal(worldStartPoint, worldEndPoint, null, 0, destroySlicedObjects, -1, ref slicedObjectInfo, layerMask, null);
}
/// <summary>
/// Slices any sprites that are intersected by the given vector
/// </summary>
/// <param name="worldStartPoint">Slice start point in world coordinates.</param>
/// <param name="worldEndPoint">Slice end point in world coordinates.</param>
/// <param name="destroySlicedObjects">Controls whether the parent objects are automatically destroyed. Set to false if you need to perform additional processing on them after slicing</param>
/// <param name="maxCutDepth">The maximum number of times that any sprite can be subdivided</param>
/// <param name="slicedObjectInfo">A list of SpriteSlicer2DSliceInfo that will be fill out with details about slice locations, slcied objects, and created child objects.</param>
public static void SliceAllSprites(Vector3 worldStartPoint, Vector3 worldEndPoint, bool destroySlicedObjects, int maxCutDepth, ref List<SpriteSlicer2DSliceInfo> slicedObjectInfo)
{
LayerMask layerMask = -1;
SliceSpritesInternal(worldStartPoint, worldEndPoint, null, 0, destroySlicedObjects, maxCutDepth, ref slicedObjectInfo, layerMask, null);
}
/// <summary>
/// Slices any sprites that are intersected by the given vector
/// </summary>
/// <param name="worldStartPoint">Slice start point in world coordinates.</param>
/// <param name="worldEndPoint">Slice end point in world coordinates.</param>
/// <param name="destroySlicedObjects">Controls whether the parent objects are automatically destroyed. Set to false if you need to perform additional processing on them after slicing</param>
/// <param name="maxCutDepth">The maximum number of times that any sprite can be subdivided</param>
/// <param name="slicedObjectInfo">A list of SpriteSlicer2DSliceInfo that will be fill out with details about slice locations, slcied objects, and created child objects.</param>
/// <param name="layerMask">Layermask to use in raycast operations.</param>
public static void SliceAllSprites(Vector3 worldStartPoint, Vector3 worldEndPoint, bool destroySlicedObjects, int maxCutDepth, ref List<SpriteSlicer2DSliceInfo> slicedObjectInfo, LayerMask layerMask)
{
SliceSpritesInternal(worldStartPoint, worldEndPoint, null, 0, destroySlicedObjects, maxCutDepth, ref slicedObjectInfo, layerMask, null);
}
/// <summary>
/// Slices a specific sprite if it is intersected by the given vector
/// </summary>
/// <param name="worldStartPoint">Slice start point in world coordinates.</param>
/// <param name="worldEndPoint">Slice end point in world coordinates.</param>
/// <param name="sprite">The sprite to cut</param>
public static void SliceSprite(Vector3 worldStartPoint, Vector3 worldEndPoint, GameObject sprite)
{
if(sprite)
{
LayerMask layerMask = -1;
List<SpriteSlicer2DSliceInfo> slicedObjectInfo = null;
SliceSpritesInternal(worldStartPoint, worldEndPoint, sprite, 0, true, -1, ref slicedObjectInfo, layerMask, null);
}
}
/// <summary>
/// Slices a specific sprite if it is intersected by the given vector
/// </summary>
/// <param name="worldStartPoint">Slice start point in world coordinates.</param>
/// <param name="worldEndPoint">Slice end point in world coordinates.</param>
/// <param name="sprite">The sprite to cut</param>
/// <param name="destroySlicedObjects">Controls whether the parent objects are automatically destroyed. Set to false if you need to perform additional processing on them after slicing</param>
/// <param name="slicedObjectInfo">A list of SpriteSlicer2DSliceInfo that will be fill out with details about slice locations, slcied objects, and created child objects.</param>
public static void SliceSprite(Vector3 worldStartPoint, Vector3 worldEndPoint, GameObject sprite, bool destroySlicedObjects, ref List<SpriteSlicer2DSliceInfo> slicedObjectInfo)
{
if(sprite)
{
LayerMask layerMask = -1;
SliceSpritesInternal(worldStartPoint, worldEndPoint, sprite, 0, destroySlicedObjects, -1, ref slicedObjectInfo, layerMask, null);
}
}
/// <summary>
/// Slices a specific sprite if it is intersected by the given vector
/// </summary>
/// <param name="worldStartPoint">Slice start point in world coordinates.</param>
/// <param name="worldEndPoint">Slice end point in world coordinates.</param>
/// <param name="sprite">The sprite to cut</param>
/// <param name="destroySlicedObjects">Controls whether the parent objects are automatically destroyed. Set to false if you need to perform additional processing on them after slicing</param>
/// <param name="maxCutDepth">The maximum number of times that any sprite can be subdivided</param>
/// <param name="slicedObjectInfo">A list of SpriteSlicer2DSliceInfo that will be fill out with details about slice locations, slcied objects, and created child objects.</param>
public static void SliceSprite(Vector3 worldStartPoint, Vector3 worldEndPoint, GameObject sprite, bool destroySlicedObjects, int maxCutDepth, ref List<SpriteSlicer2DSliceInfo> slicedObjectInfo)
{
if(sprite)
{
LayerMask layerMask = -1;
SliceSpritesInternal(worldStartPoint, worldEndPoint, sprite, 0, destroySlicedObjects, maxCutDepth, ref slicedObjectInfo, layerMask, null);
}
}
/// <summary>
/// Explode a sprite by cutting it multiple times through the centre and then applying a force away from the center
/// </summary>
/// <param name="sprite">The sprite to cut</param>
/// <param name="numCuts">How many random cuts to create.</param>
/// <param name="explosionForce">The explosive force that will be applied to the newly created objects.</param>
public static void ExplodeSprite(GameObject sprite, int numCuts, float explosionForce)
{
if(sprite)
{
List<SpriteSlicer2DSliceInfo> slicedObjectInfo = null;
// Need to remember our objects if we're applying forces to them
if(explosionForce != 0.0f)
{
slicedObjectInfo = new List<SpriteSlicer2DSliceInfo>();
}
ExplodeSprite(sprite, numCuts, explosionForce, true, ref slicedObjectInfo);
}
}
/// <summary>
/// Explode a sprite by cutting it multiple times and then applying a force away from the center
/// </summary>
/// <param name="sprite">The sprite to cut</param>
/// <param name="numCuts">How many random cuts to create.</param>
/// <param name="explosionForce">The explosive force that will be applied to the newly created objects.</param>
/// <param name="destroySlicedObjects">Controls whether the parent objects are automatically destroyed. Set to false if you need to perform additional processing on them after slicing</param>
/// <param name="slicedObjectInfo">A list of SpriteSlicer2DSliceInfo that will be fill out with details about slice locations, slcied objects, and created child objects.</param>
public static void ExplodeSprite(GameObject sprite, int numCuts, float explosionForce, bool destroySlicedObjects, ref List<SpriteSlicer2DSliceInfo> slicedObjectInfo)
{
if(!sprite)
{
return;
}
Bounds spriteBounds;
if(GetSpriteBounds(sprite, out spriteBounds))
{
LayerMask layerMask = -1;
int parentSpriteID = sprite.GetInstanceID();
Vector3 centre = sprite.transform.position;
float maxRadius = spriteBounds.size.x + spriteBounds.size.y;
float actualRad = spriteBounds.size.magnitude;
for(int loop = 0; loop < numCuts; loop++)
{
float randomAngle = UnityEngine.Random.Range(0, Mathf.PI * 2);
Vector3 angleOffset = new Vector3(Mathf.Sin(randomAngle), Mathf.Cos(randomAngle), 0.0f) * maxRadius;
randomAngle = UnityEngine.Random.Range(0, Mathf.PI * 2);
// Randomly apply jitter to ensure that every slice doesn't go through the centre
Vector3 jitterOffset = new Vector3(Mathf.Sin(randomAngle), Mathf.Cos(randomAngle), 0.0f) * actualRad * 0.65f;
angleOffset += centre;
Vector3 worldStartPoint = centre + angleOffset + jitterOffset;
Vector3 worldEndPoint = centre - angleOffset + jitterOffset;
SliceSpritesInternal(worldStartPoint, worldEndPoint, null, parentSpriteID, destroySlicedObjects, -1, ref slicedObjectInfo, layerMask, null);
s_SubSlicesCont.Clear();
// Without this we only end up slicing the original object twice regardless of the slices requested
for(int i = 0; i < slicedObjectInfo.Count; i++)
{
for(int j = 0; j < slicedObjectInfo[i].ChildObjects.Count; j++)
{
SliceSpritesInternal(worldStartPoint, worldEndPoint, slicedObjectInfo[i].ChildObjects[j], 0, destroySlicedObjects, -1, ref s_SubSlicesCont, layerMask, null);
}
}
slicedObjectInfo.AddRange(s_SubSlicesCont);
}
if(slicedObjectInfo != null)
{
for(int slice = 0; slice < slicedObjectInfo.Count; slice++)
{
for(int child = 0; child < slicedObjectInfo[slice].ChildObjects.Count; child++)
{
Rigidbody2D childRigidBody = slicedObjectInfo[slice].ChildObjects[child].GetComponent<Rigidbody2D>();
if(childRigidBody)
{
childRigidBody.AddForceAtPosition(new Vector2(0.0f, 1.0f) * explosionForce, centre);
}
}
}
}
}
}
/// <summary>
/// Shatters a sprite into its constituent polygons and applies an optional force
/// </summary>
/// <param name="sprite">The sprite to cut</param>
/// <param name="explosionForce">The explosive force that will be applied to the newly created objects.</param>
public static void ShatterSprite(GameObject spriteObject, float explosionForce)
{
List<SpriteSlicer2DSliceInfo> slicedObjectInfo = null;
ShatterSprite(spriteObject, explosionForce, true, ref slicedObjectInfo);
}
/// <summary>
/// Shatters a sprite into its constituent polygons and applies an optional force
/// </summary>
/// <param name="sprite">The sprite to cut</param>
/// <param name="explosionForce">The explosive force that will be applied to the newly created objects.</param>
/// <param name="destroySlicedObjects">Controls whether the parent objects are automatically destroyed. Set to false if you need to perform additional processing on them after slicing</param>
/// <param name="slicedObjectInfo">A list of SpriteSlicer2DSliceInfo that will be fill out with details about slice locations, slcied objects, and created child objects.</param>
public static void ShatterSprite(GameObject spriteObject, float explosionForce, bool destroySlicedObjects, ref List<SpriteSlicer2DSliceInfo> slicedObjectInfo)
{
Rigidbody2D parentRigidBody = spriteObject.GetComponent<Rigidbody2D>();
if(!parentRigidBody)
{
Debug.LogWarning("Could not shatter sprite - no attached rigidbody");
return;
}
#if TK2D_SLICING_ENABLED
tk2dSprite parenttk2dSprite = parentRigidBody.GetComponent<tk2dSprite>();
#endif
SlicedSprite parentSlicedSprite = null;
SpriteRenderer parentUnitySprite = null;
// The object we're cutting must either be a unity sprite, a tk2D sprite, or a previously sliced sprite
#if TK2D_SLICING_ENABLED
if(parenttk2dSprite == null)
#endif
{
parentUnitySprite = spriteObject.GetComponent<SpriteRenderer>();
if(parentUnitySprite == null)
{
parentSlicedSprite = spriteObject.GetComponent<SlicedSprite>();
if(parentSlicedSprite == null)
{
return;
}
}
}
Vector2[] polygonPoints = null;
PolygonCollider2D polygonCollider = spriteObject.GetComponent<PolygonCollider2D>();
PhysicsMaterial2D physicsMaterial = null;
if(polygonCollider)
{
polygonPoints = polygonCollider.points;
physicsMaterial = polygonCollider.sharedMaterial;
}
else
{
BoxCollider2D boxCollider = spriteObject.GetComponent<BoxCollider2D>();
if(boxCollider)
{
polygonPoints = new Vector2[4];
polygonPoints[0] = new Vector2(-boxCollider.size.x * 0.5f, -boxCollider.size.y * 0.5f);
polygonPoints[1] = new Vector2(boxCollider.size.x * 0.5f, -boxCollider.size.y * 0.5f);
polygonPoints[2] = new Vector2(boxCollider.size.x * 0.5f, boxCollider.size.y * 0.5f);
polygonPoints[3] = new Vector2(-boxCollider.size.x * 0.5f, boxCollider.size.y * 0.5f);
physicsMaterial = boxCollider.sharedMaterial;
}
else
{
CircleCollider2D circleCollider = spriteObject.GetComponent<CircleCollider2D>();
if(circleCollider)
{
int numSteps = 32;
float angleStepRate = (Mathf.PI * 2)/numSteps;
polygonPoints = new Vector2[32];
for(int loop = 0; loop < numSteps; loop++)
{
float angle = angleStepRate * loop;
polygonPoints[loop] = new Vector2(Mathf.Sin(angle), Mathf.Cos(angle)) * circleCollider.radius;
}
physicsMaterial = circleCollider.sharedMaterial;
}
}
}
if(polygonPoints != null && polygonPoints.Length > 3)
{
SpriteSlicer2DSliceInfo sliceInfo = null;
if(slicedObjectInfo != null)
{
sliceInfo = new SpriteSlicer2DSliceInfo();
slicedObjectInfo.Add(sliceInfo);
if(!destroySlicedObjects)
{
sliceInfo.SlicedObject = spriteObject;
}
}
Vector2 parentCentre = spriteObject.transform.position;
int[] triangles = Triangulate(polygonPoints);
List<Vector2> points = new List<Vector2>(3);
points.Add(Vector2.zero);
points.Add(Vector2.zero);
points.Add(Vector2.zero);
float parentArea = Mathf.Abs(Area(polygonPoints));
for(int loop = 0; loop < triangles.Length; loop += 3)
{
points[0] = polygonPoints[triangles[loop]];
points[1] = polygonPoints[triangles[loop + 1]];
points[2] = polygonPoints[triangles[loop + 2]];
SlicedSprite childSprite;
PolygonCollider2D childCollider;
CreateChildSprite(parentRigidBody, physicsMaterial, points, parentArea, out childSprite, out childCollider);
childSprite.gameObject.name = spriteObject.name + "_child";
#if TK2D_SLICING_ENABLED
if(parenttk2dSprite)
{
childSprite.InitFromTK2DSprite(parenttk2dSprite, childCollider);
}
else
#endif
if(parentSlicedSprite)
{
childSprite.InitFromSlicedSprite(parentSlicedSprite, childCollider);
}
else if(parentUnitySprite)
{
childSprite.InitFromUnitySprite(parentUnitySprite, childCollider);
}
Vector2 centrePosition = parentCentre + ((points[0] + points[1] + points[2]) * 0.33f);
Vector2 forceDirection = centrePosition - parentCentre;
forceDirection.Normalize();
childSprite.GetComponent<Rigidbody2D>().AddForceAtPosition(forceDirection * explosionForce, parentCentre);
if(sliceInfo != null)
{
sliceInfo.ChildObjects.Add(childSprite.gameObject);
}
}
if(destroySlicedObjects)
{
GameObject.Destroy(parentRigidBody.gameObject);
}
else
{
parentRigidBody.gameObject.SetActive(false);
}
}
}
#endregion
/// <summary>
/// Get the sprite bounds from the given game object
/// </summary>
static bool GetSpriteBounds(GameObject sprite, out Bounds spriteBounds)
{
spriteBounds = new Bounds();
bool boundsValid = false;
#if TK2D_SLICING_ENABLED
tk2dSprite parenttk2dSprite = sprite.GetComponent<tk2dSprite>();
if(parenttk2dSprite)
{
spriteBounds = parenttk2dSprite.GetBounds();
boundsValid = true;
}
else
#endif
{
SpriteRenderer parentUnitySprite = sprite.GetComponent<SpriteRenderer>();
if(parentUnitySprite)
{
spriteBounds = parentUnitySprite.sprite.bounds;
boundsValid = true;
}
else
{
SlicedSprite parentSlicedSprite = sprite.GetComponent<SlicedSprite>();
if(parentSlicedSprite != null)
{
spriteBounds = parentSlicedSprite.SpriteBounds;
boundsValid = true;
}
}
}
return boundsValid;
}
/// <summary>
/// Slice any sprite with an attached PolygonCollider2D that intersects the given ray
/// </summary>
/// <param name="worldStartPoint">Cut world start point.</param>
/// <param name="worldEndPoint">Cut world end point.</param>
/// <param name="spriteObject">The specific sprite to cut - pass null to cut any sprite</param>
/// <param name="spriteInstanceID">The specific sprite unique ID to cut - pass 0 to cut any sprite</param>
/// <param name="destroySlicedObjects">Whether to automatically destroy the parent object - if false, the calling code must be responsible</param>
/// <param name="maxCutDepth">Max cut depth - prevents a sprite from being subdivided too many times. Pass -1 to divide infinitely.</param>
/// <param name="slicedObjectInfo">A list of information regarding the sliced objects, cut locations etc.</param>
static void SliceSpritesInternal(Vector3 worldStartPoint, Vector3 worldEndPoint, GameObject spriteObject, int spriteInstanceID, bool destroySlicedObjects, int maxCutDepth, ref List<SpriteSlicer2DSliceInfo> slicedObjectInfo, LayerMask layerMask, string tag)
{
Vector3 direction = Vector3.Normalize(worldEndPoint - worldStartPoint);
float length = Vector3.Distance(worldStartPoint, worldEndPoint);
RaycastHit2D[] cutStartResults = Physics2D.RaycastAll(worldStartPoint, direction, length, layerMask.value);
RaycastHit2D[] cutEndResults = Physics2D.RaycastAll(worldEndPoint, -direction, length, layerMask.value);
if(cutStartResults.Length == cutEndResults.Length)
{
for(int cutResultIndex = 0; cutResultIndex < cutStartResults.Length && cutResultIndex < cutEndResults.Length; cutResultIndex++)
{
RaycastHit2D cutEnter = cutStartResults[cutResultIndex];
int cutExitIndex = -1;
// Find the matching cut end point in the cut end results
for(int endResultIndex = 0; endResultIndex < cutEndResults.Length; endResultIndex++)
{
if(cutEndResults[endResultIndex].collider == cutEnter.collider)
{
cutExitIndex = endResultIndex;
break;
}
}
if(cutExitIndex == -1)
{
continue;
}
RaycastHit2D cutExit = cutEndResults[cutExitIndex];
if(cutEnter.rigidbody == cutExit.rigidbody)
{
Rigidbody2D parentRigidBody = cutEnter.rigidbody;
if(!parentRigidBody)
{
continue;
}
if(parentRigidBody.gameObject.isStatic || parentRigidBody.isKinematic)
{
continue;
}
if(spriteObject != null && cutEnter.rigidbody.gameObject != spriteObject)
{
continue;
}
if(tag != null && parentRigidBody.tag != tag)
{
continue;
}
#if TK2D_SLICING_ENABLED
tk2dSprite parenttk2dSprite = parentRigidBody.GetComponent<tk2dSprite>();
#endif
SlicedSprite parentSlicedSprite = null;
SpriteRenderer parentUnitySprite = null;
// The object we're cutting must either be a unity sprite, a tk2D sprite, or a previously sliced sprite
#if TK2D_SLICING_ENABLED
if(parenttk2dSprite == null)
#endif
{
parentUnitySprite = parentRigidBody.GetComponent<SpriteRenderer>();
if(parentUnitySprite == null)
{
parentSlicedSprite = parentRigidBody.GetComponent<SlicedSprite>();
if(parentSlicedSprite == null || (maxCutDepth >= 0 && parentSlicedSprite.CutsSinceParentObject >= maxCutDepth))
{
continue;
}
}
}
// If we've passed in a specific spriteInstanceID, then only that specific object or sliced
// objects derived from it can be cut
if(spriteInstanceID != 0 && parentRigidBody.gameObject.GetInstanceID() != spriteInstanceID)
{
if(parentSlicedSprite == null || parentSlicedSprite.ParentInstanceID != spriteInstanceID)
{
continue;
}
}
Vector3 cutStartLocalPoint = parentRigidBody.gameObject.transform.InverseTransformPoint(worldStartPoint);
Vector3 cutEndLocalPoint = parentRigidBody.gameObject.transform.InverseTransformPoint(worldEndPoint);
Vector3 cutEnterLocalPoint = parentRigidBody.gameObject.transform.InverseTransformPoint(cutEnter.point);
Vector3 cutExitLocalPoint = parentRigidBody.gameObject.transform.InverseTransformPoint(cutExit.point);
Vector2[] polygonPoints = null;
PolygonCollider2D polygonCollider = parentRigidBody.GetComponent<PolygonCollider2D>();
PhysicsMaterial2D physicsMaterial = null;
if(polygonCollider)
{
polygonPoints = polygonCollider.points;
physicsMaterial = polygonCollider.sharedMaterial;
}
else
{
BoxCollider2D boxCollider = parentRigidBody.GetComponent<BoxCollider2D>();
if(boxCollider)
{
polygonPoints = new Vector2[4];
polygonPoints[0] = new Vector2(-boxCollider.size.x * 0.5f, -boxCollider.size.y * 0.5f);
polygonPoints[1] = new Vector2(boxCollider.size.x * 0.5f, -boxCollider.size.y * 0.5f);
polygonPoints[2] = new Vector2(boxCollider.size.x * 0.5f, boxCollider.size.y * 0.5f);
polygonPoints[3] = new Vector2(-boxCollider.size.x * 0.5f, boxCollider.size.y * 0.5f);
physicsMaterial = boxCollider.sharedMaterial;
}
else
{
CircleCollider2D circleCollider = parentRigidBody.GetComponent<CircleCollider2D>();
if(circleCollider)
{
int numSteps = 32;
float angleStepRate = (Mathf.PI * 2)/numSteps;
polygonPoints = new Vector2[32];
for(int loop = 0; loop < numSteps; loop++)
{
float angle = angleStepRate * loop;
polygonPoints[loop] = new Vector2(Mathf.Sin(angle), Mathf.Cos(angle)) * circleCollider.radius;
}
physicsMaterial = circleCollider.sharedMaterial;
}
}
}
if(polygonPoints != null)
{
// Collision rays must travel through the whole object - if the ray either starts or
// ends inside the object then it is considered invalid
if(IsPointInsidePolygon(cutStartLocalPoint, polygonPoints) ||
IsPointInsidePolygon(cutEndLocalPoint, polygonPoints))
{
if(s_DebugLoggingEnabled && spriteObject != null)
{
Debug.LogWarning("Failed to slice " + parentRigidBody.gameObject.name + " - start or end cut point is inside the collision mesh");
}
continue;
}
if(!s_AllowConvexSlicing && !IsConvex(new List<Vector2>(polygonPoints)))
{
if(s_DebugLoggingEnabled)
{
Debug.LogWarning("Failed to slice " + parentRigidBody.gameObject.name + " - original shape is not convex");
}
continue;
}
List<Vector2> childSprite1Vertices = new List<Vector2>();
List<Vector2> childSprite2Vertices = new List<Vector2>();
childSprite1Vertices.Add(cutEnterLocalPoint);
childSprite1Vertices.Add(cutExitLocalPoint);
childSprite2Vertices.Add(cutEnterLocalPoint);
childSprite2Vertices.Add(cutExitLocalPoint);
for(int vertex = 0; vertex < polygonPoints.Length; vertex++)
{
Vector2 point = polygonPoints[vertex];
float determinant = CalculateDeterminant2x3(cutStartLocalPoint, cutEndLocalPoint, point);
if (determinant > 0)
{
childSprite1Vertices.Add(point);
}
else
{
childSprite2Vertices.Add(point);
}
}
childSprite1Vertices = new List<Vector2>(ArrangeVertices(childSprite1Vertices));
childSprite2Vertices = new List<Vector2>(ArrangeVertices(childSprite2Vertices));
if(!AreVerticesAcceptable(childSprite1Vertices) || !AreVerticesAcceptable(childSprite2Vertices))
{
continue;
}
else
{
SlicedSprite childSprite1, childSprite2;
PolygonCollider2D child1Collider, child2Collider;
float parentArea = Mathf.Abs(Area(polygonPoints));
CreateChildSprite(parentRigidBody, physicsMaterial, childSprite1Vertices, parentArea, out childSprite1, out child1Collider);
childSprite1.gameObject.name = parentRigidBody.gameObject.name + "_child1";
CreateChildSprite(parentRigidBody, physicsMaterial, childSprite2Vertices, parentArea, out childSprite2, out child2Collider);
childSprite2.gameObject.name = parentRigidBody.gameObject.name + "_child2";
#if TK2D_SLICING_ENABLED
if(parenttk2dSprite)
{
childSprite1.InitFromTK2DSprite(parenttk2dSprite, child1Collider);
childSprite2.InitFromTK2DSprite(parenttk2dSprite, child2Collider);
}
else
#endif
if(parentSlicedSprite)
{
childSprite1.InitFromSlicedSprite(parentSlicedSprite, child1Collider);
childSprite2.InitFromSlicedSprite(parentSlicedSprite, child2Collider);
}
else if(parentUnitySprite)
{
childSprite1.InitFromUnitySprite(parentUnitySprite, child1Collider);
childSprite2.InitFromUnitySprite(parentUnitySprite, child2Collider);
}
SpriteSlicer2DSliceInfo sliceInfo = new SpriteSlicer2DSliceInfo();
sliceInfo.SlicedObject = parentRigidBody.gameObject;
sliceInfo.ChildObjects.Add(childSprite1.gameObject);
sliceInfo.ChildObjects.Add(childSprite2.gameObject);
sliceInfo.SliceEnterWorldPosition = cutEnter.point;
sliceInfo.SliceExitWorldPosition = cutExit.point;
// Send an OnSpriteSliced message to the sliced object
parentRigidBody.gameObject.SendMessage("OnSpriteSliced", sliceInfo, SendMessageOptions.DontRequireReceiver);
if(slicedObjectInfo != null)
{
// Need to null the parent object as we're about to destroy it
if(destroySlicedObjects)
{
sliceInfo.SlicedObject = null;
}
slicedObjectInfo.Add(sliceInfo);
}
if(destroySlicedObjects)
{
GameObject.Destroy(parentRigidBody.gameObject);
}
else
{
parentRigidBody.gameObject.SetActive(false);
}
}
}
}
}
}
}
/// <summary>
/// Create a child sprite from the given parent sprite, using the provided vertices
/// </summary>
static void CreateChildSprite(Rigidbody2D parentRigidBody, PhysicsMaterial2D physicsMaterial, List<Vector2> spriteVertices, float parentArea, out SlicedSprite slicedSprite, out PolygonCollider2D polygonCollider)
{
float childArea = GetArea(spriteVertices);
GameObject childObject = new GameObject();
childObject.transform.parent = parentRigidBody.transform.parent;
childObject.transform.position = parentRigidBody.transform.position;
childObject.transform.rotation = parentRigidBody.transform.rotation;
childObject.transform.localScale = parentRigidBody.transform.localScale;
// Child sprites should inherit the rigid body behaviour of their parents
Rigidbody2D childRigidBody = childObject.AddComponent<Rigidbody2D>();
childRigidBody.mass = parentRigidBody.mass * (childArea/parentArea);
childRigidBody.drag = parentRigidBody.drag;
childRigidBody.angularDrag = parentRigidBody.angularDrag;
childRigidBody.gravityScale = parentRigidBody.gravityScale;
childRigidBody.fixedAngle = parentRigidBody.fixedAngle;
childRigidBody.isKinematic = parentRigidBody.isKinematic;
childRigidBody.interpolation = parentRigidBody.interpolation;
childRigidBody.sleepMode = parentRigidBody.sleepMode;
childRigidBody.collisionDetectionMode = parentRigidBody.collisionDetectionMode;
childRigidBody.velocity = parentRigidBody.velocity;
childRigidBody.angularVelocity = parentRigidBody.angularVelocity;
polygonCollider = childObject.AddComponent<PolygonCollider2D>();
polygonCollider.points = spriteVertices.ToArray();
polygonCollider.sharedMaterial = physicsMaterial;
slicedSprite = childObject.AddComponent<SlicedSprite>();
}
#region "HELPER_FUNCTIONS"
static float CalculateDeterminant2x3(Vector2 start, Vector2 end, Vector2 point)
{
return start.x * end.y + end.x * point.y + point.x * start.y - start.y * end.x - end.y * point.x - point.y * start.x;
}
public static float CalculateDeterminant2x2(Vector2 vectorA, Vector2 vectorB)
{
return vectorA.x * vectorB.y - vectorA.y * vectorB.x;
}
// Trianglulate the given polygon
static int[] Triangulate(Vector2[] points)
{
List<int> indices = new List<int>();
int n = points.Length;
if (n < 3)
return indices.ToArray();
int[] V = new int[n];
if (Area(points) > 0)
{
for (int v = 0; v < n; v++)
V[v] = v;
}
else
{
for (int v = 0; v < n; v++)
V[v] = (n - 1) - v;
}
int nv = n;
int count = 2 * nv;
for (int m = 0, v = nv - 1; nv > 2; ) {
if ((count--) <= 0)
return indices.ToArray();
int u = v;
if (nv <= u)
u = 0;
v = u + 1;
if (nv <= v)
v = 0;
int w = v + 1;
if (nv <= w)
w = 0;
if (Snip(points, u, v, w, nv, V))
{
int a, b, c, s, t;
a = V[u];
b = V[v];
c = V[w];
indices.Add(a);
indices.Add(b);
indices.Add(c);
m++;
for (s = v, t = v + 1; t < nv; s++, t++)
V[s] = V[t];
nv--;
count = 2 * nv;
}
}
indices.Reverse();
return indices.ToArray();
}
// Get the area of the given polygon
static float Area (Vector2[] points)
{
int n = points.Length;
float A = 0.0f;
for (int p = n - 1, q = 0; q < n; p = q++)
{
Vector2 pval = points[p];
Vector2 qval = points[q];
A += pval.x * qval.y - qval.x * pval.y;
}
return (A * 0.5f);
}
static bool Snip (Vector2[] points, int u, int v, int w, int n, int[] V)
{
int p;
Vector2 A = points[V[u]];
Vector2 B = points[V[v]];
Vector2 C = points[V[w]];
if (Mathf.Epsilon > (((B.x - A.x) * (C.y - A.y)) - ((B.y - A.y) * (C.x - A.x))))
{
return false;
}
for (p = 0; p < n; p++)
{
if ((p == u) || (p == v) || (p == w))
{
continue;
}
Vector2 P = points[V[p]];
if (InsideTriangle(A, B, C, P))
{
return false;
}
}
return true;
}
// Check if a point is inside a given triangle
static bool InsideTriangle (Vector2 A, Vector2 B, Vector2 C, Vector2 P)
{
float ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy;
float cCROSSap, bCROSScp, aCROSSbp;
ax = C.x - B.x; ay = C.y - B.y;
bx = A.x - C.x; by = A.y - C.y;
cx = B.x - A.x; cy = B.y - A.y;
apx = P.x - A.x; apy = P.y - A.y;
bpx = P.x - B.x; bpy = P.y - B.y;
cpx = P.x - C.x; cpy = P.y - C.y;
aCROSSbp = ax * bpy - ay * bpx;
cCROSSap = cx * apy - cy * apx;
bCROSScp = bx * cpy - by * cpx;
return ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f));
}
// Helper class to sort vertices in ascending X coordinate order
public class VectorComparer : IComparer<Vector2>
{
public int Compare(Vector2 vectorA, Vector2 vectorB)
{
if (vectorA.x > vectorB.x)
{
return 1;
}
else if (vectorA.x < vectorB.x)
{
return -1;
}
return 0;
}
}
/// <summary>
/// Sort the vertices into a counter clockwise order
/// </summary>
static Vector2[] ArrangeVertices(List<Vector2> vertices)
{
float determinant;
int counterClockWiseIndex = 1;
int clockWiseIndex = vertices.Count - 1;
Vector2[] sortedVertices = new Vector2[vertices.Count];
vertices.Sort(new VectorComparer());
Vector2 startPoint = vertices[0];
Vector2 endPoint = vertices[vertices.Count - 1];
sortedVertices[0] = startPoint;
for(int vertex = 1; vertex < vertices.Count - 1; vertex++)
{
determinant = CalculateDeterminant2x3(startPoint, endPoint, vertices[vertex]);
if (determinant < 0)
{
sortedVertices[counterClockWiseIndex++] = vertices[vertex];
}
else
{
sortedVertices[clockWiseIndex--] = vertices[vertex];
}
}
sortedVertices[counterClockWiseIndex] = endPoint;
return sortedVertices;
}
/// <summary>
/// Work out the area defined by the vertices
/// </summary>
static float GetArea(List<Vector2> vertices)
{
// Check that the total area isn't stupidly small
float area = vertices[0].y * (vertices[vertices.Count-1].x- vertices[1].x);
for(int i = 1; i < vertices.Count; i++)
{
area += vertices[i].y * (vertices[i-1].x - vertices[(i+1)% vertices.Count].x);
}
return Mathf.Abs(area * 0.5f);
}
/// <summary>
/// Check if this list of points defines a convex shape
/// </summary>
public static bool IsConvex(List<Vector2> vertices)
{
float determinant;
Vector3 v1 = vertices[0] - vertices[vertices.Count-1];
Vector3 v2 = vertices[1] - vertices[0];
float referenceDeterminant = CalculateDeterminant2x2(v1, v2);
for (int i=1; i< vertices.Count - 1; i++)
{
v1 = v2;
v2 = vertices[i+1] - vertices[i];
determinant = CalculateDeterminant2x2(v1, v2);
if (referenceDeterminant * determinant < 0.0f)
{
return false;
}
}
v1 = v2;
v2 = vertices[0] - vertices[vertices.Count-1];
determinant = CalculateDeterminant2x2(v1, v2);
if (referenceDeterminant * determinant < 0.0f)
{
return false;
}
return true;
}
/// <summary>
/// Verify if the list of vertices are suitable to create a new 2D collider shape
///
static bool AreVerticesAcceptable(List<Vector2> vertices)
{
// Polygons need to at least have 3 vertices, not be convex, and have a vaguely sensible total area
if (vertices.Count < 3)
{
if(s_DebugLoggingEnabled)
{
Debug.LogWarning("Vertices rejected - insufficient vertices");
}
return false;
}
if(GetArea(vertices) < 0.0001f)
{
if(s_DebugLoggingEnabled)
{
Debug.LogWarning("Vertices rejected - below minimum area");
}
return false;
}
if(!s_AllowConvexSlicing && !IsConvex(vertices))
{
if(s_DebugLoggingEnabled)
{
Debug.LogWarning("Vertices rejected - shape is not convex");
}
return false;
}
return true;
}
/// <summary>
/// Use the polygon winding algorithm to check whether a point is inside the given polygon
/// </summary>
static bool IsPointInsidePolygon(Vector2 pos, Vector2[] polygonPoints)
{
int winding = 0;
for(int vertexIndex = 0; vertexIndex < polygonPoints.Length; vertexIndex++)
{
int nextIndex = vertexIndex + 1;
if(nextIndex >= polygonPoints.Length)
{
nextIndex = 0;
}
Vector2 thisPoint = polygonPoints[vertexIndex];
Vector2 nextPoint = polygonPoints[nextIndex];
if(thisPoint.y <= pos.y)
{
if(nextPoint.y > pos.y)
{
float isLeft = ((nextPoint.x - thisPoint.x) * (pos.y - thisPoint.y) - (pos.x - thisPoint.x) * (nextPoint.y - thisPoint.y));
if(isLeft > 0)
{
winding++;
}
}
}
else
{
if(nextPoint.y <= pos.y)
{
float isLeft = ((nextPoint.x - thisPoint.x) * (pos.y - thisPoint.y) - (pos.x - thisPoint.x) * (nextPoint.y - thisPoint.y));
if(isLeft < 0)
{
winding--;
}
}
}
}
return winding != 0;
}
#endregion
}
#if UNITY_EDITOR
public static class SpriteSlicerConvexHelper
{
[MenuItem("Tools/Sprite Slicer 2D/Make Convex")]
static void MakeConvex()
{
for(int loop = 0; loop < Selection.gameObjects.Length; loop++)
{
PolygonCollider2D polyCollider = Selection.gameObjects[loop].GetComponent<PolygonCollider2D>();
if(polyCollider)
{
List<Vector2> vertices = new List<Vector2>(polyCollider.points);
int originalNumVertices = vertices.Count;
int iterations = 0;
if(SpriteSlicer2D.IsConvex(vertices))
{
Debug.Log(Selection.gameObjects[loop].name + " is already convex - no work to do");
}
else
{
do
{
float determinant;
Vector3 v1 = vertices[0] - vertices[vertices.Count-1];
Vector3 v2 = vertices[1] - vertices[0];
float referenceDeterminant = SpriteSlicer2D.CalculateDeterminant2x2(v1, v2);
for (int i=1; i< vertices.Count - 1;)
{
v1 = v2;
v2 = vertices[i+1] - vertices[i];
determinant = SpriteSlicer2D.CalculateDeterminant2x2(v1, v2);
if (referenceDeterminant * determinant < 0.0f)
{
vertices.RemoveAt(i);
}
else
{
i++;
}
}
v1 = v2;
v2 = vertices[0] - vertices[vertices.Count-1];
determinant = SpriteSlicer2D.CalculateDeterminant2x2(v1, v2);
if (referenceDeterminant * determinant < 0.0f)
{
vertices.RemoveAt(vertices.Count - 1);
}
iterations++;
} while(!SpriteSlicer2D.IsConvex(vertices) && iterations < 25);
}
if(SpriteSlicer2D.IsConvex(vertices))
{
polyCollider.points = vertices.ToArray();
Debug.Log(Selection.gameObjects[loop].name + " points reduced to " + vertices.Count.ToString() + " from " + originalNumVertices.ToString() );
}
else
{
Debug.Log(Selection.gameObjects[loop].name + " could not be made convex, please adjust shape manually");
}
}
}
}
}
#endif
/// <summary>
/// Simple class that takes a sprite and a polygon collider, and creates
/// a render mesh that exactly fits the collider.
/// </summary>
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class SlicedSprite : MonoBehaviour
{
public MeshRenderer MeshRenderer { get { return m_MeshRenderer; } }
public Vector2 MinCoords { get { return m_MinCoords; } }
public Vector2 MaxCoords { get { return m_MaxCoords; } }
public Bounds SpriteBounds { get { return m_SpriteBounds; } }
public int ParentInstanceID { get { return m_ParentInstanceID; } }
public int CutsSinceParentObject { get { return m_CutsSinceParentObject; } }
public bool Rotated { get { return m_Rotated; } }
public bool HFlipped { get { return m_HFlipped; } }
public bool VFlipped { get { return m_VFlipped; } }
MeshRenderer m_MeshRenderer;
MeshFilter m_MeshFilter;
Vector2 m_MinCoords;
Vector2 m_MaxCoords;
Bounds m_SpriteBounds;
int m_ParentInstanceID;
int m_CutsSinceParentObject;
bool m_Rotated;
bool m_VFlipped;
bool m_HFlipped;
static List<Material> s_MaterialList = new List<Material>();
/// <summary>
/// Sliced sprites share materials wherever possible in order to ensure that dynamic batching is maintained when
/// eg. slicing lots of sprites that share the same sprite sheet. If you want to clear out this list
/// (eg. on transitioning to a new scene) then simply call this function
/// </summary>
public static void ClearMaterials()
{
s_MaterialList.Clear();
}
#if TK2D_SLICING_ENABLED
/// <summary>
/// Initialise the sliced sprite using an existing tk2dsprite
/// </summary>
public void InitFromTK2DSprite(tk2dSprite sprite, PolygonCollider2D polygon)
{
tk2dSpriteDefinition spriteDefinition = sprite.GetCurrentSpriteDef();
bool isRotated = spriteDefinition.flipped != tk2dSpriteDefinition.FlipMode.None;
bool isHFlipped = sprite.FlipX;
bool isVFlipped = sprite.FlipY;
InitSprite(sprite.gameObject, polygon, spriteDefinition.uvs[0], spriteDefinition.uvs[spriteDefinition.uvs.Length - 1], spriteDefinition.GetBounds(), spriteDefinition.material, isRotated, isHFlipped, isVFlipped);
m_ParentInstanceID = sprite.gameObject.GetInstanceID();
}
#endif
/// <summary>
/// Initialise this sliced sprite using an existing SlicedSprite
/// </summary>
public void InitFromSlicedSprite(SlicedSprite slicedSprite, PolygonCollider2D polygon)
{
InitSprite(slicedSprite.gameObject, polygon, slicedSprite.MinCoords, slicedSprite.MaxCoords, slicedSprite.SpriteBounds, slicedSprite.m_MeshRenderer.sharedMaterial, slicedSprite.Rotated, slicedSprite.HFlipped, slicedSprite.VFlipped);
m_ParentInstanceID = slicedSprite.GetInstanceID();
m_CutsSinceParentObject = slicedSprite.CutsSinceParentObject + 1;
}
/// <summary>
/// Initialise using a unity sprite
/// </summary>
public void InitFromUnitySprite(SpriteRenderer unitySprite, PolygonCollider2D polygon)
{
Material material = null;
for(int loop = 0; loop < s_MaterialList.Count; loop++)
{
if(s_MaterialList[loop].mainTexture.GetInstanceID() == unitySprite.sprite.texture.GetInstanceID())
{
material = s_MaterialList[loop];
}
}
if(material == null)
{
material = new Material(unitySprite.material.shader);
material.mainTexture = unitySprite.sprite.texture;
material.name = unitySprite.name + "_sliced";
s_MaterialList.Add(material);
}
Rect textureRect = unitySprite.sprite.textureRect;
Vector2 minTextureCoords = new Vector2(textureRect.xMin/(float)unitySprite.sprite.texture.width, textureRect.yMin/(float)unitySprite.sprite.texture.height);
Vector2 maxTextureCoords = new Vector2(textureRect.xMax/(float)unitySprite.sprite.texture.width, textureRect.yMax/(float)unitySprite.sprite.texture.height);
InitSprite(unitySprite.gameObject, polygon, minTextureCoords, maxTextureCoords, unitySprite.sprite.bounds, material, false, false, false);
m_ParentInstanceID = unitySprite.gameObject.GetInstanceID();
}
/// <summary>
/// Initialise this sprite using the given polygon definition
/// </summary>
void InitSprite(GameObject parentObject, PolygonCollider2D polygon, Vector3 minCoords, Vector3 maxCoords, Bounds spriteBounds, Material material, bool rotated, bool hFlipped, bool vFlipped)
{
m_MinCoords = minCoords;
m_MaxCoords = maxCoords;
m_SpriteBounds = spriteBounds;
m_VFlipped = vFlipped;
m_HFlipped = hFlipped;
m_Rotated = rotated;
m_SpriteBounds = spriteBounds;
gameObject.tag = parentObject.tag;
gameObject.layer = parentObject.layer;
Mesh spriteMesh = new Mesh();
spriteMesh.name = "SlicedSpriteMesh";
m_MeshFilter = GetComponent<MeshFilter>();
m_MeshFilter.mesh = spriteMesh;
int numVertices = polygon.points.Length;
Vector3[] vertices = new Vector3[numVertices];
Color[] colors = new Color[numVertices];
Vector2[] uvs = new Vector2[numVertices];
int[] triangles = new int[numVertices * 3];
// Convert vector2 -> vector3
for(int loop = 0; loop < vertices.Length; loop++)
{
vertices[loop] = polygon.points[loop];
colors[loop] = Color.white;
}
Vector2 uvWidth = maxCoords - minCoords;
for(int vertexIndex = 0; vertexIndex < numVertices; vertexIndex++)
{
float widthFraction = 0.5f + (polygon.points[vertexIndex].x / spriteBounds.size.x);
float heightFraction = 0.5f + (polygon.points[vertexIndex].y / spriteBounds.size.y);
if(hFlipped)
{
widthFraction = 1.0f - widthFraction;
}
if(vFlipped)
{
heightFraction = 1.0f - heightFraction;
}
Vector2 texCoords = new Vector2();
if(rotated)
{
texCoords.y = maxCoords.y - (uvWidth.y * (1.0f - widthFraction));
texCoords.x = minCoords.x + (uvWidth.x * heightFraction);
}
else
{
texCoords.x = minCoords.x + (uvWidth.x * widthFraction);
texCoords.y = minCoords.y + (uvWidth.y * heightFraction);
}
uvs[vertexIndex] = texCoords;
}
int triangleIndex = 0;
for(int vertexIndex = 1; vertexIndex < numVertices - 1; vertexIndex++)
{
triangles[triangleIndex++] = 0;
triangles[triangleIndex++] = vertexIndex + 1;
triangles[triangleIndex++] = vertexIndex;
}
spriteMesh.Clear();
spriteMesh.vertices = vertices;
spriteMesh.uv = uvs;
spriteMesh.triangles = triangles;
spriteMesh.colors = colors;
spriteMesh.RecalculateBounds();
spriteMesh.RecalculateNormals();
spriteMesh.Optimize();
m_MeshRenderer = GetComponent<MeshRenderer>();
m_MeshRenderer.material = material;
m_MeshRenderer.sortingLayerID = parentObject.GetComponent<Renderer>().sortingLayerID;
m_MeshRenderer.sortingOrder = parentObject.GetComponent<Renderer>().sortingOrder;
}
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class SpriteSlicer2DDemoManager : MonoBehaviour
{
List<SpriteSlicer2DSliceInfo> m_SlicedSpriteInfo = new List<SpriteSlicer2DSliceInfo>();
TrailRenderer m_TrailRenderer;
struct MousePosition
{
public Vector3 m_WorldPosition;
public float m_Time;
}
List<MousePosition> m_MousePositions = new List<MousePosition>();
float m_MouseRecordTimer = 0.0f;
float m_MouseRecordInterval = 0.05f;
int m_MaxMousePositions = 5;
/// <summary>
/// Start this instance.
/// </summary>
void Start ()
{
m_TrailRenderer = GetComponentInChildren<TrailRenderer>();
}
/// <summary>
/// Update this instance.
/// </summary>
void Update ()
{
// Right mouse button - explode any sprite the we click on
if(Input.GetMouseButtonDown(0) && (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.LeftAlt)))
{
Vector3 mouseWorldPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
mouseWorldPosition.z = Camera.main.transform.position.z;
RaycastHit2D rayCastResult = Physics2D.Raycast(mouseWorldPosition, new Vector3(0, 0, 0), 0.0f);
if (rayCastResult.rigidbody)
{
if(Input.GetKey(KeyCode.LeftControl))
{
SpriteSlicer2D.ExplodeSprite(rayCastResult.rigidbody.gameObject, 16, 8000.0f, true, ref m_SlicedSpriteInfo);
if(m_SlicedSpriteInfo.Count == 0)
{
// Couldn't cut for whatever reason, add some force anyway
rayCastResult.rigidbody.AddForce(new Vector2(0.0f, 8000.0f));
}
}
}
}
// Left mouse button - hold and swipe to cut objects
else if(Input.GetMouseButton(0))
{
bool mousePositionAdded = false;
m_MouseRecordTimer -= Time.deltaTime;
// Record the world position of the mouse every x seconds
if(m_MouseRecordTimer <= 0.0f)
{
MousePosition newMousePosition = new MousePosition();
newMousePosition.m_WorldPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
newMousePosition.m_Time = Time.time;
m_MousePositions.Add(newMousePosition);
m_MouseRecordTimer = m_MouseRecordInterval;
mousePositionAdded = true;
// Remove the first recorded point if we've recorded too many
if(m_MousePositions.Count > m_MaxMousePositions)
{
m_MousePositions.RemoveAt(0);
}
}
// Forget any positions that are too old to care about
if(m_MousePositions.Count > 0 && (Time.time - m_MousePositions[0].m_Time) > m_MouseRecordInterval * m_MaxMousePositions)
{
m_MousePositions.RemoveAt(0);
}
// Go through all our recorded positions and slice any sprites that intersect them
if(mousePositionAdded)
{
for(int loop = 0; loop < m_MousePositions.Count - 1; loop++)
{
SpriteSlicer2D.SliceAllSprites(m_MousePositions[loop].m_WorldPosition, m_MousePositions[m_MousePositions.Count - 1].m_WorldPosition, true, ref m_SlicedSpriteInfo);
if(m_SlicedSpriteInfo.Count > 0)
{
// Add some force in the direction of the swipe so that stuff topples over rather than just being
// sliced but remaining stationary
for(int spriteIndex = 0; spriteIndex < m_SlicedSpriteInfo.Count; spriteIndex++)
{
for(int childSprite = 0; childSprite < m_SlicedSpriteInfo[spriteIndex].ChildObjects.Count; childSprite++)
{
Vector2 sliceDirection = m_MousePositions[m_MousePositions.Count - 1].m_WorldPosition - m_MousePositions[loop].m_WorldPosition;
sliceDirection.Normalize();
m_SlicedSpriteInfo[spriteIndex].ChildObjects[childSprite].GetComponent<Rigidbody2D>().AddForce(sliceDirection * 500.0f);
}
}
m_MousePositions.Clear();
break;
}
}
}
if(m_TrailRenderer)
{
Vector3 trailPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
trailPosition.z = -9.0f;
m_TrailRenderer.transform.position = trailPosition;
}
}
else
{
m_MousePositions.Clear();
}
// Sliced sprites sharing the same layer as standard Unity sprites could increase the draw call count as
// the engine will have to keep swapping between rendering SlicedSprites and Unity Sprites.To avoid this,
// move the newly sliced sprites either forward or back along the z-axis after they are created
for(int spriteIndex = 0; spriteIndex < m_SlicedSpriteInfo.Count; spriteIndex++)
{
for(int childSprite = 0; childSprite < m_SlicedSpriteInfo[spriteIndex].ChildObjects.Count; childSprite++)
{
Vector3 spritePosition = m_SlicedSpriteInfo[spriteIndex].ChildObjects[childSprite].transform.position;
spritePosition.z = -1.0f;
m_SlicedSpriteInfo[spriteIndex].ChildObjects[childSprite].transform.position = spritePosition;
}
}
m_SlicedSpriteInfo.Clear();
}
/// <summary>
/// Draws the GUI
/// </summary>
void OnGUI ()
{
if(GUI.Button(new Rect(20,20,60,20), "Reset"))
{
Application.LoadLevel(Application.loadedLevel);
}
GUI.Label(new Rect((Screen.width/2) - 400,20,900,20), "Left Mouse Button + Drag Cursor: Slice Objects");
GUI.Label(new Rect((Screen.width/2) - 400,40,900,20), "(Cuts objects intersected by the cursor movement vector)");
GUI.Label(new Rect((Screen.width/2) + 0,20,900,20), "Ctrl + Click Left Mouse Button: Explode Objects");
GUI.Label(new Rect((Screen.width/2) + 0,40,900,20), "(Randomly slices an objects multiple times, then applies an optional force)");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment