Scene Graph Feedback Thread

Simulate Play in the scene graph world (at least) would be huge. To be able to test simple movement like a lantern swaying without having to wait one minute for a server push every time would be a huge workflow improvement.

1 Like

I think it would make sense to have an onDestroy subscribable on the component base class.

There really are a couple of design flaws with spawn on components right now.

component_a := class(component):
    ComponentB: component_b
    OnSimulate<override>()<suspends>: void =
        ComponentB.DoSomething()

component_b := class(component):
    DoSomething<public>(): void =
        spawn{SomeLoop()}
    SomeLoop<private>()<suspends>: void =
        loop:
            Print("Tick")
            Sleep(0.0)

When will task for SomeLoop be cancelled?

  • never, it will keep running
  • when ComponentA is disposed or detached
  • when ComponentB is disposed or detached
  • something else

The answer is, that the task spawned in a method of component_b is being cancelled when component_a leaves the scene. I could easily mention several cases that show that this implicit component ownership is cursed.

Let’s say we have a player and a npc. The npc freezes the player (just an example task) and then dies/despawns in the meantime. That freeze would just cancel, even tho it is not supposed to be like that.

Or imagine a turret spawning a projectile and spawning a task that makes the bullet follow a specific object. If the turret is being destroyed, that task would be canceled and the bullet would stop moving (which doesn’t make any sense at all).

This basically makes very little sense from a design perspective. Tasks/spawn{} should not depend on a random component. IF you want to have tasks that are dependent on a component, do not make it this way. Make it explicit, like component.Spawn(Callback, Args), which then makes every programmer aware of what the resulting task is tied to.

Here is my convoluted workaround for reference because there is no way that i will ever use spawn{} again the way it currently is:

    component_task_base<public> := class<abstract><unique>:
        var Finished: logic = false

        Run()<suspends>: void
        Cancel<public>(): void
        Await<public>()<suspends>: ?any
            
    component_task(args_type: type, res_type: type) := class(component_task_base):
        Callback: type{__(:args_type)<suspends>: res_type}
        Args: args_type

        CancelEvent: event() = event(){}
        FinishedEvent: event(?res_type) = event(?res_type){}

        Run<override>()<suspends>: void =
            if (Finished?):
                false
            else:
                MaybeResult: ?res_type = race:
                    block:
                        Result := Callback(Args)
                        set Finished = true
                        option. Result
                    block:
                        CancelEvent.Await()
                        false
                FinishedEvent.Signal(MaybeResult)
        Cancel<override>(): void =
            CancelEvent.Signal()
            set Finished = true
        Await<override>()<suspends>: ?res_type =
            if (Finished?):
                false
            else:
                FinishedEvent.Await()

    advanced_component<public> := class(component):
        var TickHandle<private>: ?cancelable = false
        var QueuedTasks<private>: []component_task_base = array{}

        Spawn<public>(Callback(:args_type)<suspends>: res_type, Args: args_type where args_type: type, res_type: type): component_task_base =
            ComponentTask := component_task(args_type, res_type):
                Callback := Callback
                Args := Args

            set QueuedTasks += array. ComponentTask
            if (not TickHandle?):
                Handle := TickEvents.PostPhysics.Subscribe(SpawnTasksFromQueue)
                set TickHandle = option. Handle
            ComponentTask

        SpawnTasksFromQueue<private>(:any): void =
            for (TaskInfo: QueuedTasks, TaskInfo.Finished = false):
                spawn{TaskInfo.Run()}
            set QueuedTasks = array{}
            if (TickHandle?.Cancel()):
                set TickHandle = false

This is not a perfect solution and i wish there would be some official API for this instead, but here is some example usage:

component_a := class(advanced_component):
    ComponentB: component_b
    OnSimulate<override>()<suspends>: void =
        ComponentB.DoSomething()

component_b := class(advanced_component):
    DoSomething<public>(): void =
        Spawn(SomeLoop, ()) #This will not be randomly canceled when ComponentA is detached and is *properly* owned by ComponentB instead.
    SomeLoop<private>()<suspends>: void =
        loop:
            Print("Tick")
            Sleep(0.0)

Also for anyone that uses this code, feel free to enjoy a very nice feature, that task fails to provide called “Cancel”…

It would be nice if they changed the concept of “actor classes” or “Entities” for “DEVICE classes with events” to take advantage of the entire CREATIVE class system WITHIN UNREAL ENGINE, this has 2 benefits, unreal developers learn to use unreal devices and within unreal we have the entire class system of the devices, using the inheritance and event system of each type of class.




The system of events associated with the class type is very important in an engine. In this way, with a simple button like with BP, you can create an enter trigger or any event and think directly about what that class was made for.
If you use components regardless of the class type, you will break that and you will get rid of that way of thinking that for me is the best thing about Unreal and Godot.



So the device classes would have packaged functionalities, but above all the event system, which is very good and is very similar to the Godot signal system, for me 80% of the work is how the engine handles the game events, a trigger enter, a mouse enter, etc…

We could even have all of Fortnite in Unreal and we could use it within our game.

This would be like Godot’s reusable nodes with signals and script, but much more powerful, because creative devices have much more complex and practical functionalities…

So having that concept inside the engine would help to be able to create assets that would be DEVICE and could be reused with entities and would have a whole system of events associated with the class system, which is essential to improve and make the development of a complex game easier.

To summarize: A device that can inherit from Verse Script with an event system associated with the class system and within them they can have entities with components and those components can also be seen as a script… That would be having the best of Godot, Unity and Unreal in a single engine.

So:
a godot node is a type of class.
an unreal Device is a type of class
Captura de pantalla 2024-07-28 164048

This way you can reuse the entire unreal class system, use inheritance which is so important within unreal and make the object hierarchy within entities make much more sense and be easier to read.

But don’t just stick to the godot concept, also use the Unity concept, so that you can also add components and component scripts to those “entities” DEVICE.
Captura de pantalla 2024-07-28 164617

Above all, not losing the ability to think about inheritance when we create actors, the ability to understand what we are doing, and also, if necessary, extend those functionalities packaged in a concept of classes with event and components.




Another concept that they have to add within the graphs are animations.
Look how godot handles the animations, in the nodes, it is having references to the scene objects.
I would love to have a level sequencer, so I can create prefab-based animations that I can call whenever, wherever and however I want, without depending on what is in the scene.

In this video you can see that I have the same instance many times and each one has a reference to an animation node that refers to the objects of that scene, I change a parameter in each one independently. That’s what I mean.

As a user of Unreal Engine for more than 3 years and Godot for more than 5 years, I would not like to lose the possibility of using inheritance in actors, having the entire system of events associated with the class type and not losing that way of thinking about actors and how they relate to reusable logic using inheritance.

I was also testing it and for some reason I couldn’t add a verse script component, it was buggy.

That’s my feedback that I can give you for now, but later I will tell you more things.
Sorry, if you don’t understand, my English is very bad, my native language is Spanish.

Thanks @Daigoro. You’re absolutely correct. This is just an implementation detail but the Verse tasks are mapped to a set of “Verse context” objects within the engine. Looking through the scene graph code I think these are set up with far too fine a granularity, which is what causes the issues you’re highlighting. I’ve logged an issue internally to rethink how we’re scoping out and cancelling tasks related to the scene graph. My initial thinking is that we should just have one context for the whole scene graph, scoped at the Simulation Entity level. That way the only time tasks are cancelled is when we’re resetting the map, not when objects within the map come and go.

2 Likes

That’s a great idea totally in my favour. If you want to add tasks that are tied to components back in at a later point, then i guess it might be the best to just wait until we get lambda support for Verse and add a component.Spawn or entity.Spawn function, that takes a suspending lambda as argument.

Thanks for sharing some insight and have a nice day.

Hey all,

EDIT: I think I identified the issue! If I name the root entity first in a prefab, the error in the video below happens when I choose a mesh. I have to connect the mesh first to the generated name and then rename the entity. Did I miss this as an error somewhere? I know there is a renaming bug, but didn’t think that cascaded down to a component mesh add…

Posted this issue on the forums, a.k.a. it won’t let me add my custom mesh as a component and reverts back to a cube:

It turns out starting a new project and trying the exact same thing with the same .fbxs and it suddenly works as intended. The original project was not complex at all, a few dozen objects and a camera at most. So I don’t think it’s me doing something wrong as before… Can post this in bugs but figured the feedback thread may be better given the experimental tag.

Sorry for double dipping so quickly, but I have a separate feedback / request.

The game we’re developing is almost all diagetic:

We’ll need our diagetic UI to be moving with the camera, which is easily done with traditional UEFN outliner stuff by having our geo attached as children to the camera as seen in that clip…

Prefabs don’t attach to actors of course… Is it planned to have a camera entity in the future and/or an entity to be attached to a camera at any point?

Not sure how I can detect collision after I added collision components. There is no documentation available: [DOCUMENTATION] Missing documentation for all scene graph components

Could you provide a basic example? Or at least let us know how it should work top-level / in theory.

Collision detection capabilities for creators are not provided yet. Those are work in progress and very likely to be opened up together with the announcement of physics.

Is there a way that we can change the collision preset on the collision component?