Search code examples
godotareagdscript

Godot - Player affect only one area (when there is more areas which should affected)


My Problem:

  • My areas "Black Hole" are shrinking when "Player" going body_entered and stops when body_exited - but ONLY ONE Black Hole are working, and shrinking even when i go throgh another one.

Main Scene

When i go through every "Black_Hole's" only one is shrinking (that 1st. one)

Explanation image:

  • Red Circle - Player
  • 1 - Black_Hole
  • 2 - Black_Hole2
  • 3 - Black_Hole3

Black_Hole Scene

Gameplay

My expectetion:

  • Each "Black_Hole" shrinking (coorectly with animation) like "standolone" object - independent area from each other

Accordings to the comments - Script of "Black_Hole":

extends Spatial

var animation_finished
var scale_down_black_hole
var animation_played
onready var namex = get_name()
onready var group_holes = get_node("Group_Holes")
var group_black_holes = group_holes.get_child()

func _ready():
    animation_finished = false
    scale_down_black_hole = false
    animation_played = false
#   print(get_name())
    print(group_black_holes)
    
    $AnimationPlayer.stop(true)
#   $AnimationPlayer.stop("Animation_Black_Hole") #kod pauzuja̧cy animacjȩ
#   $AnimationPlayer.play("Animation_Black_Hole") #kod wznawiaja̧cy animacjȩ
    pass

func _process(delta):
    if scale_down_black_hole:
        $AnimationPlayer.play("Animation_Black_Hole")
        animation_played = true
    elif !scale_down_black_hole:
            $AnimationPlayer.stop(false)
    if animation_finished:
            queue_free()
        
#       print($Mesh_Black_Hole.scale)
#   print(animation_finished)
    pass


func _on_AnimationPlayer_animation_finished(Animation_Black_Hole):
    animation_finished = true
    print("End_Animation_Black_hole")
    pass # Replace with function body.




func _on_OmniLight_signal_black_hole(black_hole):
    if black_hole:
        scale_down_black_hole = true
        print("scale_down_black_hole_True)
    if !black_hole:
        scale_down_black_hole = false
        print("scale_down_black_hole_False")
        
#       if !black_hole:
#           scale_down_black_hole = false
    pass # Replace with function body.

(with var group_holes (etc.) i am experimanting right now ... so is n that sreennshot...)


Solution

  • I take that the code is the script attached to the "Black_Hole" Area, despite the script being set as Spatial. That is something you would want to set correctly.

    Being the script attached to the "Black_Hole" Area, it makes sense that it uses $AnimationPlayer, since the "Black_Hole" Area has an AnimationPlayer child not surprisingly called "AnimationPlayer".


    On node references

    I want to comment this line:

    onready var group_holes = get_node("Group_Holes")
    

    That the "Black_Hole" Area does not have a child called "Group_Holes". The line as is would work if the script was attached to the "Terrain" Node.

    I do not recommend to reach for a Node that is not a child of where your script is attached. If you need to do it regardless, you need a node path that goes up (".."), see NodePath. and Understanding node paths.

    The reason why I don't recommend it is because it is fragile (i.e. if you move the nodes around in the scene, the code is likely to break because it would be pointing to something that you moved and thus no longer there). See Node Communication.

    You might also be interested in node groups.


    On this line:

    var group_black_holes = group_holes.get_child()
    

    I suppose you mean to use get_children(). I suppose this is not being highlighted as an error because you are not using types. Consider using types.


    Node connections

    Alright, the main act. You play the animation from this line:

    $AnimationPlayer.play("Animation_Black_Hole")
    

    Which the execution flow would only reach if scale_down_black_hole is true:

        if scale_down_black_hole:
            $AnimationPlayer.play("Animation_Black_Hole")
    

    And you only set scale_down_black_hole to true here:

    func _on_OmniLight_signal_black_hole(black_hole):
        if black_hole:
            scale_down_black_hole = true
    

    In Godot value is considered false if it is zeroed (e.g. null). So the execution flow only enters the conditional when black_hole is not zeroed.

    Since there is one black hole that works, I believe black_hole is not zeroed.

    However, since that one black hole that works, works regardless of with which you interact, it suggest that only black hole is getting all the signals.


    If I understand correctly, you are going through some hoops to make that work. That is, instead of have the Area connect the signals to itself, you have them connected to an OmniLight, which then emits a signal that is supposed to be connected back to the Area.

    Thus, presumably (I don't have the code on the OmniLight script to confirm), the black_hole argument is the Area that originally emitted the signal.

    This is how I imagine the code in the OmniLight script looks light:

    func _on_Black_Hole_body_entered(body):
        # some other code
        emit_signal("signal_black_hole", black_hole)
    
    func _on_Black_Hole2_body_entered(body):
        # some other code
        emit_signal("signal_black_hole", black_hole2)
    
    func _on_Black_Hole3_body_entered(body):
        # some other code
        emit_signal("signal_black_hole", black_hole3)
    

    Which is not great. Using a Signal bus we can change it, I'll get back to this.

    A fix for this, without changing the approach, would be to make sure all the black hole Areas are connected to the OmniLight, and that the OmniLight signal is connected to all of them as well. And passing what they should.

    And to make sure only the black hole you want fires, you should check it is the intended target:

    func _on_OmniLight_signal_black_hole(black_hole):
        if black_hole == self:
            scale_down_black_hole = true
    

    Change of approach

    Alright, I would suggest to change the approach. Having to connect every black hole is error prone. When you want to add more black holes to your scene you might forget to connected them or connect them incorrectly, as I believe you did. Thus, let us remove the need to make those connection.

    I'll talk about the connection from the OmniLight to the Areas first. It is important, although the code will change once we introduce the signal bus…

    If the OmniLight has a reference to the black hole (which I believe it does), it can call a method on it. For example:

    func _on_Black_Hole_body_entered(body):
        # some other code
        if body.has_method("_on_OmniLight_signal_black_hole"):
            black_hole._on_OmniLight_signal_black_hole(black_hole)
    
    # and so on for the other black holes
    

    And thus, you don't need to have a signal in the OmniLight that the Areas connect to. With that we get rid of half the connections you have to make. Although the code here is not great yet.

    I'll also mention that you don't need to pass the black hole itself. And - of course - you could pick a better name for the method.

    But why do you do this anyway? You can connect the back hole Areas "body_entered" signal to themselves. And in doing so, allowing them to work without the OmniLight. I understand you might need to notify the OmniLight anyway, so let us address that next.


    If you need the connection from the Area to the OmniLight, I'm going to suggest a Signal Bus (event bus). So you would have an autoload where you define the signal, then all the black_hole Areas emit it, and the OmniLight or whatever connects to it, without they knowing each other.

    Assuming you have created an autoload SignalBus with a trigger_black_hole signal like this:

    signal trigger_black_hole(black_hole)
    

    The black hole Areas can connect their own "body_entered" signal to themselves in the black hole scene, and there emit the signal in the SignalBus:

    func _on_Black_Hole_body_entered(body):
        SignalBus.emit_signal("trigger_black_hole", self)
    

    Consider starting the animation there too.

    And the OmniLight can connect to it from code:

    func _ready() -> void:
        SignalBus.connect("trigger_black_hole", self, "_trigger_black_hole")
    
    func _trigger_black_hole(black_hole) -> void:
        # whatever
    

    If the OmniLight needs to call a method on the black hole (for example to have it start the animation), it can do so there too.

    You could also pass the body if necessary, by adding a parameter in the definition of the signal, passing it as argument when you emit it, and - of course - adding it to the method that receives the signal.

    Using the signal bus, you don't need to connect the signals, so you don't have to remember to connect the signals, and you won't connect the signal wrong either.


    I'd advice to have everything that the black holes need to work contained in their scene. In other words, I would advice against them depending on the OmniLight to work correctly.


    Addendum

    Assuming you are sending a signal like this:

    func _on_Black_Hole_body_entered(body):
        SignalBus.emit_signal("trigger_black_hole", self)
    

    Where you pass self as argument. And you receive the signal like this:

    func _ready() -> void:
        SignalBus.connect("trigger_black_hole", self, "_trigger_black_hole")
    
    func _trigger_black_hole(black_hole) -> void:
        # whatever
    

    The argument black_hole is the object that sent the signal (i.e. the black hole Area). So you can call methods on it, for example, you have a something method that takes a value:

    func _on_Black_Hole_body_entered(body):
        SignalBus.emit_signal("trigger_black_hole", self)
    
    func something(value):
        # whatever
    

    Then you can call it like this:

    func _ready() -> void:
        SignalBus.connect("trigger_black_hole", self, "_trigger_black_hole")
    
    func _trigger_black_hole(black_hole) -> void:
        black_hole.something(value)
    

    Another thing you can do is connect another signal, but on the opposite direction. I'll define a reply signal on the Signal Bus:

    signal trigger_black_hole(black_hole)
    signal reply(black_hole, value)
    

    So we can connect it on the Area:

    func _ready() -> void:
        SignalBus.connect("reply", self, "_on_reply")
    
    
    func _on_reply(black_hole, value) -> void:
        if black_hole != self:
            return
    
        # whatever
    
    
    func _on_Black_Hole_body_entered(body):
        SignalBus.emit_signal("trigger_black_hole", self)
    

    And then on the other side you do something like this:

    func _ready() -> void:
        SignalBus.connect("trigger_black_hole", self, "_trigger_black_hole")
    
    func _trigger_black_hole(black_hole) -> void:
        SignalBus.emit_signal("reply", black_hole, value)
    

    Since signals don't return, you might prefer to not use them. So, yet another alternative is to go back to the idea of node groups.

    You can add anode to a group from the editor, but I'll do it here from code.

    For example, let us add the OmniLight to a group I'll call "black_hole_monitors".

    func _ready() -> void:
        add_to_group("black_hole_monitors")
    

    Here "black_hole_monitors" is the name of the group. Which, of course, is just name. It could be whatever you prefer, as long as you know what name you are using… Because we are going to use it for communication. For example the black hole Areas could call a method on the OmniLight like this:

    func _on_Black_Hole_body_entered(body):
        for node in get_tree().get_nodes_in_group("black_hole_monitors"):
            # if not node.has_method("black_hole_triggered"): continue
            prints(node.black_hole_triggered())
    

    While the OmniLight could do this:

    func _ready() -> void:
        add_to_group("black_hole_monitors")
    
    func black_hole_triggered() -> bool:
        # whatever
        return true
    

    So the black hole Area would be calling the method black_hole_triggered on the OmniLight and also get a return value. And of course, you could add parameters to the method, or have multiple methods you call. You might also add the black hole Areas to their own group and access them in a similar fashion, and so on.

    Note that there could be none, one, or multiple nodes on the group. So I would encourage to write the logic in such way that it works regardless of how many nodes are in the group.