top of page

GOAP

C++ goal oriented action planning. Enemies determining which goals to reach and having different actions based on determined factors on how to reach that goal.

Main attributes

  • GOAP system:

    • ​Created the brain of the AIs, goals and actions and how they're run​​

  • GOAP implementations:

    • ​Implemented the gameplay around this system. Movements, actions, animations...​​

  • GameObject system:

    • ​Created game object and component system, both for engine and game.

GOAP System

GOAP stands for goal oriented action planning, meaning AIs in game have several goals to achieve, and different actions to take them there. The actions can have different costs, effectiveness and determining factors to take them towards that goal, making some actions more worth taking than others.​

{78C327C8-87F0-452D-9D4E-2113A8EE83D0}.png
{74482483-9669-407D-A2AC-FC500B317E0A}_e

What I implemented was goals with conditions consisting of WorldStates. WorldStates began as an unordered_map with floats and strings, and was replaced with an array of floats with a specific enum size to increase performance by skipping hashing during runtime. I considered using booleans, but decided floats are more flexible in its use.

Actions consisted of cost, effectiveness, a behaviortype, preconditions and effects. And for debugging, a string as a name. 

{6EB166E9-1CC4-449A-8893-13E20CF34966}.png
{D6C53D98-C30D-4D01-B254-226264EC84FD}.png

Preconditions determine whether the action can be taken according to current worldstate, and effects affects the current worldstate if the action goes through. I added a helper function for comparing cost with effectiveness (percentage).

I had a GOAP-Planner class to choose which goal to take during runtime, and to make a plan if no plan existed using current worldstate.

{F5D7C14F-50C7-4C1E-A374-FE8155267407}.png

GOAP Implementation

Project started out as a simple boids project, so the enemies are called boidy, but when flocking behaviors got done I wished to add more. I created a behavior tree which I made work with more behaviors. But then I wanted more, so I expanded this to a GOAP test.

3 goals,

Befriend, Roam, and Attack.

 

5 actions,

Spiral (befriend), Eat (if food is available), Free Roam (flocking), Army and CircleAttack.

Friendshipmeter

The enemies have a friendshipmeter which is clamped between 0 and 10. They don't know if player is friend or foe, and depending on which events the player sends out, they either like player more, or dislike player more. 0 is lowest when they don't like player, and 10 is max when they like player the most. In the video, it's made clear through their color, red is 0, white is 5, and green is 10.

GOAP friendship croped.gif
GOAP player.gif

When friendshipmeter is lower the boidys will attack, army, or go into flocking behaviors, depending on how low it is. The higher the friendshipmeter is, the more likely it will spiral or roam. Eat is only triggered if there's food, and depending on healthiness, tastiness and friendshipmeter, will the boidies eat.

Spiral

Spiral uses a Lemniscate curve, together with a seperation direction (from closest Boidy), and blends between them using a blendfactor to determine their next direction. I considered a sinewave curve, but it felt to robotic.

GOAP flock better.gif
GOAP spiral.gif

Free Roam

Free roam is the boids movement, flocking behavior including seperation, cohesion, and alignment, weighing them towards each other.

Circle Attack

With the help of an EnemyManager the boidies gets a spot around player in a circle that they travel to, to then attack one at the time.

GOAP army.gif
GOAP attack.gif

Army

After circle attack the EnemyManager helps give them a spot in an army grid that's constantly moving. One of the few times I had to adjust MovementComponent to stop using force and only set their new positions as they otherwise never reached their spot.

Eat

Player can give food to try and befriend the boidies. First boidies check how tasty that food is, and they have a HungerComponent that slowly ticks up. Even if the food is super tasty, they wont eat if they're feeling full.

GOAP eat2.gif
GOAP eat1.gif

If they want to eat the food, they also check friendshipmeter. If player is foe, they will only dislike player more. If not, after eating, there's a healthiness variable determining whether they like or dislike player after eating.

Game Object System

This project has been refactored a few times, and hopefully how it ended up looks good. I strived to make the game object system as similar to Unity as possible, gameobjects with components.

{5EABC187-0E0E-432C-9365-1FFD23924E9E}.png
{1BE2A925-456A-45DD-B058-504AE6FCC3DF}.png
{ABC73221-9528-4518-82D1-9D5448222364}.png

Components exist in both engine and game. Those in engine like TransformComponent could be reused for other games created in this engine. While components in game like FriendshipComponent is specifically for this project. 

Other

Player

Player uses a StateMachine, can move, dash, pet, feed and smile. Both player and boidies subscribes to an eventhandler, and almost each player state sends a message which bodies listen and react to.

GOAP player2.gif
GOAP player.gif

Some states are simpler, dash decreases friendshipmeter, smile increases friendshipmeter, and then feed and pet are more complex, which food and boidies figure out what to do.

Food

There are four types of food, orange, apple, banana and chocolate. I based tastiness on my own taste, and I HATE ORANGES, so their tastiness is low. They all use a statemachine, have a full state and a half state.

GOAP eat1.gif
GOAP eat2 croped.gif

All food used to inherit from Food class until I refactored all logic into Food class. Orange is super healthy, banana and apple as well, while chocolate is super unhealthy, makes boidies angrier, and hungrier.

Object Pool

I used an object pool for the spawned food in the scene to be very careful with avoiding memory leaks.

{FD97774A-9E5B-4CA9-9B6F-86D0D62FF49C}.png
GOAP apple croped.gif

The scene updates normal gameobjects during its runtime, and has a container with raw gameobject pointers which the object pool owns.

Performance

I've refactored to increase performance as well. First it was WorldStates hashing during runtime when updating current values.

Previous:

{2068AE15-B524-4381-B5DD-96AA3B25E6A7}.png

Current:

{C7F61218-F864-4FEA-992E-685133F1FFD9}_e
{2F3D9368-B60C-4E9E-91DD-73F01B603936}.png

Then it was gameobject GetComponent, which looped through all components until it found it. But since this is used in runtime I added a container with known components, now it's looking for that key instead of looping though the entire container. 

Even with over 200 gameobjects the FPS is still over 60!

bottom of page