Dungeon Ho!: Unity – Dev Blog #8

Welcome back, ex-parrots!  I’m not dead, nor am I pining for the fjords.  I do, however, have both good news, and bad news.  The good news is that I’ve done a lot of work on the ‘Ho! since we spoke last, but the bad news is that I’m now extremely behind on blog updates.  Oh well, at least I won’t be starving for material!

Let’s pick back up where we left off, and continue to talk about Items.

***

The beauty of Items is that they don’t really have to do much, due to the Effects system (…which we’ll definitely be discussing in depth in future blogs.  It’s a big’n!) – but for now, simply know that the Effects attached to an item determine what it can do.  But before we get to that, we have to know how they’re defined, placed in the world, and interacted with once the player is looming over one.  Today we’re going to talk about how they’re defined.

The good news is that Item definitions are very similar to Monster definitions, in that we serialize a set of JSON data into an ItemDefinition object and then use that definition to initialize the Item itself through the ObjectSpawner.  Let’s look at some (temporary!) items that I’ve defined in order to implement/test this process.

Here’s the current items.json file.

{
	"items":
	[
		{
			"name": "Small rock",
			"weight": 1,
			"durability": 10,
			"flags": "equippable"
		},
		{
			"name": "Tree Branch",
			"weight": 3,
			"durability": 5,
			"flags": "equippable|twoHanded"
		}
	],

	"weapons":
	[
		{
			"name": "Creepy Pointed Stick", 
			"weight": 1, 
			"durability": 2,
			"effects":
			[
				{
					"effect": "mod_attack",
					"amount": 1,
					"target": "owner"
				},
				{
					"effect": "damage",
					"type": "phys",
					"amount": 1
				},
				{
					"effect": "damage",
					"type": "dark",
					"amount": 2
				}
			]
		}
	],

	"armor":
	[
		{
			"name": "Sandwich Board",
			"weight": 5,
			"durability": 10,
			"effects":
			[
				{
					"effect": "mod_defense",
					"amount": 1,
					"target": "owner",
					"duration": "permanent"
				}
			]
		}
	]
}

First thing you’ll notice is that I’ve broken up the definitions into three categories, much like how I did the monster types.  They’re itemsarmor, and weapons.  For now, you can ignore the effects data… but as an exercise, try to think about what that data might do once we get there!  Skim past the Small Rock item and take a look at the Tree Branch.

{
	"name": "Tree Branch",
	"weight": 3,
	"durability": 5,
	"flags": "equippable|twoHanded"
}

The name and weight parameters are the same as they are for Entities, as we saw last time.  Durability maps to the item’s health/hit points, dictating how much damage it can take.  We’ll care about that when it comes time to implement item damage and repair, but it never hurts to plan ahead!

Anyway. next up is the clever bit; the flags.  As I mentioned before, all entities have an integer defined called (…wait for it) flags, that we can manipulate to stack data values together by way of boolean/logical operations.  I’ve defined a bunch of string constants that map to these flags, and the Item’s definition parsing code splits that string and then sets the correct flags.  Here’s how it’s done.

The flags variable is passed into the following function as a single string, which is called from the Item’s init() method:

private void processFlags(string flagString)
{
   if (flagString == null)
       return;

   string[] splitFlags = flagString.Split('|');

   foreach (string flag in splitFlags)
   {
       if (flag.Equals("equippable"))
           setFlag(EntityFlags.EQUIPPABLE);

       if (flag.Equals("consumable"))
           setFlag(EntityFlags.CONSUMABLE);

       if (flag.Equals("twoHanded"))
           setFlag(EntityFlags.TWO_HANDED);

       if (flag.Equals("stackable"))
           setFlag(EntityFlags.STACKABLE);

       if (flag.Equals("requires_arrows"))
           setFlag(EntityFlags.REQUIRES_AMMO_ARROWS);

       if (flag.Equals("requires_bolts"))
           setFlag(EntityFlags.REQUIRES_AMMO_BOLTS);

       if (flag.Equals("cursed"))
           setFlag(EntityFlags.CURSED);

       if (flag.Equals("nodrop"))
           setFlag(EntityFlags.NO_DROP);
   }
}

The string is split, using the ‘|‘ character as a delimiter, leaving us with a list of string tokens.  Then we simply loop through all of them and set the equivalent flags.  Could we have mapped the tokens to the strings using a Dictionary?  Yes, yes we could have.  Maybe I’ll put that on the ol’ TODO list and save myself the trouble of having to update these methods across Entity types.  Refactoring!  Truly the hero of the people.

Anyway, note some of the flags and try to figure out what, exactly they indicate.  It shouldn’t be too hard… which brings me to a very important point; your code should be self-documenting.  Don’t listen to those clowns who scoff at such things and tell you it’s a myth.  It’s not, and you should strive for it every time you sit down to write.  Use clean logic, plain and verbose variable names, and above all, document your process both with code comments and meaningful function names.  You’ll thank yourself for it later.

Moving on, here’s the ItemDefinition.  If you understood the MonsterDefinition, it shouldn’t surprise you at all.

[System.Serializable]
public class ItemDefinitions {

    [System.Serializable]
    public class ItemDefinition
    {
        public string name;
        public int weight;
        public int durability;

        public string flags;

        public EffectDefinition[] effects;
    }

    public ItemDefinition[] items;
    public ItemDefinition[] weapons;
    public ItemDefinition[] armor;

    public static ItemDefinitions initDefinitionsFromJSON(string json)
    {
        return JsonUtility.FromJson(json);
    }
}

See? Same format, same logic.  I won’t show you the spawnItem(), spawnWeapon(), and spawnArmor() methods of the ObjectSpawner class because they’re literally the same as the spawnMonster() method we’ve already seen.  We’ll revisit them later when they need to be added to because of the eventual expansion of the Item class.

***

Okay, so now we can spawn an item.  What do we do with it?  Well, we place it into the world same as we did the player and monster.  Here’s the relevant lines from the Dungeon class;

Item rock = ObjectSpawner.spawnItem();

rock.setPosition(2, 5);

addItem(rock);

Look familiar?  Keeping the handling of different kinds of entities as similar as possible is going to help us immensely down the road.  The addItem() method adds the rock we just spawned to the dungeon’s items and entities List objects, respectively, as well as parents it to the dungeon’s gameObject so that it appears in-game.

So, we’ve got a rock, and we can put it in the game world.  How do we get it into the player’s inventory so that they can, y’know, use it?

The easy, hacky way to do it is to simply bind a key to the “Pick Up” command in the DungeonMaster, same as we did for player movement and passing a turn.  Here’s the relevant lines from the DungeonMaster object’s collectInput() method.

if (Input.GetKeyDown(KeyCode.G))
{
     Vector2Int pos = player.getPosition();

     addCommand(new PickUpItemCommand(player, dungeon.getItemsOnTile(pos.x, pos.y)[0]));
}

 

As you can see, we check if the ‘G’ key is pressed, then query the player’s position and pass it to the dungeon’s getItemsOnTile() method, which, you guessed it, returns the item(s) on the player’s tile.  Since we only spawned one, we can assume the method only returns one, so we pass the first (and only) item in the list into the PickUpItemCommand’s constructor.

Now, if you’re paying attention you should see some flaws in this implementation.  Namely, what if the getItemsOnTile() method is called when the player is on a tile with no items?  And how do we query for those items, anyway?

Those are both good questions.  I’m going to answer them in a future blog, because – as you may very well have guessed – this is simply a temporary implementation meant to illustrate how to construct the PickUpItemCommand object.  Don’t press ‘G’ over an empty tile and you’ll be fine.

Tune in next time when I actually show you the getItemOnTile() implementation, as well as the real way to pick up all items – and monster corpses – on a tile, using a proper UI dialog.  It’ll pave the way for a full-blown Inventory screen, the likes of which we saw in a previous blog’s screenshot. Did you prefer the text-only code blocks to MonoDevelop screenshots? It’s certainly easier to work with on my end. Don’t know why I didn’t think of it sooner.

-Steve

Advertisements
Categories: Development, Dungeon Ho!, Unity | Leave a comment

Post navigation

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: