Friday, March 28, 2014

Developing the AI Middleware's Interface

As I explained in my last post, I've developed a text-based sample game that I'll use to build the initial version of the AI system. Then I'll get it to work on my 2D engine, and then finally on my 3D engine for Auxnet:Battlegrounds. I'm at the stage now where I need to develop the AI systems public interface. The AI system is still very early in its development so things will probably change, but this is how I invision using the code in a real application.

// Startup
RootAI::Engine *ai_engine = RootAI::CreateAIEngine();

RootAI::WorldDesc worlddesc;
...
RootAI::World *world = ai_engine->CreateWorld(worlddesc);

RootAI::AgentDesc aidesc;
...
RootAI::Agent *agent = world->CreateAgent(aidesc);

...

// Shutdown
world->DestroyAgent(agent);
ai_engine->DestroyWorld(world);
RootAI::DestroyEngine(ai_engine);

I haven't decided if I want to force the developer to have to explicitly destroy the agents and the world or allow just destroying the engine and have that take care of cleaning up everything. My current thoughts are to let the engine take care of cleaning things up and returning a message if there are any AI objects that weren't properly destroyed before the engine.

 Here's the preliminary code for the AI agents and their tasks. The task should be basic enough so that the AI system can tell the game engine what in a way that will be easy to implement.

struct AIAgentDesc
{
    // the initial position and orientation of the entity
    Pose initial_pose;

    // set to true if the agent can move on more than one
    // axis at a time
    bool diagonal_movement;
};

enum AITaskType
{
    TaskIdle, // The AI should wait in an idle state
    TaskGoto, // The AI should go to a point (path will be unblocked)
    TaskAttack, // The an attack
    TaskTurnTo  // turn to face a direction
};

struct AITask
{
    AITaskType type;
    union
    {
        struct
        {
            int animation_num; // index of idle animation to run
        } IdleTaskOptions;

        struct
        {
            Vector location; // location to go to
        } GotoTaskOptions;

        struct
        {
            int attack_number; // index of attack to do
            Vector direction;  // direction to attack in
        } AttackTaskOptions;

        struct
        {
            Vector direction;  // direction to turn to
        } TurnToTaskOptions;
    };
};

class Agent
{
    public:
        virtual ~Agent() {}
        virtual void SetAgentPose(Pose pose) = 0;
        virtual void GetCurrentAITask(AITask &out) const = 0;
        virtual void SetTaskDone() = 0;
};

The task currently just use unions. The reason why I decided to do it this way instead of using inheritance was I want to be able to easily copy data back and forth between the AI system and the game engine without creating new objects task objects and I can process the task without casting.

Another design choice that I've made is completely separating the public interface from the implementation. That's why the agent class is an abstract base class. The same will be true of the world and engine classes.

Let me know if you have any questions or suggestions.