Utility System C++ Architecture

I had a couple considerations and constraints in making this system. I had just launched the Steam Greenlight campaign before I made the decision to redo the AI completely. The Cats just weren’t expressive enough for me and adding new things into the Behavior Tree was causing bugs in the existing branches. It’s probably a great system, but it is not well documented and you are just expected to know how it works to use it well. So, it’s probably great for the people who made it.

This system may be horrible to someone else, not optimized or any of a number of other things, but that doesn’t matter. Because, I am the only programmer who has to use it and if it allows me to add many behaviors to the Cats so they are playing interesting animations representing there’s moods and perform actions in the world on their own volition I’m happy with it.

It is a decision making system. It needs to have these features.

1. A straight forward way of adding new Actions
2. Needs to be able to choose from a large variety of animations based on moods
3. Needs to interact with items dynamically, if they are added to the world, or taken away so that it’s flexible
4. It needs to be quick to implement.

The core of the Utility System is a Manager that picks the best Action to perform. It basically goes through each Action and gets a score for it. Each Action has a list of considerations that contribute to it’s score. All the scores are normalized between 0 and 1.

One thing to think about is when an Action can be performed on multiple Actors in the level. Such as Chase Mouse. If there are 3 mice in the level, should the system decide which mouse to chase after it’s decided that’s what it’s going to do?

I decided that didn’t make since. So, run the entire Action List for each Actor in the Level that it can interact with. That way when it picks an Action such as Chase Mouse, it also has the Mouse to Chase along with it.

One thing that was a little tricky to get right at first was the Editor.

UtilityEditor

I wanted the simplest way possible to implement it. So I added a Class specifier to the UtilitySystem pointer that the AIController holds so that it could be specified directly in the Editor.

UPROPERTY(EditAnywhere, Instanced, Category = "AI")
UIAUtilitySystem* UtilitySystem;

Here is the complete header for the Utility System as it is now. I used Structs to give it a Data Only type feel. It is more procedural because of that. The UIAUtilitySystem is tightly coupled with the AI Controller to do the processing. This should be easily extendable for different controllers.

// Copyright 2013-2014 Cat Sniper LLC. All Rights Reserved.

#pragma once

#include "Object.h"
#include "IAUtilitySystem.generated.h"

class ACatAIController;

UENUM(BlueprintType)
enum class FunctionTypeEnum : uint8
{
FTE_Linear UMETA(DisplayName = "Linear"),
FTE_Quadratic UMETA(DisplayName = "Quadratic"),
FTE_Log UMETA(DisplayName = "Log")
};

UENUM(BlueprintType)
enum class FConsiderationEnum : uint8
{
CE_Distance UMETA(DisplayName = "Distance"),
CE_Los UMETA(DisplayName = "Line Of Sight"),
CE_AttractionTo UMETA(DisplayName = "Attraction To"),
CE_EnergyLevel UMETA(DisplayName = "Energy Level"),
CE_HungerLevel UMETA(DisplayName = "Hunger Level")

};

USTRUCT(Blueprintable)
struct FAxisParameter
{
GENERATED_USTRUCT_BODY()

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
FunctionTypeEnum CurveType;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
int32 M;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
int32 K;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
int32 B;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
int32 C;
};

/**
*
*/
USTRUCT(Blueprintable)
struct CATSNIPER_API FIAAxis
{
GENERATED_USTRUCT_BODY()

UPROPERTY(EditAnywhere, Category = "AI")
FAxisParameter Parameters;

UPROPERTY(EditAnywhere, Category = "AI")
FConsiderationEnum Consideration;
};

UENUM(BlueprintType)
enum class FActionTypeEnum : uint8
{
FAT_ChaseLaser UMETA(DisplayName = "Chase Laser"),
FAT_ChaseMouse UMETA(DisplayName = "Chase Mouse"),
FAT_MeowAt UMETA(DisplayName = "Meow At"),
FAT_EatFood UMETA(DisplayName = "Eat Food"),
FAT_PlayYarn UMETA(DisplayName = "Play Yarn"),
FAT_Sleep UMETA(DisplayName = "Sleep"),
FAT_WakeUp UMETA(DisplayName = "Wake up")
};

USTRUCT(Blueprintable)
struct FIAAction
{
GENERATED_USTRUCT_BODY()

int num;

FIAAction()
{
num = 0;
num = AxisList.Num();
if (num > 0)
{

int debug = num;
}
};

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
FActionTypeEnum ActionType;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
TArray AxisList;
};

USTRUCT(Blueprintable)
struct FIAActionScore
{
GENERATED_USTRUCT_BODY()

FActionTypeEnum ActionType;
float score;
};

/**
* Utility System
*/
UCLASS(Blueprintable, EditInlineNew)
class CATSNIPER_API UIAUtilitySystem : public UObject
{
GENERATED_BODY()

public:
void Init(ACatAIController* controller);

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
TArray ActionList;

UFUNCTION(BlueprintCallable, Category = "AI")
FIAActionScore EvaluateNextActor(AActor* actor);

private:
ACatAIController* MyController;

float GetAxisValue(FAxisParameter params, FConsiderationEnum consider, AActor* actor);

bool IsInitialized;
};

Because this is Enums and Structs everything can be Added in the Editor without having to have a custom constructor that builds up the lists from a description. The enum lists are not complete, this is just what I put in as a first pass as I’m making the system. In the next post I’ll show how the processing is done.

Share Button

Leave a Reply

Your email address will not be published. Required fields are marked *