I’m currently working on a Top Down Action RPG with Rogue-Like mechanics. I want the Enemies in my game to be Procedurally Generated and I have an idea of how its going to be implemented. My biggest issue is dealing with the AI of each enemy after the generation process.
Since the art style and aesthetics of my project are kinda strange the entities (player and enemies) consist of a GameObject with 2 child objects, Aim and Body. Weapons and Augments are equipped to these 2 child objects depending on its Type. Weapons and Augments that require aiming are children to Aim and Augments that are passive or don’t require aiming are children of Body. When I generate an enemy I will grab 2 random weapons and child them to the Aim object and then generate X number of Augments that get attached depending on its Type.
With a setup like this what is a good way to structure AI so enemies have relatively unique behaviors depending on the Augments and Weapons that are attached to them? There is also the problem of enemy movement behaviors. I would like enemies to have a random set of Movement Behaviors that each fill a role. For example the Strafe and Backpedal movement behaviors are Defensive, while Dash and Blink can be Hybrid (Offensive and Defensive), and Pursue is Offensive. Whats a good way to manage these potential behaviors within each instance of enemy?
Sorry if I haven’t explained myself very well, I’m finding it difficult to put my issue into words.
Well, there are lots of approaches to managing behaviors like that. A state machine is simple and may be fine for your game. If you need something more complex, you could look into hierarchical state machines or behavior trees, both of which are essentially just a different way of looking at scripting logic- or rule-driven AIs.
As for the weapons and augments, you could consider having those actually provide the behaviors to the agent holding them. So, an agent with no weapons/augments would have only a couple of simple behaviors (e.g. fleeing), but with weapons in hand, it gets attack behaviors, etc.
I guess I see now the problem with a standard state machine, though; you certainly can’t hard-code transitions between all these states when the states available are so variable. So what you could do instead is something like this:
Give each behavior an “urgency” value that it updates on every frame according to (1) how applicable it is (e.g., swinging a melee weapon is not applicable if it’s out of range, but approaching is — and approaching is not applicable if you’re already in range), and (2) how long it’s been since the behavior has been used. Then just pick the behavior with the highest urgency, with a bit of hysteresis so you don’t switch behaviors too quickly.
This will cause your agents to sort of cycle between applicable behaviors, and react very flexibly both to what’s going on, and what options they have available based on their equipment and state.
It also lets you give each agent a lot of personality, by having multipliers for the various urgencies; e.g., some agents could be more aggressive by having a 1.2 multiplier on all aggressive behaviors; others could have a multiplier for escape behaviors, making them seem cowardly, etc.
Thank you for the awesome reply. It’s really got me thinking. I’ve made a couple “template” classes with the setup you described. There are a few things I may need some help with, like how I would implement a Hysteresis value into the behavior switching logic, and the best way to handle choosing a behavior based on the time since it was last used. This is the design I have so far :
base behavior class *
#region Using Statements
using UnityEngine;
using System.Collections;
#endregion
public enum BehaviorUrgency
{
VeryLow = 0,
Low = 25,
Medium = 50,
High = 75,
VeryHigh = 100
}
public enum BehaviorCombatType
{
Offensive,
Defensive,
Hybrid
}
public enum BehaviorCategory
{
CombatBehavior,
MovementBehavior
}
public abstract class BaseAIBehavior : MonoBehaviour
{
public const int _UrgencyChangeRate = 5;
public BehaviorCombatType Type;
public BehaviorCategory Category;
public int Urgency { get; protected set; }
public float UrgencyModifier { get; set; }
public float TimeSinceLastUse { get; protected set; }
protected bool executingBehavior;
protected Enemy owner;
protected Player player;
protected virtual void Start()
{
owner = GetComponent<Enemy>();
player = GameManager.Instance.Player;
}
protected virtual void Update()
{
Urgency = Mathf.Clamp(Urgency, (int)BehaviorUrgency.VeryLow, (int)BehaviorUrgency.VeryHigh);
if (!executingBehavior)
{
DetermineUrgency();
TimeSinceLastUse += Time.deltaTime;
}
else
{
ExecuteBehavior();
}
}
protected virtual void DetermineUrgency()
{
}
protected virtual void ExecuteBehavior()
{
}
public virtual void StartBehavior()
{
executingBehavior = true;
TimeSinceLastUse = 0;
}
public virtual void EndBehavior()
{
executingBehavior = false;
}
}
AI Behavior Controller *
#region Using Statements
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;
#endregion
public class AIController : MonoBehaviour
{
private float OffensiveModifier { get; set; }
private float DefensiveModifier { get; set; }
private float MovementModifier { get; set; }
private List<BaseAIBehavior> master_aiBehaviors;
private BaseAIBehavior currentBehavior;
private int currentIndex;
void Start()
{
master_aiBehaviors = GetComponentsInChildren<BaseAIBehavior>().ToList();
OffensiveModifier = 1;
DefensiveModifier = 1;
MovementModifier = 1;
}
void SetUrgencyModifiers()
{
List<BaseAIBehavior> offensiveBehaviors = master_aiBehaviors.FindAll((b1) => b1.Type == BehaviorCombatType.Offensive || b1.Type == BehaviorCombatType.Hybrid);
List<BaseAIBehavior> defensiveBehaviors = master_aiBehaviors.FindAll((b1) => b1.Type == BehaviorCombatType.Defensive || b1.Type == BehaviorCombatType.Hybrid);
List<BaseAIBehavior> movementBehaviors = master_aiBehaviors.FindAll((b1) => b1.Category == BehaviorCategory.MovementBehavior);
for (int i = 0; i < offensiveBehaviors.Count; i++)
{
offensiveBehaviors[i].UrgencyModifier = OffensiveModifier;
}
for (int i = 0; i < defensiveBehaviors.Count; i++)
{
defensiveBehaviors[i].UrgencyModifier = DefensiveModifier;
}
for (int i = 0; i < movementBehaviors.Count; i++)
{
movementBehaviors[i].UrgencyModifier = MovementModifier;
}
}
void Update()
{
int bestBehavior = FindHighestUrgencyBehavior();
if (bestBehavior != currentIndex)
{
BaseAIBehavior nextBehavior = master_aiBehaviors[bestBehavior];
if (nextBehavior.TimeSinceLastUse < 3)
{
int nextBestBehavior = FindNextBestBehavior(bestBehavior);
currentBehavior.EndBehavior();
currentIndex = nextBestBehavior;
currentBehavior = master_aiBehaviors[currentIndex];
currentBehavior.StartBehavior();
}
else
{
currentBehavior.EndBehavior();
currentIndex = bestBehavior;
currentBehavior = master_aiBehaviors[currentIndex];
currentBehavior.StartBehavior();
}
}
}
private int FindNextBestBehavior(int lastIndex)
{
int urgencyCieling = master_aiBehaviors[lastIndex].Urgency;
int lowestUrgency = 0;
int highestBehavior = -1;
for (int i = 0; i < master_aiBehaviors.Count; i++)
{
if (master_aiBehaviors[i].Urgency > lowestUrgency &&
master_aiBehaviors[i].Urgency < urgencyCieling)
{
highestBehavior = i;
lowestUrgency = master_aiBehaviors[i].Urgency;
}
}
return highestBehavior;
}
private int FindHighestUrgencyBehavior()
{
int lowestUrgency = 0;
int highestBehavior = -1;
for (int i = 0; i < master_aiBehaviors.Count; i++)
{
if (master_aiBehaviors[i].Urgency > lowestUrgency)
{
highestBehavior = i;
lowestUrgency = master_aiBehaviors[i].Urgency;
}
}
return highestBehavior;
}
}
Its pretty much a rough draft at this point. If you don’t mind could you please let me know if there is anything I’ve misunderstood about your post. I’m still trying to figure out a good way to separate the movement behaviors from the other combat behaviors so they can execute alongside each other. I guess I could manage a list separately from the master list that is only movement behaviors. This way when an offensive combat behavior is used I could look for the best offensive movement behavior to execute alongside it (and the same for defensive behaviors).
Edit : I forgot to mention that at this stage I will be adding the Behaviors and Modifier values to the Weapons and Augments. When they are equipped it will add to the AIController’s modifier fields. After all the Weapons/Augments are added to the enemy the AIController’s SetUrgencyModifiers method will be called.
I’m traveling for the next few days, and won’t have time to study this in any depth. From a cursory glance it looks to me like you’re on the right track.
Keeping separate lists for movement and offensive/defensive behaviors seems reasonable to me. Another way to tackle it is to flag what “resources” each behavior needs — where resources could be things like Feet and Weapon. Most movement behaviors use Feet, and attack behaviors use Weapon, but this could vary (a kick attack might tie up the Feet and Weapon both, for example). So then you just keep track of what resources are in use, and pick a behavior among the ones whose resources are still free. (Be sure to include a “null” behavior for each resource, so that the character doesn’t have to find something to do with his hands while walking around!)
This is good advice. Linking the low level behaviours to the procedurally generated content would simplify things.
You could still manage the high level behaviours with a state machine. States like ‘Attacking,’ ‘Fleeing’, ‘Seeking health’ ect could be generic enough to use a state machine.
Another avenue to investigate would be fuzzy logic. (Looking at your code you have started down this path already.)
I’d also suggest building some stats into the procedural content. An entity should be able to examine itself and go “I’m an invincible tank, nothing can hurt me, therefore I should charge straight in” Or “I can deal a lot of damage, but don’t have much health. I should keep my distance and hide behind tanks” Or “I can do no damage, and have no special abilities. I’m going to run from everything.”