Guest User

Procedural Generation - C#

a guest
Jun 20th, 2013
3,543
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  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. }
RAW Paste Data

Adblocker detected! Please consider disabling it...

We've detected AdBlock Plus or some other adblocking software preventing Pastebin.com from fully loading.

We don't have any obnoxious sound, or popup ads, we actively block these annoying types of ads!

Please add Pastebin.com to your ad blocker whitelist or disable your adblocking software.

×