Godot Version
4.3 Stable
Question
I am trying to make a simple prototype level that is made up of duplicates of a Node/Scene. These Nodes are just basic terrain chunks from the Asset Library, and I have a script to generate flat scenes. I want to be able to mouse over each chunk, and have GDScript behavior to “highlight” it.
I suspect I may be going about this wrong, because, while I can get the intended behavior for a single node, I cannot get the intended behavior for more than one. When I mouse over any node, every node is highlighted. After going over the project details, I’ll talk about my troubleshooting steps.
Project Details:
these are the nodes in question.
This is what each one is:
And this is how they look in the scene tree in question:
Now for the scripts in question. This is where I think the issue might be. This first script is attached to each instance of a chunk that is created. It handles the material updating to produce a highlighting effect and revert nodes back to normal once they are not targeted by the user.
# The script for each chunk that makes up the game space.
extends StaticBody3D
var tile_mesh;
var surface_material;
var default_surface_material;
var highlight_material;
signal highlight;
signal unhighlight;
func _ready():
tile_mesh = get_child(1).mesh;
surface_material = tile_mesh.surface_get_material(0).duplicate();
default_surface_material = surface_material;
highlight_material = preload("res://highlighted_tile.gdshader")
highlight.connect(activate_highlight);
unhighlight.connect(deactivate_highlight);
print("Tile script _ready() called for:", self.name) # Add this line
func activate_highlight():
var highlight_material_instance = highlight_material.duplicate()
tile_mesh.surface_set_material(0, highlight_material_instance);
func deactivate_highlight():
tile_mesh.surface_set_material(0, default_surface_material);
This is where the logic is handled in terms of mousing over things, raycasting, and detection. I know it needs a lot of work, but I am almost sure there are no issues here in terms of highlighting every node at once instead of one.
Controls.gd
extends Camera3D
var mouse_position_3d;
var mouse_position_2d;
var projection_3d;
var viewport;
var marker;
var object_held;
var last_highlighted;
const STARTING_POSITION = Vector3(0, 2, -3);
const STARTING_ROTATION = Vector3(-0, -160, 0);
# Called when the node enters the scene tree for the first time.
func _ready():
set_up_marker();
set_up_transform();
last_highlighted = null;
func set_up_marker():
viewport = get_viewport();
marker = preload('res://marker.tscn').instantiate();
add_child(marker)
func set_up_transform():
position = STARTING_POSITION;
rotation = STARTING_ROTATION;
func _input(event):
if event is InputEventMouse:
mouse_position_2d = viewport.get_mouse_position();
transform_screen_to_world_coordinates(mouse_position_2d);
# Projects mouse coordinates from screen to 3D world.
func transform_screen_to_world_coordinates(mouse_position):
var origin := project_ray_origin(mouse_position);
var direction := project_ray_normal(mouse_position);
var ray_length := far;
var end := origin + direction * ray_length;
var space_state := get_world_3d().direct_space_state;
var query := PhysicsRayQueryParameters3D.create(origin, end);
var result := space_state.intersect_ray(query);
if result:
move_marker(result)
else:
mouse_position_3d = origin + direction * 1; # Place marker 1 unit in front of camera, but we prob wanna make it invisible or put/hide it somewhere else.
check_for_collisions(query);
func move_marker(result):
var surface_normal = result.get("normal")
var look_at_target = surface_normal
marker.look_at(look_at_target, Vector3.UP);
mouse_position_3d = result.get("position");
# Detects if objects are in the mouses path.
func check_for_collisions(query):
var collision = get_world_3d().direct_space_state.intersect_ray(query);
if(last_highlighted != null):
last_highlighted.emit_signal("unhighlight");
print("Dceollision with: " + last_highlighted.name)
if(!collision.values().is_empty()):
var collider = collision.values()[4];
collider.emit_signal("highlight")
last_highlighted = collider;
print("Collision with: " + collider.name)
func move_object():
marker.global_position = mouse_position_3d # Set the position directly
This is the script that generates a level, which I am just including for context. I don’t think anything is wrong here, but I could be wrong.
TerrainGenerator.gd
const SPACING : int = 2;
const TERRAIN_LOW_NODE_PATH = 'res://terrain_low.tscn';
var nodes : Array = Array();
func _ready():
init_world()
func init_world():
var terrain_low_scene = load(TERRAIN_LOW_NODE_PATH) # Load the scene once
var first_node = terrain_low_scene.instantiate()
add_child(first_node)
first_node.name = "Terrain_0_0" # give it a name for debugging
for x_block_count in range(WORLD_SIZE):
for z_block_count in range(WORLD_SIZE):
var new_node = terrain_low_scene.instantiate().duplicate() # Instantiate a new node from the scene
add_child(new_node)
new_node.name = "Terrain_" + str(x_block_count) + "_" + str(z_block_count) # give it a name for debugging
new_node.global_translate(Vector3(x_block_count, 0, z_block_count)) # Use global_translate for
nodes.append(new_node);
func save_world():
pass;
Troubleshooting Steps
I’ve done a lot of LLM debugging which has just driven me further into insanity. I now require the help of actual people. I did confirm a few key things via print statement :
- Each chunk does get duplicated and loaded into the scene, confirmed by the print statement in the chunk’s ready() method being called for each instance created. I kind of knew this but needed to make sure I was not mistaken.
- The mouse movement/raycasting indeed does register hitting each different, unique tile. This is confirmed by the output from the print statements in the check_for_collisions() method in the controls script.
So to recap: I want only the tile specified in the debugging output to be highlighted. Currently, every node is highlighted. That is, if the output says “Collision with: Terrain 0_1” only that node in the sceen tree should have the method activate_highlight() called. For some reason, that method is being called on all the nodes, or the material somehow is being updated for all nodes related to that material. You can probably see by reading this last paragraph how confused I am.
Any and all help or advice is appreciated of course. Thank you!