What would be the best way of attaching behavior

Godot Version

4.3

Question

What would be the best way of going about this task?

func _on_body_shape_entered(body_rid: RID, body: Node, body_shape_index: int, local_shape_index: int) -> void:
	if body.has_method("hit"):
		body.hit()

This is a falling spike which will call the hit method on the object that enters. Is their a better way of doing that, and finally how would be the best way of making the function. I think classes would be the way to go, but if i had for example this class:

class_name hit

func hit():
    pass

But how would i take advantge of this class. I am stuck on how to actally use this class and attach it to something.

“Best” depends on what you ultimately want to achieve. If your method works, what friction has it caused?

The friction would be adding arguments to the function, having to update it everywhere…

Ah, sadly that will always happen with functions, unless you add new arguments with a default parameter. I suppose using class_name could at least catch missing arguments before running the game, but then every body entering it has to be extended of a “Hittable” class, may be a deal breaker for some systems.

So instead, i should think of going about it in a different way? Could you give me some pointers maybe?

I doubt you will have to use a different solution, the issue adding function parameters is almost inherit in programming languages, that will affect you everywhere in programming. Your sample code is perfectly reasonable.

You could use it as-is, the pros being that it’s simple and not limiting, only requiring a function definition. cons are that it could be hard to debug, say you misspell a func hut, your code would simply ignore the collision, though this is an equal issue with the alternatives.

There are usually two schools of systems in games, Godot as an outlier prefers inheritance, where Unity and Unreal use a component based approach to system design.


Inheritance

You could use an inheritance based systems like so

# Colliding body
extends CharacterBody2D
class_name Hittable

var health: int = 100

func hit(damage: int) -> void:
    health -= damage
    if health <= 0:
        self.queue_free()

With the class_name you can detect the type of body with is, and the Hittable class can be extended for more variables or a more complex hit function, so long as it has the same number of arguments. This also has auto-complete because it uses the static type system.

func _on_body_entered(body: Node2D) -> void:
    if body is Hittable:
        body.hit(10)

Composition

In a composed system you would give a child “Node” a script for health or taking damage, then you can check if the colliding body has that node, or use metadata to get the component.

# Child of colliding body
extends Node
class_name HealthComponent

var health: int = 100

func hit(damage: int) -> void:
    health -= damage
    if health <= 0:
        get_parent().queue_free()

Shockingly similar to the inheritance script, but must be retrieved differently. The pros for composed systems is that the HealthComponent can be added as a child to any other node, CharacterBodies, RigidBodies, even StaticBodies. It does not interfere with the parent node’s type. The con being more difficult to manage, this incurs more scene tree searching than the other methods.

func _on_body_entered(body: Node2D) -> void:
    var health: HealthComponent = body.get_node_or_null("HealthComponent")
    # or via metadata `body.get_meta("HealthComponent", null)`
    if health:
        health.hit(10)
3 Likes

You can make it more type-safe instead of using String references, see my article on The Godot Barn