Test your mettle as a commander in a Tactical Roguelike Auto Battler where positioning and unit combinations are key! Write your own story in Roguelike mode, try out over 100 unique challenges or create interesting battles with the Sandbox tool!
Because whenever I read any kind of writeup, I love to see where we're heading straight away, so here's what we're trying to achieve:
[img]https://i.imgur.com/KUndyPf.png[/img]
In this thread, I will discuss how we created a procedurally generated roguelike map and my thought process going into it. This isn’t really a tutorial, more of a post about how I got to it, how I did it, and why I did it this way. I’m not saying that my way is the right way, nor that it is the most performant way (definitely not), but I’m hoping it might be useful to some of you who are new to game development. This is a relatively short write-up, where you’ll need to fill in some of the blanks, because again, it’s not exactly a step-by-step tutorial. Lastly, before we start, I’ll be often referring to people by their nicknames because it’s much easier than typing DESIGNER, CODER etc. So. Kiwi (designer), Gothim (programmer and UI guy) and me (Komisarek, a code monkey).
[h1]Defining the requirements[/h1]
One of the most important stages before I even got to coding was defining the requirements needed for the entire thing to work. We had three people working on this, me doing the generation, one colleague doing the UI and all the code associated with displaying the map (which means he’d be using my data to create the visual map), and a designer who created all the requirements for this task. This post will focus mostly on the generation part.
Generation requirements were:
[list]
[*] The map needs to be divided into X number of acts, with each act having between Y and Z MOVES. A move represents a step forward in the act, onto a node
[*] The map will start with a single “Start” node and a single “End” node
[*] Except for the above, each node has a type. It can be one of: “Shop”, “Event”, “Combat”
[*] Each move can have between 2 to 3 nodes for the player to choose from
[*] Nodes can only be connected diagonally, and in straight lines. For example node 2 in a move with 2 columns, cannot connect to node 1 in 3 columns (because it’s too high).
[/list]
We don’t care about what happens when we step on a shop for now, or anything like that – implementing this will come later. For now, I visualised what we needed, and came up with more or less something like this:
[img]https://i.imgur.com/qULDHtt.png[/img]
[h1]Coding[/h1]
I started with creating an “Act” class. This is a simple class that holds the basic data, which the designer can modify.
[code]public class Act
{
public Vector2 Events;
public Vector2 Shops;
public Vector2 Moves;
}
[/code]
He can set a min and max number of Events, Shops and Moves in each act. I could implement some gatekeeping logic here to prevent errors (like no max moves less than max shops + events), but I decided that it’s simply not necessary. It’s something that Kiwi, can keep in mind himself.
Then, of course, I created a List that holds the acts, so he can add as many or as few as he likes. We’d use this list to generate the map.
Now that we had the data we needed (num. of acts, moves, shops etc…) we could get to generating.
Next part was to create the data structure for nodes and columns (moves). The idea is that each column will know which act it is in, which move it represents, and has a list of nodes that are on it.
[code]public class RGNodeColumn
{
public int Act;
public int Move;
public List Nodes = new List();
}
[/code]
Each node has a type. If we wanted to be SUPER DUPER ROBUST we could create a base class, and then derive others from it. Like ShopNode, BattleNode etc… but let’s be honest. This game is not massive. I decided to just do it with an enum.
[code]public enum NodeType
{
Start, Unassigned, Combat, Shop, Event, End
}
[/code]
[code][System.Serializable]
public class RGNode
{
public bool Victory = false;
public NodeType NodeType = NodeType.Unassigned;
public RogueArmyData ArmyData; //holds the enemy army on this node
public BattleReward Reward; //this is the reward structure
public string GetName() {} //returns localised name
public string GetDescription() {} // returns localised description
public string GetCombatRewardDescription() {} // returns description of rewards
}
[/code]
I know what you’re thinking, not every node will need army data or a reward… that’s fine. I’ll just ignore them when necessary. My room is a little bit messy, nothing will happen if this is a little bit messy too!
So, to the actual generation part. I created a monobehaviour called RGWorldGen. It holds a list of Nodes and Columns, so all data should be available when Gothim wants to ultimately access it, when creating the visual map. We also have a static instance (yes, singleton!)
[code]public static RGWorldGen instance;
public List Columns = new List();
public List Nodes = new List();
private void Awake()
{
if (instance == null)
instance = this;
else
{
Destroy(gameObject);
return;
}
}
[/code]
So I’m going to paste a bunch of code now, it’s all pretty simple. But just in case I added lots of comments in. Idea is pretty simple: Generate the world by going through X acts, and for each of those create as many columns as there are moves. We also assign node types here.
Mind you, this is quite long, so I threw it on [url=https://pastebin.com/VN9eAcij]pastebin[/url]. There is no syntax highlighting here and too much white reminds me of the times I used to edit code in notepad. I don't like those memories.
Now that we have the data structure generated every time we fire up the roguelike mode, we can start playing around with it.
First of all we need to give UI easy access to the following:
[list]
[*] The current node the player is standing on
[*] The node the player wants to travel to (destination node)
[*] List of visited nodes (so we can draw connections between them in different colour)
[/list]
We also need to get connected nodes to each node. You might remember I mentioned the connections early on in the post. I REALLY, REALLY could not be bothered to work out the algorithm for this, even though I assume it’s really simple, so I just… hardcoded it. IT’S FINE, IT WORKS.
[url=https://pastebin.com/Cd4nBibx]pastebin link![/url]
I'm not going to give you too much detail on the UI side, because that's a whole another write-up, but the final result looks more or less like this:
[img]https://i.imgur.com/KUndyPf.png[/img]
Let's cover some of the important things you might be curious about though:
[h3]Node icons[/h3]
Since we have node types we can easily assign icons to Shops and Events, as well as Start and End (they are always the same). Each COMBAT node has an ArmyData object you might have spotted before. This object also contains an enum for the enemy race. We set an icon based on that enum.
[code]public enum Race
{
Greenskin, Undead, Barbarian, Kingdom
}[/code]
[h3]Node descriptions[/h3]
[img]https://i.imgur.com/aLrKYPI.png[/img]
Each node also features a description. The RGNode class contains a method called GetDescription which returns a string. We have a hardcoded string for each race, such as "battle_desc_greenskin", or "battle_desc_barbarians", and of course for all other node types, for example "ui_prompt_info_end_desc". The localisation manager returns the localised string from an XML file.
The icons are hardcoded sprites as well. If you're curious, you can see the code for that [url=https://pastebin.com/i58WFGRb]HERE[/url]
You can see the final outcome in action, here: https://www.youtube.com/watch?v=BYEtN7qMyl4
Lastly, if you liked this post, please follow the game's development, and make sure to [url=https://store.steampowered.com/app/2594290?utm_source=devpost01]WISHLIST here![/url]
[url=https://store.steampowered.com/app/2594290?utm_source=devpost01][img]https://i.imgur.com/97jyF3g.png[/img][/url]