There’ll be a variety of different occupations in the game; some you can dive straight into and others will require training or apprenticeships. One of the jobs you can take part in pretty much from the off is farming.
I want the game to be accessibly complex - i.e. an aesthetically simplistic interface concealing a ridiculously complex backend. The foremost component, and arguably most complicated, of this is the AI. Dynamic economies, weather, day/night cycles, war are all, in their own right, difficult to create, but ultimately they both derive from and - at the same time - directly affect the AI lying within the inhabitants of the game world. This is the same with regards to player and non-player character statistics.
I decided to start with an animal, and - for no particular reason - I decided upon a cow.
Before committing to any kind of code, graphics, or whatever, I needed to work out the rudiments of what forms a cow. What is the behaviour that defines this creature? Well, I figured that it had a few: it predominantly ate, it can breed - and so this requires it to find a mate, it sleeps, it can attack predators, or flee and it can be born and ultimately die.
Great stuff. So, how could I translate this into code so that I could actually begin to make a cow? Making a new script, inspirationally entitled cowAI, I began to put together a finite state machine.
For those in the know, don’t bother reading the next part, but for the benefit of others who may not have come across this topic before, stay a while and listen.
Finite State Machines
A finite state machine (FSM) is basically a series of ‘states’. Each of these states contains methods that can be called upon to discern a certain action. These states cannot happen simultaneously - they move in and out of each other when criteria have been met. Changing from one state to another is called a transition.
To paraphrase, it is essentially defining different things that you want - whatever it is - to do. For example, a tomato is planted, it grows and then it is either harvested or dies. So, the ‘states’ for a tomato would be ‘plant’, ‘grow’, ‘harvest’, ‘die’. It’s a simple as that. The FSM itself merely houses these states so that a transition can be made between each of them.
A few worthy notes regarding the FSMs - never call it within your update method as it’ll jam-up (updates are called every frame). I generally begin the coroutine within the start method, as this is only called once and then the FSM intrinsically runs throughout anyway, by virtue that certain conditions and methods are called in order to change it.
Anyway, back to my cow.
So, having thought about what I wanted my cow to do, I began to code it. At this stage I wanted to keep it simple, so the beginnings of my FSM looked thusly:
public enum State {
Search,
Eat,
Return,
Milk,
Flee
}
’Search’ was essentially my default state for the cow. When creating FSM machines some programmers opt to have a ‘setup’ and ‘idle’ state, so that the AI is arranged and then goes into waiting. However, because I want my world to be ‘alive’ the cow should always be doing something, and I call my setup parameters in the awake or start method anyhow.
Having defined the different things that I wanted my cow to do, I now had to go about actually creating those things. I’ll only discuss one method here, so if people are desperate to find out more they can message me, I suppose.
Firstly, I set up the FSM as a private IEnumerator, because it returns a value:
private IEnumerator FSM() {
while(_alive) {
switch(_state) {
case State.Search:
Search ();
break;
case State.Eat:
Eat ();
break;
case State.Return:
Return ();
break;
case State.Milk:
Milk ();
break;
case State.Flee:
Flee();
break;
}
yield return null;
}
}
This is what houses and operates the FSM and it is started a coroutine in the start method. _alive is just a boolean value to ascertain if the cow is alive or dead - if the poor old cow is dead, then fanny adams happens. Otherwise, the initial state is search.
Search: as the default state for my cow, I decided that its primary objective should be finding food; in this particular case, a fresh supply of grass. In terms of game objects this meant the creating the cow, and the grass. The grass would, therefore, become the cow’s target and the cow would walk towards it. Upon touching the grass, the cow would transition into eat, which is a separate method.
In it’s most basic form, the method looked like this:
private void Search() {
target = _grass.transform;
currentSpeed = walkSpeed;
Move();
}
So, what the fuck is going on here?
Well, first off, the method is void because it’s not returning us a value - it’s simply defining behaviour. Within the method the target is set to be _grass.transform. _grass is a public GameObject that is, actually, defined within the start function, but can be manually altered through the inspector because it is public. The .transform indicates that I want the target to be the transform of the grass, which is the element that contains its vector3 reference (it’s x, y, z axis points).
The currentSpeed is a public float that defines how fast the cow should move - in this case it is by its walkSpeed, which is also another float that I have set at a certain number. This is basically the amount that the movement value will be multiplied by to set the movement speed of the cow within real time (delta time).
The next item is actually another method that makes the cow move. When I began coding this game, I decided that I was going to create my own movement system, as I didn’t like the simplicity of the character controller within unity. However, I will discuss this in a separate post as it got pretty ugly pretty quickly.
So, within this method, that is called within the state of search, the cow finds a target and begins to walk towards it. Wow.
When the cow gets to the target I want it to stop and eat the grass. This is done through OnTriggerEnter - the grass that the cow has touched has a sphere collider attached to it that is a trigger. The grass itself is also tagged as ‘grassSupply’:
void OnTriggerEnter(Collider other) {
if(other.CompareTag("grassSupply")) {
_state = State.Eat;
}
So, when the cow touches the GameObject that has the tag “grassSupply” (the target the cow has been moving towards) it provokes a transition into the state of eat. This next method, of course, has a whole heap of other shit that happens.
To summarise - the state of search contains the following actions:
Find target - move towards target - reach target - eat.
And that there is the rudiment of what makes a cow do what it does. After working on it further, naturally, it has become more complex; but it was important for me to make even the simplest creature have as robust an AI as possible. Humans are basically advanced animals who do more things, and so - believe it or not - once the cow has a brain it’s not too much of a jump to make it a person.
I am going to make a cow a person.
A good read. You're learning a lot James. Awesome work - good luck on the game.
ReplyDeleteCheers!
ReplyDeleteCode is one of the few things I've found where trial and error (whilst time consuming) yields definitive and rewarding results. I can see why you specialised in AI, dude - it's a fascinating subject! I hope to get to grips with the basics of it more and more as I continue to work. For me, getting a cow to successfully gestate was an achievement in itself... let alone milking the fucker!