From c466699ba24bcba32e61fcfe40f147354f1a1b95 Mon Sep 17 00:00:00 2001 From: Pheubel Date: Fri, 10 Nov 2023 01:35:11 -0800 Subject: [PATCH] Render backgrounds in sub-viewports and transition them using shaders (#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 --- .../Modules/Background/default_background.gd | 27 ++-- .../Background/default_background.tscn | 12 +- .../default_background_transition.gdshader | 34 +++++ .../default_background_transition.tres | 17 +++ .../Modules/Background/dialogic_background.gd | 25 ++-- .../Background/node_background_holder.gd | 4 +- .../Background/subsystem_backgrounds.gd | 132 ++++++++++++------ .../Default/DialogicDefaultLayout.tscn | 29 ++-- 8 files changed, 202 insertions(+), 78 deletions(-) create mode 100644 addons/dialogic/Modules/Background/default_background_transition.gdshader create mode 100644 addons/dialogic/Modules/Background/default_background_transition.tres diff --git a/addons/dialogic/Modules/Background/default_background.gd b/addons/dialogic/Modules/Background/default_background.gd index 152b26d26..675f0f4e6 100644 --- a/addons/dialogic/Modules/Background/default_background.gd +++ b/addons/dialogic/Modules/Background/default_background.gd @@ -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: diff --git a/addons/dialogic/Modules/Background/default_background.tscn b/addons/dialogic/Modules/Background/default_background.tscn index 0b5d2db15..0e5057446 100644 --- a/addons/dialogic/Modules/Background/default_background.tscn +++ b/addons/dialogic/Modules/Background/default_background.tscn @@ -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 diff --git a/addons/dialogic/Modules/Background/default_background_transition.gdshader b/addons/dialogic/Modules/Background/default_background_transition.gdshader new file mode 100644 index 000000000..d631d9ff0 --- /dev/null +++ b/addons/dialogic/Modules/Background/default_background_transition.gdshader @@ -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); +} diff --git a/addons/dialogic/Modules/Background/default_background_transition.tres b/addons/dialogic/Modules/Background/default_background_transition.tres new file mode 100644 index 000000000..0d0d3895b --- /dev/null +++ b/addons/dialogic/Modules/Background/default_background_transition.tres @@ -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") diff --git a/addons/dialogic/Modules/Background/dialogic_background.gd b/addons/dialogic/Modules/Background/dialogic_background.gd index 2f0a25889..a7794c31b 100644 --- a/addons/dialogic/Modules/Background/dialogic_background.gd +++ b/addons/dialogic/Modules/Background/dialogic_background.gd @@ -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 @@ -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 + diff --git a/addons/dialogic/Modules/Background/node_background_holder.gd b/addons/dialogic/Modules/Background/node_background_holder.gd index f3aab044a..35f4565ed 100644 --- a/addons/dialogic/Modules/Background/node_background_holder.gd +++ b/addons/dialogic/Modules/Background/node_background_holder.gd @@ -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") diff --git a/addons/dialogic/Modules/Background/subsystem_backgrounds.gd b/addons/dialogic/Modules/Background/subsystem_backgrounds.gd index 50e9725bd..8e73751e3 100644 --- a/addons/dialogic/Modules/Background/subsystem_backgrounds.gd +++ b/addons/dialogic/Modules/Background/subsystem_backgrounds.gd @@ -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 #################################################################################################### @@ -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() diff --git a/addons/dialogic/Modules/DefaultLayouts/Default/DialogicDefaultLayout.tscn b/addons/dialogic/Modules/DefaultLayouts/Default/DialogicDefaultLayout.tscn index febe11c07..3cb1c51e1 100644 --- a/addons/dialogic/Modules/DefaultLayouts/Default/DialogicDefaultLayout.tscn +++ b/addons/dialogic/Modules/DefaultLayouts/Default/DialogicDefaultLayout.tscn @@ -1,7 +1,8 @@ -[gd_scene load_steps=31 format=3 uid="uid://uan2wdyuprb6"] +[gd_scene load_steps=32 format=3 uid="uid://uan2wdyuprb6"] [ext_resource type="Script" path="res://addons/dialogic/Modules/DefaultLayouts/Default/DialogicDefaultLayout.gd" id="1"] [ext_resource type="Script" path="res://addons/dialogic/Modules/Text/node_dialog_text.gd" id="2"] +[ext_resource type="Material" uid="uid://0wc7bedn7ee8" path="res://addons/dialogic/Modules/Background/default_background_transition.tres" id="2_f6bas"] [ext_resource type="Script" path="res://addons/dialogic/Modules/Text/node_name_label.gd" id="3"] [ext_resource type="Script" path="res://addons/dialogic/Modules/Character/node_portrait_container.gd" id="3_dbhei"] [ext_resource type="Script" path="res://addons/dialogic/Modules/Choice/node_choice_button.gd" id="4"] @@ -255,14 +256,22 @@ expand_margin_bottom = 5.0 script = ExtResource("1") name_label_box_modulate = Color(0.00784314, 0.00784314, 0.00784314, 0.843137) -[node name="DialogicNode_BackgroundHolder" type="CanvasLayer" parent="."] -layer = -1 +[node name="BackgroundLayer" type="CanvasLayer" parent="."] +layer = 0 + +[node name="DialogicNode_BackgroundHolder" type="ColorRect" parent="BackgroundLayer"] +material = ExtResource("2_f6bas") +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 script = ExtResource("5_uvb2c") -[node name="Portraits" type="CanvasLayer" parent="."] +[node name="PortraitLayer" type="CanvasLayer" parent="."] layer = 0 -[node name="Portraits" type="Control" parent="Portraits"] +[node name="Portraits" type="Control" parent="PortraitLayer"] unique_name_in_owner = true layout_mode = 3 anchors_preset = 15 @@ -272,7 +281,7 @@ grow_horizontal = 2 grow_vertical = 2 mouse_filter = 2 -[node name="DialogicNode_PortraitContainer1" type="Control" parent="Portraits/Portraits"] +[node name="DialogicNode_PortraitContainer1" type="Control" parent="PortraitLayer/Portraits"] layout_mode = 1 anchor_right = 0.231771 anchor_bottom = 1.0 @@ -281,7 +290,7 @@ mouse_filter = 2 script = ExtResource("3_dbhei") metadata/_edit_use_anchors_ = true -[node name="DialogicNode_PortraitContainer2" type="Control" parent="Portraits/Portraits"] +[node name="DialogicNode_PortraitContainer2" type="Control" parent="PortraitLayer/Portraits"] layout_mode = 1 anchor_left = 0.190104 anchor_right = 0.401042 @@ -293,7 +302,7 @@ script = ExtResource("3_dbhei") position_index = 1 metadata/_edit_use_anchors_ = true -[node name="DialogicNode_PortraitContainer3" type="Control" parent="Portraits/Portraits"] +[node name="DialogicNode_PortraitContainer3" type="Control" parent="PortraitLayer/Portraits"] layout_mode = 1 anchor_left = 0.371528 anchor_right = 0.625868 @@ -305,7 +314,7 @@ script = ExtResource("3_dbhei") position_index = 2 metadata/_edit_use_anchors_ = true -[node name="DialogicNode_PortraitContainer4" type="Control" parent="Portraits/Portraits"] +[node name="DialogicNode_PortraitContainer4" type="Control" parent="PortraitLayer/Portraits"] layout_mode = 1 anchor_left = 0.592882 anchor_right = 0.805556 @@ -317,7 +326,7 @@ script = ExtResource("3_dbhei") position_index = 3 metadata/_edit_use_anchors_ = true -[node name="DialogicNode_PortraitContainer5" type="Control" parent="Portraits/Portraits"] +[node name="DialogicNode_PortraitContainer5" type="Control" parent="PortraitLayer/Portraits"] layout_mode = 1 anchor_left = 0.776042 anchor_top = -0.00462963