I need a way to swap control to vehicles, characters, and enemies of different class types. So I moved input commands to a player controller that possesses other objects with a “possessable” component attached to them.
It works, but I realized a flaw. By having a single possessable component, it makes per-possessable-character logic awkward (vehicle powers down, character triggers sit animation, etc). I also realize it’s a bit odd that I’m possessing a component on characters, rather than the characters directly.
So now I’m now thinking it would make more sense to have all characters/vehicles inherit from a base class that has possession logic. But I’d like to know; Is there’s a better way to go about this entirely?
EDIT: And now I’ve learned what multiple inheritance is, and that GDScript doesn’t support it…
PlayerController singleton:
extends InputProvider
## Routes player input to the currently possess character or vehicle
## Overrides getter methods from InputProvider that playable characters
## know to look at for input
# Updates the target for the follow cam rig
signal possession_changed(new_subject: Node3D)
var possessed_subject: Node = null
## Called externally - depossess current subject if any, and possess new subject
func possess(target: Node) -> void:
if possessed_subject:
possessed_subject.on_depossess()
possessed_subject = target
possessed_subject.on_possess(self)
possession_changed.emit(target.get_parent()) # follow cam targets the character root
func get_move_direction() -> Vector3:
return Input.get_vector("move_left", "move_right", "move_forward", "move_backward")
func get_jump_action() -> bool:
return Input.is_action_pressed("jump")
The Possessable component:
class_name Possessable
extends Node
## Provides the ability for character/vehicle/etc to be possessed
var character: Node = null
func _ready() -> void:
character = get_parent()
character.set_physics_process(false)
func on_possess(new_controller: PlayerController) -> void:
character.is_possessed = true
character.input_provider = new_controller
character.set_physics_process(true)
func on_depossess() -> void:
character.is_possessed = false
character.input_provider = null
character.set_physics_process(false)
You could make the posessable class a base class for subcalsses specific to character types. Like:
class_name PosessableVehicle
extends Posessable
func on_possess(new_controller: PlayerController) -> void:
# Vehicle specific code here
super.on_possess(new_controller) # call on_possess in the base class to run the non-vehicle specific code
Yeah that was the direction I was thinking, but I’ve just learned about multiple inheritance and that it’s unsupported in GDScript. So I can’t have a CharacterBody3D, VehicleBody3D, etc inherit from this.
Though I suppose it’s simple enough code to just type it out for each base class and be done with it.
Yeah, I meant if you keep it as a component, not as a base class for the characters themselves, then it could work.
edit: I think it’s normal to use components like this, character scripts can become huge and messy if you want everything in the attached script. But that’s just my opinion. There might be better way to do it..
I see. Well that’s certainly one option to consider, and would mean only needing to maintain one source of the, admittedly simple, logic. I’ll have to think on it. I’m still having second thoughts about composition for this. Especially as I think about what would go inside the on_possess and on_depossess methods. The component would either need to know more about its parent to have it perform various actions, or call some methods on the parent, in which case, I might as well just have the possession related methods directly on the parent.
By your description, what happens when something is possessed differs between possessable entity types. So the functionality cannot be reduced to a single unified component. Best to handle possession logic separately for each distinct type of possessed entity and then just switch that logic on or off depending on the possession status. If you want to handle it via node components, you’ll need multiple types of possessable components.
Does this even make sense as a component? It would either need to know about its sibling components (eg play “sit_down_in_vehicle” animation) or call an on_possess() method on the parent. But then if I define on_possess() on the parent, I might as well skip the component all together.
My current thinking is that I should self impose a policy that both vehicle and character base classes will have on_possess(): pass and on_depossess(): pass, then overwrite as needed. Is there any reason this wouldn’t be a good idea? I know repeating code isn’t good practice, but what about simply defining some methods?
It totally doesn’t have to be a component. If from your current perspective it looks good to do it by implementing some functions - just do that. You can always refactor into components later.
Nothing wrong with a bit of repetition. It’s always better to have some repetition than to pick a wrong set of abstractions. DRY principle is often misunderstood. Here’s a quote (I forgot the source):
DRY isn’t about code - it’s about eliminating duplication of knowledge and intent - expressing the same idea in different ways across your system.
Got it, then I’ll go with my gut. I think I just needed to hear that, to be honest.
I’ve noticed from many of your replies on this forum that you’re all about making shit work, rather than over analyzing the problem. That’s something I get stuck on sometimes and I end up thinking about code instead of writing it.
Character still doesn’t need possession logic but it can optionally include its own possessed and unpossessed transition behaviour:
class_name CoffeePot extends CharacterBody3D
func _ready():
...
# you can include or omit this, it doesn't rely on inheritance.
$"possessable".attach_possession_hooks(
(func():
if (possessor_is_bot):
trigger_blink_lights_yellow()
else:
trigger_blink_lights_blue()
automated = true)),
(func():
trigger_blink_lights_red()
automated = false))
)
Alternatively you can make possessed and depossessed callables instead of signals and set each one individually as properties from the character script, or do it the caveman way and manually attach the signals to from the signals tab on the editor.