Godot Version
v4.3.stable.arch_linux
Question
I’m working on a top-down 2D game where the camera lerps behind the selected character and also zooms+scales over time. I’ve got two overlapped SubViewportContainer/SubViewports that render different canvas layers in order to create a “see through obstacles” hole effect when the character passes behind large objects, and I need to pass the character’s screen coordinates to a mask shader on the “in front” SubViewportContainer to have it render that viewport as a circular “hole” over the character.
I see in the docs that the recommended way to do this is to multiply the character’s canvas position by their canvas’s global transform by the screen’s transform like this:
var screen_coord = get_viewport().get_screen_transform() * get_global_transform_with_canvas() * local_pos
But when I do the equivalent in my game, the position is inaccurate. As the character moves away from the canvas origin and the camera zoom/scale changes, the hole mask slews further and further away from where it should be, and after a screen’s width or so from the origin it’s completely off screen, far from the character. I suspect this has something to do with the camera’s zoom and scale, but if that is the case I’ve yet to find a way to account for that. See red circle (mask placeholder) in video below, far from the character being tracked.
My scene hierarchy for reference. The characters are children of the “world” Node2D. “camera_base_layers” shadows the position/zoom/scale of “camera_all_layers”. “base_layers_container” has the mask shader and is where I attempt to get screen coordinates from the selected character.
This is what my screen position code on base_layers_subviewport looks like:
func _process(delta: float) -> void:
var selected_lilguy: lilguy2 = get_node("/root/lilguys").selected_lilguy
if selected_lilguy:
var viewport: SubViewport = get_node("base_layers_subviewport")
var lilguy_screen_coords = viewport.get_screen_transform() * selected_lilguy.get_global_transform_with_canvas() * selected_lilguy.position
material.set_shader_parameter("lilguy_coord", lilguy_screen_coords);
Happy to provide any other project code that might be useful to solving this. Also, if I’m totally barking up the wrong tree here and there’s an easier way to do the described hole-transparency effect, let me know what that is! Thanks.
edit - Also here is my mask shader code, I doubt this is the issue but I know it’s a possibility worth asking about. Right now it just renders a semi-transparent red circle:
shader_type canvas_item;
uniform sampler2D mask_texture;
uniform vec2 lilguy_coord;
void fragment() {
vec4 color = texture(TEXTURE, UV);
vec2 half_mask = vec2(16.0, 16.0);
if (lilguy_coord.x - half_mask.x < FRAGCOORD.x
&& FRAGCOORD.x < lilguy_coord.x + half_mask.x
&& lilguy_coord.y - half_mask.y < FRAGCOORD.y
&& FRAGCOORD.y < lilguy_coord.y + half_mask.y) {
vec2 mask_coord = vec2(
FRAGCOORD.x - (lilguy_coord.x - half_mask.x),
FRAGCOORD.y - (lilguy_coord.y - half_mask.y));
mask_coord = vec2(mask_coord.x / 32.0, mask_coord.y / 32.0);
vec4 mask_color = texture(mask_texture, mask_coord);
COLOR = vec4(vec3(1.0, 0.0, 0.0), mask_color.r * 0.5);
} else {
COLOR.a = 0.0;
}
}