raycastinggodot4

Im having problems with Raycast3D selecting one object in Godot


The problem is that when I use the RayCast to activate or deactivate one cube, the others are activated. I have them in a group and I only want the cube I clicked on to be activated.

The truth is that it seems very complicated to me since it is the first day that I have used Godot and I come from Unity where this was easy to do and worked correctly.

Here a video: https://youtu.be/2k7qQWHNvwA

I tried activating only the cube I clicked with _raycast.get_collider() and checking if the cube with the collider is in a specific group.

The objective is that when you left click on a cube it changes its transparency to be completely visible and has the collision on layer 1, which is where it collides with the player.

When you right click, the transparency is changed so that it is not completely visible and collision layer 1 is deactivated so that the player can pass through it.

All of this only has to work on the cube you click on and only if it is a red cube.

Important: The Raycast3D node is inside the Camera node inside the player and the script is in the RayCast3D.

Here the script:

extends RayCast3D

@export var raycast_path: NodePath
@onready var _raycast = get_node_or_null(raycast_path)

func _ready():
    pass

func _input(event):
    if event is InputEventMouseButton:
        if event.button_index == MOUSE_BUTTON_LEFT && event.pressed:
            _handle_left_click()
        elif event.button_index == MOUSE_BUTTON_RIGHT && event.pressed:
            _handle_right_click()

func _handle_left_click():
    if _raycast.is_colliding():
        var collider = _raycast.get_collider().owner
        if "CuboRojo" in collider.get_groups():
            var cubo_rojo = collider as GeometryInstance3D
            var material = cubo_rojo.material_override
            material.transparency = 0
            cubo_rojo.get_node("StaticBody3D").collision_layer = 1 | 2

func _handle_right_click():
    if _raycast.is_colliding():
        var collider = _raycast.get_collider().owner
        if "CuboRojo" in collider.get_groups():
            var cubo_rojo = collider as GeometryInstance3D
            var material = cubo_rojo.material_override
            material.transparency = 1
            cubo_rojo.get_node("StaticBody3D").collision_layer = 2

I tried in many ways but all the object are activating at the same time.


Solution

  • The issue is that they are all sharing the same material. So when you modify the material to add or remove transparency, the change is reflected in all the cubes which share that same material.

    The easier to grasp approach is to duplicate the material before modifying it:

    cubo_rojo.material_override = cubo_rojo.material_override.duplicate()
    

    However, of course, this means you are creating a new material object each time. Thus I'm going to suggest to define a second material with the transparency (perhaps export a variable for it in your script so you can set it in the inspector), and replace the material instead of modifying it.

    Alternatively, you could store a metadata variable to keep track if you have already duplicated the material:

    if not cubo_rojo.get_meta("material_duplicated", false):
        cubo_rojo.material_override = cubo_rojo.material_override.duplicate()
        cubo_rojo.set_meta("material_duplicated", true)
    

    Here we use get_meta to read a metadata variable from cubo_rojo called "material_duplicated", if the metadata variable is not present (which would be the starting case), then it will return false (which we specified). And, of course, we use set_meta to write the metadata variable.


    I also want to note that using collider.is_in_group("CuboRojo") will be more efficient than "CuboRojo" in collider.get_groups() or collider.get_groups().has("CuboRojo").


    Addendum

    When I wrote the answer, this escaped my mind.

    You can mark Resources (such as the material) to be duplicated with each instance of the scene instead of being reused. To do that, set property resource_local_to_scene of the Resource to true.