// monobehaviour /////////////////////////////////////////////////////////// void Awake() { // create projection var go = GameObject.CreatePrimitive(PrimitiveType.Cylinder); go.name = "NAVIGATION2D_AGENT"; go.transform.position = NavMeshUtils2D.ProjectTo3D(transform.position); // todo height 0.5 again? agent = go.AddComponent <NavMeshAgent>(); rigidbody2D = GetComponent <Rigidbody2D>(); collider2D = GetComponent <Collider2D>(); // disable navmesh and collider (no collider for now...) Destroy(agent.GetComponent <Collider>()); Destroy(agent.GetComponent <MeshRenderer>()); }
// monobehaviour /////////////////////////////////////////////////////////// void Awake() { // create projection var go = GameObject.CreatePrimitive(PrimitiveType.Cylinder); go.name = "NAVIGATION2D_OBSTACLE"; go.transform.position = NavMeshUtils2D.ProjectTo3D(transform.position); go.transform.rotation = Quaternion.Euler(NavMeshUtils2D.RotationTo3D(transform.eulerAngles)); obstacle = go.AddComponent <NavMeshObstacle>(); // disable mesh and collider (no collider for now) Destroy(obstacle.GetComponent <Collider>()); Destroy(obstacle.GetComponent <MeshRenderer>()); }
void BakeNavMesh2D() { // create a temporary parent GameObject var obj = new GameObject(); // find all static box colliders, add them to projection AddBoxCollider2Ds(obj.transform); // find all static circle colliders, add them to projection AddCircleCollider2Ds(obj.transform); // find all static polygon colliders, add them to projection AddPolygonCollider2Ds(obj.transform); // min and max point needed for ground plane (from 3d colliders) var cols = GameObject.FindObjectsOfType <Collider>(); if (cols.Length > 0) { var min = new Vector2(Mathf.Infinity, Mathf.Infinity); var max = -min; foreach (var c in cols) { var minmax = NavMeshUtils2D.AdjustMinMax(c, min, max); min = minmax[0]; max = minmax[1]; } // create ground (cube instead of plane because it has unit size) // (pos between min and max; scaled to fit min and max * scale) // note: scale.y=0 so that *groundScale doesn't make it too high var go = GameObject.CreatePrimitive(PrimitiveType.Cube); go.isStatic = true; go.transform.parent = obj.transform; float w = max.x - min.x; float h = max.y - min.y; go.transform.position = new Vector3(min.x + w / 2, -0.5f, min.y + h / 2); go.transform.localScale = new Vector3(w, 0, h) * groundScale; } // bake navmesh asynchronously, clear mesh NavMeshBuilder.BuildNavMeshAsync(); // Async causes weird results if (gizmesh != null) { gizmesh.Clear(); } needsRebuild = true; // rebuild as soon as async baking is finished // delete the gameobjects now that the path was created GameObject.DestroyImmediate(obj); }
void Update() { // copy properties to projection all the time // (in case they are modified after creating it) obst.carving = carve; obst.center = NavMeshUtils2D.ProjectTo3D(center); obst.size = new Vector3(size.x, 1, size.y); // scale and rotate to match scaled/rotated sprites center properly obst.transform.localScale = new Vector3(transform.localScale.x, 1, transform.localScale.y); obst.transform.rotation = Quaternion.Euler(NavMeshUtils2D.RotationTo3D(transform.eulerAngles)); // project position to 3d obst.transform.position = NavMeshUtils2D.ProjectTo3D(transform.position); }
void AddPolygonCollider2Ds(Transform parent) { // find all valid colliders, add them to projection var colliders = GameObject.FindObjectsOfType <PolygonCollider2D>(); var filtered = colliders.Where(co => IsValidCollider(co)).ToList(); foreach (var collider in filtered) { for (int i = 0; i < collider.pathCount; i++) { // note: creating a primitive is necessary in order for it to bake properly var go = GameObject.CreatePrimitive(PrimitiveType.Cube); go.isStatic = true; go.transform.parent = parent; // position via offset and transformpoint var localPos = new Vector3(collider.offset.x, collider.offset.y, 0); var worldPos = collider.transform.TransformPoint(localPos); go.transform.position = new Vector3(worldPos.x, 0, worldPos.y); // scale depending on scale * collider size (circle=radius/box=size/...) go.transform.localScale = NavMeshUtils2D.ScaleFromPolygonCollider2D(collider); // rotation go.transform.rotation = Quaternion.Euler(NavMeshUtils2D.RotationTo3D(collider.transform.eulerAngles)); // remove box collider. note that baking uses the meshfilter, so // the collider doesn't really matter anyway. DestroyImmediate(go.GetComponent <BoxCollider>()); // Use the triangulator to get indices for creating triangles int[] indices = Triangulation.Triangulate(collider.GetPath(i).ToList()).ToArray(); // convert vector2 points to vector3 vertices var vertices = collider.GetPath(i).Select(p => new Vector3(p.x, 0, p.y)).ToList(); // create mesh var mesh = new Mesh(); mesh.vertices = vertices.ToArray(); mesh.triangles = indices; //mesh.RecalculateNormals(); mesh.RecalculateBounds(); // assign it to the mesh filter go.GetComponent <MeshFilter>().sharedMesh = mesh; MakeUnwalkable(go); } } }
bool IsStuck() { // stuck detection: get max distance first (best with collider) float maxdist = 2; // default if no collider if (collider2D != null) { var bounds = collider2D.bounds; maxdist = Mathf.Max(bounds.extents.x, bounds.extents.y) * 2; } // stuck detection: reset if distance > max distance float dist = Vector2.Distance(transform.position, NavMeshUtils2D.ProjectTo2D(agent.transform.position)); return(dist > maxdist); }
void FixedUpdate() { // copy properties to projection all the time // (in case they are modified after creating it) agent.speed = speed; agent.angularSpeed = angularSpeed; agent.acceleration = acceleration; agent.stoppingDistance = stoppingDistance; agent.autoBraking = autoBraking; agent.radius = radius; agent.obstacleAvoidanceType = quality; agent.avoidancePriority = priority; agent.autoRepath = autoRepath; // copy projection's position var rb = GetComponent <Rigidbody2D>(); if (rb != null && !rb.isKinematic) { rb.MovePosition(NavMeshUtils2D.ProjectTo2D(agent.transform.position)); } else { transform.position = NavMeshUtils2D.ProjectTo2D(agent.transform.position); } // stuck detection: get max distance first (best with collider) float maxdist = 2; // default if no collider if (GetComponent <Collider2D>()) { var bounds = GetComponent <Collider2D>().bounds; maxdist = Mathf.Max(bounds.extents.x, bounds.extents.y) * 2; } // stuck detection: reset if distance > max distance float dist = Vector2.Distance(transform.position, NavMeshUtils2D.ProjectTo2D(agent.transform.position)); if (dist > maxdist) { // stop agent movement, reset it to current position agent.ResetPath(); agent.transform.position = NavMeshUtils2D.ProjectTo3D(transform.position); Debug.Log("stopped agent because of collision in 2D plane"); } }
void Update() { // copy position: transform in Update, rigidbody in FixedUpdate if (rigidbody2D == null || rigidbody2D.isKinematic) { transform.position = NavMeshUtils2D.ProjectTo2D(agent.transform.position); } // stuck detection if (IsStuck()) { // stop agent movement, reset it to current position agent.ResetPath(); agent.transform.position = NavMeshUtils2D.ProjectTo3D(transform.position); Debug.Log("stopped agent because of collision in 2D plane"); } }
// based on: https://docs.unity3d.com/ScriptReference/AI.NavMesh.SamplePosition.html public static bool SamplePosition(Vector2 sourcePosition, out NavMeshHit2D hit, float maxDistance, int areaMask) { NavMeshHit hit3D; if (NavMesh.SamplePosition(NavMeshUtils2D.ProjectTo3D(sourcePosition), out hit3D, maxDistance, areaMask)) { hit = new NavMeshHit2D { position = NavMeshUtils2D.ProjectTo2D(hit3D.position), normal = NavMeshUtils2D.ProjectTo2D(hit3D.normal), distance = hit3D.distance, mask = hit3D.mask, hit = hit3D.hit }; return(true); } hit = new NavMeshHit2D(); return(false); }
void AddTilemapCollider2Ds(Transform parent) { // find all grids Grid[] grids = GameObject.FindObjectsOfType <Grid>(); foreach (Grid grid in grids) { // find tilemaps (we only care about those that have colliders) var tilemaps = grid.GetComponentsInChildren <Tilemap>().Where( tm => tm.GetComponent <TilemapCollider2D>() ).ToList(); foreach (Tilemap tilemap in tilemaps) { // go through each cell BoundsInt bounds = tilemap.cellBounds; for (int y = bounds.position.y; y < bounds.size.y; ++y) { for (int x = bounds.position.x; x < bounds.size.x; ++x) { // find out if it has a collider Vector3Int cellPosition = new Vector3Int(x, y, 0); if (tilemap.GetColliderType(cellPosition) != Tile.ColliderType.None) { // convert to world space Vector3 worldPosition = tilemap.GetCellCenterWorld(cellPosition); // note: creating a primitive is necessary in order for it to bake properly var go = GameObject.CreatePrimitive(PrimitiveType.Cube); go.isStatic = true; go.transform.parent = parent; // position via offset and transformpoint go.transform.position = new Vector3(worldPosition.x, 0, worldPosition.y); // scale depending on scale * collider size (circle=radius/box=size/...) go.transform.localScale = NavMeshUtils2D.ScaleTo3D(tilemap.transform.localScale); // rotation go.transform.rotation = Quaternion.Euler(NavMeshUtils2D.RotationTo3D(tilemap.transform.eulerAngles)); MakeUnwalkable(go); } } } } } }
public static bool Raycast(Vector2 sourcePosition, Vector2 targetPosition, out NavMeshHit2D hit, int areaMask) { if (NavMesh.Raycast(NavMeshUtils2D.ProjectTo3D(sourcePosition), NavMeshUtils2D.ProjectTo3D(targetPosition), out NavMeshHit hit3D, areaMask)) { hit = new NavMeshHit2D { position = NavMeshUtils2D.ProjectTo2D(hit3D.position), normal = NavMeshUtils2D.ProjectTo2D(hit3D.normal), distance = hit3D.distance, mask = hit3D.mask, hit = hit3D.hit }; return(true); } hit = new NavMeshHit2D(); return(false); }
void AddCircleCollider2Ds(Transform parent) { // find all valid colliders, add them to projection var colliders = GameObject.FindObjectsOfType <CircleCollider2D>(); var filtered = colliders.Where(co => IsValidCollider(co)).ToList(); foreach (var collider in filtered) { // note: creating a primitive is necessary in order for it to bake properly var go = GameObject.CreatePrimitive(PrimitiveType.Cylinder); go.isStatic = true; go.transform.parent = parent; // position via offset and transformpoint var localPos = new Vector3(collider.offset.x, collider.offset.y, 0); var worldPos = collider.transform.TransformPoint(localPos); go.transform.position = new Vector3(worldPos.x, 0, worldPos.y); // scale depending on scale * collider size (circle=radius/box=size/...) go.transform.localScale = NavMeshUtils2D.ScaleFromCircleCollider2D(collider); // rotation go.transform.rotation = Quaternion.Euler(NavMeshUtils2D.RotationTo3D(collider.transform.eulerAngles)); MakeUnwalkable(go); } }
void AddEdgeCollider2Ds(Transform parent) { // find all valid colliders, add them to projection var colliders = GameObject.FindObjectsOfType <EdgeCollider2D>(); var filtered = colliders.Where(co => IsValidCollider(co)).ToList(); foreach (var collider in filtered) { // note: creating a primitive is necessary in order for it to bake properly var go = GameObject.CreatePrimitive(PrimitiveType.Cube); go.isStatic = true; go.transform.parent = parent; // position via offset and transformpoint var localPos = new Vector3(collider.offset.x, collider.offset.y, 0); var worldPos = collider.transform.TransformPoint(localPos); go.transform.position = new Vector3(worldPos.x, 0, worldPos.y); // scale depending on scale * collider size (circle=radius/box=size/...) go.transform.localScale = NavMeshUtils2D.ScaleFromEdgeCollider2D(collider); // rotation go.transform.rotation = Quaternion.Euler(NavMeshUtils2D.RotationTo3D(collider.transform.eulerAngles)); // remove box collider. note that baking uses the meshfilter, so // the collider doesn't really matter anyway. DestroyImmediate(go.GetComponent <BoxCollider>()); // create mesh from edgecollider2D by stepping through each point // and creating a triangle with point, point-1 and point-1 with y=1 List <Vector3> vertices = new List <Vector3>(); List <int> indices = new List <int>(); // start at 2nd point so we can use the first one in our triangle, for (int i = 1; i < collider.points.Length; ++i) { Vector2 a2D = collider.points[i - 1]; Vector2 b2D = collider.points[i]; // convert to 3D // point A // point B // point C := A with y+1 Vector3 a3D = new Vector3(a2D.x, 0, a2D.y); Vector3 b3D = new Vector3(b2D.x, 0, b2D.y); Vector3 c3D = new Vector3(a2D.x, 1, a2D.y); // add 3 vertices vertices.Add(a3D); vertices.Add(b3D); vertices.Add(c3D); // add last 3 vertices as indices indices.Add(vertices.Count - 1); indices.Add(vertices.Count - 2); indices.Add(vertices.Count - 3); } // create mesh var mesh = new Mesh(); mesh.vertices = vertices.ToArray(); mesh.triangles = indices.ToArray(); //mesh.RecalculateNormals(); mesh.RecalculateBounds(); // assign it to the mesh filter go.GetComponent <MeshFilter>().sharedMesh = mesh; MakeUnwalkable(go); } }
void BakeNavMesh2D() { // create a temporary parent GameObject GameObject obj = new GameObject(); // find all static box colliders, add them to projection AddBoxCollider2Ds(obj.transform); // find all static circle colliders, add them to projection AddCircleCollider2Ds(obj.transform); // find all static polygon colliders, add them to projection AddPolygonCollider2Ds(obj.transform); // find all edge polygon colliders, add them to projection AddEdgeCollider2Ds(obj.transform); // find all tilemap colliders, add them to projection #if UNITY_2017_2_OR_NEWER AddTilemapCollider2Ds(obj.transform); #endif // min and max point from 2D colliders projected to 3D. // (scanning through 3D colliders doesn't work well because the polygon // GameObjects are pure meshes without colliders) Collider2D[] cols = FindObjectsOfType <Collider2D>(); if (cols.Length > 0) { Vector2 min = new Vector2(Mathf.Infinity, Mathf.Infinity); Vector2 max = -min; foreach (Collider2D c in cols) { var minmax = NavMeshUtils2D.AdjustMinMax(c, min, max); min = minmax[0]; max = minmax[1]; } // create ground (cube instead of plane because it has unit size) // (pos between min and max; scaled to fit min and max * scale) // note: scale.y=0 so that *navmeshExtends doesn't make it too high var go = GameObject.CreatePrimitive(PrimitiveType.Cube); go.name = "Ground"; // for debugging go.isStatic = true; go.transform.parent = obj.transform; float w = max.x - min.x; float h = max.y - min.y; // IMPORTANT ground is now positioned at y=0 so that baked navmesh // is also at y=0. fixes a bug where sampling required a minimum // distance of 0.5 to make up for the difference between projection // (at y=0) and navmesh (at y=-0.5)! go.transform.position = new Vector3(min.x + w / 2, 0, min.y + h / 2); go.transform.localScale = new Vector3(w, 0, h) * navmeshExtends; } // bake navmesh asynchronously, clear mesh UnityEditor.AI.NavMeshBuilder.BuildNavMeshAsync(); // Async causes weird results if (gizmesh != null) { gizmesh.Clear(); } needsRebuild = true; // rebuild as soon as async baking is finished // delete the gameobjects now that the path was created DestroyImmediate(obj); }
public void ChangePath() { agent.ResetPath(); agent.transform.position = NavMeshUtils2D.ProjectTo3D(transform.position); }