In order to make a complex world, you're going to need to add more agents and you're also going to need
to add other inventories.
Currently, the inventories that we've created are living with the particular agent (in this case, it's just
the Baker).
So he's keeping track of how much flour is in the world and how much bread is in the world
but in order for him to really go and pick up bread from the mill, he needs to know if there's any flour
AT the mill and therefore the mill itself needs to have its own inventory.
Here you can see what I've created is a little simulation that now includes a Farmer who is delivering wheat
to the mill and the Baker (when he runs out of flour and there's no flour in the mill) sits around idle
and waits for the Farmer to fill up the mill and then will go back to his job.
So let's go about recreating this.
First of all, I'm going to stop playing. I'm going to take my Farmer I've got and turn it off so that it's not really part
of the equation right now
but what you will need to do is create yourself a farm or a paddock which I've just made a little
square
and I've made the door (which is the point where the action will take place) for harvesting, a little later
on.
I also took off the colliders on these things, so that the agent wouldn't have an issue and
he can walk into the middle of the paddock.
So the first thing you need to do is make an inventory for the Windmill.
I've taken the Windmill and I've just added the inventory that I was already using
for the Baker.
So the inventory script has been dragged-and-dropped onto the Windmill but not before a few little
changes.
Let's go and have a look.
So in here, I've just increased it by adding in a 'drawOffset' and a 'name' as well
so that this just effects the GUI system, so that when it draws it on the screen, it uses the offset
to where the inventory is positioned and also inserts the name,
so you know whose inventory it is and this will show up for the Windmill. So if you make those changes
and just save it, go back into Unity, then on the Windmill add that script and the Draw Offset I've got is 100
and the name is Windmill.
Now that means you'll need to set that for your Baker.
So if you go to your Baker and find the Inventory script, just put the name for Baker and the Draw Offset
can be 0, so that when you press Play, you will get the Baker at the top and the Windmill at the bottom
and you can keep track of what's in each of those inventories.
The script that needs to know about the windmill's
inventory is Worker,
so click on the Worker C# script and at the top, we're going to pass through the inventory from
the Windmill.
Add that in and then the flour levels in the windmill are going to be used down in the World State to
inject whether 'hasStock' is True or not
and you can see here that we're getting windmill.flourLevel greater than 4, which is going to
tell our Baker that there's enough bags (it's 5 bags, at least, for it to go and pick up in there).
That's all you need to add to the Worker class.
So save that.
The other thing that wants to know about that flour level is your PickupFlour action.
So at the top of that, you also want to pass through the Windmill's inventory here and then in order to pick
up flour,
we now have another Precondition. So addPrecondition("hasStock", true) so that when
that's true, then we can go and pick up our flour.
So we're only going to do it when there's stock at the Windmill
and we've run out of flour. So they're our first 2 initial conditions.
Now when we finally perform the PickupFlour, you have to remember that you want to add the flourLevel
to your own inventory, so that the Baker is now carrying the flour and the Windmill is losing the
flour, so you need to take it out of the Windmill's level for inventory.
So once you've done those, save all of that and then when you press Play you'll be able to watch this
in action.
The Baker's going to bake the bread, he's going to run out of flour,
he's going to go pick up some flour (which will take all of the bags of flour out of the windmill that
you can see in the inventory),
he'll come back and bake 3 more loaves of bread and then he'll have enough for a delivery and he will deliver
it
and once he's delivered it, he's run out of bread,
he's run out of flour,
the mill's run out of flour,
there's no flour anywhere, so he can't do anything and all his plans are going to fail.
He's going to sit and wait at the markets until they get some flour in the windmill, so we can actually
do that a
bit sneakily. If we select the Windmill in the Hierarchy (we're still playing) and go over to the Flour Level
and let's just deliver 10 lots of flour...and now you can see: off he goes.
So we want to now add another agent (a Farmer agent) that can take wheat from the Paddock and deliver
it into the Windmill, so that when the Windmill DOES run out of flour, that the Baker can get some more.
So to create a new Farmer agent, I duplicated the Baker and I made it green and changed its hat, just so
that it looks different. You then want to take all of the scripts relating to the Baker away, leave the GOAP
agent, the Farmer doesn't need it's own inventory (it's not actually keeping track of everything, it's
just picking up wheat and taking it to the Windmill).
Later on, if you want to make that more complicated, then feel free to.
We're going to add a Farmer script, which is the GOAP agent driving script, like the Worker script on
the Baker and 2 actions (1 Harvest and 1 DeliverWheat).
Let's have a look at the Farmer script first.
You'll want to duplicate the Worker script and just rename it to Farmer.
So at the top we've got Farmer and then everything else is the same. The GetWorldState,
there's nothing in there because it doesn't need to do anything
and then the job in the CreateGoalState is 'doJob' but this 'doJob' for this agent is based on the actions
that will be on the agent, so he's not going to be baking bread and picking up flour.
The rest of the code is exactly the same as for the Worker script that's on the other agent.
So they're the only things in there.
The 2 actions that we're adding...
first of all, we've got a 'Harvest' action (which is going to the farm and picking the wheat),
so it's called Harvest.cs, class is Harvest.
The top is the same...the
constructor, of course, is called Harvest.
Now Harvest doesn't have any Preconditions, so you can have an action without Preconditions and
you can also have an action without any Effects.
Now, nothing needs to happen for the Farmer to harvest.
You might want to set up some kind of day and night cycle in your game, so that the Farmer only harvests
during the day and then you could put that in the world conditions that are coming through in the Farmer
code
but in this case, there's nothing like that.
So the effect of the harvest is that you will have wheat, so 'hasWheat'
is set to True. Now the rest of this action,
there's really nothing going on in it at all.
So it's just the default one.
It's not updating any inventory in the 'perform' method or anything like that.
Ok, so that's the harvest.
Now the other one that we have on the Farmer is the DeliverWheat, which is to take the wheat to the
flour mill.
It's a little bit different.
Open that up and you'll see that we've got a 'public Inventory' being passed into it and this is the
Windmill's inventory. That's so that we can update the Windmill's inventory when this action has completed.
So it's called DeliverWheat, it's constructor is DeliverWheat, its Precondition is 'hasWheat' is True,
which is set to True when we do the harvest
and then the Effect is
("doJob", true)
and this is what's causing this whole little chain to run
because 'doJob' is the main goal of the Farmer and the 'name' here (which I haven't changed) should also
be 'DeliverWheat'.
This is what you get when you copy-and-paste all of your actions.
Now the rest of the code is pretty much the same...going down...here.
The only thing you'll see is when we perform this task of DeliverWheat, we're going to update the
flourLevel in the inventory by 1 bag.
This inventory is not the Farmer's inventory.
He doesn't have one.
It's the Windmill's inventory. Ok, so save those,
then let's go over to the Farmer and you want to attach those 2 scripts (those 2 actions) and the Farmer script
as well.
Both of the actions (Harvest and DeliverWheat) will both need locations, so the Harvest script is
going to happen at the Farm Door (which is our Farm/Paddock) and then the DeliverWheat is going to happen
at the Windmill Door, where we drop that off.
Now you'll see in the DeliverWheat action, we've also opened up that 'public inventory', so that we can
pass the Windmill's inventory into it, so you can just drag-and-drop the Windmill into that spot and pick
up the inventory of the Windmill that this script can now update.
Now let's run it and what we'll see is both agents working towards their own goals.
Ok, so as the Farmer delivers, you'll see that the windmill will go up 1 every time and the agent just
picked up 5 bags, which has now taken them back to bake some bread and he'll go and deliver some bread.
Now he's not going to be able to go back to the Windmill until there's 5 bags to pick up,
so he's kind of stuck in limbo as far as baking bread because he's got no flour and there's none in
the Windmill.
When that Windmill inventory hits 5 flour (which it will right now), it will trigger the Baker to perform his
goals and off he'll go, pick up some flour and then he can get into the job of baking bread.
Ok, so you can see how this can get very complex, very quickly and how you can have inventory systems
that belong just for the agents themselves so that they can keep track of what they've got
but then there's also things in the world that need inventories as well, so that you can move products
around. What's really important is if you take something from one inventory, that you add it to another
and that you make sure you minus it from the value in that previous inventory,
otherwise your numbers are going to get all out of whack and it might be a good idea if you actually
set up a spreadsheet or something to keep track of all of these things that are going around
or even have global variables in some kind of 'inventory count file' that you can keep the actual
values that you're going to be adding and minusing, so that you only have to change them in one
place in your code.
Right.
So that's the GOAP system
and I hope that has inspired you with lots of creativity for designing different types of simulation
games like this but also using GOAP for RPGs and First Person Shooters as well. So if you have a read
through that research literature link that I sent you at the beginning, you'll see that this was first
used in FPS-type games but it's very adaptable.