addmix / godot_aerodynamic_physics

MIT License
80 stars 4 forks source link

Question about having AeroBody3D as a root node and attaching my own script to it #14

Closed ilyapetrovMO closed 11 months ago

ilyapetrovMO commented 11 months ago

Have been using your library for a bit, and had lots of fun with it without any issues. Thank you for your work! I am a Godot newbie, so I hope my question/problem is not too dumb:

When creating a spawnable scene (be it an airplane, tank, bunker or whatever else) I try to follow the principle of composition. That means I have a bunch of specialized nodes with their own classes and logic, and all of them are "orchestrated" through the root node methods and events. It makes sense to use AeroBody3D as the root node, since everything in the scene will be moving relative to it. But now, since the root node is an AeroBody3D, I cannot attach my own script to this node. As a hack I've been using the following structure:

image

In my understanding, it would be better to have AeroBody3D as a complete standalone node (which requires cpp). But as a fast workaround you can also make AeroBody3D extend Node, but require a VehicleBody3D through an @export directive, like so:

image

This is also hacky and requires a way to register an _intergrate_forces handler:

extends VehicleBody3D

var my_custom_integrator: Callable

func _integrate_forces(state: PhysicsDirectBodyState3D) -> void:
    my_custom_intergrator.callv([state])

# and then in aerobody:
func _ready() -> void:
    body.my_custom_integrator = _integrate_forces

And I have to slightly modify the AeroBody3D script, meaning potential conflicts if you modify this part of the code in the future.

So my question is, am I overthinking this, or is this an actual limitation of sorts that I should work around in other ways? Or of course there might have been a way to use AeroBody3D as a root node and also have my own script attached to it, and I just don't know how to do it.

addmix commented 11 months ago

Ah, I see your predicament, it's actually rather simple to avoid. With built-in nodes, you can use the "attach script" button, but where a script is already present (as part of a plugin, or your own game logic), you need to use the "extend script" button, which will create a script that extends the existing script. image image

This is actually very useful for more than just this use case, as you can extend scripts that are not declared as a class. image image

In fact, this principle of extending a script, rather than a class is the core functionality of Godot mod loading, and can help in your case where modification of the AeroBody node is required.

I'm not sure to what extent you are modifying the AeroBody, but I would suggest rather than modifying the script directly (which will cause issues if you ever need to update to a newer version), you can instead make a script that extends the AeroBody class, and then replace/add to the existing functions as needed.

Here's an example

#this script is saved separately from the plugin, if you delete/update the plugin, the logic contained within will be unaffected.
extends AeroBody3D
#this doesn't require a new class declaration

#_integreate_forces is already declared in AeroBody3D, so to compensate for this, and retain the functionality, we call it on the parent class
func _integrate_forces(state : PhysicsDirectBodyState3D) -> void:
        #code can be added here to extend functionality

        #call _integrate forces on the parent class to retain functionality
        super._integrate_forces(state)

        #code can be added here to extend functionality

Or, in a case where you need to change the function of the existing code, you can omit the super._integrate_forces(state) call, and copy/paste the existing function, modifying as needed. (this will require more modification as updates change existing code)

#_integreate_forces is already declared in AeroBody3D, because we aren't calling it on the parent class, we need to copy over the existing logic, to then modify as needed.
func _integrate_forces(state : PhysicsDirectBodyState3D) -> void:
        current_gravity = state.total_gravity
    var total_force_and_torque := calculate_forces(state)
       #i can add or modify functionality here.
    apply_central_force(total_force_and_torque[0])
    apply_torque(total_force_and_torque[1])

These approaches have an added benefit that you keep your code decoupled, logic of one feature, which requires modification of the AeroBody class, won't be present or interfere with the base AeroBody class, and by extension other features that extend the AeroBody class will not be affected by any of these changes. This is a pattern I personally use a lot.

One unrelated thing that I notice from your screenshots, is that your wings have no sweep. The plugin does account for wing sweep in a few ways, mostly relating to drag, so it's best if you mimick the sweep angle of your wings. And remember that the wing visual aid isn't always going to be correct depending on the type of wing and it's placement.

addmix commented 11 months ago

Marking as closed

ilyapetrovMO commented 11 months ago

Thank you for the explanation, I knew I was overthinking this. For now, the "mig 21" is a mig just in name, didn't spend any time configuring anything related to the aerosurfaces just yet. But I didn't know it accounted for the sweep, I'll definitely keep that in mind.

Thanks again for the detailed, and helpful, response.