Can areas be used for this particular situation?

DetonatressDetonatress Posts: 50Member
in 3D

The situation is: there are 4 of the same object (white crystal). All are rigid bodies that can be pushed around by the player and will bounce off other objects they collide with, and bounce off each other too.
There is this ring with cross hairs. It is a static body with which the player can collide and the ring will not move, but its hole can be passed through.

Should I and can I use areas for the following, or can I do it some other way?
The goal is for the player to push one of the crystals into the ring's hole, and the ring should capture the crystal, position it at its center, and rotate it based on the ring's rotation status. Once in this state, the crystal should not be movable (could probably set that individual's dampness in such a way that it will not react to being pushed around).
If there are 2 or more crystals in the ring's hole at the same time, it should only capture one crystal and repel the rest (I suppose this part would require a check for whether it already has positioned a crystal or not).

Answers

  • bitshift-rbitshift-r Posts: 55Member

    Yes, Areas can be used for detecting when a crystal passes through the ring. I'd even go so far as to say that's the "correct" way to do it. Once detected you can either reparent the crystal to the ring, or swap it out for a static copy of the crystal. You could also disable the area at this point, so that other crystals won't be detected, but would still collide with the ring and its child crystal.

  • DetonatressDetonatress Posts: 50Member

    @bitshift-r said:
    Yes, Areas can be used for detecting when a crystal passes through the ring. I'd even go so far as to say that's the "correct" way to do it. Once detected you can either reparent the crystal to the ring, or swap it out for a static copy of the crystal. You could also disable the area at this point, so that other crystals won't be detected, but would still collide with the ring and its child crystal.

    Wouldn't reparenting the caught crystal to the ring cause it to instantly rotate? I want it to smoothly rotate and make its way to the center of the area (which coincides with the ring's center and the ring's rotation) and once its rotation is identical to the area and its position is in the center of the area, the crystal is marked as "caught". From then on, the area will always push away other crystals that have not been marked as "caught".
    Also, can the damp value be changed via code only in the caught crystal through an instruction given to it through the area object?

  • bitshift-rbitshift-r Posts: 55Member

    @Detonatress said:

    Also, can the damp value be changed via code only in the caught crystal through an instruction given to it through the area object?

    If you want to control different crystals you could use the Area to detect when a crystal enters and exits, and your code would be responsible for applying the correct forces to the correct crystals.

  • DetonatressDetonatress Posts: 50Member

    @bitshift-r said:
    @Detonatress said:

    Also, can the damp value be changed via code only in the caught crystal through an instruction given to it through the area object?

    If you want to control different crystals you could use the Area to detect when a crystal enters and exits, and your code would be responsible for applying the correct forces to the correct crystals.

    So it would count 1st crystal in: crystal 1 executes order to move to center and rotate while becoming unable to be pushed. 2nd crystal and any next crystals entering: execute order to leave the area.
    Can it work like that? How do I make the area check which is the 1st to enter?

  • bitshift-rbitshift-r Posts: 55Member

    Your code would have to keep track... something like this:

    var captured_crystal = null
    
    
    func _ready() -> void:
        $Area.connect("body_entered", self, "_on_body_entered")
    
    
    func _on_body_entered(body: Node) -> void:
        if captured_crystal == null:
            captured_crystal = body
        else:
            # we've already captured a crystal... apply an impulse to body to push it away
            pass
    

    Keep in mind that you shouldn't directly move a RigidBody. You should apply impulses. Alternatively you could use the crystal's _integrate_forces to manually steer it towards the center of the ring/area.

  • DetonatressDetonatress Posts: 50Member
    edited June 9

    @bitshift-r said:
    Your code would have to keep track... something like this:

    var captured_crystal = null
    
    
    func _ready() -> void:
      $Area.connect("body_entered", self, "_on_body_entered")
    
    
    func _on_body_entered(body: Node) -> void:
      if captured_crystal == null:
          captured_crystal = body
      else:
          # we've already captured a crystal... apply an impulse to body to push it away
          pass
    

    Keep in mind that you shouldn't directly move a RigidBody. You should apply impulses. Alternatively you could use the crystal's _integrate_forces to manually steer it towards the center of the ring/area.

    On what do I use the code? On the Area itself?
    What does "captured_crystal = body" mean? Is "body" a new state given to captured_crystal and can be replaced with 1 or anything else to declare it has been captured?
    I'm new to coding in Godot, so I don't know where I have to mention the White_Crystal model. Do I use "$White_Crystal" if I want to refer to any instance of the original? Shouldn't captured_crystal variable be added to the White_Crystal by the Area?

    I want to go with the _integrate_forces, but I don't know which tutorial to follow to figure out how to use those. I found this one, but not sure if it's useful for my case:

    Also, how will I compare if crystal's x, y and z coordinates are equal to Area.0.0.0 coordinates in order to stop telling it to travel their way? I cannot figure out even how to tell it to travel toward those points, because the crystal can be at -Area.x or +Area.x side, and so on for any axis.

  • bitshift-rbitshift-r Posts: 55Member

    @Detonatress said:
    On what do I use the code? On the Area itself?

    I would think this code would be on the ring, or even the main level.

    @Detonatress said:
    What does "captured_crystal = body" mean? Is "body" a new state given to captured_crystal and can be replaced with 1 or anything else to declare it has been captured?

    In my code example, "body" is the object that had entered the Area. Assigning it to captured_crystal just means that you're holding a reference to that body. You will have to use physics layers to control which bodies can interact with your area.

    @Detonatress said:
    Also, how will I compare if crystal's x, y and z coordinates are equal to Area.0.0.0 coordinates in order to stop telling it to travel their way? I cannot figure out even how to tell it to travel toward those points, because the crystal can be at -Area.x or +Area.x side, and so on for any axis.

    To find the direction that your crystal should travel to get to the center of the ring you would subtract the crystal's position from the ring's position (assuming that the ring's origin is at its center) and normalize it. This will give you a direction vector. Also, if the distance between the two vectors is zero you're at the center.

  • DetonatressDetonatress Posts: 50Member
    edited June 10

    @bitshift-r said:

    @Detonatress said:
    On what do I use the code? On the Area itself?

    I would think this code would be on the ring, or even the main level.

    @Detonatress said:
    What does "captured_crystal = body" mean? Is "body" a new state given to captured_crystal and can be replaced with 1 or anything else to declare it has been captured?

    In my code example, "body" is the object that had entered the Area. Assigning it to captured_crystal just means that you're holding a reference to that body. You will have to use physics layers to control which bodies can interact with your area.

    @Detonatress said:
    Also, how will I compare if crystal's x, y and z coordinates are equal to Area.0.0.0 coordinates in order to stop telling it to travel their way? I cannot figure out even how to tell it to travel toward those points, because the crystal can be at -Area.x or +Area.x side, and so on for any axis.

    To find the direction that your crystal should travel to get to the center of the ring you would subtract the crystal's position from the ring's position (assuming that the ring's origin is at its center) and normalize it. This will give you a direction vector. Also, if the distance between the two vectors is zero you're at the center.

    I've put the code on the main level's spatial script.

    I've set the main crystal instance to have these settings:

    And the Area has these settings:

    Seems area must not be parented to another node. It only detects the crystal if it's at the center of the area/collision shape for some reason. I've set a print of "Crystal caught" whenever it detects the first crystal.
    If I push the same crystal back, it will say "Crystal not needed", which tells me it already checked if it had caught a crystal. Still, no idea why the full area won't work. It has a collision shape already.

    Wouldn't it be better to tell the crystal to go to the center of the area? Because I want to have several of these rings in some scenes (but can have multiple areas), so it might try to send the crystals toward other rings' centers instead.
    How do I normalize the position?

  • DetonatressDetonatress Posts: 50Member

    I tried to set very high angular_damp and linear_damp but it won't halt the crystal. So if I have to replace it with static body, how do I do that while also transferring its current rotation/position?
    I tried this:
    body.replace_by($WhiteCrystalStatic,true)
    but it doesn't replace it with the static version, it just keeps passing through.

  • bitshift-rbitshift-r Posts: 55Member

    Sure, you could swap the crystal out for a static or kinematic body, or you can enable the RigidBody's custom_integrator property which will disable the automatic motion and allow you to control everything solely through _integrate_forces.

  • DetonatressDetonatress Posts: 50Member

    @bitshift-r said:
    Sure, you could swap the crystal out for a static or kinematic body, or you can enable the RigidBody's custom_integrator property which will disable the automatic motion and allow you to control everything solely through _integrate_forces.

    Ah that would be easier. If I understand right, I just have to tell it body.custom_integrator = true.

  • bitshift-rbitshift-r Posts: 55Member

    @Detonatress said:
    Ah that would be easier. If I understand right, I just have to tell it body.custom_integrator = true.

    Correct.

  • DetonatressDetonatress Posts: 50Member
    edited June 10

    @bitshift-r said:

    @Detonatress said:
    Ah that would be easier. If I understand right, I just have to tell it body.custom_integrator = true.

    Correct.

    It doesn't seem to do anything (other than to tell me it has detected it by printing "Crystal caught"), I can still push the object out of the ring.
    This is the code's situation:

    func _on_Area_body_entered(body):
        $Area.connect("body_entered", self, "_on_body_entered")
    
    
    func _on_body_entered(body: Node) -> void:
        if captured_crystal == null:
            captured_crystal = body
            body.custom_integrator = true
            print("Crystal caught.")
        else:
     ## we've already captured a crystal... apply an impulse to body to push it away
            print("Crystal not needed.")
    
  • bitshift-rbitshift-r Posts: 55Member

    Have you implemented _integrate_forces for the crystal?

    You could also try changing the RigidBody's mode to MODE_KINEMATIC, in which case you would move it manually during _physics_process.

  • DetonatressDetonatress Posts: 50Member

    @bitshift-r said:
    Have you implemented _integrate_forces for the crystal?

    You could also try changing the RigidBody's mode to MODE_KINEMATIC, in which case you would move it manually during _physics_process.

    In the end it seems body.mode = 3 worked, thank you. It froze in place, exactly as needed. Now all that's left is to figure out why this area only activates if the crystal hits area center instead of the rest of its cube collision shape.

    This area has been created in the level's scene, it isn't tied to anything except the level's spatial node itself. Maybe there's something I'm missing.

  • bitshift-rbitshift-r Posts: 55Member

    I suggest setting the collision shape to a sphere at least the size of the ring's opening. Then as soon as it enters that sphere it should snap to the center.

  • DetonatressDetonatress Posts: 50Member
    edited June 10

    @bitshift-r said:
    I suggest setting the collision shape to a sphere at least the size of the ring's opening. Then as soon as it enters that sphere it should snap to the center.

    It's as big as the hole now:

    Still misses collisions:

    The crystal wasn't supposed to pass through but be stopped as soon as it hits it. (I brought the crystal from the other side of the ring).

    Crystal even has this on:

  • DetonatressDetonatress Posts: 50Member
    edited June 10

    Upon further testing I have noticed something:
    The area actually does function on the full collision shape, but only after the body has entered the area, then exited the area, then entered a 2nd time. There may be a problem with the code:

    func _on_Area_body_entered(body):
        $Area.connect("body_entered", self, "_on_body_entered")
    
    
    func _on_body_entered(body: Node) -> void:
        if captured_crystal == null:
            captured_crystal = body
            body.custom_integrator = true
            body.mode = 3
            print("Crystal caught.")
    

    EDIT: I had to delete this line "func _on_body_entered(body: Node) -> void:" and now it captures it instantly.

Leave a Comment

BoldItalicStrikethroughOrdered listUnordered list
Emoji
Image
Align leftAlign centerAlign rightToggle HTML viewToggle full pageToggle lights
Drop image/file