First what you need to do is create a custom GameInstance class; call it whatever you want, in this example I’ll call it MyGameInstance.
Create a new BlueprintClass, and in the Pick Parent Class class search bar look for GameInstance.
Name your new GameInstance object class, and open it up in the BP editor.
Add an array of your ItemData structs, called “Inventory”. Add two booleans, called “ArmourActive?” and “WeaponActive?” These will replace the values in your widget class and in your MyCharacter class, so you can delete them later.
You also want to add two integer values called “ActiveArmourID” and “ActiveWeaponID”.
Save and compile your new GameInstance class.
Now open up your MyCharacter blueprint, and we’ll update it to use the new persistent inventory values.
First, add a variable called “ActiveGameInstance” of type “MyGameInstance” (or whatever you called your custom game instance class) to MyCharacter.
Then, add a variable called “Weapon” of type “Actor Reference”.
Then, in the EventGraph, from the BeginPlay event, add a call to GetGameInstance. Cast the result to your MyGameInstance class, and set the returned value to ActiveGameInstance.*
-
- Ignore the part about “LoadActiveGameInventory” for now.
Now we can build the weapon and armour equipping methods. First, add a “SetArmour” method and a “SetWeapon” method to your MyCharacter. Open up those methods and build them like these:
http://images.gigasightmedia.com/GMT_graphics/UE4Tutorials/BPInventoryPersist/004-BP-SetArmour.png
http://images.gigasightmedia.com/GMT_graphics/UE4Tutorials/BPInventoryPersist/005-BP-SetWeapon.png
You’ll find they are very similar to the methods in your widget, but a little simpler since we don’t have to cast anything or pull data from an external object.
The input pin for SetArmour is a SkeletalMesh Reference. The input pin for SetWeapon is an Actor Reference. You’ll see why we have that Weapon variable set here in a bit.
Now add two more methods to MyCharacter, one called “EquipArmour” and another called “EquipWeapon”. Let’s look at EquipArmour first:
http://images.gigasightmedia.com/GMT_graphics/UE4Tutorials/BPInventoryPersist/006-BP-EquipArmour.png
Again, it’s very similar to your functionality within the widget, but there are some important changes. First, the the Inventory data is pulled from the ActiveGameInstance variable we set on BeginPlay. Because the Inventory and ArmourActive? values are stored in the GameInstance, they will persist between level changes. I also assign the retrieved item’s ItemID value from the ItemData struct to the ActiveArmourID variable in the ActiveGameInstance. I’m assuming your ItemIDs are unique to each item in the game world, so if they’re not you’ll need to adjust this for this method to work properly. We set the ArmourActive? value to true, and then call SetArmour, passing that method the Mesh within the ItemData struct.
ButtonClicked is the same variable you pulled from MyCharacter in your widget method (I’m guessing that’s something your inventory menu assigns); but since we’re in MyCharacter already it’s a local variable.
EquipWeapon is also going to look familiar:
http://images.gigasightmedia.com/GMT_graphics/UE4Tutorials/BPInventoryPersist/007-BP-EquipWeapon.png
Again, the big differences are pulling the Inventory and WeaponActive? values from the MyGameInstance class instead of MyCharacter, and we set the ActiveWeaponID there too. After spawning the weapon actor we pass the result to our new SetWeapon method.
Now we add two more methods, “UnEquipArmour” and “UnEquipWeapon”; again, you’ll find them very similar to your widget events:
In fact, UnEquipArmour looks almost identical to EquipArmour; the only differences are performing the assignment if ArmourActive? is true, and we set ArmourActive? to false when we’re done.
UnEquipWeapon is almost completely the same, the only difference is the location of the WeaponActive? variable.
However, calling “GetAllActorsOfClass” is not a very efficient way of doing this. What if you have a bunch of enemy characters that are also carrying IronSwordBP weapons? Or if the character is wielding weapons in both hands and you only want to destroy one of them?
This is why I opted to store the spawned weapon in a local variable. Instead of using this version of UnEquipWeapon, you can do something like this:
This way we don’t have to get an array and parse it, and we don’t have to search the game world for all actors of a class (which is slow); we already have a reference to the weapon we want, so we just destroy it directly!
Now let’s do something really new; we’re going to create the method that restores your active Inventory settings after loading a new level (or loading from a game save). Create a new method called “LoadActiveInventory” and another called “GetInventoryItem”. First we’ll look at GetInventoryItem:
This is a pretty simple method; the input pin is an integer value. It pulls the Inventory array from our game instance, and then cycles through it looking for an item with an ItemID that matches the passed in TargetItemID. If it finds one, it assigns the found item to a local variable called “FoundItem” and immediately exits the method. If it doesn’t find one when it finishes cycling through the array, it exits the method and returns FoundItem without assigning a value to it. By default, FoundItem is a default ItemData struct.
This is important, so let’s look at how I have ItemData structured, so you can make sure your setup will work. I believe you have a value called “Empty” that corresponds to an empty mesh value; you may want to create an EmptyWeapon and EmptyArmour item that hold mesh and actor values appropriate for those situations, and make the default FoundItem struct correspond to them.
In my case, I’ve made the default values for the mesh and actor references empty, so if they are returned my methods assign NULL values.
Now let’s look at LoadActiveInventory:
Here we have a sequence with two branches. The first updates our Armour, and the second updates our Weapon. The branches are very similar to EquipArmour and EquipWeapon, but the key difference is that we don’t pull from the Inventory by index, we use the ActiveArmourID and ActiveWeaponID values to retrieve items from the Inventory with the GetInventoryItem we just created. If we get valid values, we call SetArmour, and SetWeapon as normal.
In the case that we receive a default ItemData struct (because a matching ItemID wasn’t found), the Equippable AND IsArmour/IsWeapon checks will not pass, so SetArmour and SetWeapon will never be called.
Also notice that we don’t modify the Active? booleans or ID integers as we do in Un/EquipArmour/Weapon; we aren’t modifying our state here, we are restoring our state.
The last modification we make to MyCharacter is to add the call to LoadActiveInventory to our BeginPlay event, we’ll do it right after we set the ActiveGameInstance value:
So now that we have all our inventory functionality built into our persistent GameInstance and the MyCharacter class, we can clean up our widget:
When the buttons are clicked, the corresponding methods are called. We can get rid of the local WeaponActive? and ArmourActive? booleans and rely on the value in MyGameInstance; and we only need the PlayerRef to call the appropriate methods from MyCharacter.
Finally, we have a very important step. We need to let the engine know to use our new GameInstance.
Open the Edit->Project Settings window, and under the Project heading click on Maps & Modes. Change the Game Instance Class to your new, custom GameInstance:
So that’s it. Now your Inventory items and state variables will persist between levels. You can also write those variables into your GameSave, and then when you reload a game you can pull the saved values from your GameSave and apply them to the active game instance. Upon level load the MyCharacter will update its local values just like it transferred between levels and restore the proper game state. Your widget clicks will automatically update the GameInstance values whenever the player modifies anything, and you don’t need to worry about keeping duplicate values in multiple objects!
I’ve also included some suggestions that might make things smoother for you going forward.
Instead of holding a reference to the Weapon Actor in your ItemData struct, you can instead hold a reference to the class. Then, when you retrieve that ItemData struct, you can pass the Class value into your SpawnActor method node, and it will spawn a weapon of that specific class. This will allow you to have the character equip different weapon types, rather than always spawning the same IronSwordBP weapon.
You can modify your EquipWeapon method, and include a SpawnWeapon instead of SetWeapon as below:
http://images.gigasightmedia.com/GMT_graphics/UE4Tutorials/BPInventoryPersist/017-BP-SpawnWeapon.png
Holding a direct reference to the actor loads the object into memory. If you have a full inventory with multiple weapons and armour, that means all of your weapons and armour are in memory the entire time. Instead, if you hold references to the classes, the objects are only loaded into RAM when you spawn them as needed.
If you opt to do that, you can eliminate the Actor and Mesh variables in the ItemData struct, and just use the ActorClass and MeshClass values.
You also might have noticed I have an Enum value called “Type” in my struct. You can use that to replace your IsWeapon? and IsArmour? booleans, and accomodate all kinds of items without having to add a unique boolean for each.
So in your EquipWeapon method, instead of IsWeapon? you can check if your Type value is “Weapon”:
When you are trying to equip your armour, weapon, clothing, or whatever, your equip methods can pull the appropriate class type (Actor, Mesh, etc) and then perform the proper spawning actions. That way all your items can be stored in the same ItemData structure with little memory overhead.
Here’s what my dummy Enum looks like:
As you can see, you can easily accommodate all sorts of items here. You can also then make a generic “EquipItem” method like this:
http://images.gigasightmedia.com/GMT_graphics/UE4Tutorials/BPInventoryPersist/021-BP-EquipItem.png
Lots of flexibility here.
Hope this helps.