public void OnExplosionFinished(int id) { var explosion = queue.Dequeue(); Exploder2DUtils.Assert(explosion.id == id, "Explosion id mismatch!"); ProcessQueue(); }
public void Explode(Exploder2DObject.OnExplosion callback) { var settings = new Exploder2DSettings { Position = Exploder2DUtils.GetCentroid(_exploder2D.gameObject), DontUseTag = _exploder2D.DontUseTag, Radius = _exploder2D.Radius, ForceVector = _exploder2D.ForceVector, UseForceVector = _exploder2D.UseForceVector, Force = _exploder2D.Force, FrameBudget = _exploder2D.FrameBudget, TargetFragments = _exploder2D.TargetFragments, DeactivateOptions = _exploder2D.DeactivateOptions, DeactivateTimeout = _exploder2D.DeactivateTimeout, ExplodeSelf = _exploder2D.ExplodeSelf, HideSelf = _exploder2D.HideSelf, DestroyOriginalObject = _exploder2D.DestroyOriginalObject, ExplodeFragments = _exploder2D.ExplodeFragments, SplitMeshIslands = _exploder2D.SplitMeshIslands, FragmentOptions = _exploder2D.FragmentOptions.Clone(), SfxOptions = _exploder2D.SFXOptions.Clone(), Callback = callback, processing = false, }; queue.Enqueue(settings); ProcessQueue(); }
/// <summary> /// create pool (array) of fragment game objects with all necessary components /// </summary> /// <param name="poolSize">number of fragments</param> public void Allocate(int poolSize) { Exploder2DUtils.Assert(poolSize > 0, ""); if (pool == null || pool.Length < poolSize) { DestroyFragments(); pool = new Fragment2D[poolSize]; for (int i = 0; i < poolSize; i++) { var fragment = new GameObject("fragment_" + i); fragment.AddComponent <SpriteRenderer>(); fragment.AddComponent <PolygonCollider2D>(); fragment.AddComponent <Rigidbody2D>(); fragment.AddComponent <Exploder2DOption>(); var fragmentComponent = fragment.AddComponent <Fragment2D>(); fragment.transform.parent = gameObject.transform; pool[i] = fragmentComponent; Exploder2DUtils.SetActiveRecursively(fragment.gameObject, false); fragmentComponent.RefreshComponentsCache(); fragmentComponent.Sleep(); } } }
void OnDrawGizmos() { if (enabled && !(ExplodeSelf && DisableRadiusScan)) { Gizmos.color = Color.red; Gizmos.DrawWireSphere(Exploder2DUtils.GetCentroid(gameObject), Radius); } }
/// <summary> /// explode cracked objects /// Use this method in combination with Crack() /// Purpose of this method is to get higher performance of explosion, Crack() will /// calculate the explosion and prepare all fragments. Calling ExplodeCracked() will /// then start the explosion (flying fragments...) immediately /// </summary> public void ExplodeCracked(OnExplosion callback) { Exploder2DUtils.Assert(crack, "You must call Crack() first!"); if (cracked) { PostCrackExplode(callback); crack = false; } }
private int GetLevel(float distance, float radius) { Exploder2DUtils.Assert(distance >= 0.0f, ""); Exploder2DUtils.Assert(radius >= 0.0f, ""); var normDistance = distance / radius * 6; var level = (int)normDistance / 2 + 1; return(Mathf.Clamp(level, 0, 10)); }
/// <summary> /// crack will calculate fragments and prepare object for explosion /// Use this method in combination with ExplodeCracked() /// Purpose of this method is to get higher performance of explosion, Crack() will /// calculate the explosion and prepare all fragments. Calling ExplodeCracked() will /// then start the explosion (flying fragments...) immediately /// </summary> public void Crack(OnCracked callback) { Exploder2DUtils.Assert(!crack, "Another crack in progress!"); if (!crack) { CrackedCallback = callback; crack = true; cracked = false; Explode(null); } }
/// <summary> /// deactivate this fragment piece /// </summary> public void Deactivate() { Exploder2DUtils.SetActive(gameObject, false); visible = false; activeObj = false; // turn off particles if (particleSystems != null) { foreach (var psystem in particleSystems) { psystem.Clear(); } } }
/// <summary> /// this is called from exploder class to start the explosion /// </summary> public void Explode() { activeObj = true; Exploder2DUtils.SetActiveRecursively(gameObject, true); visibilityCheckTimer = 0.1f; visible = true; deactivateTimer = deactivateTimeout; originalScale = transform.localScale; if (explodable) { tag = Exploder2DObject.Tag; } Emit(true); }
/// <summary> /// callback from queue, do not call this unles you know what you are doing! /// </summary> public void StartExplosionFromQueue(Vector2 pos, int id, OnExplosion callback) { Exploder2DUtils.Assert(state == State.None, "Wrong state: " + state); mainCentroid = pos; explosionID = id; state = State.Preprocess; ExplosionCallback = callback; #if DBG processingTime = 0.0f; preProcessingTime = 0.0f; postProcessingTime = 0.0f; postProcessingTimeEnd = 0.0f; isolatingIslandsTime = 0.0f; cuts = 0; #endif }
/// <summary> /// returns list of currently active (visible) fragments /// </summary> /// <returns></returns> public List <Fragment2D> GetActiveFragments() { if (pool != null) { var list = new List <Fragment2D>(pool.Length); foreach (var fragment in pool) { if (Exploder2DUtils.IsActive(fragment.gameObject)) { list.Add(fragment); } } return(list); } return(null); }
void Update() { if (activeObj) { // // clamp velocity // if (rigidBody) { if (rigidBody.velocity.sqrMagnitude > maxVelocity * maxVelocity) { var vel = rigidBody.velocity.normalized; rigidBody.velocity = vel * maxVelocity; } } else if (rigid2D) { if (rigid2D.velocity.sqrMagnitude > maxVelocity * maxVelocity) { var vel = rigid2D.velocity.normalized; rigid2D.velocity = vel * maxVelocity; } } if (deactivateOptions == DeactivateOptions.Timeout) { deactivateTimer -= Time.deltaTime; if (deactivateTimer < 0.0f) { Sleep(); activeObj = false; Exploder2DUtils.SetActiveRecursively(gameObject, false); // return fragment to previous fadout state switch (fadeoutOptions) { case FadeoutOptions.FadeoutAlpha: break; } } else { var t = deactivateTimer / deactivateTimeout; switch (fadeoutOptions) { case FadeoutOptions.FadeoutAlpha: var color = spriteRenderer.color; color.a = t; spriteRenderer.color = color; break; case FadeoutOptions.ScaleDown: gameObject.transform.localScale = originalScale * t; break; } } } visibilityCheckTimer -= Time.deltaTime; if (visibilityCheckTimer < 0.0f && UnityEngine.Camera.main) { var viewportPoint = UnityEngine.Camera.main.WorldToViewportPoint(transform.position); if (viewportPoint.z < 0 || viewportPoint.x < 0 || viewportPoint.y < 0 || viewportPoint.x > 1 || viewportPoint.y > 1) { if (deactivateOptions == DeactivateOptions.OutsideOfCamera) { Sleep(); activeObj = false; Exploder2DUtils.SetActiveRecursively(gameObject, false); } visible = false; } else { visible = true; } visibilityCheckTimer = Random.Range(0.1f, 0.3f); if (explodable) { var size = GetComponent <Collider>().bounds.size; if (Mathf.Max(size.x, size.y, size.z) < minSizeToExplode) { tag = string.Empty; } } } } }
void Update() { long cuttingTime = 0; switch (state) { case State.None: break; case State.Preprocess: { timer.Reset(); timer.Start(); #if DBG var watch = new Stopwatch(); watch.Start(); #endif // get mesh data from game object in radius var readyToCut = Preprocess(); #if DBG watch.Stop(); preProcessingTime = watch.ElapsedMilliseconds; processingFrames = 0; #endif // nothing to explode if (!readyToCut) { #if DBG Exploder2DUtils.Log("Nothing to explode " + mainCentroid); #endif OnExplosionFinished(false); } else { // continue to cutter, don't wait to another frame state = State.ProcessCutter; goto case State.ProcessCutter; } } break; case State.ProcessCutter: { #if DBG processingFrames++; var watch = new Stopwatch(); watch.Start(); #endif // process main cutter var cuttingFinished = false; switch (CuttingStrategy) { case CutStrategy.Randomized: cuttingFinished = ProcessCutterRandomized(out cuttingTime); break; // case CutStrategy.Grid: // cuttingFinished = ProcessCutterGrid(out cuttingTime); // break; default: Exploder2DUtils.Assert(false, "Invalid cutting strategy"); break; } #if DBG watch.Stop(); processingTime += watch.ElapsedMilliseconds; #endif if (cuttingFinished) { poolIdx = 0; postList = new List <CutMesh>(meshSet); // continue to next state if the cutter is finished if (splitMeshIslands) { #if DBG isolatingIslandsFrames = 0; isolatingIslandsTime = 0.0f; #endif islands = new List <CutMesh>(meshSet.Count); state = State.IsolateMeshIslands; goto case State.IsolateMeshIslands; } state = State.PostprocessInit; goto case State.PostprocessInit; } } break; case State.IsolateMeshIslands: { #if DBG var watchPost = new Stopwatch(); watchPost.Start(); #endif var isolatingFinished = IsolateMeshIslands(ref cuttingTime); #if DBG watchPost.Stop(); isolatingIslandsFrames++; isolatingIslandsTime += watchPost.ElapsedMilliseconds; #endif if (isolatingFinished) { // goto next state state = State.PostprocessInit; goto case State.PostprocessInit; } } break; case State.PostprocessInit: { InitPostprocess(); state = State.Postprocess; goto case State.Postprocess; } case State.Postprocess: { #if DBG var watchPost = new Stopwatch(); watchPost.Start(); #endif if (Postprocess(cuttingTime)) { timer.Stop(); } #if DBG watchPost.Stop(); postProcessingTime += watchPost.ElapsedMilliseconds; #endif } break; } }
private List <CutMesh> GetMeshList() { GameObject[] objects = null; if (DontUseTag) { var objs = FindObjectsOfType(typeof(Explodable2D)); var objList = new List <GameObject>(objs.Length); foreach (var o in objs) { var ex = (Explodable2D)o; if (ex) { objList.Add(ex.gameObject); } } objects = objList.ToArray(); } else { objects = GameObject.FindGameObjectsWithTag(Tag); } var list = new List <CutMesh>(objects.Length); foreach (var o in objects) { // don't destroy the destroyer :) if (!ExplodeSelf && o == gameObject) { continue; } // stop scanning for object is case of ExplodeSelf if (o != gameObject && ExplodeSelf && DisableRadiusScan) { continue; } // UnityEngine.Debug.DrawLine(mainCentroid, mainCentroid + Vector2.up*100, Color.green, 100); var distance2 = (Exploder2DUtils.GetCentroid(o) - mainCentroid).sqrMagnitude; if (distance2 < Radius * Radius) { var meshData = GetMeshData(o); var meshDataLen = meshData.Count; for (var i = 0; i < meshDataLen; i++) { var centroid = meshData[i].centroid; var distance = (centroid - mainCentroid).magnitude; // UnityEngine.Debug.Log("Distance: " + distance + " " + meshData[i].gameObject.name); var layer = FragmentOptions.InheritSortingLayer ? meshData[i].spriteRenderer.sortingLayerName : FragmentOptions.SortingLayer; var order = FragmentOptions.InheritOrderInLayer ? meshData[i].spriteRenderer.sortingOrder : FragmentOptions.OrderInLayer; var color = FragmentOptions.InheritSpriteRendererColor ? meshData[i].spriteRenderer.color : FragmentOptions.SpriteRendererColor; list.Add(new CutMesh { spriteMesh = meshData[i].spriteMesh, centroidLocal = meshData[i].gameObject.transform.InverseTransformPoint(centroid), transform = meshData[i].gameObject.transform, sprite = meshData[i].sprite, parent = meshData[i].gameObject.transform.parent, position = meshData[i].gameObject.transform.position, rotation = meshData[i].gameObject.transform.rotation, localScale = meshData[i].gameObject.transform.localScale, distance = distance, level = GetLevel(distance, Radius), original = meshData[i].parentObject, sortingLayer = layer, orderInLayer = order, color = color, option = o.GetComponent <Exploder2DOption>(), }); } } } if (list.Count == 0) { #if DBG Exploder2DUtils.Log("No explodable objects found!"); #endif return(list); } list.Sort(delegate(CutMesh m0, CutMesh m1) { return((m0.level).CompareTo(m1.level)); }); // for the case when the count of objects is higher then target fragments if (list.Count > TargetFragments) { list.RemoveRange(TargetFragments - 1, list.Count - TargetFragments); } var levelMax = list[list.Count - 1].level; var fragmentsPerLevel = GetLevelFragments(levelMax, TargetFragments); int maxCount = 0; var listCount = list.Count; var levelCount = new int[levelMax + 1]; foreach (var cutMesh in list) { levelCount[cutMesh.level]++; } for (int i = 0; i < listCount; i++) { var cutMesh = list[i]; var curLevelRatio = levelMax + 1 - cutMesh.level; var fragments = (int)((curLevelRatio * fragmentsPerLevel) / levelCount[cutMesh.level]); cutMesh.fragments = fragments; maxCount += fragments; list[i] = cutMesh; if (maxCount >= TargetFragments) { cutMesh.fragments -= maxCount - TargetFragments; maxCount -= maxCount - TargetFragments; list[i] = cutMesh; break; } } // foreach (var cutMesh in list) // { // UnityEngine.Debug.Log(cutMesh.level + " " + cutMesh.distance + " " + cutMesh.fragments); // } return(list); }
bool IsolateMeshIslands(ref long timeOffset) { var timer = new Stopwatch(); timer.Start(); var count = postList.Count; while (poolIdx < count) { var mesh = postList[poolIdx]; poolIdx++; var islandsFound = false; if (SplitMeshIslands || (mesh.option && mesh.option.SplitMeshIslands)) { var meshIslands = MeshUtils.IsolateMeshIslands(mesh.spriteMesh); if (meshIslands != null) { islandsFound = true; foreach (var meshIsland in meshIslands) { islands.Add(new CutMesh { spriteMesh = meshIsland.mesh, centroidLocal = meshIsland.centroidLocal, sprite = mesh.sprite, vertices = mesh.vertices, transform = mesh.transform, distance = mesh.distance, level = mesh.level, fragments = mesh.fragments, original = mesh.original, parent = mesh.transform.parent, position = mesh.transform.position, rotation = mesh.transform.rotation, localScale = mesh.transform.localScale, sortingLayer = mesh.sortingLayer, orderInLayer = mesh.orderInLayer, color = mesh.color, option = mesh.option, }); } } } if (!islandsFound) { islands.Add(mesh); } if (timer.ElapsedMilliseconds + timeOffset > FrameBudget) { return(false); } } #if DBG Exploder2DUtils.Log("Replacing fragments: " + postList.Count + " by islands: " + islands.Count); #endif // replace postList by island list postList = islands; return(true); }
bool Postprocess(long timeOffset) { var postTimer = new Stopwatch(); postTimer.Start(); var count = postList.Count; #if DBG postProcessingFrames++; #endif while (poolIdx < count) { var fragment = pool[poolIdx]; var mesh = postList[poolIdx]; poolIdx++; if (!mesh.original) { continue; } if (crack) { Exploder2DUtils.SetActiveRecursively(fragment.gameObject, false); } fragment.CreateSprite(mesh.spriteMesh, mesh.sprite, mesh.original.transform, mesh.sortingLayer, mesh.orderInLayer, mesh.color); var oldParent = fragment.transform.parent; fragment.transform.parent = mesh.parent; fragment.transform.position = new Vector3(mesh.position.x, mesh.position.y, mesh.original.transform.position.z); fragment.transform.rotation = mesh.rotation; fragment.transform.localScale = mesh.localScale; fragment.transform.parent = null; fragment.transform.parent = oldParent; if (!crack) { if (mesh.original != gameObject) { Exploder2DUtils.SetActiveRecursively(mesh.original, false); } else { Exploder2DUtils.EnableCollider(mesh.original, false); Exploder2DUtils.SetVisible(mesh.original, false); } } if (!FragmentOptions.DisableColliders) { Core.MeshUtils.GeneratePolygonCollider(fragment.polygonCollider2D, mesh.spriteMesh); } if (mesh.option) { mesh.option.DuplicateSettings(fragment.options); } if (!crack) { fragment.Explode(); } var force = Force; if (mesh.option && mesh.option.UseLocalForce) { force = mesh.option.Force; } // apply force to rigid body fragment.ApplyExplosion2D(mesh.transform, mesh.centroidLocal, mainCentroid, FragmentOptions, UseForceVector, ForceVector, force, mesh.original, TargetFragments); #if SHOW_DEBUG_LINES UnityEngine.Debug.DrawLine(mainCentroid, forceVector * Force, Color.yellow, 3); #endif if (postTimer.ElapsedMilliseconds + timeOffset > FrameBudget) { return(false); } } #if DBG var watch = new Stopwatch(); watch.Start(); #endif if (!crack) { if (DestroyOriginalObject) { foreach (var mesh in postList) { if (mesh.original && !mesh.original.GetComponent <Fragment2D>()) { Object.Destroy(mesh.original); } } } if (ExplodeSelf) { if (!DestroyOriginalObject) { Exploder2DUtils.SetActiveRecursively(gameObject, false); } } if (HideSelf) { Exploder2DUtils.SetActiveRecursively(gameObject, false); } #if DBG Exploder2DUtils.Log("Explosion finished! " + postList.Count + postList[0].original.transform.gameObject.name); #endif OnExplosionFinished(true); } else { cracked = true; if (CrackedCallback != null) { CrackedCallback(); } } #if DBG postProcessingTimeEnd = watch.ElapsedMilliseconds; #endif return(true); }
void PostCrackExplode(OnExplosion callback) { if (callback != null) { callback(0.0f, ExplosionState.ExplosionStarted); } var count = postList.Count; poolIdx = 0; while (poolIdx < count) { var fragment = pool[poolIdx]; var mesh = postList[poolIdx]; poolIdx++; if (mesh.original != gameObject) { Exploder2DUtils.SetActiveRecursively(mesh.original, false); } else { Exploder2DUtils.EnableCollider(mesh.original, false); Exploder2DUtils.SetVisible(mesh.original, false); } fragment.Explode(); } if (DestroyOriginalObject) { foreach (var mesh in postList) { if (mesh.original && !mesh.original.GetComponent <Fragment2D>()) { Object.Destroy(mesh.original); } } } if (ExplodeSelf) { if (!DestroyOriginalObject) { Exploder2DUtils.SetActiveRecursively(gameObject, false); } } if (HideSelf) { Exploder2DUtils.SetActiveRecursively(gameObject, false); } #if DBG Exploder2DUtils.Log("Crack finished! " + postList.Count + postList[0].original.transform.gameObject.name); #endif ExplosionCallback = callback; OnExplosionFinished(true); }
/// <summary> /// returns list of fragments with requested size /// this method pick fragments hidden from camera or sleeping rather then visible /// </summary> /// <param name="size">number of requested fragments</param> /// <returns>list of fragments</returns> public List <Fragment2D> GetAvailableFragments(int size) { if (size > pool.Length) { Debug.LogError("Requesting pool size higher than allocated! Please call Allocate first! " + size); return(null); } if (size == pool.Length) { return(new List <Fragment2D>(pool)); } var fragments = new List <Fragment2D>(); int counter = 0; // get deactivated fragments first foreach (var fragment in pool) { // get invisible fragments if (!fragment.activeObj) { fragments.Add(fragment); counter++; } if (counter == size) { return(fragments); } } foreach (var fragment in pool) { // get invisible fragments if (!fragment.visible) { fragments.Add(fragment); counter++; } if (counter == size) { return(fragments); } } // there are still live fragments ... get sleeping ones if (counter < size) { foreach (var fragment in pool) { if (fragment.IsSleeping() && fragment.visible) { Exploder2DUtils.Assert(!fragments.Contains(fragment), "!!!"); fragments.Add(fragment); counter++; } if (counter == size) { return(fragments); } } } // there are still live fragments... if (counter < size) { foreach (var fragment in pool) { if (!fragment.IsSleeping() && fragment.visible) { Exploder2DUtils.Assert(!fragments.Contains(fragment), "!!!"); fragments.Add(fragment); counter++; } if (counter == size) { return(fragments); } } } Exploder2DUtils.Assert(false, "ERROR!!!"); return(null); }
private bool ProcessCutterRandomized(out long cuttingTime) { Exploder2DUtils.Assert(state == State.ProcessCutter || state == State.DryRun, "Wrong state!"); var stopWatch = new Stopwatch(); stopWatch.Start(); bool cutting = true; bool timeBudgetStop = false; var cycleCounter = 0; cuttingTime = 0; while (cutting) { cycleCounter++; if (cycleCounter > TargetFragments) { Exploder2DUtils.Log("Explode Infinite loop!"); break; } newFragments.Clear(); meshToRemove.Clear(); cutting = false; var fragmentsCount = meshSet.Count; foreach (var mesh in meshSet) { if (levelCount[mesh.level] > 0) { var randomLineNormalized = Random.insideUnitCircle; if (!mesh.transform) { continue; } var plane = Core.Math.Line2D.CreateNormalPoint(randomLineNormalized, mesh.centroidLocal); #if DBG cuts++; #endif if (mesh.option) { splitMeshIslands |= mesh.option.SplitMeshIslands; } List <CutterMesh> meshes = null; cutter.Cut(mesh.spriteMesh, mesh.transform, plane, ref meshes); cutting = true; if (meshes != null) { foreach (var cutterMesh in meshes) { newFragments.Add(new CutMesh { spriteMesh = cutterMesh.mesh, centroidLocal = cutterMesh.centroidLocal, sprite = mesh.sprite, vertices = mesh.vertices, transform = mesh.transform, distance = mesh.distance, level = mesh.level, fragments = mesh.fragments, original = mesh.original, parent = mesh.transform.parent, position = mesh.transform.position, rotation = mesh.transform.rotation, localScale = mesh.transform.localScale, sortingLayer = mesh.sortingLayer, orderInLayer = mesh.orderInLayer, color = mesh.color, option = mesh.option, }); } meshToRemove.Add(mesh); levelCount[mesh.level] -= 1; // stop this madness! if (fragmentsCount + newFragments.Count - meshToRemove.Count >= TargetFragments) { cuttingTime = stopWatch.ElapsedMilliseconds; meshSet.ExceptWith(meshToRemove); meshSet.UnionWith(newFragments); return(true); } // computation took more than FrameBudget ... if (stopWatch.ElapsedMilliseconds > FrameBudget) { timeBudgetStop = true; break; } } } } meshSet.ExceptWith(meshToRemove); meshSet.UnionWith(newFragments); if (timeBudgetStop) { break; } } cuttingTime = stopWatch.ElapsedMilliseconds; // explosion is finished if (!timeBudgetStop) { return(true); } return(false); }