Skip to content

Commit

Permalink
Render backgrounds in sub-viewports and transition them using shaders (
Browse files Browse the repository at this point in the history
…#1839)

This keeps all the new viewport and shader stuff but offloads most of the work to behind the scenes. This means background scenes can work just as they did previously. However they are manually put into subvieports+subviewportcontainers. The backgrounds-holder is now also the background-displayer.

_fade_out() and _fade_in() have been renamed to _custom_fade_out() and _custom_fade_in(). Right now they work "additionally" to the default fade which happens as well. If you know what you are doing you can work around this though.
I have to put further thought into custom transitions as I have no real idea what the best user-experience/use case might be.

---------

Co-authored-by: Jowan-Spooner <raban-loeffler@posteo.de>
  • Loading branch information
Pheubel and Jowan-Spooner committed Nov 10, 2023
1 parent c8e2334 commit a7f565c
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 78 deletions.
27 changes: 15 additions & 12 deletions addons/dialogic/Modules/Background/default_background.gd
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
extends DialogicBackground

## The default background scene.
## The default background scene.
## Extend the DialogicBackground class to create your own background scene.

@onready var image_node = $Image
@onready var color_node = $ColorRect


func _ready() -> void:
$Image.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
$Image.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED
$Image.anchor_right = 1
$Image.anchor_bottom = 1
image_node.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
image_node.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_COVERED

image_node.anchor_right = 1
image_node.anchor_bottom = 1


func _update_background(argument:String, time:float) -> void:
if argument.begins_with('res://'):
$Image.texture = load(argument)
self.color = Color.TRANSPARENT
image_node.texture = load(argument)
color_node.color = Color.TRANSPARENT
elif argument.is_valid_html_color():
$Image.texture = null
self.color = Color(argument, 1)
image_node.texture = null
color_node.color = Color(argument, 1)
else:
$Image.texture = null
self.color = Color.from_string(argument, Color.TRANSPARENT)
image_node.texture = null
color_node.color = Color.from_string(argument, Color.TRANSPARENT)


func _should_do_background_update(argument:String) -> bool:
Expand Down
12 changes: 10 additions & 2 deletions addons/dialogic/Modules/Background/default_background.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,23 @@

[ext_resource type="Script" path="res://addons/dialogic/Modules/Background/default_background.gd" id="1_nkdrp"]

[node name="DefaultBackground" type="ColorRect"]
[node name="DefaultBackground" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(1, 1, 1, 0)
script = ExtResource("1_nkdrp")

[node name="ColorRect" type="ColorRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2

[node name="Image" type="TextureRect" parent="."]
layout_mode = 1
anchors_preset = 15
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
shader_type canvas_item;

// Indicates how far the transition is (0 start, 1 end).
uniform float progress : hint_range(0.0, 1.0);
// The previous background, transparent if there was none.
uniform sampler2D previous_background : source_color, hint_default_transparent;
// The next background, transparent if there is none.
uniform sampler2D next_background : source_color, hint_default_transparent;

// The texture used to determine how far along the progress has to be for bending in the new background.
uniform sampler2D whipe_texture : source_color;
// The size of the trailing smear of the transition.
uniform float feather : hint_range(0.0, 1.0, 0.0001) = 0.1;
// Determines if the whipe texture should keep it's aspect ratio when scaled to the screen's size.
uniform bool keep_aspect_ratio = false;

void fragment() {
vec2 frag_coord = UV;
if(keep_aspect_ratio) {
vec2 ratio = (SCREEN_PIXEL_SIZE.x > SCREEN_PIXEL_SIZE.y) // determine how to scale
? vec2(SCREEN_PIXEL_SIZE.y / SCREEN_PIXEL_SIZE.x, 1) // fit to width
: vec2(1, SCREEN_PIXEL_SIZE.x / SCREEN_PIXEL_SIZE.y); // fit to height
frag_coord *= ratio;
}

// get the blend factor between the previous and next background.
float alpha = (texture(whipe_texture, frag_coord).r) - progress;
float blend_factor = 1. - smoothstep(0., feather, alpha + (feather * (1. -progress)));

vec4 old_frag = texture(previous_background, UV);
vec4 new_frag = texture(next_background, UV);

COLOR = mix(old_frag, new_frag, blend_factor);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[gd_resource type="ShaderMaterial" load_steps=4 format=3 uid="uid://0wc7bedn7ee8"]

[ext_resource type="Shader" path="res://addons/dialogic/Modules/Background/default_background_transition.gdshader" id="1_uctph"]

[sub_resource type="Gradient" id="Gradient_4flwl"]
offsets = PackedFloat32Array(0.496403)
colors = PackedColorArray(0.524751, 0.524751, 0.524751, 1)

[sub_resource type="GradientTexture1D" id="GradientTexture1D_gkpb1"]
gradient = SubResource("Gradient_4flwl")

[resource]
shader = ExtResource("1_uctph")
shader_parameter/progress = 0.035
shader_parameter/feather = 1.0
shader_parameter/keepAspectRatio = false
shader_parameter/whipeTexture = SubResource("GradientTexture1D_gkpb1")
25 changes: 16 additions & 9 deletions addons/dialogic/Modules/Background/dialogic_background.gd
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
extends Node
class_name DialogicBackground

## This is the base class for dialogic backgrounds.
## This is the base class for dialogic backgrounds.
## Extend it and override it's methods when you create a custom background.
## You can take a look at the default background to get an idea of how it's working.


## Load the new background in here.
## The time argument is given for when [_should_do_background_update] returns true
## The subviewport container that holds this background. Set when instanced.
var viewport_container: SubViewportContainer
## The viewport that holds this background. Set when instanced.
var viewport: SubViewport


## Load the new background in here.
## The time argument is given for when [_should_do_background_update] returns true
## (then you have to do a transition in here)
func _update_background(argument:String, time:float) -> void:
pass
Expand All @@ -19,13 +25,14 @@ func _should_do_background_update(argument:String) -> bool:
return false


## Called by dialogic when first created.
## If you return false (by default) it will attempt to animate the "modulate" property.
func _fade_in(time:float) -> bool:
## Called by dialogic when first created.
## If you return false (by default) it will attempt to animate the "modulate" property.
func _custom_fade_in(time:float) -> bool:
return false


## Called by dialogic before removing (done by dialogic).
## If you return false (by default) it will attempt to animate the "modulate" property.
func _fade_out(time:float) -> bool:
## Called by dialogic before removing (done by dialogic).
## If you return false (by default) it will attempt to animate the "modulate" property.
func _custom_fade_out(time:float) -> bool:
return false

4 changes: 3 additions & 1 deletion addons/dialogic/Modules/Background/node_background_holder.gd
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
class_name DialogicNode_BackgroundHolder
extends CanvasLayer
extends ColorRect


func _ready():
add_to_group('dialogic_background_holders')
if material == null:
material = load("res://addons/dialogic/Modules/Background/default_background_transition.tres")
132 changes: 88 additions & 44 deletions addons/dialogic/Modules/Background/subsystem_backgrounds.gd
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ extends DialogicSubsystem

signal background_changed(info:Dictionary)

var _tween: Tween
var _tween_callbacks: Array[Callable]

var default_background_scene: PackedScene = load(get_script().resource_path.get_base_dir().path_join('default_background.tscn'))

var default_background_scene :PackedScene = load(get_script().resource_path.get_base_dir().path_join('default_background.tscn'))
####################################################################################################
## STATE
####################################################################################################
Expand All @@ -32,56 +35,97 @@ func load_game_state(load_flag:=LoadFlags.FULL_LOAD):
## To do so implement [_should_do_background_update()] on the custom background scene.
## Then [_update_background()] will be called directly on that previous scene.
func update_background(scene:String = '', argument:String = '', fade_time:float = 0.0) -> void:
var background_holder: DialogicNode_BackgroundHolder = get_tree().get_first_node_in_group('dialogic_background_holders')
if background_holder == null:
return

var info := {'scene':scene, 'argument':argument, 'fade_time':fade_time, 'same_scene':false}
for node in get_tree().get_nodes_in_group('dialogic_background_holders'):
if node.visible:
var bg_set: bool = false
if scene == dialogic.current_state_info.get('background_scene', ''):
for old_bg in node.get_children():
if old_bg.has_method("_should_do_background_update") and old_bg._should_do_background_update(argument):
if old_bg.has_method('_update_background'):
old_bg._update_background(argument, fade_time)
bg_set = true
info['same_scene'] = true
if !bg_set:
# remove previous backgrounds
for old_bg in node.get_children():

if !old_bg._fade_out(fade_time) and "modulate" in old_bg:
var tween := old_bg.create_tween()
tween.tween_property(old_bg, "modulate", Color.TRANSPARENT, fade_time)
tween.tween_callback(old_bg.queue_free)
else:
old_bg.queue_free()

var new_node:Node
if scene.ends_with('.tscn'):
new_node = load(scene).instantiate()
if !new_node is DialogicBackground:
printerr("[Dialogic] Tried using custom backgrounds that doesn't extend DialogicBackground class!")
new_node.queue_free()
new_node = null
elif argument:
new_node = default_background_scene.instantiate()
else:
new_node = null

if new_node:
node.add_child(new_node)

if new_node.has_method('_update_background'):
new_node._update_background(argument, fade_time)

if !new_node._fade_in(fade_time) and "modulate" in new_node:
new_node.modulate = Color.TRANSPARENT
var tween := new_node.create_tween()
tween.tween_property(new_node, "modulate", Color.WHITE, fade_time)
var bg_set := false

# First try just updating the existing scene.
if scene == dialogic.current_state_info.get('background_scene', ''):
for old_bg in background_holder.get_children():
if !old_bg.has_meta('node') or not old_bg.get_meta('node') is DialogicBackground:
continue

var prev_bg_node: DialogicBackground = old_bg.get_meta('node')
if prev_bg_node._should_do_background_update(argument):
prev_bg_node._update_background(argument, fade_time)
bg_set = true
info['same_scene'] = true

# If that didn't work, add a new scene, then cross-fade
if !bg_set:
var material: Material = background_holder.material
# make sure material is clean and ready to go
material.set_shader_parameter("progress", 0)
# swap the next background into previous, as that is now the older frame
material.set_shader_parameter("previous_background", material.get_shader_parameter("next_background"))
material.set_shader_parameter("next_background", null)

if _tween:
_tween.kill()

_tween = get_tree().create_tween()

# could be implemented as passed by the event
#material.set_shader_parameter("whipe_texture", whipe_texture) # the direction the whipe takes from black to white
#material.set_shader_parameter("feather", feather) # the trailing smear left behind when the whipe happens

_tween.tween_method(func (progress: float):
material.set_shader_parameter("progress", progress)
, 0.0, 1.0, fade_time)

## remove previous backgrounds
for old_bg in background_holder.get_children():
if old_bg is SubViewportContainer:
old_bg.get_meta('node')._custom_fade_out(fade_time)
_tween.chain().tween_callback(old_bg.queue_free)

var new_node: SubViewportContainer
if scene.ends_with('.tscn') and ResourceLoader.exists(scene):
new_node = add_background_node(load(scene), background_holder)
if not new_node.get_meta('node') is DialogicBackground:
printerr("[Dialogic] Given background scene was not of type DialogicBackground!")
elif argument:
new_node = add_background_node(default_background_scene, background_holder)
else:
new_node = null

if new_node:
new_node.get_meta('node')._update_background(argument, fade_time)
new_node.get_meta('node')._custom_fade_in(fade_time)
material.set_shader_parameter("next_background", new_node.get_child(0).get_texture())

dialogic.current_state_info['background_scene'] = scene
dialogic.current_state_info['background_argument'] = argument
background_changed.emit(info)


func add_background_node(scene:PackedScene, parent:DialogicNode_BackgroundHolder) -> SubViewportContainer:
var v_con := SubViewportContainer.new()
var viewport := SubViewport.new()
var b_scene := scene.instantiate()

parent.add_child(v_con)
v_con.visible = false
v_con.stretch = true
v_con.size = parent.size
v_con.set_anchors_preset(Control.PRESET_FULL_RECT)

v_con.add_child(viewport)
viewport.transparent_bg = true
viewport.disable_3d = true

viewport.add_child(b_scene)
b_scene.viewport = viewport
b_scene.viewport_container = v_con

v_con.set_meta('node', b_scene)

return v_con


func has_background() -> bool:
return !dialogic.current_state_info['background_scene'].is_empty() or !dialogic.current_state_info['background_argument'].is_empty()

0 comments on commit a7f565c

Please sign in to comment.