Let's create a PlayerClass
, and then, for giggles, we'll have it inherit to another PlayerClass
and then we'll cover updating it.
Let's Subclass the Parent with this JSON file:
The key thing to really note here is the parent block in the JSON,
If you note from the prior section, this mint and index is the mint and index used to create the parent class. By referencing it in the JSON file here for the child class, we are telling the contract to consider this class a child. This also means that when we choose an updatePermissivenessToUse
for creation here, we're actually using a permissiveness on the parent, not the child. So that tokenHolder is actually referencing the parent token, not the child. This is a nice way to get around needing to own updateAuthority on a specific NFT if you don't have it but want to make a game with it.
To create the subclass, run the same command as before:
The resulting show_player_class
command will yield the following:
The clever among you will probably be wondering where the BasicStatTemplates
from the parent class are. If it had had BasicStatTemplates
in the childUpdatePropagationPermissivenessArray
, it would have! In this case, we didn't add it in the previous section, so this child will only have the two stats it defines for itself, but if you try this example with that minor tweak to the parent, you can see the inheritance work.
However, we did set the propagation up for BodyParts
, and this resultant class does have both a Leg and an Arm.
First, let's start with a standard JSON file for a PlayerClass
. As you can see, you don't need to set every field, most are optional. In fact, a lot of the fields we are setting in this example here are optional, but this is a working example so you can play with it as needed.
There's a lot going on here. To skip understanding it and just see how it can be made to "go," let's just run this command:
With this, a PDA has been made on top of your NFT. Check it out with the show_player_class
command:
This produces:
It is not itself a Player
but a blueprint for one. If you want to use it in games, you'll actually need to either instantiate a Player
from this class, or, as we'll do in this example, subclass this PlayerClass
one PlayerClass
further, and then instantiate a Player
. So now this NFT represents a blueprint for an Abstract Warrior. We plan to have a bunch of different Warrior classes, let's move on and subclass it to make a more specific warrior called Concrete Warrior in the next section.
If you're interested in what is going on in the JSON file above, please take a look at this "Breaking down the Magic" section below.
With Players
and PlayerClasses
, you can specify how big you'd like them to be but be aware that if they aren't big enough (totalSpaceBytes
) you run into serialization errors when you equip too many items, for instance. So size appropriately for your game. You can also decide whether you want to store the mint and metadata and edition pubkeys on the PDAs, which take up a lot of space but help with indexing by your front-ends using storeMint
and storeMetadataFields
. If you yourself are not the metadataUpdateAuthority
, make sure to set it. This is a Token Metadata concept from Metaplex - meaning you have the right to update the NFT.
When creating a PlayerClass
, the contract will always use updateAuthority
even if you put, as we did above in updatePermissivenessToUse
, tokenHolder
. The only way it will not use updateAuthority
when creating a class on top of an NFT is if the PlayerClass
being created has a parent
field set in the JSON, meaning it will be inheriting from some other PlayerClass
. In that case, the updatePermissivenessToUse
you chose (in this case, tokenHolder
) is passed along to the parent
, so you'd be trying to prove you have a right to create this PlayerClass
because you are holding the token of the parent PlayerClass
and that PlayerClass
has in it's update_permissiveness
array an entry for tokenHolder
, allowing for such a permission to be used on it.
If you want to have a parent
, parent
can fit the format, if not null, of:
Also take a look at this garbage:
What is all this? What this mess of JSON is saying that any child class inherits all BodyParts
from the parent, and it's defaultCategory
, but nothing else. This system of selective inheritance allows you to decide what parts of your classes filter down to children and what don't.
The overridable
field, when present, determines whether or not the child's values will be completely overridden if present.
So for arrays like BodyParts
, if the parent has a Head and a Neck in its initial JSON file and the child has an Arm in its JSON file, and overridable
is false, then the resultant child will have a Head, Neck, and Arm. If overridable
is true, then the child, despite being created with an Arm in its JSON file, will end up with only a Head and a Neck during the creation call. For single-value types like defaultCategory
, what overridable
= true means is that even if you try to create a child class with an explicit value for defaultCategory
in the initial JSON, it will be ignored, whereas if you try to set it in the initial JSON when overridable
is false, it will be overwrite what was in the parent.
Basically, overridable
true indicates that the parent
crushes everything beneath it, and overridable
false means that the parent
will step aside for the child
if the child has a better or additional idea of what a value should be.
Now that you've made a Player
and two PlayerClasses
, you probably want to clean up and drain them. This is pretty easy, just run the drain commands:
Be careful to run these in order - the protocol won't lead a parent be drained until all children have first been drained. Similarly a Player
cannot be deleted until all Items
have been unequipped, removed from the backpack, and had their effects removed from BasicStats
.
Ok, so now let's build a player. Here's the JSON I will use:
When building players, the JSON provided always needs at least two levels up - the Player's
class, and that class's class, if necessary (notice the parent
block.) Also, you'll notice from the previous page that the buildPermissiveness
array did not allow tokenHolder
permissions, so instead I had to rely on the fact that I had updateAuthority
on the NFT to build the Player
.
Also notice that I did something new here too with the NFTs. While the parent PlayerClass
NFT was located on mint GwB8Zt index 2, and the child was at edo3Dz index 4, I didn't use a whole new NFT for the Player
itself. I reused the second NFT, edo3Dz, just at a different index - 5! This is a very powerful design pattern you can use to create Singletons, if you wish.
Also notice that you get your choice of totalSpaceBytes
. Make sure to set it large enough to accommodate the maximum amount of equipment that can be attached to this Player
(the math is roughly 34 bytes multiplied by the number of BodyParts
multiplied by how many items per part can be applied). At some point in the future we will add dynamic account resizing to item equipping so that the account can grow and shrink as needed.
Here's the command to run to create the Player
:
After running, you can run the show_player
command to check out the state on chain:
Or from the client side:
You'll get something like this:
Note that on mainnet-beta, you'll be charged 1 $RAIN to build a player, and you'll receive that $RAIN back if you drain the player later.