Working with components / ECS?
Posted: 13 Nov 2021, 04:27
Working in a custom tech stack, and game objects are built out of components.
Right now, my view model setup is very OOP - e.g. every object that can be seen in space has a base class view model
And each derived class implements UpdateValues, which allows the view model class to look into the registry of components/entities, using its own Entity to figure out which component(s) it owns, and setting the corresponding values, i.e. mirroring values from components into these VM classes.
My question is; have you seen people work with components differently? I'm quickly running into situations where I might have a derived VM class that does everything I want but has _some_ properties and xaml bindings a particular entity can't support due to lacking components, so it's not a good fit; necessitating yet another derived class and leading to the OOP hell I'm using ECS to escape. Can I have optional elements in XAML that somehow know if the underlying VM has those properties?
Example of a derived class
Some of the functionality here like selection would be useful for other objects like planets, but... those won't have shields. You can see the madness it would lead to. I'd love to reuse some user controls for e.g. object overlays in the world but have optional elements (e.g. shield health bar) drawn from some more generic VMs with optional bindings based on the presence of components (which I can check using the ECS reflection system by name - so if I have a binding for "Shield" I can search for the corresponding values. But I don't know if in Noesis you can go in that direction - declaring bindings in xaml and using the binding name to iterate C++ reflection structures?)
Thanks.
Right now, my view model setup is very OOP - e.g. every object that can be seen in space has a base class view model
Code: Select all
struct SpaceObjectModel : public DeferredNotifyBase
{
public:
EntityID entityID_ = INVALID_ENTITY;
virtual void UpdateValues(const Entity& entity);
private:
uint32_t GetEntityID_Converted() const;
float GetScreenPosX() const;
void SetScreenPosX(float screenPosX);
float GetScreenPosY() const;
void SetScreenPosY(float screenPosY);
Noesis::String name_;
float screenPosX_ = 0.0f;
float screenPosY_ = 0.0f;
NS_IMPLEMENT_INLINE_REFLECTION(SpaceObjectModel, DeferredNotifyBase, "rtg.SpaceObjectModel")
{
NsProp("Name", &SpaceObjectModel::name_);
NsProp("EntityID", &SpaceObjectModel::GetEntityID_Converted);
NsProp("ScreenPosX", &SpaceObjectModel::GetScreenPosX);
NsProp("ScreenPosY", &SpaceObjectModel::GetScreenPosY);
}
};
My question is; have you seen people work with components differently? I'm quickly running into situations where I might have a derived VM class that does everything I want but has _some_ properties and xaml bindings a particular entity can't support due to lacking components, so it's not a good fit; necessitating yet another derived class and leading to the OOP hell I'm using ECS to escape. Can I have optional elements in XAML that somehow know if the underlying VM has those properties?
Example of a derived class
Code: Select all
struct ShipObjectModel : public SpaceObjectModel
{
public:
float GetIntegrity() const;
void SetIntegrity(float integrity);
float GetMaxIntegrity() const;
void SetMaxIntegrity(float maxIntegrity);
float GetIntegrityPercent() const;
float GetShield() const;
void SetShield(float shield);
float GetMaxShield() const;
EShieldChargeMode GetShieldMode() const;
void SetShieldMode(EShieldChargeMode shieldMode);
bool GetIsSelected() const;
void SetIsSelected(bool bIsSelected);
void SetFactionID(FactionID factionID);
bool GetDidIntegrityDecrease() const;
virtual void UpdateValues(const Entity& entity) override
{
if (entity == INVALID_ENTITY)
{
RTG_ERROR("Trying to update ShipObjectModel without a valid associated Entity");
return;
}
SpaceObjectModel::UpdateValues(entity);
const HealthComponent& healthComponent = entity.GetComponent<const HealthComponent>();
SetIntegrity(healthComponent.integrity);
if (const ShieldComponent* pShieldComponent = entity.TryGetComponent<const ShieldComponent>())
{
SetShield(pShieldComponent->currShield);
SetShieldMode(pShieldComponent->chargeMode);
}
SetIsSelected(entity.HasComponents<const IsSelectedComponent>());
if (const FactionComponent* pFactionComponent = entity.TryGetComponent<const FactionComponent>())
{
SetFactionID(pFactionComponent->factionID);
}
else
{
SetFactionID(INVALID_FACTION_ID);
}
}
private:
uint32_t GetFactionID_Converted() const;
FactionID factionID_ = INVALID_FACTION_ID;
float integrity_ = 1000.0f;
float maxIntegrity_ = 1000.0f;
float shield_ = 1000.0f;
float maxShield_ = 1000.0f;
EShieldChargeMode shieldMode_ = EShieldChargeMode::Full;
bool bIsSelected_ = false;
bool bDidIntegrityDecrease_ = false;
NoesisApp::DelegateCommand objectSelected_;
NS_DECLARE_REFLECTION(ShipObjectModel, SpaceObjectModel)
};
Thanks.
