Explorar el Código

Added AIController and basic AI system including AIAgent, AIStateMachine, AIState and AIMessage.
Added Game::getAIController for obtaining a handle to the game's main AIController.
Added Node::setAgent and Node::getAgent for setting and getting AIAgents on Nodes.

Steve Grenier hace 13 años
padre
commit
511948a783

+ 10 - 0
gameplay/gameplay.vcxproj

@@ -16,6 +16,11 @@
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="src\AbsoluteLayout.cpp" />
+    <ClCompile Include="src\AIAgent.cpp" />
+    <ClCompile Include="src\AIController.cpp" />
+    <ClCompile Include="src\AIMessage.cpp" />
+    <ClCompile Include="src\AIState.cpp" />
+    <ClCompile Include="src\AIStateMachine.cpp" />
     <ClCompile Include="src\Animation.cpp" />
     <ClCompile Include="src\AnimationClip.cpp" />
     <ClCompile Include="src\AnimationController.cpp" />
@@ -108,6 +113,11 @@
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\AbsoluteLayout.h" />
+    <ClInclude Include="src\AIAgent.h" />
+    <ClInclude Include="src\AIController.h" />
+    <ClInclude Include="src\AIMessage.h" />
+    <ClInclude Include="src\AIState.h" />
+    <ClInclude Include="src\AIStateMachine.h" />
     <ClInclude Include="src\Animation.h" />
     <ClInclude Include="src\AnimationClip.h" />
     <ClInclude Include="src\AnimationController.h" />

+ 30 - 0
gameplay/gameplay.vcxproj.filters

@@ -285,6 +285,21 @@
     <ClCompile Include="src\Gamepad.cpp">
       <Filter>src</Filter>
     </ClCompile>
+    <ClCompile Include="src\AIController.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\AIState.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\AIStateMachine.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\AIAgent.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="src\AIMessage.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\Animation.h">
@@ -566,6 +581,21 @@
     <ClInclude Include="src\Gamepad.h">
       <Filter>src</Filter>
     </ClInclude>
+    <ClInclude Include="src\AIAgent.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\AIController.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\AIState.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\AIStateMachine.h">
+      <Filter>src</Filter>
+    </ClInclude>
+    <ClInclude Include="src\AIMessage.h">
+      <Filter>src</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="src\gameplay-main-macosx.mm">

+ 90 - 0
gameplay/src/AIAgent.cpp

@@ -0,0 +1,90 @@
+#include "Base.h"
+#include "AIAgent.h"
+#include "Node.h"
+
+namespace gameplay
+{
+
+AIAgent::AIAgent()
+    : _stateMachine(NULL), _node(NULL), _enabled(true), _next(NULL)
+{
+    _stateMachine = new AIStateMachine(this);
+}
+
+AIAgent::~AIAgent()
+{
+    SAFE_DELETE(_stateMachine);
+}
+
+AIAgent* AIAgent::create()
+{
+    return new AIAgent();
+}
+
+const char* AIAgent::getId() const
+{
+    if (_node)
+        return _node->getId();
+
+    return "";
+}
+
+Node* AIAgent::getNode() const
+{
+    return _node;
+}
+
+AIStateMachine* AIAgent::getStateMachine()
+{
+    return _stateMachine;
+}
+
+bool AIAgent::isEnabled() const
+{
+    return (_node && _enabled);
+}
+
+void AIAgent::setEnabled(bool enabled)
+{
+    _enabled = enabled;
+}
+
+void AIAgent::setListener(Listener* listener)
+{
+    _listener = listener;
+}
+
+void AIAgent::update(float elapsedTime)
+{
+    _stateMachine->update(elapsedTime);
+}
+
+bool AIAgent::processMessage(AIMessage* message)
+{
+    // Handle built-in message types.
+    switch (message->_messageType)
+    {
+    case AIMessage::MESSAGE_TYPE_STATE_CHANGE:
+        {
+            // Change state message
+            const char* stateId = message->getString(0);
+            if (stateId)
+            {
+                AIState* state = _stateMachine->getState(stateId);
+                if (state)
+                    _stateMachine->setStateInternal(state);
+            }
+        }
+        break;
+    }
+
+    // Dispatch message to registered listener.
+    if (_listener && _listener->messageReceived(message))
+        return true;
+    
+    // TODO: Fire script listener
+    
+    return false;
+}
+
+}

+ 163 - 0
gameplay/src/AIAgent.h

@@ -0,0 +1,163 @@
+#ifndef AIAGENT_H_
+#define AIAGENT_H_
+
+#include "Ref.h"
+#include "AIStateMachine.h"
+#include "AIMessage.h"
+
+namespace gameplay
+{
+
+class Node;
+
+/**
+ * Defines an AI agent that can be added to nodes in a scene.
+ *
+ * Agents represent a unit of intelligence in a game and can be used
+ * to program logic for a character or object in a game, using constrcuts
+ * such as state machines. By default, an AIAgent has an empty state 
+ * machine.
+ */
+class AIAgent : public Ref
+{
+    friend class Node;
+    friend class AIController;
+
+public:
+
+    /**
+     * Interface for listening to AIAgent events.
+     */
+    class Listener
+    {
+    public:
+
+        /**
+         * Virtual destructor.
+         */
+        virtual ~Listener() { };
+
+        /**
+         * Called when a new message is sent to the AIAgent.
+         *
+         * Both global/broadcast messages and messages sent explicitly to the
+         * AIAgent are sent through this method. Returning true from this method
+         * will mark the message as handled and it will dispose of the message
+         * and prevent any other possible recipients from receiving the message.
+         * Alternatively, returning false allows the message to continue being
+         * routed though the AI system.
+         *
+         * @param message The message received.
+         *
+         * @return true to mark the message as handled, false otherwise.
+         */
+        virtual bool messageReceived(AIMessage* message) = 0;
+    };
+
+    /**
+     * Creates a new AIAgent.
+     *
+     * @return A new AIAgent.
+     */
+    static AIAgent* create();
+
+    /**
+     * Returns the identifier for the AIAgent.
+     *
+     * @return The identifier for the agent.
+     */
+    const char* getId() const;
+
+    /**
+     * Returns the Node this AIAgent is assigned to.
+     *
+     * @return The Node this agent is assigned to.
+     */
+    Node* getNode() const;
+
+    /**
+     * Returns the state machine for the AIAgent.
+     *
+     * @return The agent's state machine.
+     */
+    AIStateMachine* getStateMachine();
+
+    /**
+     * Determines if this AIAgent is currently enabled.
+     *
+     * Agents are always disabled until they have been associated
+     * with a valid Node though Node::setAgent(AIAgent*). In addition,
+     * an AIAgent can be explicitly enabled or disabled using the
+     * setEnabled(bool) method.
+     *
+     * @return true if the agent is enabled, false otherwise.
+     */
+    bool isEnabled() const;
+
+    /**
+     * Sets whether this AIAgent is enabled.
+     *
+     * By default, AIAgents are enabled and they can receive messages and state
+     * changes. When disabled, AIAgents stop receiving messages and their state
+     * machines are halted until they are re-enabled.
+     *
+     * @param enabled true if the AIAgent should be enabled, false otherwise.
+     */
+    void setEnabled(bool enabled);
+
+    /**
+     * Sets an event listener for this AIAgent.
+     *
+     * @param listener The new AIAgent listener, or NULL to remove any existing listener.
+     */
+    void setListener(Listener* listener);
+
+private:
+
+    /**
+     * Constructor.
+     */
+    AIAgent();
+
+    /**
+     * Destructor.
+     *
+     * Hidden, use SAFE_RELEASE instead.
+     */
+    virtual ~AIAgent();
+
+    /**
+     * Hidden copy constructor.
+     */
+    AIAgent(const AIAgent&);
+
+    /**
+     * Hidden copy assignment operator.
+     */
+    AIAgent& operator=(const AIAgent&);
+
+    /**
+     * Called by the AIController to process a message for the AIAgent.
+     *
+     * @param message The message to be processed.
+     *
+     * @return true if the message was handled, false otherwise.
+     */
+    bool processMessage(AIMessage* message);
+
+    /**
+     * Called once per frame by the AIController to update the agent.
+     */
+    void update(float elapsedTime);
+
+    AIStateMachine* _stateMachine;
+    Node* _node;
+    bool _enabled;
+    Listener* _listener;
+    AIAgent* _next;
+
+};
+
+}
+
+#endif

+ 199 - 0
gameplay/src/AIController.cpp

@@ -0,0 +1,199 @@
+#include "Base.h"
+#include "AIController.h"
+#include "Game.h"
+
+// TODO:
+//
+// 1) Is std::string OK for message sender/receiver?
+// 2) Is AIMessage ok and is "dobule" ok for message parameters?
+// 3) Design of creating and deleting message (AIController deletes them)??
+// 4) Is setListener(Listener*) OK for AIState and AIStateMachine, or do we need addListener(Listener*)???
+
+// TODO: Add a way to snif messages on AIController??
+
+// TODO: only dispatch messages to agents that are in this list AND enabled. If not in the list, discard the message (and log) and if they are in the list and DISABLED, just hold on to the message until they are re-enabled.
+
+namespace gameplay
+{
+
+AIController::AIController()
+    : _paused(false), _firstMessage(NULL), _firstAgent(NULL)
+{
+}
+
+AIController::~AIController()
+{
+}
+
+void AIController::initialize()
+{
+}
+
+void AIController::finalize()
+{
+    // Remove all agents
+    AIAgent* agent = _firstAgent;
+    while (agent)
+    {
+        AIAgent* temp = agent;
+        agent = agent->_next;
+        SAFE_RELEASE(temp);
+    }
+    _firstAgent = NULL;
+
+    // Remove all messages
+    AIMessage* message = _firstMessage;
+    while (message)
+    {
+        AIMessage* temp = message;
+        message = message->_next;
+        SAFE_DELETE(temp);
+    }
+    _firstMessage = NULL;
+}
+
+void AIController::pause()
+{
+    _paused = true;
+}
+
+void AIController::resume()
+{
+    _paused = false;
+}
+
+void AIController::sendMessage(AIMessage* message, float delay)
+{
+    if (delay <= 0)
+    {
+        // Send instantly
+        if (message->getReceiver() == NULL || strlen(message->getReceiver()) == 0)
+        {
+            // Broadcast message to all agents
+            AIAgent* agent = _firstAgent;
+            while (agent)
+            {
+                if (agent->processMessage(message))
+                    break; // message consumed by this agent - stop bubbling
+                agent = agent->_next;
+            }
+        }
+        else
+        {
+            // Single recipient
+            AIAgent* agent = findAgent(message->getReceiver());
+            if (agent)
+            {
+                agent->processMessage(message);
+            }
+            else
+            {
+                GP_WARN("Failed to locate AIAgent for message recipient: %s", message->getReceiver());
+            }
+        }
+
+        // Delete the message, since it is finished being processed
+        SAFE_DELETE(message);
+    }
+    else
+    {
+        // Queue for later delivery
+        if (_firstMessage)
+            message->_next = _firstMessage;
+        _firstMessage = message;
+    }
+}
+
+void AIController::update(float elapsedTime)
+{
+    if (_paused)
+        return;
+
+    static Game* game = Game::getInstance();
+
+    // Send all pending messages that have expired
+    AIMessage* prevMsg = NULL;
+    AIMessage* msg = _firstMessage;
+    while (msg)
+    {
+        // If the message delivery time has expired, send it (this also deletes it)
+        if (msg->getDeliveryTime() >= game->getGameTime())
+        {
+            // Link the message out of our list
+            if (prevMsg)
+                prevMsg->_next = msg->_next;
+
+            AIMessage* temp = msg;
+            msg = msg->_next;
+            temp->_next = NULL;
+            sendMessage(temp);
+        }
+        else
+        {
+            prevMsg = msg;
+            msg = msg->_next;
+        }
+    }
+
+    // Update all enabled agents
+    AIAgent* agent = _firstAgent;
+    while (agent)
+    {
+        if (agent->isEnabled())
+            agent->update(elapsedTime);
+
+        agent = agent->_next;
+    }
+}
+
+void AIController::addAgent(AIAgent* agent)
+{
+    agent->addRef();
+
+    if (_firstAgent)
+        agent->_next = _firstAgent;
+
+    _firstAgent = agent;
+}
+
+void AIController::removeAgent(AIAgent* agent)
+{
+    // Search our linked list of agents and link this agent out.
+    AIAgent* prevAgent = NULL;
+    AIAgent* itr = _firstAgent;
+    while (itr)
+    {
+        if (itr == agent)
+        {
+            if (prevAgent)
+                prevAgent->_next = agent->_next;
+            else
+                _firstAgent = agent->_next;
+
+            agent->_next = NULL;
+            agent->release();
+            break;
+        }
+
+        prevAgent = itr;
+        itr = itr->_next;
+    }
+}
+
+AIAgent* AIController::findAgent(const char* id) const
+{
+    GP_ASSERT(id);
+
+    AIAgent* agent = _firstAgent;
+    while (agent)
+    {
+        if (strcmp(id, agent->getId()) == 0)
+            return agent;
+
+        agent = agent->_next;
+    }
+
+    return NULL;
+}
+
+}

+ 107 - 0
gameplay/src/AIController.h

@@ -0,0 +1,107 @@
+#ifndef AICONTROLLER_H_
+#define AICONTROLLER_H_
+
+#include "AIAgent.h"
+#include "AIMessage.h"
+
+namespace gameplay
+{
+
+/**
+ * The AIController facilitates state machine execution and message passing
+ * between AI objects in the game. This class is generally not interfaced
+ * with directly.
+ */
+class AIController
+{
+    friend class Game;
+    friend class Node;
+
+public:
+
+    /**
+     * Routes the secified message to its intended recipient(s).
+     *
+     * Messages are arbitrary packets of data that are sent either to a single or to multiple
+     * recipients in the game.
+     *
+     * Once the specified message has been delivered, it is automatically destroyed by the AIController.
+     * For this reason, AIMessage pointers should NOT be held or explicity destroyed by any code after
+     * they are sent through the AIController.
+     *
+     * @param message The message to send.
+     * @param delay The delay (in milliseconds) to wait before sending the message.
+     */
+    void sendMessage(AIMessage* message, float delay = 0);
+
+    /**
+     * Searches for an AIAgent that is registered with the AIController with the specified ID.
+     *
+     * @param id ID of the agent to find.
+     *
+     * @return The first agent matching the specified ID, or NULL if no matching agent could be found.
+     */
+    AIAgent* findAgent(const char* id) const;
+
+private:
+
+    /**
+     * Constructor.
+     */
+    AIController();
+
+    /**
+     * Destructor.
+     */
+    ~AIController();
+
+    /**
+     * Hidden copy constructor.
+     */
+    AIController(const AIController&);
+
+    /**
+     * Hidden copy assignment operator.
+     */
+    AIController& operator=(const AIController&);
+
+    /**
+     * Called during startup to initialize the AIController.
+     */
+    void initialize();
+
+    /**
+     * Called during shutdown to finalize the AIController.
+     */
+    void finalize();
+
+    /**
+     * Pauses the AIController.
+     */
+    void pause();
+
+    /**
+     * Resumes the AIController.
+     */
+    void resume();
+
+    /**
+     * Called each frame to update the AIController.
+     *
+     * @param elapsedTime The elapsed time, in milliseconds.
+     */
+    void update(float elapsedTime);
+
+    void addAgent(AIAgent* agent);
+
+    void removeAgent(AIAgent* agent);
+
+    bool _paused;
+    AIMessage* _firstMessage;
+    AIAgent* _firstAgent;
+
+};
+
+}
+
+#endif

+ 203 - 0
gameplay/src/AIMessage.cpp

@@ -0,0 +1,203 @@
+#include "Base.h"
+#include "AIMessage.h"
+
+namespace gameplay
+{
+
+AIMessage::AIMessage()
+    : _id(0), _deliveryTime(0), _parameters(NULL), _parameterCount(0), _messageType(MESSAGE_TYPE_CUSTOM), _next(NULL)
+{
+}
+
+AIMessage::~AIMessage()
+{
+    SAFE_DELETE_ARRAY(_parameters);
+}
+
+AIMessage* AIMessage::create(unsigned int id, const char* sender, const char* receiver, unsigned int parameterCount)
+{
+    AIMessage* message = new AIMessage();
+    message->_id = id;
+    message->_sender = sender;
+    message->_receiver = receiver;
+    message->_parameterCount = parameterCount;
+    if (parameterCount > 0)
+        message->_parameters = new AIMessage::Parameter[parameterCount];
+    return message;
+}
+
+unsigned int AIMessage::getId() const
+{
+    return _id;
+}
+
+const char* AIMessage::getSender() const
+{
+    return _sender.c_str();
+}
+
+const char* AIMessage::getReceiver() const
+{
+    return _receiver.c_str();
+}
+
+double AIMessage::getDeliveryTime() const
+{
+    return _deliveryTime;
+}
+
+int AIMessage::getInt(unsigned int index) const
+{
+    GP_ASSERT(index < _parameterCount);
+    GP_ASSERT(_parameters[index].type == AIMessage::INTEGER);
+
+    return _parameters[index].intValue;
+}
+
+void AIMessage::setInt(unsigned int index, int value)
+{
+    GP_ASSERT(index < _parameterCount);
+
+    clearParameter(index);
+
+    _parameters[index].intValue = value;
+    _parameters[index].type = AIMessage::INTEGER;
+}
+
+long AIMessage::getLong(unsigned int index) const
+{
+    GP_ASSERT(index < _parameterCount);
+    GP_ASSERT(_parameters[index].type == AIMessage::LONG);
+
+    return _parameters[index].longValue;
+}
+
+void AIMessage::setLong(unsigned int index, long value)
+{
+    GP_ASSERT(index < _parameterCount);
+
+    clearParameter(index);
+
+    _parameters[index].longValue = value;
+    _parameters[index].type = AIMessage::LONG;
+}
+
+float AIMessage::getFloat(unsigned int index) const
+{
+    GP_ASSERT(index < _parameterCount);
+    GP_ASSERT(_parameters[index].type == AIMessage::FLOAT);
+
+    return _parameters[index].floatValue;
+}
+
+void AIMessage::setFloat(unsigned int index, float value)
+{
+    GP_ASSERT(index < _parameterCount);
+
+    clearParameter(index);
+
+    _parameters[index].floatValue = value;
+    _parameters[index].type = AIMessage::FLOAT;
+}
+
+double AIMessage::getDouble(unsigned int index) const
+{
+    GP_ASSERT(index < _parameterCount);
+    GP_ASSERT(_parameters[index].type == AIMessage::DOUBLE);
+
+    return _parameters[index].doubleValue;
+}
+
+void AIMessage::setDouble(unsigned int index, double value)
+{
+    GP_ASSERT(index < _parameterCount);
+
+    clearParameter(index);
+
+    _parameters[index].doubleValue = value;
+    _parameters[index].type = AIMessage::DOUBLE;
+}
+
+bool AIMessage::getBoolean(unsigned int index) const
+{
+    GP_ASSERT(index < _parameterCount);
+    GP_ASSERT(_parameters[index].type == AIMessage::BOOLEAN);
+
+    return _parameters[index].boolValue;
+}
+
+void AIMessage::setBoolean(unsigned int index, bool value)
+{
+    GP_ASSERT(index < _parameterCount);
+
+    clearParameter(index);
+
+    _parameters[index].boolValue = value;
+    _parameters[index].type = AIMessage::BOOLEAN;
+}
+
+const char* AIMessage::getString(unsigned int index) const
+{
+    GP_ASSERT(index < _parameterCount);
+    GP_ASSERT(_parameters[index].type == AIMessage::STRING);
+
+    return _parameters[index].stringValue;
+}
+
+void AIMessage::setString(unsigned int index, const char* value)
+{
+    GP_ASSERT(index < _parameterCount);
+    GP_ASSERT(value);
+
+    clearParameter(index);
+
+    // Copy the string into our parameter
+    size_t len = strlen(value);
+    char* buffer = new char[len + 1];
+    strcpy(buffer, value);
+    _parameters[index].stringValue = buffer;
+    _parameters[index].type = AIMessage::STRING;
+}
+
+unsigned int AIMessage::getParameterCount() const
+{
+    return _parameterCount;
+}
+
+AIMessage::ParameterType AIMessage::getParameterType(unsigned int index) const
+{
+    GP_ASSERT(index < _parameterCount);
+
+    return _parameters[index].type;
+}
+
+void AIMessage::clearParameter(unsigned int index)
+{
+    GP_ASSERT(index < _parameterCount);
+
+    _parameters[index].clear();
+}
+
+AIMessage::Parameter::Parameter()
+    : type(UNDEFINED)
+{
+}
+
+AIMessage::Parameter::~Parameter()
+{
+    clear();
+}
+
+void AIMessage::Parameter::clear()
+{
+    switch (type)
+    {
+    case AIMessage::STRING:
+        SAFE_DELETE_ARRAY(stringValue);
+        break;
+    }
+
+    type = AIMessage::UNDEFINED;
+}
+
+}

+ 272 - 0
gameplay/src/AIMessage.h

@@ -0,0 +1,272 @@
+#ifndef AIMESSAGE_H_
+#define AIMESSAGE_H_
+
+namespace gameplay
+{
+
+/**
+ * Defines a simple, flexible message structure used for passing messages through
+ * the AI system.
+ *
+ * Messages can store an arbitrary number of parameters. For the sake of simplicity,
+ * each parameter is stored as type double, which is flexible enough to store most
+ * data that needs to be passed.
+ */
+class AIMessage
+{
+    friend class AIAgent;
+    friend class AIController;
+    friend class AIStateMachine;
+
+public:
+
+    /**
+     * Enumeration of supported AIMessage parameter types.
+     */
+    enum ParameterType
+    {
+        UNDEFINED,
+        INTEGER,
+        LONG,
+        FLOAT,
+        DOUBLE,
+        BOOLEAN,
+        STRING
+    };
+
+    /** 
+     * Destructor.
+     */
+    ~AIMessage();
+
+    /**
+     * Creates a new message.
+     *
+     * Once a message is constructed and populated with data, it can be routed to its
+     * intended recipient(s) by calling AIController::sendMessage(AIMessage*). The
+     * AIController will then handle scheduling and delivery of the message and it will
+     * also destroy the message after it has been successfully delivered. For this reason,
+     * once a message has been sent through AIController, it is unsafe to use or destroy
+     * the message pointer.
+     *
+     * @param id The message ID.
+     * @param sender AIAgent sender ID (can be empty or null for an annoymous message).
+     * @param receiver AIAgent receiver ID (can be empty or null for a broadcast message).
+     * @param parameterCount Number of parameters for this message.
+     *
+     * @return A new AIMessage.
+     */
+    static AIMessage* create(unsigned int id, const char* sender, const char* receiver, unsigned int paramterCount);
+
+    /**
+     * Returns the message ID.
+     *
+     * @return The message ID.
+     */
+    unsigned int getId() const;
+
+    /**
+     * Returns the sender for the message.
+     *
+     * @return The message sender ID.
+     */
+    const char* getSender() const;
+
+    /**
+     * Returns the receiver for the message.
+     *
+     * @return The message receiver.
+     */
+    const char* getReceiver() const;
+
+    /**
+     * Returns the value of the specified parameter as an integer.
+     *
+     * @param index Index of the paramter to get.
+     *
+     * @return The parameter value.
+     */
+    int getInt(unsigned int index) const;
+
+    /**
+     * Sets an integer parameter.
+     *
+     * @param index Index of the parameter to set.
+     * @param value The parameter value.
+     */
+    void setInt(unsigned int index, int value);
+
+    /**
+     * Returns the value of the specified parameter as a long integer.
+     *
+     * @param index Index of the paramter to get.
+     *
+     * @return The parameter value.
+     */
+    long getLong(unsigned int index) const;
+
+    /**
+     * Sets a long integer parameter.
+     *
+     * @param index Index of the parameter to set.
+     * @param value The parameter value.
+     */
+    void setLong(unsigned int index, long value);
+
+    /**
+     * Returns the value of the specified parameter as a float.
+     *
+     * @param index Index of the paramter to get.
+     *
+     * @return The parameter value.
+     */
+    float getFloat(unsigned int index) const;
+
+    /**
+     * Sets a float parameter.
+     *
+     * @param index Index of the parameter to set.
+     * @param value The parameter value.
+     */
+    void setFloat(unsigned int index, float value);
+
+    /**
+     * Returns the value of the specified parameter as a double.
+     *
+     * @param index Index of the paramter to get.
+     *
+     * @return The parameter value.
+     */
+    double getDouble(unsigned int index) const;
+
+    /**
+     * Sets a double parameter.
+     *
+     * @param index Index of the parameter to set.
+     * @param value The parameter value.
+     */
+    void setDouble(unsigned int index, double value);
+
+    /**
+     * Returns the value of the specified parameter as a boolean.
+     *
+     * @param index Index of the paramter to get.
+     *
+     * @return The parameter value.
+     */
+    bool getBoolean(unsigned int index) const;
+
+    /**
+     * Sets a long parameter.
+     *
+     * @param index Index of the parameter to set.
+     * @param value The parameter value.
+     */
+    void setBoolean(unsigned int index, bool value);
+
+    /**
+     * Returns the value of the specified parameter as a string.
+     *
+     * @param index Index of the paramter to get.
+     *
+     * @return The parameter value.
+     */
+    const char* getString(unsigned int index) const;
+
+    /**
+     * Sets a string parameter.
+     *
+     * @param index Index of the parameter to set.
+     * @param value The parameter value.
+     */
+    void setString(unsigned int index, const char* value);
+
+    /**
+     * Returns the number of parameters for this message.
+     * 
+     * @return The number of message parameters.
+     */
+    unsigned int getParameterCount() const;
+
+    /** 
+     * Returns the type of the specified parameter.
+     *
+     * @param index Index of the parameter to query.
+     *
+     * @return The parameter type.
+     */
+    ParameterType getParameterType(unsigned int index) const;
+
+private:
+
+    /**
+     * Internal message type enumeration.
+     */
+    enum MessageType
+    {
+        MESSAGE_TYPE_STATE_CHANGE,
+        MESSAGE_TYPE_CUSTOM
+    };
+
+    /**
+     * Defines a flexible message parameter.
+     */
+    struct Parameter
+    {
+        Parameter();
+
+        ~Parameter();
+
+        void clear();
+
+        union
+        {
+            int intValue;
+            long longValue;
+            float floatValue;
+            double doubleValue;
+            bool boolValue;
+            char* stringValue;
+        };
+
+        AIMessage::ParameterType type;
+    };
+
+    /**
+     * Constructor.
+     */
+    AIMessage();
+
+    /**
+     * Hidden copy construcotr.
+     */
+    AIMessage(const AIMessage&);
+
+    /**
+     * Hidden copy assignment operator.
+     */
+    AIMessage& operator=(const AIMessage&);
+
+    /**
+     * Returns the delivery time for the message.
+     *
+     * @return The delivery time for the message, or zero if the message is not currently scheduled to be delivered.
+     */
+    double getDeliveryTime() const;
+
+    void clearParameter(unsigned int index);
+
+    unsigned int _id;
+    std::string _sender;
+    std::string _receiver;
+    double _deliveryTime;
+    Parameter* _parameters;
+    unsigned int _parameterCount;
+    MessageType _messageType;
+    AIMessage* _next;
+
+};
+
+}
+
+#endif

+ 74 - 0
gameplay/src/AIState.cpp

@@ -0,0 +1,74 @@
+#include "Base.h"
+#include "AIState.h"
+#include "AIStateMachine.h"
+
+namespace gameplay
+{
+
+AIState AIState::_empty("");
+
+AIState::AIState(const char* id)
+    : _id(id)
+{
+}
+
+AIState::~AIState()
+{
+}
+
+AIState* AIState::create(const char* id)
+{
+    return new AIState(id);
+}
+
+const char* AIState::getId() const
+{
+    return _id.c_str();
+}
+
+void AIState::setListener(Listener* listener)
+{
+    _listener = listener;
+}
+
+void AIState::enter(AIStateMachine* stateMachine)
+{
+    if (_listener)
+        _listener->stateEnter(stateMachine->getAgent(), this);
+
+    // TODO: Fire script event
+}
+
+void AIState::exit(AIStateMachine* stateMachine)
+{
+    if (_listener)
+        _listener->stateExit(stateMachine->getAgent(), this);
+
+    // TODO: Fire script event
+}
+
+void AIState::update(AIStateMachine* stateMachine, float elapsedTime)
+{
+    if (_listener)
+        _listener->stateUpdate(stateMachine->getAgent(), this, elapsedTime);
+
+    // TODO: Fire script event
+}
+
+AIState::Listener::~Listener()
+{
+}
+
+void AIState::Listener::stateEnter(AIAgent* agent, AIState* state)
+{
+}
+
+void AIState::Listener::stateExit(AIAgent* agent, AIState* state)
+{
+}
+
+void AIState::Listener::stateUpdate(AIAgent* agent, AIState* state, float elapsedTime)
+{
+}
+
+}

+ 137 - 0
gameplay/src/AIState.h

@@ -0,0 +1,137 @@
+#ifndef AISTATE_H_
+#define AISTATE_H_
+
+#include "Ref.h"
+
+namespace gameplay
+{
+
+class AIAgent;
+class AIStateMachine;
+
+/**
+ * Represents a single state in an AIStateMachine.
+ *
+ * An AIState encapsulates a state and unit of work within an AI
+ * state machine. Events can be programmed or scripted when the
+ * state is entered, exited and each frame/tick in its update event.
+ *
+ * 
+ */
+class AIState : public Ref
+{
+    friend class AIStateMachine;
+
+public:
+
+    /**
+     * Interface for listening to AIState events.
+     */
+    class Listener
+    {
+    public:
+
+        /**
+         * Virtual destructor.
+         */
+        virtual ~Listener();
+
+        /** 
+         * Called when a state is entered.
+         *
+         * @param agent The AIAgent this state event is for.
+         * @param state The state that was entered.
+         */
+        virtual void stateEnter(AIAgent* agent, AIState* state);
+
+        /**
+         * Called when a state is exited.
+         *
+         * @param agent The AIAgent this state event is for.
+         * @param state The state that was exited.
+         */
+        virtual void stateExit(AIAgent* agent, AIState* state);
+
+        /**
+         * Called once per frame when for a state when it is active.
+         *
+         * This method is normally where the logic for a state is implemented.
+         *
+         * @param agent The AIAgent this state event is for.
+         * @param state The active AIState.
+         * @param elapsedTime The elapsed time, in milliseconds.
+         */
+        virtual void stateUpdate(AIAgent* agent, AIState* state, float elapsedTime);
+    };
+
+    /**
+     * Creates a new AISTate.
+     *
+     * @param id The ID of the new AIState.
+     *
+     * @return The new AIState.
+     */
+    static AIState* create(const char* id);
+
+    /**
+     * Returns the ID of this state.
+     *
+     * @return The state ID.
+     */
+    const char* getId() const;
+
+    /**
+     * Sets a listener to dispatch state events to.
+     * 
+     * @param listener Listener to dispatch state events to, or NULL to disable event dispatching.
+     */
+    void setListener(Listener* listener);
+
+private:
+
+    /**
+     * Constructs a new AIState.
+     */
+    AIState(const char* id);
+
+    /**
+     * Destructor.
+     */
+    ~AIState();
+
+    /**
+     * Hidden copy constructor.
+     */
+    AIState(const AIState&);
+
+    /**
+     * Hidden copy assignment operator.
+     */
+    AIState& operator=(const AIState&);
+
+    /**
+     * Called by AIStateMachine when this state is being entered.
+     */
+    void enter(AIStateMachine* stateMachine);
+
+    /**
+     * Called by AIStateMachine when this state is being exited.
+     */
+    void exit(AIStateMachine* stateMachine);
+
+    /**
+     * Called by AIStateMachine once per frame to update this state when it is active.
+     */
+    void update(AIStateMachine* stateMachine, float elapsedTime);
+
+    std::string _id;
+    Listener* _listener;
+
+    // The default/empty state.
+    static AIState _empty;
+
+};
+
+}
+
+#endif

+ 127 - 0
gameplay/src/AIStateMachine.cpp

@@ -0,0 +1,127 @@
+#include "Base.h"
+#include "AIStateMachine.h"
+#include "AIAgent.h"
+#include "AIMessage.h"
+#include "Game.h"
+
+namespace gameplay
+{
+
+AIStateMachine::AIStateMachine(AIAgent* agent)
+    : _agent(agent), _currentState(&AIState::_empty)
+{
+    GP_ASSERT(agent);
+}
+
+AIStateMachine::~AIStateMachine()
+{
+    // Release all states
+    for (std::list<AIState*>::iterator itr = _states.begin(); itr != _states.end(); ++itr)
+    {
+        (*itr)->release();
+    }
+}
+
+AIAgent* AIStateMachine::getAgent() const
+{
+    return _agent;
+}
+
+AIState* AIStateMachine::addState(const char* id)
+{
+    AIState* state = AIState::create(id);
+    _states.push_back(state);
+    return state;
+}
+
+void AIStateMachine::addState(AIState* state)
+{
+    state->addRef();
+    _states.push_back(state);
+}
+
+void AIStateMachine::removeState(AIState* state)
+{
+    std::list<AIState*>::iterator itr = std::find(_states.begin(), _states.end(), state);
+    if (itr != _states.end())
+    {
+        _states.erase(itr);
+        state->release();
+    }
+}
+
+AIState* AIStateMachine::getState(const char* id) const
+{
+    GP_ASSERT(id);
+
+    AIState* state;
+    for (std::list<AIState*>::const_iterator itr = _states.begin(); itr != _states.end(); ++itr)
+    {
+        state = (*itr);
+
+        if (strcmp(id, state->getId()) == 0)
+            return state;
+    }
+
+    return NULL;
+}
+
+AIState* AIStateMachine::getActiveState() const
+{
+    return _currentState;
+}
+
+bool AIStateMachine::hasState(AIState* state) const
+{
+    GP_ASSERT(state);
+
+    return (std::find(_states.begin(), _states.end(), state) != _states.end());
+}
+
+AIState* AIStateMachine::setState(const char* id)
+{
+    AIState* state = getState(id);
+    if (state)
+        sendChangeStateMessage(state);
+    return state;
+}
+
+bool AIStateMachine::setState(AIState* state)
+{
+    if (hasState(state))
+    {
+        sendChangeStateMessage(state);
+        return true;
+    }
+
+    return false;
+}
+
+void AIStateMachine::sendChangeStateMessage(AIState* newState)
+{
+    AIMessage* message = AIMessage::create(0, _agent->getId(), _agent->getId(), 1);
+    message->_messageType = AIMessage::MESSAGE_TYPE_STATE_CHANGE;
+    message->setString(0, newState->getId());
+    Game::getInstance()->getAIController()->sendMessage(message);
+}
+
+void AIStateMachine::setStateInternal(AIState* state)
+{
+    GP_ASSERT(hasState(state));
+
+    // Fire the exit event for the current state
+    _currentState->exit(this);
+
+    // Set the new state
+    _currentState = state;
+
+    // Fire the enter event for the new state
+    _currentState->enter(this);
+}
+
+void AIStateMachine::update(float elapsedTime)
+{
+    _currentState->update(this, elapsedTime);
+}
+
+}

+ 160 - 0
gameplay/src/AIStateMachine.h

@@ -0,0 +1,160 @@
+#ifndef AISTATEMACHINE_H_
+#define AISTATEMACHINE_H_
+
+#include "AIState.h"
+
+namespace gameplay
+{
+
+class AIAgent;
+
+/**
+ * Defines a simple AI state machine that can be used to program logic
+ * for an AIAgent in a game.
+ *
+ * A state machine uses AIState objects to represent different states
+ * of an object in the game. The state machine provides access to the
+ * current state of an AI agent and it controls state changes as well.
+ * When a new state is set, the stateExited event will be called for the
+ * previous state, the stateEntered event will be called for the new state
+ * and then the stateUpdate event will begin to be called each frame
+ * while the new state is active.
+ *
+ * Communication of state changes is facilated through the AIMessage class.
+ * Messages are dispatched by the AIController and can be used for purposes
+ * other than state changes as well. Messages may be sent to the state
+ * machines of any other agents in a game and can contain any arbitrary
+ * information. This mechanism provides a simple, flexible and easily
+ * debuggable method for communicating between AI objects in a game.
+ */
+class AIStateMachine
+{
+    friend class AIAgent;
+
+public:
+
+    /**
+     * Returns the AIAgent that owns this state machine.
+     *
+     * @return The AIAgent that owns this state machine.
+     */
+    AIAgent* getAgent() const;
+
+    /**
+     * Creates and adds a new state to the state machine.
+     *
+     * @param id ID of the new state.
+     *
+     * @return The newly created and added state.
+     */
+    AIState* addState(const char* id);
+
+    /**
+     * Adds a state to the state machine.
+     *
+     * The specified state may be shared by other state machines.
+     * Its reference count is increased while it is held by
+     * this state machine.
+     *
+     * @param state The state to add.
+     */
+    void addState(AIState* state);
+
+    /**
+     * Removes a state from the state machine.
+     *
+     * @param state The state to remove.
+     */
+    void removeState(AIState* state);
+
+    /**
+     * Returns a state registered with this state machine.
+     *
+     * @param id The ID of the state to return.
+     *
+     * @return The state with the given ID, or NULL if no such state exists.
+     */
+    AIState* getState(const char* id) const;
+
+    /**
+     * Returns the active state for this state machine.
+     *
+     * @return The active state for this state machine.
+     */
+    AIState* getActiveState() const;
+
+    /**
+     * Changes the state of this state machine to the given state.
+     *
+     * If no state with the given ID exists within this state machine,
+     * this method does nothing.
+     *
+     * @param id The ID of the new state.
+     *
+     * @return The new state, or NULL if no matching state could be found.
+     */
+    AIState* setState(const char* id);
+
+    /**
+     * Changes the state of this state machine to the given state.
+     *
+     * If the given state is not registered with this state machine,
+     * this method does nothing.
+     *
+     * @param state The new state.
+     *
+     * @return true if the state is successfully changed, false otherwise.
+     */
+    bool setState(AIState* state);
+
+private:
+
+    /**
+     * Constructor.
+     */
+    AIStateMachine(AIAgent* agent);
+
+    /**
+     * Destructor.
+     */
+    ~AIStateMachine();
+
+    /**
+     * Hidden copy constructor.
+     */
+    AIStateMachine(const AIStateMachine&);
+
+    /**
+     * Hidden copy assignment operator.
+     */
+    AIStateMachine& operator=(const AIStateMachine&);
+
+    /**
+     * Sends a message to change the state of this state machine.
+     */
+    void sendChangeStateMessage(AIState* newState);
+
+    /**
+     * Changes the active state of the state machine.
+     */
+    void setStateInternal(AIState* state);
+
+    /**
+     * Determines if the specified state exists within this state machine.
+     */
+    bool hasState(AIState* state) const;
+
+    /**
+     * Called by AIController to update the state machine each frame.
+     */
+    void update(float elapsedTime);
+
+    AIAgent* _agent;
+    AIState* _currentState;
+    std::list<AIState*> _states;
+
+};
+
+}
+
+#endif

+ 19 - 2
gameplay/src/Game.cpp

@@ -19,7 +19,7 @@ Game::Game()
     : _initialized(false), _state(UNINITIALIZED), 
       _frameLastFPS(0), _frameCount(0), _frameRate(0), 
       _clearDepth(1.0f), _clearStencil(0), _properties(NULL),
-      _animationController(NULL), _audioController(NULL), _physicsController(NULL), _audioListener(NULL)
+      _animationController(NULL), _audioController(NULL), _physicsController(NULL), _aiController(NULL), _audioListener(NULL)
 {
     GP_ASSERT(__gameInstance == NULL);
     __gameInstance = this;
@@ -100,6 +100,9 @@ bool Game::startup()
     _physicsController = new PhysicsController();
     _physicsController->initialize();
 
+    _aiController = new AIController();
+    _aiController->initialize();
+
     loadGamepads();
     
     _state = RUNNING;
@@ -115,6 +118,7 @@ void Game::shutdown()
         GP_ASSERT(_animationController);
         GP_ASSERT(_audioController);
         GP_ASSERT(_physicsController);
+        GP_ASSERT(_aiController);
 
         Platform::signalShutdown();
         finalize();
@@ -134,6 +138,9 @@ void Game::shutdown()
         _physicsController->finalize();
         SAFE_DELETE(_physicsController);
 
+        _aiController->finalize();
+        SAFE_DELETE(_aiController);
+
         SAFE_DELETE(_audioListener);
 
         RenderState::finalize();
@@ -151,11 +158,13 @@ void Game::pause()
         GP_ASSERT(_animationController);
         GP_ASSERT(_audioController);
         GP_ASSERT(_physicsController);
+        GP_ASSERT(_aiController);
         _state = PAUSED;
         _pausedTimeLast = Platform::getAbsoluteTime();
         _animationController->pause();
         _audioController->pause();
         _physicsController->pause();
+        _aiController->pause();
     }
 }
 
@@ -166,11 +175,13 @@ void Game::resume()
         GP_ASSERT(_animationController);
         GP_ASSERT(_audioController);
         GP_ASSERT(_physicsController);
+        GP_ASSERT(_aiController);
         _state = RUNNING;
         _pausedTimeTotal += Platform::getAbsoluteTime() - _pausedTimeLast;
         _animationController->resume();
         _audioController->resume();
         _physicsController->resume();
+        _aiController->resume();
     }
 }
 
@@ -192,6 +203,7 @@ void Game::frame()
         GP_ASSERT(_animationController);
         GP_ASSERT(_audioController);
         GP_ASSERT(_physicsController);
+        GP_ASSERT(_aiController);
 
         // Update Time.
         static double lastFrameTime = Game::getGameTime();
@@ -207,7 +219,10 @@ void Game::frame()
     
         // Update the physics.
         _physicsController->update(elapsedTime);
-        
+
+        // Update AI.
+        _aiController->update(elapsedTime);
+
         // Application Update.
         update(elapsedTime);
 
@@ -241,6 +256,7 @@ void Game::updateOnce()
     GP_ASSERT(_animationController);
     GP_ASSERT(_audioController);
     GP_ASSERT(_physicsController);
+    GP_ASSERT(_aiController);
 
     // Update Time.
     static double lastFrameTime = getGameTime();
@@ -251,6 +267,7 @@ void Game::updateOnce()
     // Update the internal controllers.
     _animationController->update(elapsedTime);
     _physicsController->update(elapsedTime);
+    _aiController->update(elapsedTime);
     _audioController->update(elapsedTime);
 }
 

+ 10 - 0
gameplay/src/Game.h

@@ -9,6 +9,7 @@
 #include "AudioController.h"
 #include "AnimationController.h"
 #include "PhysicsController.h"
+#include "AIController.h"
 #include "AudioListener.h"
 #include "Rectangle.h"
 #include "Vector4.h"
@@ -217,6 +218,14 @@ public:
      */
     inline PhysicsController* getPhysicsController() const;
 
+    /** 
+     * Gets the AI controller for managing control of artifical
+     * intelligence associated with the game.
+     *
+     * @return The AI controller for this game.
+     */
+    inline AIController* getAIController() const;
+
     /**
      * Gets the audio listener for 3D audio.
      * 
@@ -496,6 +505,7 @@ private:
     AnimationController* _animationController;  // Controls the scheduling and running of animations.
     AudioController* _audioController;          // Controls audio sources that are playing in the game.
     PhysicsController* _physicsController;      // Controls the simulation of a physics scene and entities.
+    AIController* _aiController;                // Controls AI siulation.
     AudioListener* _audioListener;              // The audio listener in 3D space.
     std::vector<Gamepad*> _gamepads;            // The connected gamepads.
     std::priority_queue<TimeEvent, std::vector<TimeEvent>, std::less<TimeEvent> >* _timeEvents;     // Contains the scheduled time events.

+ 5 - 0
gameplay/src/Game.inl

@@ -49,6 +49,11 @@ inline PhysicsController* Game::getPhysicsController() const
     return _physicsController;
 }
 
+inline AIController* Game::getAIController() const
+{
+    return _aiController;
+}
+
 template <class T>
 void Game::renderOnce(T* instance, void (T::*method)(void*), void* cookie)
 {

+ 31 - 2
gameplay/src/Node.cpp

@@ -23,7 +23,7 @@ namespace gameplay
 Node::Node(const char* id)
     : _scene(NULL), _firstChild(NULL), _nextSibling(NULL), _prevSibling(NULL), _parent(NULL), _childCount(0),
     _nodeFlags(NODE_FLAG_VISIBLE), _camera(NULL), _light(NULL), _model(NULL), _form(NULL), _audioSource(NULL), _particleEmitter(NULL),
-    _collisionObject(NULL), _dirtyBits(NODE_DIRTY_ALL), _notifyHierarchyChanged(true), _userData(NULL)
+    _collisionObject(NULL), _agent(NULL), _dirtyBits(NODE_DIRTY_ALL), _notifyHierarchyChanged(true), _userData(NULL)
 {
     if (id)
     {
@@ -52,6 +52,8 @@ Node::~Node()
     SAFE_RELEASE(_form);
     SAFE_DELETE(_collisionObject);
 
+    setAgent(NULL);
+
     // Cleanup user data
     if (_userData)
     {
@@ -1087,10 +1089,37 @@ PhysicsCollisionObject* Node::setCollisionObject(Properties* properties)
         GP_ERROR("Failed to load collision object from properties object; required attribute 'type' is missing.");
         return NULL;
     }
-    
+
     return _collisionObject;
 }
 
+AIAgent* Node::getAgent() const
+{
+    return _agent;
+}
+
+void Node::setAgent(AIAgent* agent)
+{
+    if (agent != _agent)
+    {
+        if (_agent)
+        {
+            Game::getInstance()->getAIController()->removeAgent(_agent);
+            _agent->_node = NULL;
+            SAFE_RELEASE(_agent);
+        }
+
+        _agent = agent;
+
+        if (_agent)
+        {
+            _agent->addRef();
+            _agent->_node = this;
+            Game::getInstance()->getAIController()->addAgent(_agent);
+        }
+    }
+}
+
 NodeCloneContext::NodeCloneContext()
 {
 }

+ 20 - 0
gameplay/src/Node.h

@@ -12,6 +12,7 @@
 #include "PhysicsCollisionObject.h"
 #include "PhysicsCollisionShape.h"
 #include "BoundingBox.h"
+#include "AIAgent.h"
 
 namespace gameplay
 {
@@ -554,6 +555,20 @@ public:
      */
     PhysicsCollisionObject* setCollisionObject(Properties* properties);
 
+    /**
+     * Returns the AI agent assigned to this node.
+     *
+     * @return The AI agent for this node.
+     */
+    AIAgent* getAgent() const;
+
+    /**
+     * Sets the AI agent for this node.
+     *
+     * @param agent The AI agent to set.
+     */
+    void setAgent(AIAgent* agent);
+
     /**
      * Returns the bounding sphere for the Node, in world space.
      *
@@ -750,6 +765,11 @@ protected:
      */
     PhysicsCollisionObject* _collisionObject;
     
+    /**
+     * Pointer to the AI agent attached to the Node.
+     */
+    AIAgent* _agent;
+
     /**
      * World Matrix representation of the Node.
      */

+ 6 - 0
gameplay/src/gameplay.h

@@ -74,6 +74,12 @@
 #include "PhysicsGhostObject.h"
 #include "PhysicsCharacter.h"
 
+// AI
+#include "AIController.h"
+#include "AIAgent.h"
+#include "AIState.h"
+#include "AIStateMachine.h"
+
 // UI
 #include "Theme.h"
 #include "Control.h"