Posted May 11, 2022 by MurphysDad
#ai #postmortem
I've had this idea for a Pokemon-like game, but with more focus on developing a relationship with your monster friend. To implement this, the game would require some pretty complex AI. So I decided to dip my toe into it and develop a "proof-of-concept", so I could understand the hard parts better and better determine if this idea was achievable.
I established a minimal set of requirements for my game - just enough that it could demonstrate the basic game loop I wanted in my game:
I purposefully refrained from researching AI models that would fit my requirements because:
I had prior knowledge of state machines and I know they're a common framework for implementing AI, so I started there.
My source code (a Godot project) can be found here. At a high-level, the pseudocode for the wombat AI looks something like this:
var current_state # Main function called every frame func _process(): var next_state := _determine_next_state(); _transition_to_state(current_state, next_state); current_state = next_state; _decide_actions_for_state(); _actuate();
The bulk of the AI logic is in the _determine_next_state() function, so let's dive into its pseudocode:
// Immutable - this is innate to the wombat var final fear_distance_multiplier; // Mutable - these change depending on the wombat's relationship with the player var fear; var love; var player_distance_preference; func _determine_next_state() -> State: var distance_from_player := player.global_position.distance_to(self.global_position); var closest_food := find_closest_food(); var closest_gold := find_closest_gold(); match current_state: State.IDLE: if (fear > 0.00 && distance_from_player < fear * fear_distance_multiplier): return State.DIGGING_DOWN; if (closest_food): return State.SEEKING_FOOD; if (closest_gold): return State.SEEKING_GOLD; if love > 0.00 && distance_from_player > player_distance_preference: return State.FOLLOWING_PLAYER; return State.IDLE; ... // Similar types of logic for every state return State.IDLE;
First, the _determine_next_state() function does some calculations about the "World" so the wombat can decide what the next state should be (e.g. distance_from_player, closest_food, closest_gold). Mostly it's checking how far away the player, food, and buried gold are.
Then, the wombat AI goes through a boolean decision tree (dependent on the current_state) to decide the next state. The decision tree uses the World calculations (e.g. distance_from_player, closest_food, closest_gold) in combination with some variables that reflect the wombat's level of trust in the player and innate tendencies (e.g. fear, love, player_distance_preference, fear_distance_multiplier).
On the surface, the code was able to meet the game's requirements. However, that wasn't without some hardcoding that I don't think would scale well. I've identified two major gaps in this AI model for my game requirements:
To solve these limitations, I'm thinking about logging and tracking a stream of Actions and Decisions, then basing Decisions on that stream in tandem with the World state. The AI can also use that stream for Learning: when there's an Outcome (a "positive" or "negative" event such as eating food), the AI uses the stream of Actions & Decisions to Learn and adjust the variables in the Decision algorithm. However, first I'll do my homework and research what the experts have to say about approaches to "learning" in game AI.
If you're interested in trying out the POC, check it out here.