How would you do Godot-friendly composition/aggregation here?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By bitbutter

I’d love some feedback on how i could better handle the kind of situation i’ll describe below:

I have a game in which several of the game elements can hold a single gem. (Holders of gems don’t all inherit from the same base node type). I’d like all holders of gems to have a collection of public methods for handling the gaining or relinquishing of gems, and to be able to respond to other game objects if they need to query whether the gem holder is currently holding a gem.

Methods might include

set_has_gem(bool), get_has_gem(), send_gem_sprite_to(screen_position)

My first attempt: I created a godot scene to add as a child node (emulating a component) on all entities that need to be endowed with these gem_holder methods. The scene would be called gem_holder.tscn, and attached to it would be the script gem_holder.gd where the related variables and methods would be defined.

Next, I’d attach this scene to objects in the game that need the gem handling abilities.

I first thought that other objects in the game that need to call the gem handling methods on gem holder objects would use propagate_call on the parent gem holder, e.g.

a_gem_holder.propagate_call("set_has_gem",[false])

this call would be handled by the gem_holder scene/script because it implements set_has_gem. propagate_call was initially attractive since this way I didn’t need to specify a node path to the gem_holder child node (which would be brittle, the location is likely to change).

The first difficulty is that since propagate_call passes the method call on to all children of the parent node, it doesn’t (afaik?) relay method return values to the calling context. This is a problem for what i’m trying to do since the calling objects often need to know the result of their calls to the gem_holder methods. (e.g. querying whether the object currently has a gem or not)

I hope i’ve sketched enough of the situation that it’s clear what i’m trying to do and why. I hope more experienced Godot devs can recommend a different approach that they’d take.

This isn’t great, but what I’m doing at the moment is this:

  # Behaviour.gd
    extends Node
    class_name Behaviour
    var local_base # the 'owner' node
    func _ready():
    	local_base = get_node("../../") # assume 'behaviours' node contains all behaviours

On base classes that need to support behaviours (this is duplicated in several places)

func do_behaviour(methodname,args = null): 
	for c in $behaviours.get_children():
		if c.has_method(methodname):
			var r
			if args!=null:
				r = c.call(methodname,args)
			else:
				r = c.call(methodname)
			if(typeof(r)==TYPE_ARRAY):
				return(r[0])
			else:
				return r
			break

And to call a behaviour on an object that (maybe) supports it:

var has_gem = target_node.do_behaviour("has_gem")

bitbutter | 2020-11-20 09:41