Event triggering system, cause and effect

Started by
7 comments, last by Zipster 18 years, 3 months ago
I need help designing with some kind of event triggering system for my game. I'd like to keep it as general and flexile as possible. So that the trigger condition could be any thing from a timer or the player entering a certain area of space or a particular enemy or set of enemies dying, and even having multiple conditions that all need to be satisfied or where any one could be satisfied to trip the trigger. Also it needs to be flexible on the event side where it could cause any number of events to occur like killing the player spawning new game entities or moving stuff around. Finally it needs to be configurable from a file. Meaning In some file I need to be able to write almost like and if statement:

If(Player is in location A) spawn 5 enemies
If(Enemies X Y and Z are dead) Open locked door B
The particular syntax isn’t important, it could also be:

CAUSE 1: (Player is in location A) TRIGERS EVENT 7
CAUSE 2: (Enemies X Y and Z are dead) TRIGERS EVENT 9

EVENT 7: (sawn 5 enemies)
EVENT 9: (Open locked door B)
Then somewhere in my code if have a loop that gose through all my trigers checks their conditions and then executes their causes if they are satisfied like:

vector<trigger*> Triggers;
for(int i = 0; i < Triggers.size(); i++)
{
    if(Triggers ->ConditionsMet())
    {
         Triggers ->ExecuteEvents();        
    }
}

Advertisement
For a simple cause/effect system, there's no reason to design any really complex syntax. All you need is a way of specifying conditions, events, and passing parameters to both. Something simple would look like this:
TriggerLocation "Player_1" "A"{   EventSpawn "Bat" "A" "5"}

Which is interpreted as "if an object with the name 'Player_1' is at location 'A', then spawn '5' objects of type 'Bat' at location 'A'". You could even add simple boolean logic, either explicit and/or implicit via nested conditions:
TriggerLocation "Player_1" "A" && TriggerHealth "Player_1" ">50"{   TriggerNumObjectsAlive "Troll" "=0"   {      EventSpawn "Troll" "A" "1"   }   EventSpawn "Bat" "A" "5"}

How you implement it is up to you, but if you go with a nested approach then it would be wise to have a base class that could be either a trigger or an event, and depending on what the type actually is it could perform an event or evaluate another nested trigger.
Quote:Original post by Zipster

How you implement it is up to you,


Well that what I am asking, How do I implement it? I don’t know where to begin with such a system. I need some ideas, a direction to start out in.
Quote:Original post by Grain
Quote:Original post by Zipster

How you implement it is up to you,


Well that what I am asking, How do I implement it? I don’t know where to begin with such a system. I need some ideas, a direction to start out in.


You don't?

What you're looking for seems to be tantamount to writing your own programming language and parser. Why, when not a few scripting languages like lua and python exist to handle this problem?


Anyways, I'm no expert, but the first big things I would look into is a registry for functions, a registry for variables, a de-serialization registry for types, and finally a parser to deal with the string(s) and access the registries as required. In C++ boost::function, ::bind, and ::spirit will help a bit. In C#, Regex, the ::Parse functions, and delegate::DynamicInvoke will help.
Here's a suggestion:

Right now, your trigger system seems the reverse of what I would recommend. If you have a generic script that says "if (Player is in location A) do whatever," then you have to check all your script every time an action occurs to determine if a trigger should fire.

I would instead put a script on the trigger itself. For example, Location A would be a trigger. When the unit walks onto Location A, the trigger fires and Location A's script is called. The script could look like:

"spawn Enemy whatever params."

Or when an enemy dies, a trigger fires.

Zipster's approach is pretty similar to this.

As for implementing the scripts themselves, you could either embed a language as Telastyn suggested or you could write a basic interprater. The basic interprater could read the first word (a command), and depending on what that command is, read in a certain number of parameters relevant to that command.

Hope that helps,
--Brian
Quote:
The basic interprater could read the first word (a command), and depending on what that command is, read in a certain number of parameters relevant to that command.


That (a <command> <param> <param> <...> ) is actually relatively easy. I have a nice generic class to define commands, parse strings and generally "do stuff" for that form; if that's all you want. Adding conditionals and variables like the OP example significantly complicates matters.
Quote:Original post by Telastyn
What you're looking for seems to be tantamount to writing your own programming language and parser. Why, when not a few scripting languages like lua and python exist to handle this problem?

This really isn't as bad as writing an entire language [smile] It's simple enough that you could implement it yourself. As a matter of fact, it might be more work setting up an existing scripting language than it would be writing everything yourself. Besides, if you've never done so before the learning experience alone is worth it. All you'd need is a simple token-based parser that handles string literals. The reason why I used blocks in my sample syntax is because they're easy to parse and there's a clear semantic closure (if that's an appropriate phrase) for the triggers.

Quote:Original post by Grain
Well that what I am asking, How do I implement it?

Phase 1 would definitely be the parser. Just write something that allows you to read words at a time that are delimited by whitespace or quoted strings. It might also be a good idea to optionally restrict the next token to the current line (so that you can enforce the parameters on the same line as the trigger/event but that's a preference issue). Phase 2 would be designing the internal code structure that implements a script. I'm a sucker for nesting, so it might look like this:

class BaseTrigger{public:   virtual void TriggerMe() = 0;   // ...}; class Trigger : public BaseTrigger{public:   virtual bool ShouldTrigger() = 0;    virtual void TriggerMe()   {     if(ShouldTrigger())     {        // Simple loop        for(int i = 0; i < children.size(); ++i)           children->TriggerMe();     }   }   // ...private:   std::vector<BaseTrigger*> children;}; class TriggerLocation : public Trigger{public:   virtual bool ShouldTrigger()   {      if(object->Location() == location)         return true;      else         return false;   }   // ... private:   GameObject* object;   LocationType location;}; class Event : public BaseTrigger{public:   virtual void TriggerMe() = 0;   // ...}; class EventSpawn : public Event{public:   virtual void TriggerMe()   {      entityInterface->Spawn(monsterType, count);   }   // ... private:   int monsterType;   int count;}; // ...

Off the top of my head of course. Adding boolean logic would introduce a few more interesting classes:

class BinaryTrigger : public Trigger{public:   virtual bool ShouldTrigger() = 0; private:   Trigger* triggerA;   Trigger* triggerB;}; class UnaryTrigger : public Trigger{public:   virtual bool ShouldTrigger() = 0; private:   Trigger* trigger;}; class AndTrigger : public BinaryTrigger{public:   virtual bool ShouldTrigger()   {      return triggerA->ShouldTrigger() && triggerB->ShouldTrigger()   }   // ...}; class OrTrigger : public BinaryTrigger{public:   virtual bool ShouldTrigger()   {      return triggerA->ShouldTrigger() || triggerB->ShouldTrigger()   }   // ...}; class NotTrigger : public UnaryTrigger{public:   virtual bool ShouldTrigger()   {      return !trigger->ShouldTrigger();   }};

Explicit boolean logic is more difficult to implement because now you have to deal with parenthetical groups. It might just be better to stick with just unary operations (ex. NOT) and let the nesting handle AND. OR is a pain because you have to write the event code multiple times for each condition. Parsing parenthetical groups is something you have to decide you want to implement, but it's similar to parsing bracket blocks.

The bulk of Phase 3 is then turning the script into this trigger/event structure. But that's the fun part.
Quote:Original post by Nairb
I would instead put a script on the trigger itself. For example, Location A would be a trigger. When the unit walks onto Location A, the trigger fires and Location A's script is called.

That's actually a really good idea for triggers than contain location or spatial context. That way you could also have weird-shaped "zones" as opposed to single positions or locations. But for things such as checking health levels, the existence/presence of objects, etc., you'd still need to continually evaluate the global script. Or have a system where certain events can notify the script engine of a change in state. Much more efficient, but now all possible state changes that could trigger an event need to notify the script system and it's more pervasive throughout the code.
Quote:Original post by Nairb
I would instead put a script on the trigger itself. For example, Location A would be a trigger. When the unit walks onto Location A, the trigger fires and Location A's script is called.

That's actually a really good idea for triggers than contain location or spatial context. That way you could also have weird-shaped "zones" as opposed to single positions or locations. But for things such as checking health levels, the existence/presence of objects, etc., you'd still need to continually evaluate the global script. Or have a system where certain events can notify the script engine of a change in state. Much more efficient, but now all possible state changes that could trigger an event need to notify the script system and it's more pervasive throughout the code. Evaluating the script once every handful of frames could provide a good balance between performance and ease of implementation (the downside here being that you could miss an event that comes and goes between script evaluations).

This topic is closed to new replies.

Advertisement