Skip to main content
GameDev.net gamedev.net
🔒 Locked

Need some advice on "interactable" entites in ECS

Started by smokyturbo Jun 15, 2018 at 5:48 PM 4 replies 2k views
Original Post
smokyturbo
smokyturbo

Hi all,

I'm having some trouble working out how to handle entities which can be interacted with by other entities (typically the player).

A couple of examples I'm trying to implement first are:

- Doors

- Item containers (e.g. chest)


InputSystem
-----------

OnKeyDown(key)
	if key == "interact"
		interactableEntity = FindCollidingInteractable(player) // check all nearby entities in scene with an "Interactable" component?
		interactionComp = interactableEntity.Get<InteractionComponent>();
          
		// What now?

How can interactionComp hold all data for all possible interactions?


If it's a door, we need to know if it's locked and what the required key is if so, as well as which room we teleport to once opened (Resident Evil style).
If it's a chest, it might have similar "lock" requirements as the door, as well as an inventory (InventoryComponent?) of items to transfer to the player.

I can image how this would work using interfaces & polymorphism:


class InteractionHandler {
	void HandleInteraction(Entity instigator, Entity target);
}

class DoorInteractionHandler : InteractionHandler {
	void HandleInteraction(Entity instigator, Entity target) {
		if (requiredItem != null && instigator.Get<InventoryComponent>().HasItem(requiredItem)) {
			instigator.Get<InventoryComponent>().Remove(requiredItem);
			requiredItem = null;
          
			LoadStage(nextStage);
		} else {
			// Door is not locked.
			LoadStage(nextStage);
		}
	}

	string requiredItem;
	string nextStage;
}

Where the InteractionComponent could hold a pointer to an instance of InteractionHandler and just call HandleInteraction blindly.


I understand however that components are not meant to contain any logic in a pure ECS, so how would we represent the interaction requirements on the entity?
I thought that maybe I need more components, such as a LockComponent, DoorComponent etc, but then how would this fit in with the keyboard handling code and where would the logic for acting on these go?

Very confused, so any advice appreciated.


Thanks!

Kylotan
Kylotan

Tons of ways you can choose to approach this:

  • subclasses of InteractionComponent can handle the Interact() call differently
  • InteractionComponent could check for other components (e.g. DoorComponent, ChestComponent)
  • InteractionComponent simply contains all the possible data for all the possible interactions (of which there are probably not that many, when you really think about it)
7 minutes ago, smokyturbo said:

I understand however that components are not meant to contain any logic in a pure ECS

If it's harder to do something the "pure" way, there's a good chance the pure way is not the best way.

If your component contains no logic then the problem merely moves; this becomes the responsibility of the InteractionSystem. That would decide how to handle the individual situations in much the same way - it can look for other components which implement the individual options, or it could handle each individual option itself.

None of this has anything to do with keyboard handling - the keyboard code should translate this keypress into an interaction message, which your system then handles. Or the input code calls whatever system decides that an interaction will take place, and that decides which components to call. By the time your interactables are getting notification that something is interacting, the input code should have been left far behind.

All8Up
All8Up

From the way the embedded code is shown, I would say the first thing to do is reverse the logic. What I mean is that what you posted suggests that the code is along the lines of "I'm trying to interact with you" and "I need to figure out what you are and what I can do to you". Conceptually of course is the most sensible solution, unfortunately in practice it is also the solution which fails the most often. Rather than this, I've always used subject oriented programming for interactions. This reverses the logic such that rather than the 'doer' figuring things out the subject of the interaction performs the work. Basically this means that the input system would simply set a flag on the input component saying "this entity wishes to interact". The interaction system can then do pre-cull for subject entities which are in range, which ones can be used from the doer's position, etc. Finally, you end up with a door that runs the rule checks: "You are in range, you have my key, it is Monday after noon, you are wearing a purple shirt... Ok, guess I will open now."

As to how you implement these things in the ECS itself, I suspect everyone does it differently. Personally I use a handle based approach where the interaction component simply contains a handle which refers to usually a script which checks the interaction rules. The script gets the doer and subject ID's so it can check states and make the decision. Then, likely in another script, an action is performed.

Overall this probably sounds ass backwards but it is a well proven solution to removing massive 'if/else' checks. It is also great for expansion packs and such since all the new logic is contained in the subject and you don't have to patch the doer code to understand the new interaction. Just as an example, The Sims used(still uses I assume) this subject oriented approach which allows DLC to be dropped in and mostly just work.

Hope this makes sense.

smokyturbo
smokyturbo
15 hours ago, Kylotan said:

If it's harder to do something the "pure" way, there's a good chance the pure way is not the best way.

Good point, no point forcing things if they don't fit. I think I'll start by having something within the interaction component which performs the logic.


15 hours ago, Kylotan said:

None of this has anything to do with keyboard handling - the keyboard code should translate this keypress into an interaction message, which your system then handles. Or the input code calls whatever system decides that an interaction will take place, and that decides which components to call. By the time your interactables are getting notification that something is interacting, the input code should have been left far behind.

Yeah this makes sense. I'm looking to update the keyboard code so that it sends "requests" for an actor, like, ReqInteraction, ReqAttack etc. This way when I come to work on the AI system, it can emit similar commands that the other systems can consume without knowing whether they came from the player or any other entity.


14 hours ago, All8Up said:

This reverses the logic such that rather than the 'doer' figuring things out the subject of the interaction performs the work.

Yeah that makes a lot of sense. I think that's what I was kind of heading towards with my second code example, but I wasn't quite sure.


14 hours ago, All8Up said:

Just as an example, The Sims used(still uses I assume) this subject oriented approach which allows DLC to be dropped in and mostly just work.

I'll definitely try this inverse-logic approach. I don't really know how to use scripts within games at the moment, so I think I'll code some classes instead for know and have my interaction component have a pointer to an instance, a bit like a hard coded example of your handle & script approach.

Thanks both for the advice, it's really helped.

Shaarigan
Shaarigan

You might miss a point, the system in ECS ;)

We have had in one of our games the 'object handles interaction' approach but it was that the object needed to do a lot of checks for interaction. I refactored this to a system based approach. The door just has some logic that knows what should be done if an interaction succeeds, this makes it easier to implement more interaction objects from the general interaction component. Second, the door has a requirement component that implements checks for certain circumstances that have to be match.

Now we have a system that I'll call game-master system that does all the interaction based on certain rules. The subject wants to interact with certain object so tells the system the own component (player/NPC, inventory) and the components it want to interact with. The system now performs the data processing work, looks for a requirement component and passes the player component and iventory component to it to do some checks. If successfull, the interaction components 'Do' function is called that will proceed with the interaction; swing the door or open the inventory menu of the chest for example.

This way you can add new components for requirements and interaction more easily

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.