#Design help for how to implement a component-style architecture for a specific use-case

1 messages · Page 1 of 1 (latest)

subtle rock
#

Hi! So, in my game, I have a feature where I want the player to be able to click on any metal object in the world and have it react (change shader).

Currently, I have a node called 'MetalComp' that I plan to use as a component that i'll attach to scenes that represent metal objects. I figured this would be appropriate since i'm essentially attaching behavior to a node that gives it the 'property' of being metal.

Here's the script attached, metal_comp.gd

class_name MetalObjectComp extends Node

@export var hint: HighlightHint
@export var sprite: Sprite2D

signal metal_body_clicked

func toggle_selected():
    var mat = sprite.get_material()
    if mat.get_shader_parameter("onoff") == 0:
        mat.set_shader_parameter("onoff", 1)
    else:
        mat.set_shader_parameter("onoff", 0)

func toggle_hint():
    if sprite.get_material().get_shader_parameter("Alpha") == 0:
        hint.enable_hint()
    else:
        hint.disable_hint()

I also have a node attached to the player, Painter, that acts as the controller for these 'click' events. My question is, how can I effectively link the Painter to the nodes with the MetalComp component?

If the root node of the target was MetalComp, I could just do if node is MetalComp, but since it's a child of the target node, I'm struggling to think of a clean way to do this.

#

Here's the relevant code in painter.gd

func _input(event: InputEvent) -> void:
    if state == PAINTER_STATE.ACTIVE and event.is_action_pressed("select"):
        # iterate over the bodies in the selection area under the mouse
        # if they're metal, mark them as selected and invert their shader
        # accordingly
        var bodies: Array[Node2D] = selection_area.get_overlapping_bodies()
        for i in bodies:
            if i is MetalObject: # ??? what to put here?
                i.toggle_selected() # FIXME: this should _probably_ be a signal, but this is a weird case
                print("Clicked on %s", i)
#

and the scene tree for a node with the MetalComp property

icy sluice
#

I don't know if it's a good solution but what I do in such cases is in component's _ready set its parent's metadata to reference the component
this way your painter can have something like body.has_meta(MetalObjectComp.META_KEY) (with meta_key being a const that holds the key used for this components metadata reference) 😭

subtle rock
#

metadata? is that a gdscript property i'm unaware of?

icy sluice
#

it's a feature of Object, basically like a dictionary

#

you can add key-value pairs to it, test their existence, and retrieve them

#

well and delete them

#

it's in the inspector too, at the very bottom of a node's inspector you can view and edit its metadata

subtle rock
#

Cool, and in that case should it make the actual call itself via signal or method?

#

because I guess the method would also require hard_coding the call the the component node, but i'm not sure where i'd wire the signal

#

This works, but unsure if it's idiomatic

        for i in bodies:
            if i.has_meta("IS_METAL"):
                i.get_node("MetalObjectComp").toggle_selected() # FIXME: this should _probably_ be a signal, but this is a weird case
                print("Clicked on %s", i)
func _ready():
    parent.set_meta("IS_METAL", true)
icy sluice
#

what i was proposing is to parent.set_meta("IS_METAL", self), so you could get the component without get_node

subtle rock
#

ohhhh

icy sluice
#

because other nodes having to be aware of another node's children names is not very good i think

subtle rock
#

yeah, that makes a lot more sense

#

sick, that works

#

I like that solution, thank you!

icy sluice
subtle rock
#

I guess because "call down, signal across/up" would say it should be

#

but I guess it's a unique case

icy sluice
#

I think it's fine here because we do have a direct reference already

#

and i don't even know if it can be done with a signal cleanly here

#

like you'd have to connect, emit, and disconnect, effectively just doing a call with extra steps

subtle rock
#

yeah

#

I agree

#

Thank you!