Advertisement
Guest User

Procedural Generation - C#

a guest
Jun 20th, 2013
4,450
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 16.91 KB | None | 0 0
  1. using Microsoft.Xna.Framework;
  2. using System;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Xml;
  8.  
  9. namespace ThatShooter
  10. {
  11.     public class Generation : Entity
  12.     {
  13.         // map tile types and their a* pathfinding cost
  14.         // so, for example, if it wanted to move through a Wall, it'd have to be better than moving through 4 Floor tiles
  15.         public enum Type
  16.         {
  17.             Floor = 1,
  18.             Wall = 4,
  19.             Stone = 20,
  20.             None = 0
  21.         }
  22.  
  23.         // map data
  24.         public Type[,] Map;
  25.         public int RoomCount;
  26.         public int MinRoomSize;
  27.         public int MaxRoomSize;
  28.         public int Grid = 16;
  29.         public int Padding = 4;
  30.  
  31.         // list of active rooms and the room xml templates
  32.         public List<Rectangle> Rooms;
  33.         public List<XmlElement> RoomTemplates;
  34.  
  35.         // map size
  36.         public new int Width { get { return Map.GetLength(0); } }
  37.         public new int Height { get { return Map.GetLength(1); } }
  38.  
  39.         // pathfinding cost thing
  40.         private int[,] cost;
  41.         private List<Point> currentPath;
  42.  
  43.         // coroutine
  44.         private Coroutine coroutine;
  45.  
  46.         // Called when the generation is complete
  47.         public delegate void Callback();
  48.         private Callback call;
  49.  
  50.         public Generation(List<XmlElement> templates)
  51.         {
  52.             RoomTemplates = templates;
  53.         }
  54.  
  55.         #region map generation
  56.  
  57.         /// <summary>
  58.         /// Generates the room of the given width and height
  59.         /// </summary>
  60.         /// <param name="width"></param>
  61.         /// <param name="height"></param>
  62.         /// <param name="callback">Called on generation-complete</param>
  63.         public void Generate(int width, int height, Callback callback)
  64.         {
  65.             // create the empty map and an empty list of rooms
  66.             Map = new Type[width, height];
  67.             Rooms = new List<Rectangle>();
  68.  
  69.             // fill with Wall tiles
  70.             FillMap(Type.Wall);
  71.  
  72.             call = callback;
  73.  
  74.             // set the min/max room size and the room count. Play around with these values
  75.             MinRoomSize = 6;
  76.             MaxRoomSize = (int)(Math.Min(width, height) / 4f);
  77.             RoomCount = (int)(Math.Sqrt(width * height) / 2f);
  78.  
  79.             // Start the coroutine of generating the segments
  80.             GenerateSegments();
  81.         }
  82.  
  83.         /// <summary>
  84.         /// Generates everything
  85.         /// </summary>
  86.         private void GenerateSegments()
  87.         {
  88.             // dig stuff
  89.             PlaceRooms();
  90.             PlaceTunnels();
  91.  
  92.             call();
  93.         }
  94.  
  95.         /// <summary>
  96.         /// Fills the entire map with the given type
  97.         /// </summary>
  98.         /// <param name="type"></param>
  99.         public void FillMap(Type type)
  100.         {
  101.             for (int i = 0; i < Width; i++)
  102.             {
  103.                 for (int j = 0; j < Height; j++)
  104.                     Map[i, j] = type;
  105.             }
  106.         }
  107.  
  108.         #endregion
  109.  
  110.         #region Room generation
  111.  
  112.         /// <summary>
  113.         /// Places all the rooms we're supposed to place
  114.         /// </summary>
  115.         public void PlaceRooms()
  116.         {
  117.             // place rooms
  118.             int placed = 0;
  119.             int count = 0;
  120.             while (placed < RoomCount)
  121.             {
  122.                 // choose to place a rectangle room, or a templated room
  123.                 if (Calc.Random.Choose<int>(0, 1) == 0)
  124.                 {
  125.                     if (PlaceRectRoom())
  126.                         placed++;
  127.                 }
  128.                 else
  129.                 {
  130.                     if (PlaceTemplateRoom())
  131.                         placed++;
  132.                 }
  133.  
  134.                 // this is for debug stuff - shouldn't ever happen
  135.                 count++;
  136.                 if (count > 1000)
  137.                     break;
  138.             }
  139.         }
  140.  
  141.         /// <summary>
  142.         /// Digs out a rectangular room
  143.         /// </summary>
  144.         /// <returns></returns>
  145.         public bool PlaceRectRoom()
  146.         {
  147.             int width = Util.Range(MinRoomSize, MaxRoomSize);
  148.             int height = Util.Range(MinRoomSize, MaxRoomSize);
  149.             Rectangle room = new Rectangle(Util.Range(Padding, Width - width - Padding * 2), Util.Range(Padding, Height - height - Padding * 2), width, height);
  150.  
  151.             // check room
  152.             if (Overlaps(room))
  153.             {
  154.                 Rooms.Add(room);
  155.                 DigRoom(room);
  156.                 return true;
  157.             }
  158.             return false;
  159.         }
  160.  
  161.         /// <summary>
  162.         /// Places a templated room, grabbed from the XML
  163.         /// </summary>
  164.         /// <returns></returns>
  165.         public bool PlaceTemplateRoom()
  166.         {
  167.             XmlElement room = RoomTemplates[Calc.Random.Next(RoomTemplates.Count)];
  168.             int width = room.AttrInt("width") / Grid;
  169.             int height = room.AttrInt("height") / Grid;
  170.             Rectangle area = new Rectangle(Util.Range(Padding, Width - width - Padding * 2), Util.Range(Padding, Height - height - Padding * 2), width, height);
  171.  
  172.             if (Overlaps(area))
  173.             {
  174.                 Rooms.Add(area);
  175.                 DigRoom(area.X, area.Y, room);
  176.                 return true;
  177.             }
  178.  
  179.             return false;
  180.         }
  181.  
  182.         /// <summary>
  183.         /// Digs out the rectangular room
  184.         /// </summary>
  185.         /// <param name="room"></param>
  186.         public void DigRoom(Rectangle room)
  187.         {
  188.             // place floors
  189.             for (int i = 0; i < room.Width; i++)
  190.             {
  191.                 for (int j = 0; j < room.Height; j++)
  192.                 {
  193.                     Set(room.X + i, room.Y + j, Type.Floor);
  194.                 }
  195.             }
  196.            
  197.             // place stone around the entire thing
  198.             for (int i = 0; i < room.Width; i++)
  199.             {
  200.                 Set(room.X + i, room.Y - 1, Type.Stone);
  201.                 Set(room.X + i, room.Y + room.Height, Type.Stone);
  202.             }
  203.  
  204.             for (int i = 0; i < room.Height; i++)
  205.             {
  206.                 Set(room.X - 1, room.Y + i, Type.Stone);
  207.                 Set(room.X + room.Width, room.Y + i, Type.Stone);
  208.             }
  209.            
  210.             // make some doors
  211.             int doors = 2;
  212.             for (int i = 0; i < doors; i++)
  213.             {
  214.                 if (Util.Range(0, 2) == 0)
  215.                 {
  216.                     Set(room.X + Util.Range(0, room.Width), room.Y + Calc.Random.Choose<int>(-1, room.Height), Type.Wall);
  217.                 }
  218.                 else
  219.                 {
  220.                     Set(room.X + Calc.Random.Choose<int>(-1, room.Width), room.Y + Util.Range(0, room.Height), Type.Wall);
  221.                 }
  222.             }
  223.         }
  224.  
  225.         /// <summary>
  226.         /// Digs out the templated room (based on the given room XML)
  227.         /// </summary>
  228.         /// <param name="x"></param>
  229.         /// <param name="y"></param>
  230.         /// <param name="room"></param>
  231.         public void DigRoom(int x, int y, XmlElement room)
  232.         {
  233.             Set(x, y, room.AttrInt("width") / Grid, room.AttrInt("height") / Grid, Type.Floor);
  234.  
  235.             XmlElement walls = room["Wall"];
  236.             foreach (XmlElement rect in walls)
  237.             {
  238.                 Set(x + rect.AttrInt("x"), y + rect.AttrInt("y"), rect.AttrInt("w"), rect.AttrInt("h"), Type.Wall);
  239.             }
  240.  
  241.             XmlElement stones = room["Stone"];
  242.             foreach (XmlElement rect in stones)
  243.             {
  244.                 Set(x + rect.AttrInt("x"), y + rect.AttrInt("y"), rect.AttrInt("w"), rect.AttrInt("h"), Type.Stone);
  245.             }
  246.         }
  247.  
  248.         #endregion
  249.  
  250.         #region generate tunnels
  251.  
  252.         /// <summary>
  253.         /// Places and digs out the tunnels from room-to-room
  254.         /// </summary>
  255.         public void PlaceTunnels()
  256.         {
  257.             int count = 0;
  258.  
  259.             // pathfind tunnels
  260.             Rectangle prev = Rooms[Rooms.Count - 1];
  261.             foreach (Rectangle next in Rooms)
  262.             {
  263.                 count++;
  264.  
  265.                 // pathfind from the center of the previous room to the center of the next room
  266.                 Pathfind(prev.X + prev.Width / 2, prev.Y + prev.Height / 2, next.X + next.Width / 2, next.Y + next.Height / 2);
  267.  
  268.                 // dig out the tunnel we just found
  269.                 int size = Calc.Random.Choose<int>(1, 2);
  270.                 foreach (Point point in currentPath)
  271.                 {
  272.                     Set(point.X - size / 2, point.Y - size / 2, size, size, Type.Floor, Type.Stone);
  273.                 }
  274.  
  275.                 prev = next;
  276.                 Console.WriteLine("Pathfound {0}/{1}", count, Rooms.Count);
  277.             }
  278.         }
  279.  
  280.         /// <summary>
  281.         /// Finds a path from the first poisition to the 2nd position and stores it in the currentPath variable
  282.         /// NOTE: This is probably super horrible A* Pathfinding algorithm. I'm sure there's WAY better ways of writing this
  283.         /// </summary>
  284.         public void Pathfind(int x, int y, int x2, int y2)
  285.         {
  286.             cost = new int[Width, Height];
  287.             cost[x, y] = (int)Type.Floor;
  288.  
  289.             List<Point> active = new List<Point>();
  290.             active.Add(new Point(x, y));
  291.             // pathfind
  292.             while (true)
  293.             {
  294.                 // get lowest cost point in active list
  295.                 Point point = active[0];
  296.                 for (int i = 1; i < active.Count; i ++)
  297.                 {
  298.                     Point p = active[i];
  299.                     if (cost[p.X, p.Y] < cost[point.X, point.Y])
  300.                         point = p;
  301.                 }
  302.  
  303.                 // if end point
  304.                 if (point.X == x2 && point.Y == y2)
  305.                     break;
  306.  
  307.                 // move in directions
  308.                 int currentCost = cost[point.X, point.Y];
  309.                 if (point.X - 1 >= 0 && cost[point.X -1, point.Y] == 0)
  310.                 {
  311.                     active.Add(new Point(point.X - 1, point.Y));
  312.                     cost[point.X - 1, point.Y] = currentCost + (int)Map[point.X - 1, point.Y];
  313.                 }
  314.                 if (point.X + 1 < Width && cost[point.X + 1, point.Y] == 0)
  315.                 {
  316.                     active.Add(new Point(point.X + 1, point.Y));
  317.                     cost[point.X + 1, point.Y] = currentCost + (int)Map[point.X + 1, point.Y];
  318.                 }
  319.                 if (point.Y - 1 >= 0 && cost[point.X, point.Y - 1] == 0)
  320.                 {
  321.                     active.Add(new Point(point.X, point.Y - 1));
  322.                     cost[point.X, point.Y - 1] = currentCost + (int)Map[point.X, point.Y - 1];
  323.                 }
  324.                 if (point.Y + 1 < Height && cost[point.X, point.Y + 1] == 0)
  325.                 {
  326.                     active.Add(new Point(point.X, point.Y + 1));
  327.                     cost[point.X, point.Y + 1] = currentCost + (int)Map[point.X, point.Y + 1];
  328.                 }
  329.  
  330.                 active.Remove(point);
  331.             }
  332.  
  333.             // work backwards and find path
  334.             List<Point> points = new List<Point>();
  335.             Point current = new Point(x2, y2);
  336.             points.Add(current);
  337.  
  338.             while (current.X != x || current.Y != y)
  339.             {
  340.                 int highest = cost[current.X, current.Y];
  341.                 int left = highest, right = highest, up = highest, down = highest;
  342.  
  343.                 // get cost of each direction
  344.                 if (current.X - 1 >= 0 && cost[current.X - 1, current.Y] != 0)
  345.                 {
  346.                     left = cost[current.X - 1, current.Y];
  347.                 }
  348.                 if (current.X + 1 < Width && cost[current.X + 1, current.Y] != 0)
  349.                 {
  350.                     right = cost[current.X + 1, current.Y];
  351.                 }
  352.                 if (current.Y - 1 >= 0 && cost[current.X, current.Y - 1] != 0)
  353.                 {
  354.                     up = cost[current.X, current.Y - 1];
  355.                 }
  356.                 if (current.Y + 1 < Height && cost[current.X, current.Y + 1] != 0)
  357.                 {
  358.                     down = cost[current.X, current.Y + 1];
  359.                 }
  360.  
  361.                 // move in the lowest direction
  362.                 if (left <= Calc.Min(up, down, right))
  363.                 {
  364.                     points.Add(current = new Point(current.X - 1, current.Y));
  365.                 }
  366.                 else if (right <= Calc.Min(left, down, up))
  367.                 {
  368.                     points.Add(current = new Point(current.X + 1, current.Y));
  369.                 }
  370.                 else if (up <= Calc.Min(left, right, down))
  371.                 {
  372.                     points.Add(current = new Point(current.X, current.Y - 1));
  373.                 }
  374.                 else
  375.                 {
  376.                     points.Add(current = new Point(current.X, current.Y + 1));
  377.                 }
  378.             }
  379.  
  380.             points.Reverse();
  381.             currentPath = points;
  382.         }
  383.  
  384.         #endregion
  385.  
  386.         #region map data - set/check
  387.  
  388.         /// <summary>
  389.         /// Sets the given cell to the given type, if it's not already set to the untype
  390.         /// </summary>
  391.         public void Set(int x, int y, Type type, Type untype = Type.None)
  392.         {
  393.             if (x < 0 || y < 0 || x >= Width || y >= Height)
  394.                 return;
  395.             if (Map[x, y] != untype)
  396.                 Map[x, y] = type;
  397.         }
  398.  
  399.         /// <summary>
  400.         /// Sets the given rectangle of cells to the type
  401.         /// </summary>
  402.         public void Set(int x, int y, int w, int h, Type type, Type untype = Type.None)
  403.         {
  404.             for (int i = x; i < x + w; i++)
  405.             {
  406.                 for (int j = y; j < y + h; j++)
  407.                 {
  408.                     Set(i, j, type);
  409.                 }
  410.             }
  411.         }
  412.  
  413.         /// <summary>
  414.         /// Makes sure the area doesn't overlap any floors
  415.         /// </summary>
  416.         /// <param name="area"></param>
  417.         /// <returns></returns>
  418.         public bool Overlaps(Rectangle area)
  419.         {
  420.             for (int i = 0; i < area.Width; i++)
  421.             {
  422.                 for (int j = 0; j < area.Height; j++)
  423.                 {
  424.                     if (Map[area.X + i, area.Y + j] != Type.Wall && Map[area.X + i, area.Y + j] != Type.Stone)
  425.                         return false;
  426.                 }
  427.             }
  428.             return true;
  429.         }
  430.  
  431.         /// <summary>
  432.         /// Returns false if the position is outside the map
  433.         /// </summary>
  434.         /// <returns></returns>
  435.         public bool CellOutside(int x, int y)
  436.         {
  437.             return x < 0 || y < 0 || x >= Width || y >= Height;
  438.         }
  439.  
  440.         /// <summary>
  441.         /// Gets the type of the given cell
  442.         /// </summary>
  443.         /// <returns></returns>
  444.         public Type GetCell(int x, int y)
  445.         {
  446.             if (!CellOutside(x, y))
  447.                 return Map[x, y];
  448.             else return Type.None;
  449.         }
  450.  
  451.         #endregion
  452.  
  453.         /// <summary>
  454.         /// Returns a 2D bool array used for checking collisions in entities
  455.         /// </summary>
  456.         /// <returns></returns>
  457.         public bool[,] GetCollideData()
  458.         {
  459.             bool[,] data = new bool[Width, Height];
  460.             for (int i = 1; i < Width - 1; i++)
  461.             {
  462.                 for (int j = 1; j < Height - 1; j++)
  463.                 {
  464.                     // this looks a bit weird, but it basically just only places "Edge" walls (it doesn't fill the insides)
  465.                     // so it just checks to make sure each tile is adjacent to a floor tile
  466.                     if (Map[i, j] != Type.Floor)
  467.                     {
  468.                         if (Map[i - 1, j] == Type.Floor || Map[i - 1, j - 1] == Type.Floor || Map[i, j - 1] == Type.Floor || Map[i + 1, j - 1] == Type.Floor
  469.                             || Map[i + 1, j] == Type.Floor || Map[i + 1, j + 1] == Type.Floor || Map[i, j + 1] == Type.Floor || Map[i - 1, j + 1] == Type.Floor)
  470.                         {
  471.                             data[i, j] = true;
  472.                         }
  473.                     }
  474.                        
  475.                 }
  476.             }
  477.             return data;
  478.         }
  479.  
  480.         /// <summary>
  481.         /// Gets a random position in the map that is a Floor (not a wall)
  482.         /// </summary>
  483.         /// <returns></returns>
  484.         public Vector2 GetRandomPosition()
  485.         {
  486.             int room = Calc.Random.Next(0, Rooms.Count - 1);
  487.             Vector2 point = Vector2.Zero;
  488.             while (point == Vector2.Zero || Map[(int)point.X, (int)point.Y] != Type.Floor)
  489.             {
  490.                 point = new Vector2(
  491.                     Util.Range(Rooms[room].X, Rooms[room].X + Rooms[room].Width),
  492.                     Util.Range(Rooms[room].Y, Rooms[room].Y + Rooms[room].Height)
  493.                 );
  494.             }
  495.  
  496.             return point * Grid;
  497.         }
  498.     }
  499. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement