guerro323 / GameHost

MIT License
0 stars 0 forks source link

Introduce Entity Layouts (GameWorld) #101

Closed guerro323 closed 2 years ago

guerro323 commented 3 years ago

A layout is a specialized component, which enforce a strict component layout on an entity.
It will make it easier to create contextualized entities, and query them easily.

All layouts will be based on IEntityLayout, which provide a single method that get the required components.

Adding a layout will add the components to an entity, but removing it will not remove the components.

Maybe it should remove components/child-layouts that were not "added" by the user (see example on bottom)

interface IEntityLayout : IEntityComponent
{
    void Components<TList>(GameWorld gameWorld, TList list) where TList : IList<ComponentType>;
}
public struct MovableLayout : IEntityLayout
{
    public void Components<TList>(GameWorld gameWorld, TList list)
        where TList : IList<ComponentType>
    {
        list.Add(gameWorld.AsComponentType<Position>());
        list.Add(gameWorld.AsComponentType<Velocity>());
    }
}

public struct CharacterLayout : IEntityLayout
{
    public void Components<TList>(GameWorld gameWorld, TList list)
        where TList : IList<ComponentType>
    {
        list.Add(gameWorld.AsComponentType<MovableLayout>());
        list.Add(gameWorld.AsComponentType<LivableHealthLayout>());
        list.Add(gameWorld.AsComponentType<CurrentWeapon>());
        list.Add(gameWorld.AsComponentType<Relative<PlayerDescription>>());
    }
}

void MyStuff()
{
    var myMovable = GameWorld.CreateEntity();

    GameWorld.AddComponent(myMovable, GameWorld.AsComponentType<MovableLayout>());
    // or
    GameWorld.AddLayout<MovableLayout>(myMovable);
    // maybe we could also pass some arguments in the layout?
    // (this would mostly be useful if the layout need some initilization)
    GameWorld.AddLayout(myMovable, new MovableLayout() { Position = new(0, 42, 0) });

    var myCharacter = GameWorld.CreateEntity();

    GameWorld.AddLayout<CharacterLayout>(myCharacter);
    // We're removing Position which is in CharacterLayout->MovableLayout
    // What should happen?
    // 1. Should it throw an exception that would say to remove the layout first?
    // 2. Should it remove the layout? 
    // 
    // If 1. the user might want to brute force removing the component and the layout
    // If 2. the user might want to remove the component, but didn't knew it was part of a layout that need it
    //
    // 3. Introduce two new methods called:
    //      - IsComponentFromLayout(GameEntityHandle, ComponentType)
    //          Will return true if the component is part of the layout
    //      - ForceRemoveComponent(GameEntityHandle, ComponentType)
    //          Will have the same effect as 2. 
    //
    // The layout might also have a method called 'LayoutRemoved(bool forced)' that is fired when the layout is removed 
    GameWorld.RemoveComponent<Position>(myCharacter); 

    foreach (var entity in GameWorld.QueryWith(new[] { GameWorld.AsComponentType<MovableLayout>() }))
    {
        // will return the first and second entity of this example (if we don't take in account the fact that we removed Position)
    }

    foreach (var entity in GameWorld.QueryWith(new[] { GameWorld.AsComponentType<CharacterLayout>() }))
    {
        // will return the second entity of this example (if we don't take in account the fact that we removed Position)
    }
}

void RemoveLayout()
{
    var myCharacter = GameWorld.CreateEntity();
    // Now our character will have three layouts layouts and these components:
    //  CharacterLayout
    //      MovableLayout
    //          Position
    //          Velocity
    //      LivableHealthLayout
    //          LivableHealth
    //          BufferOf<EntityWith<HealthComponentLayout>>
    //      CurrentWeapon
    //      Relative<PlayerDescription>
    GameWorld.AddLayout<CharacterLayout>(myCharacter);

    // This will destroy MovableLayout, and so will destroy CharacterLayout
    // It will remove all non-overrided components.
    GameWorld.ForceRemoveComponent(myCharacter, GameWorld.AsComponentType<Position>()); 

    // This will remove all non-overrided components of CharacterLayout
    GameWorld.RemoveLayout<CharacterLayout>(myCharacter);

    // The tree will now become like this
    //  Position
    //  CharacterLayout
    //      MovableLayout
    //          ../../Position
    //          Velocity
    //      LivableHealthLayout
    //          LivableHealth
    //          BufferOf<EntityWith<HealthComponentLayout>>
    //      CurrentWeapon
    //      Relative<PlayerDescription>
    GameWorld.AddComponent(myCharacter, new Position());

    // This will remove all non-overrided components of CharacterLayout, so Position will be kept
    GameWorld.RemoveLayout<CharacterLayout>(myCharacter);

    // The tree will now become like this
    //  MovableLayout
    //      Position
    //      Velocity
    //  CharacterLayout
    //      ../MovableLayout
    //      LivableHealthLayout
    //          LivableHealth
    //          BufferOf<EntityWith<HealthComponentLayout>>
    //      CurrentWeapon
    //      Relative<PlayerDescription>
    GameWorld.AddLayout<MovableLayout>(myCharacter);

    // This will remove all non-overrided components of CharacterLayout, so MovableLayout (Position and Velocity) will be kept
    GameWorld.RemoveLayout<CharacterLayout>(myCharacter);
}
guerro323 commented 2 years ago

Implemented in GameHost.Simulation.V3 (not yet in the repository) Usage:

world.AddEntityLayoutModule(); // this call may not be needed in the future
var componentTypeA = world.RegisterComponent("TypeA", new LinkedDataComponentBoard(sizeof(int), world));
var componentTypeB = world.RegisterComponent("TypeB", new LinkedDataComponentBoard(sizeof(int), world));

var layout = world.RegisterLayout("MyLayout", stackalloc[]
{
    componentTypeA,
    componentTypeB
});

var entity = world.CreateEntity();

// MyLayout
//    TypeA
//    TypeB
world.AddComponent(entity, layout);

// No components and no layout
world.RemoveComponent(entity, layout);

// MyLayout
//    TypeA
// TypeB (overriden component)
world.AddComponent(entity, layout);
world.AddComponent(entity, componentTypeB);

// TypeB
// 
// TypeA and MyLayout removed
world.RemoveComponent(entity, layout);

// MyLayout
//    TypeA
//    TypeB
//
// TypeB is not overriden anymore (the behavior may change in the future to keep the overridness) 
world.AddComponent(entity, layout);

// MyLayout and TypeB removed on next archetype update
world.RemoveComponent(entity, componentTypeA);

// force the archetype update
world.GetArchetype(entity);