Tactics RPG Items and Equipment

Now that we have stats, we will need new ways to modify them. Of course one way is through leveling up your character, but a potentially more fun way is by items. Equiping a sword which is not only cool looking but provides a great bonus to your ATK (attack) stat can be very rewarding. Likewise, when you are damaged in battle a health potion might be in order to boost your HP (hit points) stat. In this lesson we will examine a few ways to create items, both consumable and equippable, as well as how to manage your equipment.

Feature

The main purpose of an item is to modify something. I will describe this ability to modify a thing as a feature of the item. Every item will need some sort of feature, and possibly multiple features, so let’s start by creating an abstract class by this name. Add the script to the Scripts/View Model Component/Features folder.

The feature class supports two main use-cases:

  1. A feature can be activated for a time and then be deactivated. This would be the case with equipment – equip a sword for an attack boost but when you un-equip the sword your attack stat drops back down. I have exposed the methods Activate and Deactivate for this purpose.
  2. A feature can have a one-shot (permanent) application. This would be the case when you consume a health potion – you get a boost to your hit point stat which doesn’t need to be un-done by the item. I have exposed the method Apply for this purpose.

The base class applications are not virtual, so they will always work in a very specific way. Concrete subclasses implement what happens when the feature is activated in the OnApply and OnRemove methods which were left empty.

using UnityEngine;
using System.Collections;

public abstract class Feature : MonoBehaviour
{
	#region Fields / Properties
	protected GameObject _target { get; private set; }
	#endregion

	#region Public
	public void Activate (GameObject target)
	{
		if (_target == null)
		{
			_target = target;
			OnApply();
		}
	}

	public void Deactivate ()
	{
		if (_target != null)
		{
			OnRemove();
			_target = null;
		}
	}

	public void Apply (GameObject target)
	{
		_target = target;
		OnApply();
		_target = null;
	}
	#endregion

	#region Private
	protected abstract void OnApply ();
	protected virtual void OnRemove () {}
	#endregion
}

Stat Modifier Feature

There are potentially many types of features which could be added in an RPG, such as reviving a fallen ally, providing some sort of buff, causing a status-ailment, etc. Of course, those are a bunch of systems we haven’t implemented yet, so all we will focus on at the moment is the simple ability to modify a stat.

This simple component can be applied to any stat type, either as a good or bad modifier, and is compatible both with consumable and equippable items. Add a new script named StatModifierFeature to the Scripts/View Model Component/Features folder. The implementation follows:

using UnityEngine;
using System.Collections;

public class StatModifierFeature : Feature
{
	#region Fields / Properties
	public StatTypes type;
	public int amount;

	Stats stats 
	{ 
		get 
		{ 
			return _target.GetComponentInParent<Stats>();
		}
	}
	#endregion

	#region Protected
	protected override void OnApply ()
	{
		stats[type] += amount;
	}

	protected override void OnRemove ()
	{
		stats[type] -= amount;
	}
	#endregion
}

Merchandise

Since we are bringing up the subject of an item, a common concern is how to obtain them? They could simply be given to the player at the beginning of the game, found in chests or as awards from battle, but probably most frequently, they can be purchased from a shop. Likewise, unwanted inventory items can also be sold. In order to facilitate the ability to purchase and or sell an item let’s add a new component named Merchandise to the Scripts/View Model Component/Item folder.

This script is exceedingly simple – it merely exposes fields for a buy price and a sell price. We won’t be doing anything with it at the moment, but additional future functionality might be in order once we get around to implementing shops.

using UnityEngine;
using System.Collections;

public class Merchandise : MonoBehaviour
{
	public int buy;
	public int sell;
}

Consumable

If an item has a consumable component, then our systems which allow the use of items in battle will be able to show the item as an option for use. The consumption of said item would then apply whatever features were also on the item, to whichever target is specified with the Consume method. Note that I want to specify a target because it shouldn’t be assumed that the consumable will be applied to the user – while the user might wish to use a health potion on himself, there might be times he wishes to use a health potion on an ally, or perhaps the consumable is a bomb, and then the target would almost certainly be an opponent.

Add another script named Consumable to the Scripts/View Model Component/Item folder. The implementation follows:

using UnityEngine;
using System.Collections;

public class Consumable : MonoBehaviour 
{
	public void Consume (GameObject target)
	{
		Feature[] features = GetComponentsInChildren<Feature>();
		for (int i = 0; i < features.Length; ++i)
			features[i].Apply(target);
	}
}

Equip Slots

Before we can implement the Equippable item component, I need to specify a new enum. This enum will be a bit-mask which can specify one or more combinations of locations to equip an item. For example, there might be one-handed as well as two-handed weapons, so the item will need to indicate how many of these slots to occupy.

Add a new script named EquipSlots to the Scripts/Enums folder. The implementation follows:

using UnityEngine;
using System.Collections;

[System.Flags]
public enum EquipSlots
{
	None = 0,
	Primary = 1 << 0, 	// usually a weapon (sword etc)
	Secondary = 1 << 1,	// usually a shield, but could be another sword (dual-wield) or occupied by two-handed weapon
	Head = 1 << 2,		// helmet, hat, etc
	Body = 1 << 3,		// body armor, robe, etc
	Accessory = 1 << 4	// ring, belt, etc
}

Equippable

Items which are equippable have features which are activated for as long as the item is still equipped. However, with equipment you are required to manage what you choose to wear. For instance, there may only be a single accessory slot and you will have to choose between the amulet of speed or the buckler of defense. Perhaps different missions would justify swapping out your equipment.

Add a new script named Equippable to the Scripts/View Model Component/Item folder. The implementation is below. Note that I have added three public fields of our EquipSlots enum:

  • defaultSlots – The EquipSlots flag which is the default equip location(s) for this item. For example, a normal weapon would only specify primary, but a two-handed weapon would specify both primary and secondary.
  • secondarySlots – Some equipment may be allowed to be equipped in more than one slot location, such as when dual-wielding swords.
  • slots – The slot(s) where an item is currently equipped.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Equippable : MonoBehaviour 
{
	#region Fields
	public EquipSlots defaultSlots;
	public EquipSlots secondarySlots;
	public EquipSlots slots;
	bool _isEquipped;
	#endregion

	#region Public
	public void OnEquip ()
	{
		if (_isEquipped)
			return;

		_isEquipped = true;

		Feature[] features = GetComponentsInChildren<Feature>();
		for (int i = 0; i < features.Length; ++i)
			features[i].Activate(gameObject);
	}

	public void OnUnEquip ()
	{
		if (!_isEquipped)
			return;

		_isEquipped = false;

		Feature[] features = GetComponentsInChildren<Feature>();
		for (int i = 0; i < features.Length; ++i)
			features[i].Deactivate();
	}
	#endregion
}

Equipment

The equippable component gets attached to an item, but we need a different component to attach to our actors so that they can manage which items they wish to wear, and where they will be attached. For this we will create a new script called Equipment in the Scripts/View Model Component/Actor folder.

This component will post notifications when equipping and un-equipping items so that other components or game systems can respond (like actually updating a visual character model with the item). It exposes a readonly list of the items which are currently equipped, just in case another system needs to review them. Also, the system is somewhat smart in that it will automatically un-equip any item which had been previously equipped in an overlapping slot(s) before equipping a new item in the same slot(s).

Note that the class isn’t perfectly safe for just any use-case. For example I am not stopping you from equipping the same item more than once. I didn’t bother to safe-guard against that use-case because I am imagining that the user interface will be constructed in such a way as to prevent it for us. For example, whatever menu allows you to equip an item will only show items from your inventory, and when you equip an item it will first be removed from your inventory.

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

public class Equipment : MonoBehaviour
{
	#region Notifications
	public const string EquippedNotification = "Equipment.EquippedNotification";
	public const string UnEquippedNotification = "Equipment.UnEquippedNotification";
	#endregion

	#region Fields / Properties
	public IList<Equippable> items { get { return _items.AsReadOnly(); }}
	List<Equippable> _items = new List<Equippable>();
	#endregion

	#region Public
	public void Equip (Equippable item, EquipSlots slots)
	{
		UnEquip(slots);

		_items.Add(item);
		item.transform.SetParent(transform);
		item.slots = slots;
		item.OnEquip();

		this.PostNotification(EquippedNotification, item);
	}

	public void UnEquip (Equippable item)
	{
		item.OnUnEquip();
		item.slots = EquipSlots.None;
		item.transform.SetParent(transform);
		_items.Remove(item);

		this.PostNotification(UnEquippedNotification, item);
	}
	
	public void UnEquip (EquipSlots slots)
	{
		for (int i = _items.Count - 1; i >= 0; --i)
		{
			Equippable item = _items[i];
			if ( (item.slots & slots) != EquipSlots.None )
				UnEquip(item);
		}
	}
	#endregion
}

Demo

Now that I have created several new components to play with, let’s create a new scene and script to show off some potential ways they can be used together. I will be creating a mock battle which plays out on its own. The script will create everything it needs to help make this test easier on you, however, you should note that in practice I would not create the combatants and items all in place like this.

It isn’t necessarily bad to have a Factory class to create your items, etc. but as a Unity developer I would prefer to have created all of the items and actors ahead of time and stored them in some sort of prefab or scriptable object. Then I only need to obtain a reference (perhaps through Resources.Load) and instantiate them. For more on this idea see my post on Bestiary Management and Scriptable Objects.

Create a new script named TestItems anywhere in your project (I dont intend to keep it). Create a new scene and attach this script to your camera and press Play. Watch how the battle unfolds in the Console output window. The hero and monster take turns, and the hero may choose to use items or change equipment. Eventually one of the two will lose all of its hit points and the battle will end. If you play again you should see a different experience.

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

public class TestItems : MonoBehaviour 
{
	#region Fields
	List<GameObject> inventory = new List<GameObject>();
	List<GameObject> combatants = new List<GameObject>();
	#endregion

	#region MonoBehaviour
	void Start () 
	{
		CreateItems();
		CreateCombatants();
		StartCoroutine(SimulateBattle());
	}

	void OnEnable ()
	{
		this.AddObserver(OnEquippedItem, Equipment.EquippedNotification);
		this.AddObserver(OnUnEquippedItem, Equipment.UnEquippedNotification);
	}

	void OnDisable ()
	{
		this.RemoveObserver(OnEquippedItem, Equipment.EquippedNotification);
		this.RemoveObserver(OnUnEquippedItem, Equipment.UnEquippedNotification);
	}
	#endregion

	#region Notification Handlers
	void OnEquippedItem (object sender, object args)
	{
		Equipment eq = sender as Equipment;
		Equippable item = args as Equippable;
		inventory.Remove(item.gameObject);
		string message = string.Format("{0} equipped {1}", eq.name, item.name);
		Debug.Log(message);
	}

	void OnUnEquippedItem (object sender, object args)
	{
		Equipment eq = sender as Equipment;
		Equippable item = args as Equippable;
		inventory.Add(item.gameObject);
		string message = string.Format("{0} un-equipped {1}", eq.name, item.name);
		Debug.Log(message);
	}
	#endregion

	#region Factory
	GameObject CreateItem (string title, StatTypes type, int amount)
	{
		GameObject item = new GameObject(title);
		StatModifierFeature smf = item.AddComponent<StatModifierFeature>();
		smf.type = type;
		smf.amount = amount;
		return item;
	}

	GameObject CreateConumableItem (string title, StatTypes type, int amount)
	{
		GameObject item = CreateItem(title, type, amount);
		item.AddComponent<Consumable>();
		return item;
	}

	GameObject CreateEquippableItem (string title, StatTypes type, int amount, EquipSlots slot)
	{
		GameObject item = CreateItem(title, type, amount);
		Equippable equip = item.AddComponent<Equippable>();
		equip.defaultSlots = slot;
		return item;
	}

	GameObject CreateHero ()
	{
		GameObject actor = CreateActor("Hero");
		actor.AddComponent<Equipment>();
		return actor;
	}

	GameObject CreateActor (string title)
	{
		GameObject actor = new GameObject(title);
		Stats s = actor.AddComponent<Stats>();
		s[StatTypes.HP] = s[StatTypes.MHP] = UnityEngine.Random.Range(500, 1000);
		s[StatTypes.ATK] = UnityEngine.Random.Range(30, 50);
		s[StatTypes.DEF] = UnityEngine.Random.Range(30, 50);
		return actor;
	}
	#endregion

	#region Private
	void CreateItems ()
	{
		inventory.Add( CreateConumableItem("Health Potion", StatTypes.HP, 300) );
		inventory.Add( CreateConumableItem("Bomb", StatTypes.HP, -150) );
		inventory.Add( CreateEquippableItem("Sword", StatTypes.ATK, 10, EquipSlots.Primary) );
		inventory.Add( CreateEquippableItem("Broad Sword", StatTypes.ATK, 15, (EquipSlots.Primary | EquipSlots.Secondary)) );
		inventory.Add( CreateEquippableItem("Shield", StatTypes.DEF, 10, EquipSlots.Secondary) );
	}
	
	void CreateCombatants ()
	{
		combatants.Add( CreateHero() );
		combatants.Add( CreateActor("Monster") );
	}

	IEnumerator SimulateBattle ()
	{
		while (VictoryCheck() == false)
		{
			LogCombatants();
			HeroTurn();
			EnemyTurn();
			yield return new WaitForSeconds(1);
		}
		LogCombatants();
		Debug.Log("Battle Completed");
	}

	void HeroTurn ()
	{
		int rnd = UnityEngine.Random.Range(0, 2);
		switch (rnd)
		{
		case 0:
			Attack(combatants[0], combatants[1]);
			break;
		default:
			UseInventory();
			break;
		}
	}

	void EnemyTurn ()
	{
		Attack(combatants[1], combatants[0]);
	}

	void Attack (GameObject attacker, GameObject defender)
	{
		Stats s1 = attacker.GetComponent<Stats>();
		Stats s2 = defender.GetComponent<Stats>();
		int damage = Mathf.FloorToInt((s1[StatTypes.ATK] * 4 - s2[StatTypes.DEF] * 2) * UnityEngine.Random.Range(0.9f, 1.1f));
		s2[StatTypes.HP] -= damage;
		string message = string.Format("{0} hits {1} for {2} damage!", attacker.name, defender.name, damage);
		Debug.Log(message);
	}

	void UseInventory ()
	{
		int rnd = UnityEngine.Random.Range(0, inventory.Count);

		GameObject item = inventory[rnd];
		if (item.GetComponent<Consumable>() != null)
			ConsumeItem(item);
		else
			EquipItem(item);
	}

	void ConsumeItem (GameObject item)
	{
		inventory.Remove(item);
		// This is dummy code - a user would know how to use an item and who to target with it
		StatModifierFeature smf = item.GetComponent<StatModifierFeature>();
		if (smf.amount > 0)
		{
			item.GetComponent<Consumable>().Consume( combatants[0] );
			Debug.Log("Ah... a potion!");
		}
		else
		{
			item.GetComponent<Consumable>().Consume( combatants[1] );
			Debug.Log("Take this you stupid monster!");
		}
	}

	void EquipItem (GameObject item)
	{
		Debug.Log("Perhaps this will help...");
		Equippable toEquip = item.GetComponent<Equippable>();
		Equipment equipment = combatants[0].GetComponent<Equipment>();
		equipment.Equip (toEquip, toEquip.defaultSlots );
	}

	bool VictoryCheck ()
	{
		for (int i = 0; i < 2; ++i)
		{
			Stats s = combatants[i].GetComponent<Stats>();
			if (s[StatTypes.HP] <= 0)
				return true;
		}
		return false;
	}

	void LogCombatants ()
	{
		Debug.Log("============");
		for (int i = 0; i < 2; ++i)
			LogToConsole( combatants[i] );
		Debug.Log("============");
	}

	void LogToConsole (GameObject actor)
	{
		Stats s = actor.GetComponent<Stats>();
		string message = string.Format("Name:{0} HP:{1}/{2} ATK:{3} DEF:{4}", actor.name, s[StatTypes.HP], s[StatTypes.MHP], s[StatTypes.ATK], s[StatTypes.DEF]);
		Debug.Log( message );
	}
	#endregion
}

Summary

In this lesson we created several components which would be required to implement items in our game. This included components used for buying and selling items, components which give the items a feature which can modify the game in some way, components which make an item consumable, and components which make an item equippable. Afterwards we created a quick demo script which showed a random battle between two combatants and which included the use of both consumable and equippable items to help the battle play out differently.

Don’t forget that the project repository is available online here. If you ever have any trouble getting something to compile, or need an asset, feel free to use this resource.

20 thoughts on “Tactics RPG Items and Equipment

  1. Really enjoying your posts. I haven’t come across a tutorial for games like this until now. Do you utilize any of the assets from the Unity store to help your workflow?

    1. Thanks, I am glad you are enjoying them! I’m certainly not opposed to using assets from the Unity store, particularly for things like art. I don’t normally use code plugins, but that is because I enjoy programming and I want to know how to do everything myself. If I were to use code it would be in areas that I simply find less interesting to develop, such as social plugins for sharing or wrappers for native platform functionality – stuff that Prime31 has a lot of plugins for.

  2. I’m waiting for this post for a week, and didn’t find the post even after work at 7pm yestoday. Do you post every Monday evening? I’m new to game develop and really like this tutorial. I will try to make a tactics game demo like Fire Emblem after studying your tutorials; I also like Fanal Fantasy and have almost played the whole series from FF1 to FF12, but that is hard for me to develop.
    I have question: a weapon has a property like 10% Attack Increasing, and a ring has the same property like 7% Attack Increasing, they can be equipped at the same time, but only one can make effect. Will you talk about something like that in the tutorials?

    1. My goal has been to post every Monday morning (around 7 or 8 CST) which I have accomplished for the most part pretty consistently. I’m not sure why you wouldn’t have seen it until 7pm though I don’t know where on the globe you live. I’m glad you are enjoying the posts so far. I hope your project turns out great, I’d love for you to share the results of your hard work when you can.

      Regarding the “exclusive” feature question, I hadn’t planned on something like that. A “stackable” feature is easier and potentially more fun. On the other hand, it is possibly much harder to keep the game balanced, so I can certainly see why you may wish to go that way. I would probably implement it as another system. Perhaps something like I did with the value modifier (see the post on stats if you don’t know what I am talking about) where it sent out a notification and everything had a chance to modify the value argument by adding itself with a certain priority. In this case you would send out the notification and it would be specific to the “Attack” stat, and only listeners modifying that stat would take an action. Instead of appending themselves to a list, they would replace any information there if they had the highest modifier value. Make sure that the system which posted the notification also knows which item provided the highest stat boost, so that if the next time it posts a notification a different item with a higher stat boost is provided, that it can first remove the bonus from the original item before adding the bonus from the better item.

      1. Ah ha. I ignored I live in China…
        I can understand what you say; I also think it’s not necessary to implement that in a tactics game, and it’s just a common question when I am playing games as a new game developer. Thanks for your detailed explaining.

  3. When I try to implement an inventory list, I start to appreciate you work on custom component framework (IPart/IWhole). In this current framework. It’s odd to generate many empty GameObjects when I try to create a list of items owned by the team. Maybe I should use a list of data which holds the specific components(equipable/ consumable) of a certain item, and instantiate the GameObject(and add the specified component) only when the item is used? Ethier way of implementation I can think of seems kind of weird to me.
    Well, I think I’d better sit down and watch your show. πŸ˜€

    1. Thanks Buzz, I am glad someone finally sees value in that component architecture. I enjoyed it during my early tests with it, but overall there are enough other features packed into Unity that I don’t think I will miss it too much.

      Today’s post on Jobs may help clear up some ideas on how to implement the inventory. We programmatically create assets (prefabs this time) based on a spreadsheet of data, and the idea would work perfectly for items too. An inventory could contain a list of references to the prefabs – which wouldn’t need to be instantiated in the scene until they were ready to be used. When saving or loading you could just save a list of the names of the prefabs.

  4. Loved this article! Couple of questions though :p

    I don’t think you mention it, but items are just empty game objects right? Where you add the components later on.

    In the actual game, I assume these objects would be pooled at startup, but how would you go about accessing their references? (Unity answers has given a terror of the find methods) Add them all to an appropriate list at creation? For this a sort of controller script would have to made. And since this already is being made, we could simply specify the item type(s) and let the controller decide which components are needed (maybe put all this info in a scriptableobject?)

    1. Yep – an item is an item because of the components that make it up, but ultimately its just a GameObject like anything else. It’s nice to have this level of freedom because they can be so very different. You could of course have an actual “Item” component as an easier way to hint to other scripts what game objects would fit the general criteria, but depending on how you set it up it won’t be necessary.

      You wouldn’t need to pool or pre-create “all” the items in the scene or “Find” by name or anything like that. Using Resources.Load is all you really need. Using a scriptable object as a recipe to help create the item, or actually loading a pre-created prefab would both work fine.

      As I mentioned to Buzz, if you keep following along I present some options for ways to generate your prefabs. As long as you put them in a “Resources” folder you will be able to reference the project level asset (and read it’s component values etc) and/or Instantiate a copy of it for equipping on a unit.

      If you go the Resources route, then when persisting data from a unit or a store’s inventory, it is as simple as saving a list of the Resource names.

  5. Hey, i found your blog when researching for doing my first game, and i love it so far, keep up the good work! I’m just having one problem:

    I’m trying right now to make a component script that i could add to an item to increase the experience received. I know that i need to add a ValueChangeException with the MultDeltaModifier (from Status Effects post), and i managed to add it with a notification (just like you did on your demo for the Stats), but since it worked by a notification, it would be active all the time, independent if the item is equipped or not.

    The problem that I’m having is making the Exception into a Feature, so when i equip the item it will activate it by the Equippable script. Do you havve any idea on how i could do that?

    Thanks!

    1. Thanks, and I’d be glad to offer help. In this lesson I have an example of Stat Modifier features being attached to equipment. Note that the benefit (for example extra strength from a sword) is only applied while the item is actually equipped. This is because the stat boost is incremented in the “OnApply” method and then removed in the “OnRemove” method.

      The ability to gain extra experience based on an equipped item would be the same idea. You were right on track to think of the Notifications and the Value modifier. The trick is that you would register to listen for the notification in the “OnApply” method and then unregister in the “OnRemove” method. When you register the handler, you can specify a “sender” of the event – find the “Stats” component of the unit which has equipped the item and use that as the sender that you wish to listen for. Then, the effect is only applied to a single unit and only if it is wearing the item.

      I hope that helps!

  6. Help me here.

    Very basic question.

    How do I make a given mesh (exp: a sword) to be rendered in relation to a bone of the character rig?

    In other words: How do I “activate” the sword mesh in the hand of the character when it is equipped?

    Thanks.

    1. I usually do this kind of thing by object hierarchy. For example, I would parent an empty game object to the hand bone and rotate it appropriately. The equipment component would maintain a reference to that. Separately, the sword mesh would be parented to another empty game object so that the center of the handle appears at the origin of its parent and this whole thing would become a prefab that I would load as needed. When it comes time to equip a sword, you just parent the prefab to the appropriate container object and reset the position and rotation and it will stick like magic.

    1. You can add more than one StatModifierFeature- by adding one for strength, one for dexterity, and one for speed, you have now made a complex item that does all of the above.

    1. Sure, depending on the game, those would certainly be helpful additions. Feel free to run with it and change it to your needs!

Leave a Reply

Your email address will not be published. Required fields are marked *