예제 #1
0
        /// <summary>
        /// Spawn things along an edge
        /// </summary>
        /// <param name="d"></param>
        /// <param name="edge"></param>
        /// <param name="thing"></param>
        private static void spawnThingOnEdge(Dungeoneer d, Edge edge, GameObject thing, float thingLength)
        {
            int     thingsToSpawn = Mathf.CeilToInt(edge.length / thingLength);
            float   lerpDistance  = 1 / (float)thingsToSpawn;
            float   lerp          = 0;
            Vector3 p             = edge.p.toVector3AtHeight(0);
            Vector3 q             = edge.q.toVector3AtHeight(0);
            Vector3 perpV         = Vector3.zero;

            for (int i = 0; i < thingsToSpawn; ++i)
            {
                lerp         += lerpDistance;
                Vector3 where = Vector3.Lerp(p, q, lerp);

                GameObject gro = UnityEngine.Object.Instantiate(thing, where, thing.transform.rotation);

                // The last segment is placed too close to "q," so it ends up
                // not getting rotated. Face the center (0,0,0) instead.
                if (Vector3.Distance(q, where) > 1e-1)
                {
                    perpV = Vector3.Cross(q - where, Vector3.up).normalized;
                }
                gro.transform.LookAt(where + perpV);
                gro.transform.Rotate(0, 90, 0);

                // It still might not get rotated if the wall segment is 1 tile long
                if (thingsToSpawn == 1)
                {
                    gro.transform.LookAt(Vector3.zero);
                }
                gro.transform.parent = d.thingOrganizer.transform;
            }
        }
예제 #2
0
        /// <summary>
        /// Stand up trigger zones on the bounding box of the world for
        /// enemy movement AI to detect using raycasts. Also stands up
        /// walls to prevent the player from falling off of the world.
        /// </summary>
        private static void constructEnemyAvoidanceBoundingBox(Dungeoneer d)
        {
            GameObject trigger     = GameObject.Find("EnemyAvoidanceTrigger");
            GameObject floor       = GameObject.FindGameObjectWithTag("Ground");
            Bounds     floorBounds = floor.GetComponent <Renderer>().bounds;

            Vector3 topLeft    = new Vector3(0, 0, floorBounds.extents.z * 2);
            Vector3 topRight   = new Vector3(floorBounds.extents.x * 2, 0, floorBounds.extents.z * 2);
            Vector3 bottomLeft = new Vector3(0, 0, 0);

            // Update the dungeoneer object with info about the min/max reachable points. These points are implied by the bounds of the floor.
            d.topRightWallCorner   = floorBounds.max;
            d.bottomLeftWallCorner = floorBounds.min;

            float xlen = Vector3.Distance(topLeft, topRight);
            float zlen = Vector3.Distance(topLeft, bottomLeft);

            Vector3 west  = new Vector3(floorBounds.min.x, d.currentDungeonType.wallHeight / 2, d.currentDungeonType.height / 2);
            Vector3 north = new Vector3(d.currentDungeonType.width / 2, d.currentDungeonType.wallHeight / 2, floorBounds.max.z);
            Vector3 east  = new Vector3(floorBounds.max.x, d.currentDungeonType.wallHeight / 2, d.currentDungeonType.height / 2);
            Vector3 south = new Vector3(d.currentDungeonType.width / 2, d.currentDungeonType.wallHeight / 2, floorBounds.min.z);

            setAvoidanceBoxOnSide(trigger, west, 1, d.currentDungeonType.wallHeight, zlen, d.currentDungeonType.wall.prefab);
            setAvoidanceBoxOnSide(trigger, north, xlen, d.currentDungeonType.wallHeight, 1, d.currentDungeonType.wall.prefab);
            setAvoidanceBoxOnSide(trigger, east, 1, d.currentDungeonType.wallHeight, zlen, d.currentDungeonType.wall.prefab);
            setAvoidanceBoxOnSide(trigger, south, xlen, d.currentDungeonType.wallHeight, 1, d.currentDungeonType.wall.prefab);

            // The original one isn't used, but it stays in the middle of the map, so destroy it to prevent weirdness on the AI
            UnityEngine.Object.Destroy(trigger.gameObject);
        }
예제 #3
0
        /// <summary>
        /// Make walls along an arbitrary edge
        /// </summary>
        /// <param name="wall"></param>
        private static void constructWallOnEdge(Dungeoneer d, Edge wall)
        {
            // Since the prefab is stretched equally along x and y, it must be placed at the center for both x and y
            Vector3 p          = wall.p.toVector3AtHeight(d.currentDungeonType.wallHeight / 2);
            Vector3 q          = wall.q.toVector3AtHeight(d.currentDungeonType.wallHeight / 2);
            Vector3 wallCenter = (p + q) / 2;

            GameObject wallObject = UnityEngine.Object.Instantiate(d.currentDungeonType.wall.prefab, wallCenter, d.currentDungeonType.wall.prefab.transform.rotation);

            wallObject.transform.parent = d.wallOrganizer.transform;
            wall.wallObjects.Add(wallObject);

            // Stretch wall
            wallObject.transform.localScale = new Vector3(
                wall.length,                                                         // length of the original edge
                wallObject.transform.localScale.y * d.currentDungeonType.wallHeight, // desired wall height
                wallObject.transform.localScale.z                                    // original prefab thickness
                );

            // Rotate the wall so that it's placed along the original edge
            Vector3 lookatme = Vector3.Cross(q - wallCenter, Vector3.up).normalized;

            wallObject.transform.LookAt(wallCenter + lookatme);

            // Attach info to game object for later use
            wallObject.GetComponent <WallInfo>().associatedEdge = wall;
            wall.isWall = true;

            /*
             * Vector3 direction = (q-p).normalized;
             #if UNITY_EDITOR
             * Debug.DrawRay(p, direction * wall.length, Color.white, 512);
             #endif
             */
        }
예제 #4
0
 /// <summary>
 /// Make walls only on Voronoi rooms
 /// </summary>
 /// <param name="d"></param>
 private static void constructWallsOnRooms(Dungeoneer d)
 {
     foreach (VoronoiRoom r in d.roomVoronoi)
     {
         constructWallsOnPolygon(d, r.exterior);
     }
 }
예제 #5
0
 /// <summary>
 /// Make walls on all edges of a polygon
 /// </summary>
 /// <param name="d"></param>
 /// <param name="p"></param>
 private static void constructWallsOnPolygon(Dungeoneer d, Polygon p)
 {
     foreach (Edge e in p.sides)
     {
         constructWallOnEdge(d, e);
     }
 }
예제 #6
0
        public static void makeEscapePoint(Dungeoneer d)
        {
            Debug.Log("Placing Escape portal");
            int escapeSite = makePortal(d, d.currentDungeonType.returnPrefab);

            d.validCells[escapeSite].spawnedObject.name = "Escape Point";
        }
예제 #7
0
        public static void makeDelvePoint(Dungeoneer d)
        {
            Debug.Log("Placing Delve portal");
            int delveSite = makePortal(d, d.currentDungeonType.delvePrefab);

            d.validCells[delveSite].spawnedObject.name = "Delve Point";
        }
예제 #8
0
        /// <summary>
        /// Generate the selected prefab along the specified graph's edges
        /// </summary>
        /// <param name="d">reference to Dungeoneer</param>
        /// <param name="edges">list of edges to spawn things on</param>
        /// <param name="thing">thing to spawn</param>
        public static void generateOnEdges(Dungeoneer d, List <Edge> edges, GameObject thing)
        {
            float thingLength = thing.GetComponentInChildren <Renderer>().bounds.size.x;

            foreach (Edge e in edges)
            {
                spawnThingOnEdge(d, e, thing, thingLength);
            }
        }
예제 #9
0
        /// <summary>
        /// Calls all functions to create walls in a dungeon
        /// </summary>
        /// <param name="d"></param>
        public static void setWalls(Dungeoneer d)
        {
            constructWallsOnVoronoi(d);
            constructWallsOnRooms(d);
            List <GameObject> wallsToKeep = destroyWallsForCorridors(d);

            destroyLagWalls(d, wallsToKeep);
            constructEnemyAvoidanceBoundingBox(d);
        }
예제 #10
0
        /// <summary>
        /// Generate walls all over the Voronoi, except for room interiors. Used
        /// to do subtractive corridor generation.
        /// </summary>
        /// <param name="d"></param>
        private static void constructWallsOnVoronoi(Dungeoneer d)
        {
            List <Cell> roomCells = d.roomVoronoi.SelectMany(r => r.cells).ToList();

            foreach (Cell c in d.vd.cells)
            {
                if (!roomCells.Contains(c))
                {
                    constructWallsOnPolygon(d, c);
                }
            }
        }
예제 #11
0
        /// <summary>
        /// Main function to generate walls and update pathfinding graphs.
        /// The order of functions is important! Some variables are set in
        /// certain functions, and other conditions check for the existence
        /// of walls under the expectation that they are (or are not) there.
        /// </summary>
        /// <param name="d">dungeoneer object</param>
        /// <returns>list of cells that may be used for A* pathfinding</returns>
        public static List <Cell> setWalls(Dungeoneer d)
        {
            // Create walls everywhere, except for the rooms. This also
            // excludes the exteriors of the rooms.
            constructWallsOnVoronoi(d);

            // Create walls on the exteriors of the rooms.
            constructWallsOnRooms(d);

            // Eliminate walls where corridors should exist.
            List <GameObject> wallsToKeep = destroyWallsForCorridors(d);

            // Bounding box needs to be set up before reachableCells.
            // This function sets the min/max vectors on the dungeoneer
            // object that reachableCells needs to check.
            constructEnemyAvoidanceBoundingBox(d);

            // Enumerate reachable cells for A* pathfinding.
            // Note: Close-range pathfinding does not take reachable cells into
            // account.
            // A cell is only "A*-reachable" under the following conditions:
            // 1. At least one of its sides is not a wall
            // 2. None of its vertices exist outside the bounds of the walls
            // The bounds are tracked as 3D vertices, so the 2D cell vertex's y
            // must be compared to the 3D corner's z.
            List <Cell> reachableCells = d.vd.cells.Where(c => {
                c.reachable = (
                    !c.sides.All(s => s.isWall) &&
                    !c.vertices.Any(v =>
                                    v.x <d.bottomLeftWallCorner.x ||
                                         v.x> d.topRightWallCorner.x ||
                                    v.y <d.bottomLeftWallCorner.z ||
                                         v.y> d.topRightWallCorner.z)
                    );
                return(c.reachable);
            }).ToList();

            // Extraneous walls must be destroyed *after* reachableCells are
            // determined. Otherwise, the cleared area is considered reachable.
            destroyLagWalls(d, wallsToKeep);

            return(reachableCells);
        }
예제 #12
0
        /// <summary>
        /// Places floor tiles under a polygon.
        /// Should only be used with convex polygons!
        /// </summary>
        /// <param name="d"></param>
        /// <param name="p"></param>
        public static void constructFloorUnderPolygon(Dungeoneer d, Polygon p)
        {
            Vector2 center       = new Vector2(d.currentDungeonType.width / 2, d.currentDungeonType.height / 2);
            float   actualWidth  = d.currentDungeonType.width * 1.1f;
            float   actualHeight = d.currentDungeonType.width * 1.1f;
            // fudge factor on ground cube y to make it line up more nicely
            GameObject flo = stretchCube(d.currentDungeonType.ground.prefab, actualWidth, actualHeight, -0.2f, center);

            flo.transform.parent = d.organizer.transform;
            flo.name             = "Ground";

            // add optional ceiling
            if (d.currentDungeonType.ceiling != null)
            {
                GameObject ceiling = stretchCube(d.currentDungeonType.ceiling.prefab, actualWidth, actualHeight, d.currentDungeonType.wallHeight, center);
                ceiling.transform.parent = d.organizer.transform;
                ceiling.name             = "Ceiling";
            }
        }
예제 #13
0
        public static void setFloor(Dungeoneer d)
        {
            Debug.Log("Spawning floor");
            IEnumerable <Vertex> vs = d.roomVoronoi.SelectMany(r => r.vertices);
            float   mix             = vs.Min(v => v.x);
            float   max             = vs.Max(v => v.x);
            float   miy             = vs.Min(v => v.y);
            float   may             = vs.Max(v => v.y);
            Polygon floorever       = new Polygon(
                new List <Vertex> {
                new Vertex(mix, miy),
                new Vertex(mix, may),
                new Vertex(max, may),
                new Vertex(max, miy)
            }
                );

            // already a bounding box because we just made a bounding box
            constructFloorUnderPolygon(d, floorever);
        }
예제 #14
0
        public static int makePortal(Dungeoneer d, GameObject portal)
        {
            int  portalSite = d.mt.Next(d.validCells.Count - 1);
            Cell chosenCell = d.validCells[portalSite];

            // Remove any currently spawned objects here
            if (chosenCell.spawnedObject != null)
            {
                UnityEngine.Object.Destroy(chosenCell.spawnedObject);
            }

            Vector3 where            = new Vector3(chosenCell.site.x, 0, chosenCell.site.y);
            chosenCell.spawnedObject = UnityEngine.Object.Instantiate(portal, where, portal.transform.rotation);
            // Randomly rotate about the y-axis
            float rotation = d.mt.Next(360);

            chosenCell.spawnedObject.transform.Rotate(0, rotation, 0);
            chosenCell.hasPortal = true;

            Bounds bounds = chosenCell.spawnedObject.GetComponentInChildren <Renderer>().bounds;

            // Try to respawn it if it's clipping
            // For some reason, Physics.Check[Primitive] is always true...
            Collider[] wtf = Physics.OverlapBox(where, bounds.extents, chosenCell.spawnedObject.transform.rotation, layersToAvoid);
            foreach (Collider col in wtf)
            {
                if (col.gameObject.layer == LayerMask.NameToLayer("Wall"))
                {
                    Debug.Log($"Need to retry portal: object was {col.gameObject} on layer {LayerMask.LayerToName(col.gameObject.layer)}");
                    return(makePortal(d, portal));
                }
                ObjectInfo oi = col.gameObject.GetComponent <ObjectInfo>();
                if (oi != null)
                {
                    oi.toBeDestroyed = true;
                }
            }

            return(portalSite);
        }
예제 #15
0
        /// <summary>
        /// Remove walls that don't need to exist for performance reasons
        /// </summary>
        /// <param name="d">dungeoneer object</param>
        /// <param name="wallsToKeep">list of walls to avoid destroying</param>
        private static void destroyLagWalls(Dungeoneer d, List <GameObject> wallsToKeep)
        {
            foreach (VoronoiRoom r in d.roomVoronoi)
            {
                foreach (Edge e in r.exterior.sides)
                {
                    wallsToKeep.AddRange(e.wallObjects);
                }
            }

            foreach (GameObject w in GameObject.FindGameObjectsWithTag("Wall"))
            {
                if (w != null && !wallsToKeep.Contains(w))
                {
                    WallInfo wi = w.GetComponent <WallInfo>();
                    if (wi != null)
                    {
                        wi.associatedEdge.isWall = false;
                    }
                    w.name = $"Destroyed {w.name}";
                    UnityEngine.Object.Destroy(w);
                }
            }
        }
예제 #16
0
        /// <summary>
        /// Destroys wall objects where corridors should exist
        /// </summary>
        /// <param name="d"></param>
        /// <returns>list of walls that are valid</returns>
        private static List <GameObject> destroyWallsForCorridors(Dungeoneer d)
        {
            LayerMask         wallMask    = 1 << LayerMask.NameToLayer("Wall");
            List <GameObject> wallsToKeep = new List <GameObject>();
            // Need to make sure we can fit through these ones
            List <Edge> shortDestroyedWalls = new List <Edge>();

            foreach (Edge e in d.spanningTree)
            {
                float len = e.length;

                Vector3 p         = e.p.toVector3AtHeight(1);
                Vector3 q         = e.q.toVector3AtHeight(1);
                Vector3 direction = (q - p).normalized;
                // uncomment to debug raycasts

                /*
                 #if UNITY_EDITOR
                 * Debug.DrawRay(p, direction * len, Color.blue, 512);
                 #endif
                 */

                // Cast a ray to destroy initial set of walls. SphereCast does
                // not always catch all walls close to the sphere center
                List <RaycastHit> hits = new List <RaycastHit>(Physics.CapsuleCastAll(p, p + Vector3.up * d.currentDungeonType.wallHeight, d.currentDungeonType.hallWidth, direction, len, wallMask));
                foreach (RaycastHit hit in hits)
                {
                    Edge er = hit.collider.gameObject.GetComponent <WallInfo>().associatedEdge;
                    er.isWall = false;

                    if (er.length < FATTEST_CONTROLLER * 2)
                    {
                        shortDestroyedWalls.Add(er);
                    }
                    hit.collider.gameObject.name = $"Destroyed {hit.collider.gameObject.name}";
                    UnityEngine.Object.Destroy(hit.collider.gameObject);
                }

                // Cast a wider sphere to determine what walls to keep during cleanup
                foreach (RaycastHit hit in Physics.SphereCastAll(p, d.currentDungeonType.hallWidth * 4, direction, len, wallMask))
                {
                    wallsToKeep.Add(hit.collider.gameObject);
                }
            }

            // Sometimes, only a really short wall is removed, so the player still can't fit. Probably a Unity spherecast bug
            foreach (Edge w in shortDestroyedWalls)
            {
                int    pwalls = w.p.incidentEdges.Count(e => e.isWall);
                int    qwalls = w.q.incidentEdges.Count(e => e.isWall);
                float  minp   = (pwalls > 0)? w.p.incidentEdges.Where(e => e.isWall).Min(e => e.length) : 0;
                float  minq   = (qwalls > 0)? w.q.incidentEdges.Where(e => e.isWall).Min(e => e.length) : 0;
                Vertex v;
                // knock out the least-connected side
                if (pwalls < qwalls)
                {
                    v = w.p;
                }
                else if (pwalls == qwalls)
                {
                    if (pwalls == 0)    // don't do anything if neither have a wall to remove...
                    {
                        continue;
                    }
                    v = (minp > minq)? w.q : w.p;
                }
                else
                {
                    v = w.q;
                }

                // remove the shortest edge
                // float equality is dumb, so this is ugly
                Edge  shortestEdge = null;
                float s            = Mathf.Infinity;
                foreach (Edge hurr in v.incidentEdges)
                {
                    if (hurr.isWall && hurr.length < s)
                    {
                        shortestEdge = hurr;
                        s            = hurr.length;
                    }
                }
                if (shortestEdge == null)    // why does this happen?!
                {
                    continue;
                }
                shortestEdge.isWall = false;
                foreach (GameObject wo in shortestEdge.wallObjects)
                {
                    wallsToKeep.Remove(wo);
                    UnityEngine.Object.Destroy(wo);
                }
            }

            return(wallsToKeep);
        }