Screen Transitions

Header image for Screen Transitions

A fancy wipe effect that you can use to transition between screens in Godot.

8 minute reading time of 1740 words (inc code)

Intro

This takes the work of DDRKirby and wraps it up in the script needed to do something with it in Godot 4.

Together, that script and shader make an effective transition that wipes in a solid colour, lets you swap out the old screen underneath it, and then wipes out that colour to reveal the new screen.

Transition Scene

You’ll first need to create a new scene of two nodes - a root node of a CanvasLayer and a child node of a ColorRect.

Then set the anchors of the ColorRect to be a full rectangle, and its mouse filter to ignore, and attach the following script to the root:

extends CanvasLayer

@export var delay := 1.5
@export var start_color := Color(0.5, 0, 1)
@export var end_color := Color(0, 0, 0)
@export_range(5,50,0.1) var start_size := 30.0
@export_range(5,50,0.1) var end_size := 10.0

@export var demo := true

@onready var background = $ColorRect

signal okay_to_swap
signal finished


func _ready() -> void:
  # get the background ready for the first time we call start()
  background.color = end_color
  background.material.set_shader_parameter("progress", 0.0)
  background.material.set_shader_parameter("diamondPixelSize", end_size)
  
  if demo:
    demonstrate()
  
  
func demonstrate() -> void:
  # code just to set up the demo

  var screen1 = ColorRect.new()
  screen1.color = Color.GREEN
  screen1.set_anchors_preset(Control.PRESET_FULL_RECT, true)
  
  var screen2 = ColorRect.new()
  screen2.color = Color.BLUE
  screen2.set_anchors_preset(Control.PRESET_FULL_RECT, true)
  
  add_child(screen1)
  move_child(screen1, 0)
  
  print("transition demo: before screen change")
  var timer = get_tree().create_timer(2.0)
  await timer.timeout
  
  # now do the swap from screen1 to screen2
  
  print("transition demo: starting screen change")
  start()
  
  # listen out for the signal that says we're covering the screen
  await okay_to_swap
  print("transition demo: switching screens")
  
  # we've got the signal, so now delete the old node
  screen1.queue_free()
  # and add the new one
  add_child(screen2)
  move_child(screen2, 0)
  
  # and a final signal in case there's anything else you want to do
  await finished
  print("transition demo: after screen change")


func start():
  # disable input while we're swapping the screen
  get_tree().get_root().set_disable_input(true)
  
  # start the wipe in that hides our swap
  await wipe(start_color, start_size, 1.0)
  
  # announce it's okay to change the screen now
  emit_signal("okay_to_swap")
  
  # and now start the wipe out that reveals the new screen
  await wipe(end_color, end_size, 0.0)
  
  # re-enable input now it's over
  get_tree().get_root().set_disable_input(false)
  
  # announce everything's done and back to normal
  emit_signal("finished")
  


func wipe(to_color : Color, to_size : float, to_fade : float, direction : int = -1, easing : Tween.EaseType = Tween.EASE_IN, transition : Tween.TransitionType = Tween.TRANS_CUBIC):
  if demo: print("transition demo: starting wipe %s" % [to_fade])
  
  # see the shader for what the direction param does
  background.material.set_shader_parameter("wipeDirection", randi_range(0,8) if direction == -1 else direction)
  
  var tween = get_tree().create_tween().set_parallel(true)
  tween.tween_property(background, "color", to_color, delay).set_trans(transition).set_ease(easing)
  tween.tween_property(background.material, "shader_parameter/progress", to_fade, delay).set_trans(transition).set_ease(easing)
  tween.tween_property(background.material, "shader_parameter/diamondPixelSize", to_size, delay).set_trans(transition).set_ease(easing)
  
  tween.play()
  await tween.finished

  if demo: print("transition demo: finished wipe %s" % [to_fade])

Instantiate this completed scene elsewhere in your scene tree - just make sure the CanvasLayer layer will show on top of whatever nodes you’re swapping around underneath it.

Material Shader

Here’s the (Godot 4) version of DDRKirby’s shader that combines their suggested different styles of screen wipe into one script.

This is attached to the shader property of a ShaderMaterial material of the ColorRect background.

shader_type canvas_item;

// how far along in the animation are we?
uniform float progress : hint_range(0.0, 1.0) = 0.0;

// how big our screen sub division is
uniform float diamondPixelSize : hint_range(5.0, 50.0) = 10.0;

// wipe direction
uniform int wipeDirection : hint_range(0, 8) = 1;

void fragment() {
  float xFraction = fract(FRAGCOORD.x / diamondPixelSize);
  float yFraction = fract(FRAGCOORD.y / diamondPixelSize);
  float xDistance = abs(xFraction - 0.5);
  float yDistance = abs(yFraction - 0.5);
  
  bool test = false;
  
  switch (wipeDirection) {
    case 0: // fill stationary
      test = (xDistance + yDistance > progress * 2.0);
      break;
      
    case 1: // fill TL to BR 
      test = (xDistance + yDistance + UV.x + UV.y > progress * 4.0);
      break;
    case 2: // fill BR to TL
      test = (xDistance + yDistance + UV.x + UV.y < (1.0 - progress) * 4.0);
      break;
      
    case 3: // fill BL to TR
      test = ((1.0 - xDistance) + (1.0 - yDistance) + (UV.x - UV.y) > progress * 3.0);
      break;
    case 4: // fill TR to BL
      test = ((1.0 - xDistance) + (1.0 - yDistance) + (UV.x - UV.y) < (1.0 - progress) * 3.0);
      break;
      
    case 5: // fill left to right
      test = (xDistance + yDistance + UV.x > progress * 2.0);
      break;
    case 6: // fill right to left
      test = (xDistance + yDistance + UV.x < (1.0 - progress) * 2.0);
      break;
      
    case 7: // fill top to bottom
      test = (xDistance + yDistance + UV.y > progress * 2.0);
      break;
    case 8: // fill bottom to top
      test = (xDistance + yDistance + UV.y < (1.0 - progress) * 2.0);
      break;
  }
  
  if (test) {
    discard;
  }
}

Finally, once you’re using it in a real project, call start() and listen out for the signals from whatever main parent scene you’ve instantiated this from.

(And strip out the demo bits of code…)


Reply via email