Godot Version
4.7.1.stable.arch_linux
Question
I’m new to Godot and to shaders, so please bear with me.
I’m working on a 2D platformer which uses a TileMapLayer for the terrain. The game has a mechanic which requires highlighting the grid area around the mouse cursor - something I managed to get working with a shader from the net, albeit only for parts of the grid that actually have tiles. My goal is to have the grid highlight show up anywhere in the viewport on hover, regardless of whether there’s a tile there or not.
I was partially successful after adding a ColorRect and connecting it to the same shader stuff as the TileMapLayer. I set its anchor to a Full Rect, which displays the highlight anywhere in that rectangle, but it doesn’t follow the viewport. Per my search results, I’ve tried moving the ColorRect node around and giving it Control parents, but nothing’s worked yet, so here I am.
My Code
I’ve tried to add everything relevant and omit stuff that isn’t. Let me know if you need anything else.
The shader:
// Shader source: https://godotshaders.com/shader/2d-tilemap-grid-overlay-with-mouse-highlight/
// Displays a grid overlay on top of a tilemap, and highlights the grid near the mouse cursor
// Based on https://godotshaders.com/shader/repeated-texture-overlay-for-tilemaps/ by jess.codes
shader_type canvas_item;
// The grid is just a repeated texture
// The texture is drawn on top of the tilemap, so you can use transparency in the grid texture
// For pixel perfect games, you need the grid texture to have the same size as the tiles, and the grid cannot be perfectly centered on your tiles (or you have double width edges)
// If you don't need pixel perfect, remove `filter_nearest` and use a hollow square texture that's twice the width/height of the tilemap
uniform sampler2D overlay_texture: repeat_enable, filter_nearest;
// Scale for sampling the grid texture, should correspond to 1 / tileset_size_in_px
uniform float scale = 0.0625; // 1/16
// The opacity of the grid overlay
// Set to 0 to hide the base grid and only use the mouse highlight
uniform float base_opacity = 0.0;
// The extra opacity added to the grid overlay near the mouse
// Set to 0 to disable mouse highlight
uniform float highlight_opacity = 0.4;
// The distance to the mouse after which no extra opacity is added
uniform float distance_scaling = 50.0;
// The local mouse position on the tilemap layer. Needs to be set by an external script:
// func _process(delta: float) -> void:
// (tilemap_layer.material as ShaderMaterial).set_shader_parameter("mouse_position", tilemap_layer.get_local_mouse_position())
uniform vec2 mouse_position;
varying vec2 world_position;
void vertex(){
// calculate the world position for use in the fragment shader
world_position = (MODEL_MATRIX * vec4(VERTEX, 0.0, 1.0)).xy;
}
void fragment() {
vec4 color = texture(TEXTURE, UV);
// Only process pixels with the specificed floor color
// We use distance because floats cannot be compared for equality due to precision errors, and we ignore transparent pixels
vec4 grid_color = texture(overlay_texture, world_position * scale);
float distance_to_mouse = distance(mouse_position, world_position);
// We get a distance from [0, 1] that indicates how close the pixel is to the mouse
float normalized = 1.0 - (clamp(distance_to_mouse, 0, distance_scaling) / distance_scaling);
// Make white pixels of GridBackground's material transparent
if (color.rgb == vec3(1.0, 1.0, 1.0)) {
color.a = 0.0;
}
// Mix the regular color with the grid to preserve the original texture and just "add" the grid on top
COLOR = mix(color, grid_color, grid_color.a * (base_opacity + highlight_opacity * normalized));
}
GDScript for the TileMapLayer:
extends TileMapLayer
@onready var tile_map: TileMapLayer = $"."
@onready var grid_background: ColorRect = $"../GridBackground"
func _process(_delta: float) -> void:
# The grid overlay
if material and material is ShaderMaterial:
material.set_shader_parameter("mouse_position", get_local_mouse_position())
# Update background ColorRect shader
# Convert the mouse position to the ColorRect's local space
if grid_background and grid_background.material and grid_background.material is ShaderMaterial:
var bg_mouse_pos = grid_background.get_global_transform().affine_inverse() * tile_map.get_global_mouse_position()
grid_background.material.set_shader_parameter("mouse_position", bg_mouse_pos)
Project Structure:
Game scene:
Game (Node2D)
Player (scene; CharacterBody2D)
World (Node)
Foreground (TileMapLayer)
GridBackground (ColorRect)
...
Player scene:
Player (CharacterBody2D)
Sprite (AnimatedSprite2D)
Collider (CollisionShape2D)
Viewport (Camera2D)