sschmid / Entitas

Entitas is a super fast Entity Component System (ECS) Framework specifically made for C# and Unity
MIT License
7.16k stars 1.11k forks source link

Features to support "singleton" entity component #928

Closed ianeinman closed 1 year ago

ianeinman commented 4 years ago

Sometimes there is a component which, at any given moment, can be applied to one, and only one, entity. I called this a "singleton" although that's more of an object-oriented term and I'm not sure is right here, but let's go with it for a minute.

An example might be which entity has focus (is being controlled by the player). Only one entity at any given time can have focus. If one entity is given focus the existing entity which had focus needs to lose it.

I can attach an "empty" component to an entity which functions as a boolean flag, and this makes it easy to query which entities have focus, or do not have focus. But when I have a component which should function as a singleton, I'd additionally like:

If there's an attribute to say a component should behave in this way, then I think setting one entity to have "isFocused" to true should clear any others. Instead of having it be a boolean member of every entity, the generated code should be a property accessor and setting it would change which entity is focused.

Of course one can already do stuff like this outside of Entitas but then you can't use the component as a filter in a GroupMatch. So I think it would be great if Entitas could help here.

Arpple commented 4 years ago

is that what Unique attribute for? https://github.com/sschmid/Entitas-CSharp/wiki/Attributes#unique-entities

ianeinman commented 4 years ago

Yes, perfect. Thanks. Sorry I missed that.

I think I misinterpreted what was being marked as "Unique" because the title "Unique Entities" was misleading for me. It is actually the component which is unique, not the entity. Had I read it thoroughly I would have picked up on this but maybe the title of the section would be better as "Unique Components"

ianeinman commented 4 years ago

Actually, I may have spoken too soon. I think your title of "Unique Entities" may have actually been the correct title and what I'm looking for is slightly different.

There is a difference between what Unique currently does, and what I'd prefer the attribute to do. After applying [Unique], here's the generated code:

public partial class GameContext {

    public GameEntity playerControlledEntity { get { return GetGroup(GameMatcher.PlayerControlled).GetSingleEntity(); } }

    public bool isPlayerControlled {
        get { return playerControlledEntity != null; }
        set {
            var entity = playerControlledEntity;
            if (value != (entity != null)) {
                if (value) {
                    CreateEntity().isPlayerControlled = true;
                } else {
                    entity.Destroy();
                }
            }
        }
    }
}

It appears that if I set a second entity to isPlayerControlled = true, that it destroys the first entity. But that is not what I want. I just want it to set isPlayerControled = false on that entity.

I want isPlayerControlled to act such that only one can be true, and setting a second one to true is not a "programmer mistake" but is a valid way of just setting it for the one and clearing the others, much like you'd set the current selection of a radio button.

Of course I know of other ways to do this, but it would be easier to be in the framework.

c0ffeeartc commented 4 years ago

Of course I know of other ways to do this

Yeah, this could be a good feature. Share your way to do this, and maybe it gets into the next version.

c0ffeeartc commented 4 years ago

It appears that if I set a second entity to isPlayerControlled = true, that it destroys the first entity. But that is not what I want. I just want it to set isPlayerControled = false on that entity.

It's not how it works actually. Just for future readers - there are two interfaces generated for Unique components, one for context and another for entity; Flag component is a special case for generated code.

ribbanya commented 4 years ago

Just use a PrimaryEntityIndex and set a unique component on the context to a given index. For example, EntityA has a primary index of "A", EntityB has a primary index of "B", and the context has a Unique string component containing either "A" or "B". Then your systems use context.GetEntityWithPlayerControlledIndex(context.currentPlayerControlledIndex) to grab the entity.

You can write an extension method, or write a code generator, to make this easier:

public static GameEntity GetPlayerController(this GameContext @this) {
  return @this.GetEntityWithPlayerControlledIndex(@this.currentPlayerControlledIndex);
}

and call it with context.GetPlayerController()

ianeinman commented 4 years ago

Thanks for the suggestion, but this is not exactly solving the problem. I may have explained things poorly.

I have a component, PlayerControlled, which acts as a flag on several different types of entities (vehicles, characters, etc.) At any given time, the player can only control one thing, so if this component is added to any entity, it should be removed from any other.

I can, of course, enforce this myself by, whenever I want to set a new entity as player controlled, getting the old player controlled entity, removing the PlayerControlled component, and then adding the PlayerControlled component to the new entity.

public partial class GameEntity {
   public bool PlayerControlled {
      get {
         return isPlayerControlled;
      }
      set {
         if (Contexts.sharedInstance.game.playerControlledEntity != null)
            Contexts.sharedInstance.game.playerControlledEntity.isPlayerControlled = false;

         isPlayerControlled = true;
      }
   }
}

That's what I'm trying to do. This code here is used by calling "entity.PlayerControlled = true". But I wish there was a way that I didn't need to override it, I could just get this behavior when calling "entity.isPlayerControlled = true". It seems like I wouldn't be the only one who wants to do something like this.

sschmid commented 1 year ago

Entitas itself has no concept of single entities or components. However, as we know the code generator introduces single entities by extending the partial context with helper methods. That does not help you in the example above, because you actually want to flag specific existing entities with the PlayerControllerComponent. So you're already doing it right, by manually deselecting entities before assigning to a new one

public partial class GameEntity
{
   public bool PlayerControlled
   {
      get {
         return isPlayerControlled;
      }
      set {
         var entity = Contexts.sharedInstance.game.playerControlledEntity;
         if (entity != null)
            entity.isPlayerControlled = false;

         isPlayerControlled = true;
      }
   }
}