First, congratulations on the being the first non-me post in the Dinghy forums
Secondly, I’m really interested in this line:
I had the moment of enlightenment shortly thereafter. “Scene” means Unity Scene, not Godot Scene.
I forgot that people that may try out Dinghy may be coming from Godot, where yeah Scene definitely means something different (more like a Unity prefab). I’m wondering if maybe I should rename Scene to Stage to avoid the collision in naming/understanding of the general concept?
Onto the code!
Your intuition was mostly right though in your first example — a ball is in a scene (though not that the ball is the scene). As far as I can tell the first option you sent in your code works fine — this is what I see:
For custom functionality, the question is more “where you want the functionality to be/what is the custom functionality”. Puppeting an entity’s behavior through the scene it’s inside of is fine (though maybe unmanagable if you have lots of entites).
For more complex custom behavior/interactions, you’d want to either create an entity that has that specific functionality and is put together through ECS components + systems, or through working with something that I devised as a bit of hack, which is to use ManagedComponent.
ECS/Systems Way
However, for your example the main thing it looks like you’re trying to custom do is adjust the position based on the velocity. Dinghy does right now have a default velocity system in (system in the ECS sense), so by trying to update the velocity on top of the velocity you’re like getting a strange result there. Additionally, Entities don’t get any Update call to them, they are only acted on externally by Systems (or otherwise other logic). The thing I would suggest in your model based on the above options is to write a system that updates the velocity based on the wall collisions, and have another component that represents Grab state. So something like this:
//add the component
public record struct Grabbed (bool value);
//elsewhere you create a shape, add the component to the shape
_shape.ECSEntity.Add(new Grabbed(false));
//set grabbed either in your scene with input callbacks
ref var grabbed = ref _shape.ECSEntity.Get<Grabbed>();
grabbed.value = true;
//then define a system that acts on this
public class WallBounceSystem : DSystem, IUpdateSystem
{
QueryDescription query = new QueryDescription().WithAll<Active,HasManagedOwner,Position, Velocity,Grabbed>();
public void Update(double dt)
{
Engine.ECSWorld.Query(in query, (Arch.Core.Entity e, ref HasManagedOwner owner, ref Position pos, ref Velocity vel, ref Grabbed grabbed) => {
//do your wall check/vel update stuff in here
//this line is only important if you access X/Y of the entity outside of a system
//owner.e.SetPositionRaw(pos.x,pos.y,pos.rotation,pos.scaleX,pos.scaleY,pos.pivotX,pos.pivotY);
});
}
}
You’d then need to register that system with the game’s systems:
Engine.RegisterSystem(new WallBounceSystem());
This may however still act weird with the velocity system, which is also why I’ve been considering taking the velocity system out of the core engine stuff because it’s likely that devs may want to handle it somehow themselves.
Managed Component way
I don’t think working with ECS is “cozy” in the way I want Dinghy games to be (see some of what I’m thinking here), so something that’s in the engine as a bit of a short-term concession is the concept of a ManagedComponent. This is just a component that directly receives PreUpdate/Update/PostUpdate calls, acting more like a Unity-style monobehavior. You can define a component by extending Component:
public class WallCollision(Entity parentEntity) : Component(parentEntity)
{
public override void PreUpdate() {}
public override void Update() {}
public override void PostUpdate(){}
}
And inside those functions write your logic. You can then add this to any entity like:
_shape.ECSEntity.Add(new WallCollision(_shape))
Future
The ECS way is technically the “correct” way but is definitely a little weird from an API standpoint because it requires you to understand a lot about the way ECS/Dinghy’s ECS works in order to set it up. The ManagedComponent way feels more familiar coming from Unity/Godot but also long term will remediate a lot of the pitfalls with that type of design (state is everywhere, hard to track logic flow, etc.). My goal is definitely doing something more “magic” here to allow you to easily author correct ECS “types” and connect up systems, as straddling the various ways of doing things now can quickly get sticky (like you’re seeing).
Let me know how you end up doing this and what you think of the above! And just to surface two possible API changes from this:
- Rename “Scene” to “Stage” to avoid conceptually mismatch for people coming from Godot vs. Unity
- Get rid of the default Velocity system — core systems should just handle rendering, not behavior