From 83f51a6892629921b4cc482b986656a0a5cc5f6a Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Thu, 3 May 2018 14:41:01 -0400 Subject: Added simple AI implementation The new AutomatingSystem and AutomatableComponent are responsible for simple AI tasks. This currently is limited to moving entities at a certain speed for certain periods of time. These tasks are arranged as a set of behaviors, which are picked randomly when automation starts or when a behavior finishes executing. A behavior is a sequence of actions that run one after another. Currently, if an automated entity is blocked from moving by a collision, it will be coerced out of its intended path. This is because the automation parameters are stored as a speed and a duration, rather than a starting location and an ending location. This may end up being changed, or made configurable, as this is an early implementation of this feature and will need to be more complex later. Added an RNG object to the Game class, so that the AutomatingSystem can pick behaviors at random. --- CMakeLists.txt | 1 + src/components/automatable.h | 96 ++++++++++++++++++++++++ src/game.cpp | 4 +- src/game.h | 9 ++- src/main.cpp | 6 +- src/systems/automating.cpp | 71 ++++++++++++++++++ src/systems/automating.h | 19 +++++ src/systems/realizing.cpp | 174 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 377 insertions(+), 3 deletions(-) create mode 100644 src/components/automatable.h create mode 100644 src/systems/automating.cpp create mode 100644 src/systems/automating.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cd652e2..04ca668 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,6 +67,7 @@ add_executable(Aromatherapy src/systems/playing.cpp src/systems/scheduling.cpp src/systems/realizing.cpp + src/systems/automating.cpp vendor/stb_image.cpp ) diff --git a/src/components/automatable.h b/src/components/automatable.h new file mode 100644 index 0000000..b37945f --- /dev/null +++ b/src/components/automatable.h @@ -0,0 +1,96 @@ +#ifndef AUTOMATABLE_H_3D519131 +#define AUTOMATABLE_H_3D519131 + +#include "component.h" +#include +#include + +class AutomatableComponent : public Component { +public: + + /** + * Helper class that defines an automatable action. + */ + class Action { + public: + + /** + * The horizontal and vertical speed, in pixels/sec, that the entity should + * move at. + */ + double speedX; + double speedY; + + /** + * The duration of the action in seconds. + */ + double dur; + }; + + /** + * Helper type that defines a behavior that an entity can exhibit, which is a + * list of actions that are stepped through in sequence. + */ + using Behavior = std::vector; + + /** + * A group of behaviors that the entity can exhibit, which are picked at + * random at the start of automation and whenever a behavior completes. + * + * @managed_by RealizingSystem + */ + std::vector behaviors; + + /** + * A random distribution over the above behaviors. + * + * @managed_by RealizingSystem + */ + std::discrete_distribution behaviorDist; + + /** + * A flag indicating whether a behavior is currently executing. + * + * @managed_by AutomatingSystem + */ + bool behaviorRunning = false; + + /** + * A flag indicating whether an action is currently executing. + * + * @managed_by AutomatingSystem + */ + bool actionRunning = false; + + /** + * The index of the currently executing behavior, if there is one. + * + * @managed_by AutomatingSystem + */ + size_t currentBehavior; + + /** + * The index of the currently executing action, if there is one. + * + * @managed_by AutomatingSystem + */ + size_t currentAction; + + /** + * The amount of time remaining, in seconds, of the currently executing + * action. + * + * @managed_by AutomatingSystem + */ + double remaining; + + /** + * If this flag is disabled, the entity will be ignored by the automating + * system. + * + * @managed_by RealizingSystem + */ + bool active = false; +}; + +#endif /* end of include guard: AUTOMATABLE_H_3D519131 */ diff --git a/src/game.cpp b/src/game.cpp index d10c52c..bf2b10b 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -12,6 +12,7 @@ #include "systems/playing.h" #include "systems/scheduling.h" #include "systems/realizing.h" +#include "systems/automating.h" #include "animation.h" #include "consts.h" @@ -29,12 +30,13 @@ void key_callback(GLFWwindow* window, int key, int, int action, int) game.systemManager_.input(key, action); } -Game::Game() +Game::Game(std::mt19937& rng) : rng_(rng) { systemManager_.emplaceSystem(*this); systemManager_.emplaceSystem(*this); systemManager_.emplaceSystem(*this); systemManager_.emplaceSystem(*this); + systemManager_.emplaceSystem(*this); systemManager_.emplaceSystem(*this); systemManager_.emplaceSystem(*this); systemManager_.emplaceSystem(*this); diff --git a/src/game.h b/src/game.h index 92a67d9..dc256c6 100644 --- a/src/game.h +++ b/src/game.h @@ -1,6 +1,7 @@ #ifndef GAME_H_1014DDC9 #define GAME_H_1014DDC9 +#include #include "entity_manager.h" #include "system_manager.h" #include "renderer/renderer.h" @@ -8,10 +9,15 @@ class Game { public: - Game(); + Game(std::mt19937& rng); void execute(); + inline std::mt19937& getRng() + { + return rng_; + } + inline Renderer& getRenderer() { return renderer_; @@ -36,6 +42,7 @@ public: private: + std::mt19937 rng_; Renderer renderer_; EntityManager entityManager_; SystemManager systemManager_; diff --git a/src/main.cpp b/src/main.cpp index ddbc15f..d59d0f9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,11 +1,15 @@ +#include #include "muxer.h" #include "game.h" int main() { + std::random_device randomDevice; + std::mt19937 rng(randomDevice()); + initMuxer(); - Game game; + Game game(rng); game.execute(); destroyMuxer(); diff --git a/src/systems/automating.cpp b/src/systems/automating.cpp new file mode 100644 index 0000000..0d85957 --- /dev/null +++ b/src/systems/automating.cpp @@ -0,0 +1,71 @@ +#include "automating.h" +#include "game.h" +#include "components/automatable.h" +#include "components/ponderable.h" +#include "systems/pondering.h" + +void AutomatingSystem::tick(double dt) +{ + auto entities = game_.getEntityManager().getEntitiesWithComponents< + AutomatableComponent, + PonderableComponent>(); + + for (id_type entity : entities) + { + auto& automatable = game_.getEntityManager(). + getComponent(entity); + + if (!automatable.active) + { + continue; + } + + if (automatable.behaviorRunning && + (automatable.remaining <= 0.0)) + { + automatable.currentAction++; + automatable.actionRunning = false; + + if (automatable.currentAction == + automatable.behaviors[automatable.currentBehavior].size()) + { + automatable.behaviorRunning = false; + } + } + + if (!automatable.behaviorRunning) + { + automatable.currentBehavior = automatable.behaviorDist(game_.getRng()); + automatable.currentAction = 0; + automatable.behaviorRunning = true; + } + + AutomatableComponent::Action& curAction = + automatable.behaviors + [automatable.currentBehavior] + [automatable.currentAction]; + + if (!automatable.actionRunning) + { + automatable.remaining = curAction.dur; + automatable.actionRunning = true; + } + + auto& ponderable = game_.getEntityManager(). + getComponent(entity); + + ponderable.velX = curAction.speedX; + ponderable.velY = curAction.speedY; + + automatable.remaining -= dt; + } +} + +void AutomatingSystem::initPrototype(id_type prototype) +{ + auto& automatable = game_.getEntityManager(). + getComponent(prototype); + + automatable.behaviorRunning = false; + automatable.actionRunning = false; +} diff --git a/src/systems/automating.h b/src/systems/automating.h new file mode 100644 index 0000000..c78b7cf --- /dev/null +++ b/src/systems/automating.h @@ -0,0 +1,19 @@ +#ifndef AUTOMATING_H_E6E5D76E +#define AUTOMATING_H_E6E5D76E + +#include "system.h" + +class AutomatingSystem : public System { +public: + + AutomatingSystem(Game& game) : System(game) + { + } + + void tick(double dt); + + void initPrototype(id_type prototype); + +}; + +#endif /* end of include guard: AUTOMATING_H_E6E5D76E */ diff --git a/src/systems/realizing.cpp b/src/systems/realizing.cpp index 3656acb..8e670ac 100644 --- a/src/systems/realizing.cpp +++ b/src/systems/realizing.cpp @@ -12,9 +12,11 @@ #include "components/playable.h" #include "components/ponderable.h" #include "components/transformable.h" +#include "components/automatable.h" #include "systems/mapping.h" #include "systems/animating.h" #include "systems/pondering.h" +#include "systems/automating.h" inline xmlChar* getProp(xmlNodePtr node, const char* attr) { @@ -27,6 +29,92 @@ inline xmlChar* getProp(xmlNodePtr node, const char* attr) return key; } +void parseAI( + xmlNodePtr node, + std::vector& behavior, + const std::map& items) +{ + xmlChar* key = nullptr; + + if (!xmlStrcmp( + node->name, + reinterpret_cast("switch"))) + { + key = getProp(node, "item"); + std::string switchItem = reinterpret_cast(key); + xmlFree(key); + + for (xmlNodePtr switchNode = node->xmlChildrenNode; + switchNode != nullptr; + switchNode = switchNode->next) + { + if (!xmlStrcmp( + switchNode->name, + reinterpret_cast("case"))) + { + key = getProp(switchNode, "value"); + int caseValue = atoi(reinterpret_cast(key)); + xmlFree(key); + + if (items.at(switchItem) == caseValue) + { + for (xmlNodePtr caseNode = switchNode->xmlChildrenNode; + caseNode != nullptr; + caseNode = caseNode->next) + { + parseAI( + caseNode, + behavior, + items); + } + } + } + } + } else if (!xmlStrcmp( + node->name, + reinterpret_cast("move"))) + { + key = getProp(node, "direction"); + std::string direction = reinterpret_cast(key); + xmlFree(key); + + key = getProp(node, "length-var"); + std::string lengthVar = reinterpret_cast(key); + xmlFree(key); + + key = getProp(node, "speed-var"); + std::string speedVar = reinterpret_cast(key); + xmlFree(key); + + double length = items.at(lengthVar); + double speed = items.at(speedVar); + + AutomatableComponent::Action action; + + if (direction == "left") + { + action.speedX = -speed; + action.speedY = 0; + } else if (direction == "right") + { + action.speedX = speed; + action.speedY = 0; + } else if (direction == "up") + { + action.speedX = 0; + action.speedY = -speed; + } else if (direction == "down") + { + action.speedX = 0; + action.speedY = speed; + } + + action.dur = length / speed; + + behavior.push_back(std::move(action)); + } +} + // TODO: neither the XML doc nor any of the emplaced entities are properly // destroyed if this method throws an exception. EntityManager::id_type RealizingSystem::initSingleton( @@ -211,6 +299,70 @@ EntityManager::id_type RealizingSystem::initSingleton( game_.getSystemManager().getSystem(). initializeBody(mapObject, PonderableComponent::Type::vacuumed); + // Look for any object configuration. + std::map items; + + for (xmlNodePtr objectNode = mapNode->xmlChildrenNode; + objectNode != nullptr; + objectNode = objectNode->next) + { + if (!xmlStrcmp( + objectNode->name, + reinterpret_cast("item"))) + { + key = getProp(objectNode, "id"); + std::string itemName = reinterpret_cast(key); + xmlFree(key); + + key = xmlNodeGetContent(objectNode); + int itemVal = atoi(reinterpret_cast(key)); + xmlFree(key); + + items[itemName] = itemVal; + } + } + + // Add any AI behaviors. + std::vector behaviorWeights; + + for (xmlNodePtr protoSubNode = prototypeNode->xmlChildrenNode; + protoSubNode != nullptr; + protoSubNode = protoSubNode->next) + { + if (!xmlStrcmp( + protoSubNode->name, + reinterpret_cast("ai"))) + { + if (!game_.getEntityManager(). + hasComponent(mapObject)) + { + game_.getEntityManager(). + emplaceComponent(mapObject); + } + + auto& automatable = game_.getEntityManager(). + getComponent(mapObject); + + key = getProp(protoSubNode, "chance"); + behaviorWeights.push_back(atof(reinterpret_cast(key))); + xmlFree(key); + + std::vector behavior; + + for (xmlNodePtr aiNode = protoSubNode->xmlChildrenNode; + aiNode != nullptr; + aiNode = aiNode->next) + { + parseAI( + aiNode, + behavior, + items); + } + + automatable.behaviors.push_back(std::move(behavior)); + } + } + mappable.objects.push_back(mapObject); } else if (!xmlStrcmp( mapNode->name, @@ -304,6 +456,7 @@ void RealizingSystem::loadMap(id_type mapEntity) auto& animating = game_.getSystemManager().getSystem(); auto& pondering = game_.getSystemManager().getSystem(); + auto& automating = game_.getSystemManager().getSystem(); std::set players = game_.getEntityManager().getEntitiesWithComponents< @@ -366,6 +519,11 @@ void RealizingSystem::loadMap(id_type mapEntity) pondering.initPrototype(prototype); } + if (game_.getEntityManager().hasComponent(prototype)) + { + automating.initPrototype(prototype); + } + enterActiveMap(prototype); } @@ -399,6 +557,14 @@ void RealizingSystem::enterActiveMap(id_type entity) ponderable.active = true; } + + if (game_.getEntityManager().hasComponent(entity)) + { + auto& automatable = game_.getEntityManager(). + getComponent(entity); + + automatable.active = true; + } } void RealizingSystem::leaveActiveMap(id_type entity) @@ -418,4 +584,12 @@ void RealizingSystem::leaveActiveMap(id_type entity) ponderable.active = false; } + + if (game_.getEntityManager().hasComponent(entity)) + { + auto& automatable = game_.getEntityManager(). + getComponent(entity); + + automatable.active = false; + } } -- cgit 1.4.1