Skip to content

ToggleableTileMapLayer: Apply shader transition - #2699

Open
manuq wants to merge 1 commit into
mainfrom
toggleable-transition
Open

ToggleableTileMapLayer: Apply shader transition#2699
manuq wants to merge 1 commit into
mainfrom
toggleable-transition

Conversation

@manuq

@manuq manuq commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Bridges and Void barriers made with ToggleableTileMapLayer appeared
and disappeared when toggled. This change give them an optional fade
transition, reusing the world-rewoven effect.

The shader require several changes to be extended to work on a
TileMapLayer (or any other CanvasItem), in addition to keep
working on a CanvasGroup. And now it works better for the prior
CanvasGroup case too.

Toggleable transition

Add transition_duration to ToggleableTileMapLayer . When a usable
material is present it tweens the shader's progress parameter
instead of flipping the layer instantly.

  • The state is committed halfway through the fade, so the change is
    felt mid-transition rather than at the end. Only
    collision_enabled, navigation_enabled and occlusion_enabled
    flip at the midpoint. The prior enabled is left until the very
    end, because it also controls rendering and turning it off early
    would hide the second half of the animation.
  • Interrupting a transition doesn't reset it. Each half of the tween
    lasts in proportion to the distance it covers, so progress moves
    at a constant rate and a toggle that interrupts another one is
    proportionally shorter.
  • The material is looked up once, in target first and then in the
    first ancestor CanvasGroup, since the material may be wrapped in
    one. This is the case for Void barriers because they already have a
    shader, the Void chromakey. It is only accepted if it is a
    ShaderMaterial with a progress parameter. Otherwise the toggle
    stays instant.

In the Toggleable base class, set_toggled() now has an immediate
parameter, and initialize_with_value() passes true. This is to
keep the initialization immediate on scene entry.

World rewoven shader changes

Before the effect was for a CanvasGroup to create cutscenes. Now
it's extended to serve both cases.

  • Add target_canvas_group toggle for the two cases.
  • The mask rect is now explicit, sampled from world by new
    mask_origin / mask_size uniforms, instead of by UV. Because on
    a TileMapLayer the UV is the coordinate into the tileset atlas
    for each tile, so the mask was repeating on every single tile. The
    mask_texture also gained repeat_disable so the region clamps
    rather than wrapping.
  • Drop render_mode unshaded. The engine only applies a
    CanvasModulate's color and 2D lights to items that are not
    unshaded, so the layer stayed untinted while the rest of the scene
    darkened.
  • Fix progress extremes with a threshold, so 0 hides every pixel and
    1 shows every pixel regardless of what the mask contains. In
    between, the mask still shapes the wipe. Previously, pixels at the
    mask minimum value stayed invisible at progress = 1.
  • Make burn look a bit more as light. The new canvas_modulate
    uniform (which unfortunately has to be passed, matching the scene)
    divides out the scene tint so the burn keeps full brightness, and
    the default burn_color is now brighter than white. This is a
    detail: when not passed the effect still looks ok. Check
    custom_hookable_objects_test.tscn for a scene that uses it.

New TileMapMaskBounds helper node

Because the mask is now positioned in world units, something has to
supply the rect. This behavior node computes the mask rect from
get_used_rect() of the TileMapLayers in its target (itself and any
descendants) and writes mask_origin / mask_size into the shader
material. It has an "Update bounds" button to apply it after tile
edits.

Note: only TileMapLayer nodes are considered (wouldn't work for
WorldNew / WorldOld in cutscenes). Sprite2Ds and other CanvasItems are
ignored because Godot doesn't have a general method to obtain bounds
on them. This is surfaced as a configuration warning when no tile
layer is found.

Add BaseCanvasItemBehavior and refactor ModulateAsSkyBehavior to use
it, plus this new behavior above.

Scene changes

Transitions applied to existing toggleable layers, each paired with a
TileMapMaskBounds:

  • frays_end.tscn: Dev Archipelago bridge fixed / broken. Also updated waiters in the townie dialogue lines to out-last the new bridge transition.
  • frays_end.tscn: Void barrier (same style as in linenville below).
  • song_sanctuary_path.tscn: The side-quest bridge fixed / broken.
  • grapple_gym_2.tscn: The bridge that appears (I had to initialize it in the scene script).
  • linenville_path.tscn: The Void barrier. For this one I used a noise texture and a different burn color, because it's not actually a rewoven, it's a "void cleared" effect.
  • custom_hookable_objects_test.tscn and custom_repellable_objects_test.tscn: The fence doors.

Also the two world_rewoven_cutscene_*.tscn scenes were updated for
the new uniforms (target_canvas_group, mask_origin, mask_size,
canvas_modulate, new burn_color).

Helps #2675

@github-actions

Copy link
Copy Markdown

Play this branch at https://play.threadbare.game/branches/endlessm/toggleable-transition/.

(This launches the game from the start, not directly at the change(s) in this pull request.)

@manuq

manuq commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

As part of #2675 I'm trying to animate the ToggleableTileMapLayers that toggle with quest changes:

toggleable-transition.webm

Also works with any ToggleableTileMapLayer of course:

hookable-transition.webm
repellable-transition.webm

@manuq

manuq commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

It took me some time (and back and forth with the help of Claude) to make it work both for CanvasGroups (as before in cutscenes) and for a single CanvasItem, which may be affected by other shaders: like the Light2D nodes and the CanvasModulate inside TimeAndWeather.

This works for the existing broken bridges, with a bit of setup. In the future we may want to have more complex "old world / new world" to the actual levels than a single bridge tile. But they are enough for now!

The setup is:

  • Add a material with the same shader and texture parameters as the cutscenes to the TileMapLayer.
  • Add a new WorldRewovenMaskBounds to update the bounds of the shader.

It uses a Tween internally so an AnimationPlayer is not needed.

Because the bounds now have to be specified, the cutscenes are also reliable to edit now in the editor. The problem I commented in this video (the effect moving as the tilemap crops in the editor) is now gone!

Grabacion.de.pantalla.desde.2026-08-12.18-24-48.mp4

I also improved the shader to make it fully transparent at progress = 0 regardless of the mask texture, and fully visible at progress = 1.

@manuq
manuq force-pushed the toggleable-transition branch from 98cb626 to 046a7b9 Compare August 13, 2026 14:56
@manuq

manuq commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

I went to aplly the transition to the Void barrier in Linenville.. uh oh it has as shader already (the Void chromakey). Fine, I can add it to a child of a CanvasGroup and apply the transition effect to it.. but now I need to make the toggleable script smarter and lookup in the SceneTree the first node that is a CanvasGroup. This is getting a bit complex.

Grabacion.de.pantalla.desde.2026-08-13.13-08-19.mp4

@manuq
manuq force-pushed the toggleable-transition branch 2 times, most recently from 6dd001c to 591856b Compare August 14, 2026 12:41
Bridges and Void barriers made with `ToggleableTileMapLayer` appeared
and disappeared when toggled. This change give them an optional fade
transition, reusing the world-rewoven effect.

The shader require several changes to be extended to work on a
`TileMapLayer` (or any other `CanvasItem`), in addition to keep
working on a `CanvasGroup`. And now it works better for the prior
`CanvasGroup` case too.

Toggleable transition

Add `transition_duration` to `ToggleableTileMapLayer` . When a usable
material is present it tweens the shader's `progress` parameter
instead of flipping the layer instantly.

- The state is committed halfway through the fade, so the change is
  *felt* mid-transition rather than at the end. Only
  `collision_enabled`, `navigation_enabled` and `occlusion_enabled`
  flip at the midpoint. The prior `enabled` is left until the very
  end, because it also controls rendering and turning it off early
  would hide the second half of the animation.
- Interrupting a transition doesn't reset it. Each half of the tween
  lasts in proportion to the distance it covers, so `progress` moves
  at a constant rate and a toggle that interrupts another one is
  proportionally shorter.
- The material is looked up once, in `target` first and then in the
  first ancestor `CanvasGroup`, since the material may be wrapped in
  one. This is the case for Void barriers because they already have a
  shader, the Void chromakey. It is only accepted if it is a
  `ShaderMaterial` with a `progress` parameter. Otherwise the toggle
  stays instant.

In the `Toggleable` base class, `set_toggled()` now has an `immediate`
parameter, and `initialize_with_value()` passes `true`. This is to
keep the initialisation immediate on scene entry.

World rewoven shader changes

Before the effect was for a `CanvasGroup` to create cutscenes. Now
it's extended to serve both cases.

- Add `target_canvas_group` toggle for the two cases.
- The mask rect is now explicit, sampled from world by new
  `mask_origin` / `mask_size` uniforms, instead of by `UV`. Because on
  a `TileMapLayer` the UV is the coordinate into the tileset *atlas*
  for each tile, so the mask was repeating on every single tile. The
  `mask_texture` also gained `repeat_disable` so the region clamps
  rather than wrapping.
- Drop `render_mode unshaded`. The engine only applies a
  `CanvasModulate`'s color and 2D lights to items that are not
  unshaded, so the layer stayed untinted while the rest of the scene
  darkened.
- Fix `progress` extremes with a threshold, so 0 hides every pixel and
  1 shows every pixel regardless of what the mask contains. In
  between, the mask still shapes the wipe. Previously, pixels at the
  mask minimum value stayed invisible at `progress = 1`.
- Make burn look a bit more as light. The new `canvas_modulate`
  uniform (which unfortunately has to be passed, matching the scene)
  divides out the scene tint so the burn keeps full brightness, and
  the default `burn_color` is now brighter than white. This is a
  detail: when not passed the effect still looks ok. Check
  custom_hookable_objects_test.tscn for a scene that uses it.

New TileMapMaskBounds helper node

Because the mask is now positioned in world units, something has to
supply the rect. This behavior node computes the mask rect from
`get_used_rect()` of the TileMapLayers in its target (itself and any
descendants) and writes `mask_origin` / `mask_size` into the shader
material. It has an "Update bounds" button to apply it after tile
edits.

Note: only TileMapLayer nodes are considered (wouldn't work for
WorldNew / WorldOld in cutscenes). Sprite2Ds and other CanvasItems are
ignored because Godot doesn't have a general method to obtain bounds
on them. This is surfaced as a configuration warning when no tile
layer is found.

Add BaseCanvasItemBehavior and refactor ModulateAsSkyBehavior to use
it, plus this new behavior above.

Scene changes

Transitions applied to existing toggleable layers, each paired with a
TileMapMaskBounds:

- `frays_end.tscn`: Dev Archipelago bridge fixed / broken. Also updated waiters in the townie dialogue lines to out-last the new bridge transition.
- `frays_end.tscn`: Void barrier (same style as in linenville below).
- `song_sanctuary_path.tscn`: The side-quest bridge fixed / broken.
- `grapple_gym_2.tscn`: The bridge that appears (I had to initialize it in the scene script).
- `linenville_path.tscn`: The Void barrier. For this one I used a noise texture and a different burn color, because it's not actually a rewoven, it's a "void cleared" effect.
- `custom_hookable_objects_test.tscn` and `custom_repellable_objects_test.tscn`: The fence doors.

Also the two `world_rewoven_cutscene_*.tscn` scenes were updated for
the new uniforms (`target_canvas_group`, `mask_origin`, `mask_size`,
`canvas_modulate`, new `burn_color`).
@manuq
manuq force-pushed the toggleable-transition branch from 591856b to ca4f65c Compare August 14, 2026 14:05
@manuq
manuq marked this pull request as ready for review August 14, 2026 14:07
@manuq
manuq requested a review from a team as a code owner August 14, 2026 14:07
@manuq

manuq commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

but now I need to make the toggleable script smarter and lookup in the SceneTree the first node that is a CanvasGroup. This is getting a bit complex.

So yeah I ended up doing this (making the script lookup the CanvasGroup ancestor).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant