This post was co-authored by Twirlbound's technical director Marc Peyré and general programmer Martin Stuurwold.
On the island of Albamare, every organism is alive. We are working with a digital ecology in which dynamic agents respond as if in a simulation, rather than with static behavior – they have needs, react to each other and make decisions of their own.
In this project highlight, we’d like to dive a bit deeper into the systems behind this, the way we are building this (being the small team we are) and more. These are the species and organisms of Pine!
In Pine, we work with genetic algorithms to give a true, technical spin to the theme of evolution.
Each organism has a set of genes. Their decision-making, visual traits and behavior is based on these genes. Within a species, these genes mutate across generations, by picking the best genes from a shared gene pool. While keeping variety, this means that the average of genes specializes more and more towards a specific player.
At the moment, we have built 4 fully-fledged species that we are testing and iterating on. We plan to make 4 more, eventually filling the game world with 8 different species. Next to these, about a dozen critters fill up the world, serving as food and as ecological balances.
However, it wouldn’t make sense if the Alpafant of the forests looks the same as the one in the dunes – an example of where that genetic variety would come in handy.
As you can see in this gif, we are letting code decide on specific details of how any organism looks. They have different colors and outfits, and some are more buff than others. Even their individual personalities are reflected in the way they are posing (arched, upright, etc.).
This way, you can naturally observe the traits of a certain species. Their strength, health and specific behavior is hinted towards using the amendments and genetic algorithms we use through our systems – if a species is wearing more armor, it’ll take you longer to beat them. If they’re using large, stone clubs, they’re probably going to be a bit stronger than their wooden-club-wearing relatives.
This doesn’t mean species can go all-in for each area – they’ll have to specialize in some areas, and be worse in others. This simple triangle shows what this can mean:
Instead of species evolving superbly in all areas, they focus on areas the player is bad against. This means you get a personalized AI against you!
That specialization is mostly noticeable in combat. Although we’re still experimenting with some unique combat additions, this Friday we’re sharing a technical look into the species’ state machines and how they ‘think’ for themselves in conflicts.
Through genetic algorithms, this process of specialization becomes stronger as you play. The more data the game has, the more we can make the AI grow to something personalized.
Not only these genetics, organisms and species, but almost everything we do in Pine is of a systemic nature. In order to make high-quality content with the small team we are, we try to think in systems and generic solutions, rather than in situations and scenarios. This means a couple of things:
When we first put two species together in a scene, they started fighting – a good example of systemic gameplay! When we dropped the player in there, they started shifting their attention between the other species and the player, based on friendships and the hierarchy.
This created strong scenarios that inspired us to focus on that more. By making each species share the same core structure, we can polish their behavior, the ally-and-enemies system and cooperation to perfection.
The core idea of generalization and systemic design is a very strong factor in the world we are creating. Although lots of our ideas might sound ambitious, testing and iterating becomes much more doable through these systemic approaches. So far it has been worth looking at the underlying core, rather than at situations – if the base is strong, amazing scenarios can pop up.
But species aren’t fighting all day. In Pine, we focus on the basic needs of hunger, shelter and territory.
Our world is scattered with points of interest (POI), which indicate important places the organisms visit. These are often places where food can be gathered, but also strategic points for lookouts or defense.
Of course, this domestic behavior is interrupted if something more interesting happens nearby. Species live in villages that are placed procedurally. Based on how well a species is doing in the hierarchy, their population and village size will increase or decrease. The number of organisms you’ll see from a certain species is based on how they’re doing against each other, and how you interact with them.
We’re working hard on these villages, and even in their modular states we make sure they fit well with the species inhabiting them. So far we showed the Litter and Cariblin village pieces:
Pine, being medium-sized, won’t focus on an 80-hour storyline, nor on a 100km2 island to play around in. Instead, we’ve discovered that our strongest situations have come out of species interactions, by assuming them to be like players.
The individual variety we’re aiming for in the species also works well – some are more aggressive, some more defensive, because they might have a different nature. Just like humans.
We are treating each character, (the player and the species they may encounter) as Organisms that contain multiple components that give them their appearance and behavior. An important part of this is the state machine system, which is located inside the brain component of each Organism.
Our state machines govern how an organism behaves and reacts to the world. Here is a quick overview to how a state machine works:
Each state has the functions
OnExit()functions are called when the state machine transitions to and from this state, respectively. While a state is entered, it calls the
Process() function each frame.
As an example, let's say that in the above image, the Organism is in the state labelled Idle, and wants to transition to Attack. Due to how the nature of state machines work, in order to successfully transition to the new state, it needs to move up the hierarchy, calling the
OnExit() function on each state as it moves up, before then calling
OnEnter() on each state as it moves down. The flow looks something like this:
Idle --EXIT-> Passive --EXIT-> Root --ENTER-> Combat --ENTER-> Attack.
Each state represents a certain behaviour of the Organism, so for example inside the Attack state, we:
Because almost every state needs some sort of visual representation, the animations of the Organism are quite closely tied to the state it is currently in. In Unity, character animations can be defined using an Animator Controller component. The Animator Controller itself is a state machine for animations only, and we often found ourselves with an Organism state machine that was out of sync with the Animator Controller's state machine.
A small, simple Animator Controller.
This could sometimes lead to situations where the Organism's state machine moved to a different state, while the Animator Controller's state machine was not yet ready - causing the animations to get stuck as the Organism's state machine would not be triggering the correct animator transitions. The fact that these two state machines were so similar and needed to rely on each other, lead us to try using our state machine to control the flow of the Animator Controller. This would mean getting rid of all the transition lines inside the Animator Controller, and letting the Organism's state machine handle all the transition logic as well.
Luckily, Unity provides editor functionality to generate an Animator Controller through code, as well as manually transitioning between animation states. This would then let us automatically generate an Animator Controller based on the layout of our Organism's state machine, as well as manually force animation transitions when a state is changed. This completely eliminated any chance of the two state machines going out of sync, as the Animator Controller was now completely dependent on the Organism's state machine. This also had the added benefit of reducing our iteration time whenever we added new behaviours, as we did not need to fiddle around with the Animator Controller and manually set up all possible transitions.
An example of us creating a new state in code, and updating the Animator Controller from that code.
In Pine, as a player you are allowed to attack from many states, for example from multiple Idle, Movement, and Attack states. Without our system of generating the Animator Controller and transitioning in code, we would need to manually set up each possible transition from every state to every other state. This would make it very viable for us to forget to set up a transition in the Animator Controller, causing the two state machines to once again fall out of sync when that transition is supposed to occur.
An example of how the Animator Controller would look like if you would only have a few states, but still want to transition between most of them.
As a bonus to us treating the player the same as any other Organism, we can easily swap out the processing logic inside each state to specific AI/player-controlled versions. For example, in a player-controlled version of a state we would poll for input to decide where to transition next, while in an AI-controlled version of the same state we could then let the AI decide where to transition next. This allows us to swap between player-controlled and AI-controlled Organisms at runtime.
With such a systemic approach, a small team like ours is able to constantly iterate on the core technology, rather than on instances of mechanics and situations. We attempt to creating a living world by making the rules, rather than individual inhabitants.
By making sure as much as possible is automized and generalized, you can create new possibilities and a lot more content than you might be when crafting everything by hand.