Jump to content
  • Advertisement
Sign in to follow this  
crancran

Game Event Triggers

This topic is 2599 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

[color="#333333"]
In designing my event notification framework for my Component-based entity system, I primarily focused on inter-communication among the components of a single entity using boost signals2. I then realized that I was only scratching the surface of what my event system truly needs to represent, particularly when we start to consider full scale level design where an entity reacts to another entities' conditions.

For example, we could design a small level where there is a hallway that leads to a room. Right before the room, there is a door that is open by default. Inside the room exists a big bad boss and a door behind the boss that is initially closed. In this scenario, the entry door will want to subscribe to the bosses' ENTER_COMBAT and LEAVE_COMBAT events to trigger closing/opening the door. Similarly, the exit door will want to subscribe to a DEATH event triggered by the boss when he dies, so that the exit door will open under that condition.

In the above example, the exit door's DoorOpenTriggerComponent (or some game logic component such as a script) would have some code that resembles this:

void CDoorOpenTriggerComponent::OnInitialize() {
EventSystem::getSingleton().addListener(EventSystem::E_DEATH, &CDoorOpenTriggerComponent::OnDeath);
}
void CDoorOpenTriggerComponent::OnDeath(GameEvent& evt) {
if(evt.senderEntityId == bossEntityId) { // how would we get boss' entity id???
m_doorState = OPEN_STATE;
}
}

Lets add a bit more complexity to the above scenario. Every 60 seconds our boss spawns a minion randomly in the room while it is alive. The player must kill this minion or else they'll continue to spawn and overwhelm the player. But if the boss dies while a minion is still alive, we have two options. Either the minion would despawn with the death event or the minion must continue to be killed.

Under the first scenario (despawn); the above open door trigger logic would still continue to work. However, under the second scenario, the logic would need to be a bit more complex because not only should the door open when the boss dies; but when the boss and all his spawned minions have been killed too.

[color="#333333"]How have others connected the dots in this scenario to make it work in your experience? In the above scenario, the E_DEATH event for all creatures are interpreted by the above component (which I'd rather avoid and thus have the subscription specifically only fire if the E_DEATH was related to the boss entity itself). But how does the door obtain information necessary to tell the event subsystem that it only cares about entity id X of the boss? Is this done using a parent/child hierarchy of entities with the boss being the parent and the doors and all spawned minions being children of the boss entity so that this logic can quickly traverse the entity tree and get the root entity?

Share this post


Link to post
Share on other sites
Advertisement
If you want to keep the minions alive after the boss dies and require the minions to be killed before the player can move on, why not just wait to send the boss dead event until both the boss and the minions are dead? Use an intermediate event handler that is called when a minion or boss dies to determine if it should fire the boss dead event.

Share this post


Link to post
Share on other sites
Is that you Jason? :)
Its Magnus here.

Remember you can keep a cache of global "quest" variables too (alot of old school click and drag programmable rpg makers work this way), just make it so all game entities (door, player, monster, any moving object basicly) can read and write to the same area of memory, then youd be able to increment quest variables when the boss spawns an entity, and decrement variables when a minion gets killed... when it comes back to 0 again, thats what the door checks for, "trigger on 0." it would be a unique little script on the door, that no other door has - and unique for the boss and its minions, it goes in their scripts, to access the global communication cache.

Share this post


Link to post
Share on other sites
You could also defer the boss battle to a pure "logical", invisible game object that takes care of the boss fight game logic and keeps track of spawned minions. This object can then trigger the "boss battle finished" event and cause the door to open.

Share this post


Link to post
Share on other sites

You could also defer the boss battle to a pure "logical", invisible game object that takes care of the boss fight game logic and keeps track of spawned minions. This object can then trigger the "boss battle finished" event and cause the door to open.


So essentially the two doors and the boss would all contain a logic component that will transmit events to one another to maintain the state of the fight?

Example:


// Entry door
void LogicComponent::OnInitialize() {
pBossEntity = EntityManager::getInstance()->getEntityByName("BigBadBoss");
pBossEntity->registerCallback(GameEvents::E_ENTER_COMBAT, boost::bind(&LogicComponennt::OnEnterCombat, this, _1));
pBossEntity->registerCallback(GameEvents::E_ENCOUNTER_SUCCESSFUL, boost::bind(&LogicComponent::OnSuccess, this, _1));
pBossEntity->registerCallback(GameEvents::E_ENCOUNTER_FAILED, boost::bind(&LogicComponent::OnFailed, this, _1));
}

void LogicComponent::OnEnterCombat(CGameEvent& e) {
// This entity has a DoorComponent that waits for E_DOOR_CLOSE and E_DOOR_OPEN.
// When this logic component enter's combat, it notifies entity to close the door.
getEntity()->notify(GameEvents::E_DOOR_CLOSE);
}


void LogicComponent::OnSuccess(CGameEvent& e) {
// This entity has a DoorComponent that waits for E_DOOR_CLOSE and E_DOOR_OPEN.
// When this logic component is notified of a successful kill, it notifies entity to open the door.
getEntity()->notify(GameEvents::E_DOOR_OPEN);
}


void LogicComponent::OnFailed(CGameEvent& e) {
// This entity has a DoorComponent that waits for E_DOOR_CLOSE and E_DOOR_OPEN.
// When this logic component is notified of a failed attempt, it notifies entity to open the door.
getEntity()->notify(GameEvents::E_DOOR_OPEN);
}

// Similar things exist for the exit door naturally

// Boss
void LogiComponent::OnInitialize() {
// is there anything to put here ??
}

void LogiComponent::update(float delta) {
// check the AI state of the fight
if(boss successfully killed && minion count == 0)
getEntity()->notify(GameEvents::E_ENCOUNTER_SUCCESSFUL);
else if( (boss alive or minion count > 0) && left combat)
getEntity()->notify(GameEvents::E_ENCOUNTER_FAILED)
else if(boss entered combat)
getEntity()->notify(GameEvents:E_ENTER_COMBAT);

}


While I can consider having a LuaLogicScriptComponent that loads the AI logic from a lua script and updates it each with game loop iteration, this does seem to be overly verbose to accomplish what I am trying to do.... Am I going about this in the wrong direction or is there a better approach that as I outlined here?

Share this post


Link to post
Share on other sites
How I would structure it (pseudocode):


class Boss {
Events: awake, killed, minionSpawned
bool isAwake = false;
update() {

if (!dead && !isAwake && canSee(player)) {
isAwake = true;
fireEvent(awake);
}

if (!dead && isAwake && timeToSpawnNextMinion) {
spawn Minion;
fireEvent(minionSpawned);
}
...
}
}

class Minion {
Events: killed
...
}

class Door {
open() {...}
close() {...}
}

class BossBattle {
bool bossDead = false
int numMinions = 0

start() {
addHandler(thaBoss, awake, onBossAwake);
addHandler(thaBoss, minionSpawned, onMinionSpawned);
addHandler(thaBoss, killed, onBossKilled);
}

onBossAwake() {
entryDoor.close();
}

onBossKilled() {
bossKilled = true;
checkBattleFinished();
}

onMinionSpawned(minion) {
numMinions++;
handleEvent(minion, killed, onMinionKilled)
}

onMinionKilled(minion) {
numMinions--;
checkBattleFinished();
}

checkBattleFinished() {
if (bossDead && numMinions == 0) {
exitDoor.open();
}
}
}

Boss thaBoss;
Door entryDoor, exitDoor;
BossBattle battleRules;



See how the responsibilities are divided amongst the classes? The Bosses only goal is to try to kill the player and spawning minions every now and then. The Minions are also trying to kill the player. The doors just sit there dumb, doing nothing. The component that bind all of them together is the BossBattle class that receives all those different events and intertwines them into the game we expect to see. That way you can reuse the Door, Boss and Minion classes without the specific Battle AI in them.
I think it shows in your code that you were a little on the wrong track by naming the events E_DOOR_CLOSE and E_DOOR_OPEN. The event should not define what happens after the event fires but only be an indicator of: "hey something happened here. If you're interested, here are all the details. Go make somthing out of it".

So to sum it up:

1. Face the boss
2. slam! door closes
3. kill the boss and everyone else
4. exit door opens
5. ???
6. profit

:D

Share this post


Link to post
Share on other sites
More or less, your Boss Battle class above could be considered a Behavioral Tree where the level designer can design the fight mechanics and how the entities they've placed into the scene work together.

This way, the tree can be exported by the design tool to a scripting language, such as Lua. The design tool would associate the tree to a ScriptComponent at design time that could be attached to an entity in the scene to control the scene's interaction. For example, a trigger area that controls the boss' aggro radius could hold the reference to the boss' battle AI script component, coming back to the point above that not everything is visible to control mechanics of the game. :).

What I do continue to struggle with is something more technical such as the creation steps and the inter-communication among components.

It makes sense to me from a modular design to separate things like position, physics, render (meshes), and animations into their respective components. I want to follow the idea that components are managed by a respective subsystem, so logically it makes sense that the subsystem is responsible for creating/destroying their components. Basically pass the subsystem the entity that wants that component, the subsystem creates it, adds it to an internal list for updates and associates the entity to that component.

When the player hits the W key to move forward I assume something applies a forward force that the physics system uses? Perhaps along with the position component we have a transform component as well which handles converting the move forward to a constant force movement vector. The physics system iterates these transforms and positions each update() and determines if the player's position can be updated and if so; the force gets applied to the position, thus moving the player. The force applied is known whether to be considered WALK or RUN. What triggers which animation to fire here? What is a good way to update the scene node so that the render subsystem reflects the movement?

Share this post


Link to post
Share on other sites
I don't think, in this case, you should rely on the events on the individual enemies alone, but rather the events of of the scenario as a whole. Create a BossObserver class that checks for these special conditions and register the events for that class.

Share this post


Link to post
Share on other sites
That makes sense as well.


Ultimately, some piece of code must be responsible for the encounter's state and issue the respective events when certain states in the encounter are met so that entities can respond accordingly. I believe some engines aid this by allowing the level designer to attach FSM (Finite State Machines) or BT (Behavioral Tree) components to some entity in the scene (whether its the boss or an invisible observer node) that manages all this.

The basis of my post was to try and get an understanding of what others consider viable event/trigger frameworks. I mentioned boost signals (in my case using signals2); however there is also other ways I could implement my event system without leveraging the boost library. I could consider using an event listener interface to dispatch messages and the receiving objects must implement that interface or manage the event/object/function pointers myself.

Aren't there scenarios where I need to queue an event with a delay? If so, then signal/slot wouldn't work in this case since the calling of the signal is immediate. A more formal event dispatcher with either a common listener interface or handling of the function pointer relationship of an object to an event would be my responsibility to code, right? Should I be using the event system as a way for component A to get information from component B if that component exists?

How have others explored and implemented this in your games?
Were any methods better than others?

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!