Welcome back, coding fiends! Today we’re going to look at the core app design for Dungeon Ho!: Unity, from both a project and code perspective.
As I mentioned previously, I decided to go with a single-Scene approach. Each game menu, screen, and dialog would be organized in a hierarchy of Unity Canvas objects, which will keep me from having to worry about data persistence or nested scene management. As this will be a single-developer project, encapsulation and decoupling is not going to be my biggest concern.
So, let’s look at the basic scene tree;
As you can see, within the scene I have a GameObject called Game. This, as the name suggests, is the object that contains the entire game; the map, the monsters, the players, all of that good stuff. It will also eventually contain things like AudioListeners, high score tables, and so on – but, we’ll burn those bridges when we come to them. For now, the Game contains our Dungeon, which consists of a Map that has three layers, represented by Unity Tilemap objects; Floor, Walls, and Details. These layers are separate for both stacking/rendering and collision purposes, which we will set up later. Floor and Walls are self-explanatory, but what about Details? The Details layer will represent “props” that decorate the dungeon, like torch brackets, furniture, and cracks in the floor. By keeping those tiles on their own layer, we can generate a wide variety of scenery without having to make a tile for every possible combination. The Camera will eventually be attached to our player object, but for now we can let it float free.
Next up is our overall project structure for asset management. I’ve created some preliminary folders, as shown in the Project view;
As you may already know, the Tile objects are created from sprite sheets sliced from singular images. Currently, I’m using placeholder art imported from the original Dungeon Ho! assets, but these will eventually be replaced. The tile assets themselves are kept under the Resources directory so that they can be dynamically loaded at runtime. I should probably look into making them AssetBundles instead. We’ll throw that on the ol’ TODO list.
With the initial set of GameObjects created and a basic project folder structure in place, let’s start coding.
There is, of course, a Game.cs script – but as our Game currently does bupkis, it’s simply an empty MonoBehavior. We’ll fill it in as the game progresses. The most important thing to do right now is to create our Dungeon/Map classes so that we have something to run around in. The Dungeon GameObject has a Dungeon.cs script attached to it, which currently (we’ll change this eventually) generates a new dungeon map when the game starts.
Note that we do a GameObject.Find() call to locate the Map GameObject within the class and grab a reference to its DungeonMap component (…which I haven’t discussed yet; it’s the DungeonMap.cs script). We do this so we can access the script attached to it and manipulate the Tilemap, which we’ll see in a moment.
The DungeonGenerator member variable deserves special mention. Initially, I had the DungeonGenerator class not inherit from anything (IE, it wasn’t a MonoBehavior or ScriptableObject) that just had a single static method called generateDungeon(). I switched it to a ScriptableObject and made it public so that I could swap out the DungeonGenerator object from within the editor. This is so that I could dynamically assign different DungeonGenerators to the object, which would generate different kinds of dungeons. It’s currently using a class called TestDungeonGenerator, which generates a single, simple 10×10 room with a center wall and both entry and exit tiles. When the actual DungeonGenerator is ready, and I’m done using this small room for testing, I’ll simply swap in the other one. ScriptableObjects; you gotta love ’em. I’ll go over that process a bit more next time.
Here’s the DungeonMap.cs;
Pretty straightforward so far. I’ve defined constants to represent the layers, as well as the different tile types. There’s an array of Tiles (for our loaded Tile resources) as well as TileMaps (for our Tilemap layers). We do some Find() calls to locate our in-scene Tilemaps; again, we could easily just have assigned them from the editor, but this is pretty much the same thing (whoops! Took the screenshot before I changed the DETAILS layer to find the “Details” object! Rest assured this was fixed in the code). We also define a method to load a particular tileset, which grabs all Tile assets in the provided folder, and a setTile() method that sets a particular tile on a particular layer to a particular index. We simply pass in the TILE_ constant and the code indexes it into the Tile array and finds the proper Tile object to use. Easy!
Tune in next time, and I’ll show you how I generated the test dungeon… and, if we’re lucky, I’ll also have had time to get a player wandering around in there.