2D Roguelike: Q&A

Perhaps you’re right, but like I said there have been Unity tutorials tagged as intermediate before and the code complexity in those has been the usual Unity level, ie. just components, no inheritance, generics etc. The intermediate part just dealt with the scope of the tutorial and the number of concepts taught.

I understand what you’re saying, but like I said it caught me by surprise. I think I understand everything in the tutorial now and why it’s done the way it is, but it took me some time and I have probably 15 years of programming experience. To my knowledge this is the first tutorial project Unity has done which has been at this level of code complexity. Perhaps I was expecting a gentler introduction. I’ve read all about the the different parts of C# before, but without any real incentive to use any of them it didn’t stick.

Something like a short text description of the project and what it builds upon or expects from the viewer could be something to consider. “This tutorial uses inheritance, generics, etc. etc. and if you’re not comfortable with these concepts then perhaps consider checking out these links first” or something.

And those last questions I just threw in here because Matthew seemed like he knew his stuff and I expected there were more rookie Unity’ers with some of the same questions. :slight_smile:

Jasper Flick creates awesome, advanced text based Unity tutorials: Maze, a Unity C# Tutorial

It would be really neat if Unity adopted a Q&A system similar to his. Such that beneath each video in a tutorial series there could be a list of any special concepts covered in that video with links to an explanation about those concepts. And then I’m thinking human readable explanations, not the MSDN kind. :stuck_out_tongue:

That way you wouldn’t have to write the explanation yourself each time if it already exists. You just link to it. Sort of like a tagging system. Except it would be a searchable database. One point of entry could for instance be that a user wondering what “protected” means. He goes to the tutorials section and types in “protected” in the search box and the site gives him the written explanation of the keywords along with the different tutorials implementing this concept. The other would be the Jasper Flick way of listing the keyword with a link to the explanation beneath the video for those following along with the tutorial.

I’m thinking out loud here and I’m not critiquing this particular tutorial, I’m listing ways that I feel Unity could improve its tutorials. I’ve been writing tutorials and end-user guides for my clients for nearly 10 years and I don’t take any knowledge for granted anymore. Every time I do I’m punished with a phone call or worse. :stuck_out_tongue: And honestly it doesn’t take me that much longer to do. Looking at the Unity comments it’s easy to tell i’m not the only one taken by surprise by this particular tutorial. I think a lot of these questions could have been tackled straight on with a more elaborate learning “hub”. It’s all about democratizing game development, am I right? :slight_smile:

1 Like

Just finished it tonight. Well Done Guys :slight_smile:

enjoyed working through it, yea the coding takes a big direction change for me, but, this has forced me to go away and learn more. purely as i wanted to learn and improve my understanding why something was written that way.

so, had a good read up on Generics over the last couple of day. (head hurts a bit)
dug more into inheritance and namespaces.

good times, learned loads! well done again!

DaZ

I got to part 11 before I realized the player should have been moving a few sections prior. Looking at the comments on the YouTube video, it seems some others have had problems with this too. I even copied the completed code and made some changes to get it working without errors. Everything looks great, but when I press the arrow key, nothing happens.

Anyone else run into this? I’m using Unity 4.6.

Here is the error whenever I am playing the game and press a movement key:

NullReferenceException: Object reference not set to an instance of an object
MovingObject.Move (Int32 xDir, Int32 yDir, UnityEngine.RaycastHit2D& hit) (at Assets/Scripts/MovingObject.cs:34)
MovingObject.AttemptMove[Walls] (Int32 xDir, Int32 yDir) (at Assets/Scripts/MovingObject.cs:99)
Player.AttemptMove[Walls] (Int32 xDir, Int32 yDir) (at Assets/Scripts/Player.cs:77)
Player.Update () (at Assets/Scripts/Player.cs:65)

Morning FreeHunter, thought id try and help until Matt gets back to you,

just out of curiosity, have you been following the series in the correct sequence? just that you were saying that you were on the enemy animator before going back.

have a double check of the code in MovingObject.cs script, looks like its trying to use something that hasnt been assigned.

check the base virtual Start function in MovingObject.cs, are you assigning the boxCollider?
(as line 3 below)

    protected virtual void Start()
    {
        boxCollider = GetComponent<BoxCollider2D>();
        rb2D = GetComponent<Rigidbody2D>();
        inverseMoveTime = 1f /moveTime;
    }

are you getting any yellow warnings coming up at all when you start, saying that something has not been assigned or used?

DaZ

Yes, I’ve been following in order. I see that the movement code went in during part 9, but it wasn’t tested until part 11. I went back to see if I had missed anything, but I’ve been going in order.

Here is my Start function in MovingObject:

protected virtual void Start ()
{
            boxCollider = GetComponent <BoxCollider2D> ();
            rb2D = GetComponent <Rigidbody2D> ();
            inverseMoveTime = 1f / moveTime;
}

I don’t have any yellow warnings, but when I start I do have several red warnings:

NullReferenceException: Object reference not set to an instance of an object
Enemy.Start () (at Assets/Scripts/Enemy.cs:15) (line 15 is GameManager.instance.AddEnemyToList(this):wink:

NullReferenceException: Object reference not set to an instance of an object
Player.Start () (at Assets/Scripts/Player.cs:24) (line 24 is food = GameManager.instance.playerFoodPoints;)

And when I try to move, I get the same error I previously posted, happening maybe 5 times every time I push a movement key.

ill have another work the tute tomorrow.

cant be too much wrong, must be a minor problem, but a niggly one :slight_smile:
im guessing something in the gamemanager or tags , or a base class , but ill have a dig.

I wanted the level progression to have a rhythm of low and high suspense stages, instead of constantly increasing the difficulty. I implemented that by moving the board settings (walls, food, enemies) to config files. For anyone interested, here is the process in tutorial form.

First, add a ScriptableObject class for the config files. It stores all board config data as well as transition data for deciding which config to use in the next level. That way we can let the random number generator decide whether to use the current config again or to move to a successor config.
BoardConfig.cs

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Completed
{
   public class BoardConfig : ScriptableObject
   {
     public BoardManager.Count wallCount;   // Range for our random number of walls per level.
     public BoardManager.Count foodCount;   // Range for our random number of food items per level.
     public BoardManager.Count enemyCount;   // Range for our random number of enemy units per level.

     public List<BoardConfigTransition> transitions;   // all possible successor configs

     // returns a BoardConfig randomly selected from the transitions list
     public BoardConfig GetNext()
     {
       float totalWeights = transitions.Sum( tr => tr.weight );
       float rnd = Random.Range(0, totalWeights);
       float sumSoFar = 0;
   
       foreach(var transition in transitions)
       {
         sumSoFar += transition.weight;
         if (sumSoFar >= rnd)
           return transition.target;
       }

       return transitions.Last().target;
     }
   }

   [System.Serializable]
   public class BoardConfigTransition
   {
     public BoardConfig target;
     public float weight;

     public BoardConfigTransition(BoardConfig target, float weight)
     {
       this.target = target;
       this.weight = weight;
     }
   }
}

Now you can remove the wallCount and foodCount variables from BoardManager, and make the SetupScene method take its object counts from a config.
BoardManager.SetupScene

     //SetupScene initializes our level and calls the previous functions to lay out the game board
     public void SetupScene (int level, BoardConfig boardConfig)
     {
       //Creates the outer walls and floor.
       BoardSetup ();
   
       //Reset our list of gridpositions.
       InitialiseList ();
   
       //Instantiate a random number of wall tiles based on minimum and maximum, at randomized positions.
       LayoutObjectAtRandom(wallTiles, boardConfig.wallCount.minimum, boardConfig.wallCount.maximum);
   
       //Instantiate a random number of food tiles based on minimum and maximum, at randomized positions.
       LayoutObjectAtRandom(foodTiles, boardConfig.foodCount.minimum, boardConfig.foodCount.maximum);
   
       //Instantiate a random number of enemies based on minimum and maximum, at randomized positions.
       LayoutObjectAtRandom(enemyTiles, boardConfig.enemyCount.minimum, boardConfig.enemyCount.maximum);
   
       //Instantiate the exit tile in the upper right hand corner of our game board
       Instantiate (exit, new Vector3 (columns - 1, rows - 1, 0f), Quaternion.identity);
     }

GameManager is responsible for storing the config. Add a variable:

     public BoardConfig boardConfig;               //Configuration settings for the game board

Add it as an argument to the boardScript.SetupScene call, and update it when a new level is about to be created:

     //This is called each time a scene is loaded.
     void OnLevelWasLoaded(int index)
     {
       //Add one to our level number.
       level++;
       //Select the next board config
       boardConfig = boardConfig.GetNext();
       //Call InitGame to initialize our level.
       InitGame();
     }

You’re almost done with code, just one more tool for creating our config files:
Editor/CreateBoardConfig.cs

using UnityEngine;
using UnityEditor;

public class CreateBoardConfig
{
   [MenuItem("Assets/Create/BoardConfig")]
   static void DoCreateBoardConfig()
   {
     var config = ScriptableObject.CreateInstance<Completed.BoardConfig>();
     AssetDatabase.CreateAsset(config, "Assets/EntryBoard.asset");
     AssetDatabase.SaveAssets();
   }
}

Now you need to set up the data files.
First, use the menu Assets > Create > BoardConfig to generate the config file Assets/EntryBoard. Make three copies of it (CRTL+D) and name them EasyBoard, RegularBoard and ChallengeBoard. The EntryBoard config is for the first level, the other ones will cycle throughout the remaining levels.

Use the inspector to populate them, I picked these values:

EntryBoard:
   wallCount 10-12
   foodCount 1-1
   enemyCount 0-0
   transitions:
     EasyBoard 1

EasyBoard:
   wallCount 5-10
   foodCount 2-3
   enemyCount 1-1
   transitions:
     EasyBoard 1
     RegularBoard 1

RegularBoard:
   wallCount 5-10
   foodCount 1-2
   enemyCount 2-2
   transitions:
     RegularBoard 1
     ChallengingBoard 1
 
ChallengingBoard:
   wallCount 4-8
   foodCount 1-1
   enemyCount 3-5
   transition:
     EasyBoard 1

Lastly, find the GameManager prefab and assign the EntryBoard asset to its boardConfig variable. Now you’re ready for testing. If you’re not sure what config is active at any point, inspect the GameManager instance in the scene.

You can see an alternative way to edit the config transitions graph here:

In the video I’m using the relations inspector with the following script:
Editor/BoardConfigBackend.cs

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using RelationsInspector;
using RelationsInspector.Backend;
using System.Linq;

namespace Completed
{
   public class BoardConfigBackend : ScriptableObjectBackend<BoardConfig,float>
   { 
     public override IEnumerable<Tuple<BoardConfig, float>> GetRelations(BoardConfig config)
     {
       if (config.transitions == null)
         yield break;

       foreach (var transition in config.transitions)
         yield return new Tuple<BoardConfig, float>(transition.target, transition.weight);
     }

     public override void CreateRelation(BoardConfig source, BoardConfig target, float tag)
     {
       if (source.transitions == null)
         source.transitions = new List<BoardConfigTransition>();

       source.transitions.Add( new BoardConfigTransition(target, tag) );
       api.AddRelation(source, target, tag);
     }

     public override void DeleteRelation(BoardConfig source, BoardConfig target, float tag)
     {
       var targetEntries = source.transitions.Where( t => target == t.target);

       if (!targetEntries.Any())
       {
         Debug.LogError("RemoveRelation: source is not related to target");
         return;
       }

       source.transitions.Remove(targetEntries.First());
       api.RemoveRelation(source, target, tag);
     }
   }
}
2 Likes

Thank you for the tutorial, it was very helpful.

[SOLVED - left this here in case anyone else runs into the same issue]
I have one bug in my version - the player can walk through the enemies.
The player and enemies all have their collision set up, all are set to the BlockingLayer Layer, and all have blockingLayer public variable correctly pointed to the BlockingLayer.
Looking through the code, the only line (as far as I can work out) that is supposed to detect the player moving through enemies is this:
hit = Physics2D.Linecast (start, end, blockingLayer);

Any suggestions as to why this might not be working for me (I typed all the code in by hand rather than copy-pasting as I find that that makes me attend to the reason for each line - on the other hand it does mean that the bug is likely to be a typo somewhere…)

EDIT: Ha! As is often the way with these things, as soon as you ask for help with a bug, you spot it yourself…
I couldn’t see anything wrong with the code so I tried flicking back and forth between my enemy prefabs and the ones in the Completed/Prefab folder to see if I could spot any difference. Somehow the Box Collider 2D Size X & Y had been changed to 0.0001 on both my enemies! I have no idea how this happened…

3 Likes

Thanks so much for the tutorial Matt, I didn’t have any issues following your instructions.

I do hope you (or anyone reading this ;)) will consider extending this project just by a little bit by doing small things like adding the ability to start combat with a zombie determined (for starters, you could just reuse the wall destroy code and add a Bool isDead and then put the zombie on a different layer to get this sort of thing in a very basic way.) by simple RNG rolls and perhaps a player found melee object or implementing a more advance level generator to the project.

I’m sure a lot of people would appreciate it if this project could be improved in those ways.

1 Like

Hey man, first thing thanks for this great tutorial! I’m just a beginner though.

second I’m already into part 5 and so far everything seems good but when I tried to play it (already put all the prefrabs in their respective places) it’s like they’re overlapping, I used 254x254 sprites though…

Hi all,

I’ve changed a lot of the code but used this as a base to work from. I’ve also used code from the Amaranth open source project available on BitBucket. The tiles are from the open source project Pixel Dungeon over on github.

So far room and dungeon generation is pretty much complete and working well. I’ll be releasing this pretty soon so others can have a look.

I’ve tried to add as many options to the inspector so creating a different feel to the dungeon is pretty simple.

Thanks for the Roguelike tutorial it was a great read :slight_smile:

1 Like

@thefreehunter Is it possible that your GameManager object is not being loaded? Is the GM in the hierarchy when you get these errors?

@seldom_1 Cool! Thanks for extending this and sharing what you did, will have to check this out when I get a second.

@hodge_podge Thanks for the feedback, as you can see some others have been taking a stab at extending parts of the project, if you’d like to give it a shot and post what you get here, even if it’s incomplete I’m sure people would be interested to discuss and chime in. As you can see in the thread some of our community members already are.

@godwintineractive check your pixels to units setting in the import settings for the sprite. If yours are 254 then you should set all of them to 254 in there.

@brandc Neat! Really interested to see what you came up with, I’ve actually got a little prototype where I implemented some of these ideas: http://www.vlambeer.com/2013/04/02/random-level-generation-in-wasteland-kings/ (as suggested by @Aiursrage2k ) that I’ll probably share on an upcoming live stream (once I get the kinks figured out) but I’d be very interested to see what you came up with for yours, it looks quite a bit fancier than what I’ve done (including doors! cool!) And glad to hear you enjoyed the project, I had a ton of fun making it :slight_smile:

Wow, I just wanted to say how great this is. I’ve been making small roguelike-ish things for the last year iteratively improving upon code I originally found here. There’s so many different things I wanted to implement or have already done, but kind of half-assed, that you fully cleared up and made look super easy. Even stuff like setting Random = UnityEngine.Range or simplifying generating random amounts between two values by using a serialized class I wouldn’t have thought of or gone out of my way to find out about. Thanks so much.

Thanks man! Very happy to help. Hopefully as this thread goes on we can gather further resources for people interested in the same ideas here.

One thing I never got around to fully figuring out was how to make large maps without every tile being a unity object. There was some vague information online about writing sprites onto a plane and doing it in NxN sized chunks.

Another aspect to it was using instances of a serialised tile script to track each imaginary tile location and whether it was passable or not plus other information, with all the scripts stored in a board manager script. Your method of keeping every sprite one unity length and using raycasting is probably more straightforward though. Besides the fact that in terms of efficiency there’s probably going to be a lot less “stuff” unity objects than tile unity objects. So once you’ve taken out all the tile objects, that’s the largest performance boost.

Edit: I totally forgot to mention (cause I never use it anymore) but the reason you want a tile script for each tile is because each acts as a node in eric lippert’s implementation of the A* pathfinding algorithm.

I agree with what others have said about some of the complexity feeling unnecessary. The singleton pattern serves a purpose but the inheritance code really seems like the kind of thing that you would do when going after perfection afterwards.

Like, in a game jam I would just chuck an open-ended MovingObject.cs component on all movable objects and have another separate component call its functions depending on if it were a player or enemy, I feel it would be more agile and Unity-ish that way.

I’m getting the error “An object reference is required to access non-static member `GameManager.boardScript’”

When I try to access a variable in boardscript from another script, like this

GameManager.instance.boardScript.CreateEdgeTiles(5, 5, (int)mapPos.x, (int)mapPos.y);

(CreateEdgeTiles is my own function I’ve created and made public).

I appreciate that you took the time to respond. I took my own shot at expanding a few parts of the project. Currently, I added a shroud/fog of war system to the game where the player character can only reveal tiles 2 units from his position and the ability to remove zombies from the board if they are attacked 4 times (just like walls).

All you have to do really for those features is just slightly modify the wall code to work within the enemy script for the ability to disable objects and add a bool to check if they are disabled for their attack code for the zombies.

For the fog of war/shroud, all you have to do is create a pitch black tile and layer it above all other prefabs. Then you prefab said tile and instantiate it on the BoardSetup method. You then create detector object with a unique tag, rb, follow script and its own 2d Collider. You then size its 2D Collider to the how far you want your player to see. All you have to do at that point is write a simple script that checks for the tag you put on the detector object and have it disable the shroud/fog of war instance on collision.

After that you should be good after make sure the follow script on the detector follows the player object (iirc if you try child the detector to the player its Collider will override the player’s)I’m sure their are better ways to handle this, so if anyone has a better solution, feel free to share it.

Just started watching this series of videos. Currently at board manager, the first part to get in depth on the scripting involved in this project.

I have a suggestion about how Scripting is demonstrated in future tutorials. It seems that most tutorials simply dive head first into the script and explain as they go. While it works, it is a bit confusing, even with a significant amount of coding experience, and leaves me rewinding and fast forwarding constantly to keep up.

My suggestion then, is that a brief, high-level overview of the code be given before the demonstrator begins to write, that way the viewer knows what to expect and can see where the specific implementation is going as it is written.

Sorry if this is expounded upon enough, crunched for time at the moment!

1 Like

Hi, I finished the tutorial series and it’s amazing, but how English is my second language, I guess I missed something.

When the player die, how do you restart the game? Because you stop the music and disable the GameManager.

1 Like