'GODOT How can I xray through tilemaps around me
So I'm looking to create an effect of having a bubble around my player which, when he enters a hidden area (hidden by tilemaps) the bubble activates and it essentially has an xray effect. So I can see the background, the ground and all the items inside the area I just can't see the blocks themselves.
So pretty much going from this
And as I go further in the more gets revealed
I have no idea what to even begin searching for this. So any direction would be greatly appreciated
Solution 1:[1]
First of all, I want to get something out of the way: Making things appear when they are nearby the player is easy, you use a light and a shader. Making things disappear when they are nearby the player by that approach is impossible in 2D (3D has flags_use_shadow_to_opacity
).
This is the plan: We are going to create a texture that will work as mask for what to show and what not to show. Then we will use that texture mask with a shader to make a material that selectively disappears. To create that texture, we are going to use a Viewport
, so we can get a ViewportTexture
from it.
The Viewport
setup is like this:
Viewport
? ColorRect
? Sprite
Set the Viewport
with the following properties:
- Size: give it the window size (the default is 1024 by 600)
- Hdr: disable
- Disable 3D: enable
- Usage: 2D
- Update mode: Always
For the Sprite
you want a grayscale texture, perhaps with transparency. It will be the shape you want to reveal around the player.
And for the ColorRect
you want to set the background color as either black or white. Whatever is the opposite of the color on the Sprite
.
Next, you are going to attach a script to the Viewport
. It has to deal with two concerns:
Move the
Sprite
to match the position of the player. That looks like this:extends Viewport export var target_path:NodePath func _process(_delta:float) -> void: var target := get_node_or_null(target_path) as Node2D if target == null: return $Sprite.position = target.get_viewport().get_canvas_transform().origin
And you are going to set the
target_path
to reference the player avatar.In this code
target.get_viewport().get_canvas_transform().origin
will give us the position of the target node (the player avatar) on the screen. And we are placing theSprite
to match.Handle window resizes. That looks like this:
func _ready(): # warning-ignore:return_value_discarded get_tree().get_root().connect("size_changed", self, "_on_size_changed") func _on_size_changed(): size = get_tree().get_root().size
In this code we connect to the
"size_changed"
of the rootViewport
(the one associated with the Window), and change the size of thisViewport
to match.
The next thing is the shader. Go to your TileMap
or whatever you want to make disappear and add a shader material. This is the code for it:
shader_type canvas_item;
uniform sampler2D mask;
void fragment()
{
COLOR.rgb = texture(TEXTURE, UV).rgb;
COLOR.a = texture(mask, SCREEN_UV).r;
}
As you can see, the first line will be setting the red, green, and blue channels to match the texture the node already has. But the alpha channel will be set to one of the channels (the red one in this case) of the mask texture.
Note: The above code will make whatever is in the black parts fully invisible, and whatever is in the white parts fully visible. If you want to invert that, change COLOR.a = texture(mask, SCREEN_UV).r;
to COLOR.a = 1.0 - texture(mask, SCREEN_UV).r;
.
We, of course, need to set that mask texture. After you set that code, there should be a shader param under the shader material called "Mask", set it to a new ViewportTexture
and set the Viewport
to the one we set before.
And we are done.
I tested this with this texture from publicdomainvectors.org:
Plus some tiles from Kenney. They are all, of course, under public domain.
This is how it looks like:
Experiment with different textures for different results. Also, you can add a shader to the Sprite
for extra effect. For example add some ripples, by giving a shader material to the Sprite
with code like this one:
shader_type canvas_item;
void fragment()
{
float width = SCREEN_PIXEL_SIZE.x * 16.0;
COLOR = texture(TEXTURE, vec2(UV.x + sin(UV.y * 32.0 + TIME * 2.0) * width, UV.y));
}
So you get this result:
There is an instant when the above animation stutters. That is because I didn't cut the loop perfectly. Not an issue in game. Also the animation has much less frames per second than the game would.
Addendum A couple things I want to add:
- You can create a texture by other means. I have a couple other answer where I cover some of it
- How can I bake 2D sprites in Godot at runtime? where we use
blit_rect
. You might also be interested inblit_rect_mask
. - Godot repeating breaks script where we are using lockbits.
- How can I bake 2D sprites in Godot at runtime? where we use
- I wrote a shader that outputs on the alpha channel here. Other options include:
- Using
BackBufferCopy
. - To
discard
fragments.
- Using
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |