Example #1
0
    // 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>());
    }
Example #2
0
    // 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>());
    }
Example #3
0
    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);
    }
Example #5
0
    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);
    }
Example #7
0
    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");
        }
    }
Example #8
0
    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);
    }
Example #10
0
    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);
                        }
                    }
                }
            }
        }
    }
Example #11
0
 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);
 }
Example #12
0
    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);
        }
    }
Example #13
0
    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);
        }
    }
Example #14
0
    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);
    }
Example #15
0
 public void ChangePath()
 {
     agent.ResetPath();
     agent.transform.position = NavMeshUtils2D.ProjectTo3D(transform.position);
 }