Trigger System

From FIFE development wiki
Revision as of 15:05, 1 June 2012 by Prock (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

This article is outdated and is just stored for archive purposes. Archived.png

This article became outdated and is just stored for archive purposes in this wiki. There are several reasons why an article could become outdated. The development team may have decided to use a different concept or even the author itself felt that the article is not really up-to-date with the current development status of the project anymore.

Introduction

This article is a draft on a proposal on a trigger system for the FIFE AI Architecture.

What's a trigger?

In this article, we will be referring to a trigger as a non-visible element of a game which can be used to define logic events agents can respond to.

An example

To better illustrate the idea behind the concept of trigger, let's take a look at a possible use of triggers in a (real-time RPG) game:

"The player is walking in a ruined city. All seems to be in calm, when. suddenly, a monster appears near him. The monster (who has seen the player through his visibility trigger) starts to move around the player following the position of the dynamic trigger located on him (the player). They start fighting, and, when the player kills the monster, his corpse remains in the ground for a few seconds (until its visibility trigger expires)."


Trigger system design

To poll or not to poll?

As it stands, the trigger system avoids polling because of its efficiency implications. Polling would require each agent to look for events that might be of interest to him; optimizations in the form of culling can be applied to avoid expensive searches, but I think doing the opposite thing, that is, letting the triggers themselves inform the interested agents, is a more clean, reusable and efficient approach.

Components

Two main components will serve our purpose: Trigger and TriggerManager. We will start by taking a look at the characteristics that define a trigger:

  • ID and name: The trigger's name and identification number.
  • State: The trigger's state. A trigger can (and should) be in one -and only one- of the following states at any time: idle, active and killed. When a trigger gets registered by the manager, it is automatically put on 'idle' state (waiting and doing nothing); when the initial delay of the trigger (if any) expires, the trigger passes to the 'active' state; when the trigger is killed, it is automatically put on the 'killed' state.
  • Priority: Each trigger has a priority associated to it. Agents will respond first to those triggers with higher priority.
  • Initial delay: Triggers can have an initial delay in order not to pass to the 'active' state automatically when registered, but after a concrete period of time.
  • Duration: Triggers can have a limited life. Following the example above, the trigger that determines the visibility of the corpse only lives for a few seconds.
  • Position: Although not being graphical, triggers have a position in the game's world.
  • Influence radius: The influence radius indicates the area on which an agent is affected by the trigger.
  • Static/Dynamic trigger: A trigger can have a fixed position in the world or move dynamically (for example, following an agent's movement).
  • Update Ratio: Should the trigger enter into the update loop each frame? Very possibly some triggers don't have to update themselves each frame.


The trigger manager is the central access point to the triggers. It mainly registers, removes and updates triggers. Pointers to registered triggers are stored in a multimap ordered by priority.


Example implementation

Below is a basic isolated implementation of a trigger system as the one described before (this is not meant to be directly compilable, but to provide an example vision of how a trigger system could be implemented, as some assumptions on data types and utility functions are made). This implementation is based on the one proposed by Jeff Orkin on AI Game Programming Wisdom, although it has several changes and additions, and more features are to be added (trigger grouping being one of them):


Trigger.h

  #ifndef TRIGGER_H
  #define TRIGGER_H
  
  #include <string>
  #include "Math/Vector.h"
  
  
  class Trigger
  {
     public:
        friend class TriggerManager;
     
     enum TriggerState
     {
        IDLE,
        STARTED,
        FROZEN,
        KILLED
     };
  
     Trigger(const std::string& name, 
        int startDelay,
        int updateRatio,
        int radius,
        int duration, 
        int killCode,
        bool mustCallEnd,
        bool isDynamic,
        const Vector& pos,
        unsigned short priority = 0,
        TriggerState state = IDLE);
     virtual ~Trigger();
  
     virtual void Start();
     virtual void Update();
     virtual void End();
  
     // Getters and setters
     // ...
  
  protected:
     std::string m_name;
     int m_ID;
     int m_startDelay;
     int m_updateRatio;
     float m_radius;
     int m_duration;
     int m_killCode;
     bool m_mustCallEndOnKill;
     bool m_isDynamic;
     Vector m_pos;
     unsigned short m_priority;
     TriggerState m_state;
  };
  
  #endif


Trigger.cpp

  #include "Trigger.h"
  
  
  Trigger::Trigger(const std::string& name, int startDelay, int updateRatio, int radius, int duration, int killCode, 
                   bool mustCallEnd, bool isDynamic, const Vector& pos, unsigned short priority, TriggerState state)
  : m_name(name), m_startDelay(startDelay), m_updateRatio(updateRatio), m_radius(radius), m_duration(duration),
    m_killCode(killCode), m_mustCallEndOnKill(mustCallEnd), m_isDynamic(isDynamic), m_pos(pos), m_priority(priority), m_state(state)
  {
  }
  
  Trigger::~Trigger()
  {
  }
  
  void Trigger::Start()
  {
  }
  
  void Trigger::Update()
  {
  }
  
  void Trigger::End()
  {
  }


TriggerManager.h

  #ifndef TRIGGER_MANAGER_H
  #define TRIGGER_MANAGER_H
  
  #include <map>
  
  class Trigger;
  
  
  class TriggerManager
  {
     public:
        TriggerManager();
        virtual ~TriggerManager();
  
        virtual void Add(Trigger* trigger);
        virtual int Kill(int killCode = 0);
        virtual void Update();
  
        void Pause();
        void UnPause(); 
  
  
     protected:
        typedef std::multimap<unsigned short, Trigger*, std::greater<unsigned short> > TriggerMap;
        typedef TriggerMap::iterator TriggerIterator;
        TriggerMap m_triggers;
  
        bool m_triggersPaused;
  
        virtual void Update(TriggerMap* triggerMap);
  };
  
  #endif


TriggerManager.cpp

  #include "TriggerManager.h"
  #include "Trigger.h"
  
  
  TriggerManager::TriggerManager() : m_triggersPaused(false)
  {
  }
  
  TriggerManager::~TriggerManager()
  {
  }
  
  void TriggerManager::Add(Trigger* trigger)
  {
     if (trigger) {
        m_triggers.insert(TriggerMap::value_type(trigger->m_priority, trigger));
     }
  }
  
  int TriggerManager::Kill(int killCode)
  {
     int killed = 0;
  
     TriggerIterator it = m_triggers.begin();
     Trigger* t;
  
     while (it != m_triggers.end()) {
        t = it->second;
        if (!killCode || killCode == t->m_killCode) {
           killed++;
           t->m_state = Trigger::KILLED;
           if (t->m_mustCallEndOnKill) {
              t->End();
           }
        }
        ++it;
     }
  
     return killed;
  }
  
  void TriggerManager::Update()
  {
     if (!m_triggersPaused) {
        Update(&m_triggers);
     }
  }
  
  void TriggerManager::Update(TriggerMap* triggerMap)
  {
     // To be _heavily_ modified
     float distance = 0.0f;
  
     TriggerIterator it = m_triggers.begin();
     Trigger* t;
  
     while (it != m_triggers.end()) {
        t = it->second;
        if (t->m_state == IDLE) {
           t->m_state = STARTED;
        }
  
        if (t->m_duration != 0) {
           delete t;
           it = m_triggers.erase(it);
        }
        else {
           if (t->m_isDynamic) {
              t->updatePos();
           }
           ++it;
        }
        t->m_duration--;
     }
  
     for (unsigned long i = 0; i < g_numAgents; i++) {
        pAgent = g_pAgentList[i];
  	
        for (it = m_triggers.begin(); it != m_triggers.end(); ++it) {
           t = it->second;
              
           if (distanceBetween(pAgent->getPos(), t->m_pos) < t->m_radius) {
              pAgent->handleTrigger(t);
           }
        }
     }
  }
  
  void TriggerManager::Pause()
  {
     m_triggersPaused = true;
  }
  
  void TriggerManager::UnPause()
  {
     m_triggersPaused = false;
  }


FIFEdit and triggers

I thought it could be very interesting and useful to be able to define triggers in a visual manner in FIFEdit, so one could move and place triggers around, attach them to critters and set the trigger's properties by double-clicking over them. If I remember correctly, StarCraft's editor made something similar to this possible.


TODO

  • Relationship between the trigger system and scripting (of course, one should be able to access all trigger system's functionality via scripts).
  • More detailed aspects about the system design and its integration with the current code.
  • Complete and add more detail to all this. :-)