diff --git a/README.md b/README.md index 7e71841..d6e34d9 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,9 @@ Plugin for managing transitions and Node references between scenes. Expect more Alternatively, you may download this repo from github and add the `addons` folder to your project, then enable it from your Project Settings~ -## How to use +## Features + +**Simple functions to change/reload scenes with a nice transition** (no manual animation required) When SceneManager is installed, you will gain access to the `SceneManager` singleton. You can then trigger it's methods directly like so: @@ -27,7 +29,39 @@ There are similar methods for reloading your scene and making a fade without tra --- -We have also added the Entity Singleton feature in v0.3! It's an easy way to keep track of important Nodes that only exist once in your scenes (like your player or your level tilemap), regardless of the name they have. +**Custom animations** (BETA) + +Now you can add your very own `AnimationPlayer` to complement the one packaged in SceneManager! + +First off, setup a new `Scene` that has an `AnimationPlayer` as its **root** node. Make and name all the animations you want, and make sure you have a `RESET` animation that keeps all your visuals out of the screen. + +![Custom animation setup](/custom_animation_setup.gif) + +Then, set your player in SceneManager via code (We recommend doing this as early as possible in your game. For example in the `_ready()` method of an autoload or your first scene): + +```gd +SceneManager.set_animation_player('res://demo/animation_player.tscn') +``` + +Now, whenever you call `SceneManager.change_scene()` or any other transition function, you will be able to add new `animation_name`, `animation_name_enter` and/or `animation_name_leave` options with the name of your animation. + +You can even match an enter/leave pattern from `SceneManager`! Just remember that `animation_name` takes precedence, so you'll want to use the opposite enter/leave suffix on `animation_name`. + +``` +SceneManager.change_scene('res://demo/test2.tscn', {"animation_name_enter": "roll", "pattern_leave": "squares"}) +``` + +![Custom animation demonstration](/custom_animation_showcase.gif) + +As always, check the demo in this repo or the [API](#api) docs for more info! + +**BETA DISCLOSURE**: This feature is brand-new, bug reports and suggestions are appreciated in the Github issues tab! + +--- + +**Entity Singletons** + +An easy way to keep track of important Nodes that only exist once in your scenes (like your player or your level tilemap), regardless of the name they have. First off, you just have to set the flag and name in the editor: @@ -62,7 +96,7 @@ but for ease-of-use we recommend using the `reload_scene(options)` function expl You can pass the following options to this function in a dictionary: -- `speed : float = 2`: Speed of the moving transition. +- `speed : float = 2`: Speed of the moving transition. (Also affects custom animations) - `color : Color = Color('#000000')`: Color to use for the transition screen. It's black by default. - `wait_time : float = 0.5`: Time spent in the transition screen while switching scenes. Leaving it at 0 with fade will result in the screen not turning black, so it waits a little bit by default. - `skip_scene_change : Bool = false`: If set to true, skips the actual scene change/reload, leaving only the transition. @@ -75,6 +109,9 @@ You can pass the following options to this function in a dictionary: - `ease : float = 1.0`: Amount of ease the animation should have during the transition. - `ease_enter : float = ease`: Amount of ease the animation should have during the fade-to-black transition. - `ease_leave : float = ease`: Amount of ease the animation should have during the fade-from-black transition. +- `animation_name : String`: Name of an animation set in `set_animation_player()` which will be played for both in and out transitions +- `animation_name_enter : String`: Name of an animation set in `set_animation_player()` which will be played for the fade-to-black transition +- `animation_name_leave : String`: Name of an animation set in `set_animation_player()` which will be played for the fade-from-black transition The following patterns are available out-of-the-box: @@ -127,6 +164,25 @@ It can take the following options, with the same defaults as `change_scene`: - `invert_on_leave` - `ease` +### `func set_animation_player(animation_player: String || PackedScene)` + +Set a custom `AnimationPlayer` make your own transitions easily. We recommend doing this only once as early as possible in your game, for example in the `_ready()` method of an autoload or your first scene. + +This method receives a path to (or a `PackedScene` of) the location of your `AnimationPlayer` scene. + +```gd +SceneManager.set_animation_player('res://demo/animation_player.tscn') +``` + +Your `AnimationPlayer` scene must follow these rules: + +1. The root node **must** be a Godot `AnimationPlayer` +2. The animation player **must** have a `RESET` animation that keeps all your graphics outside of the viewport. This is because the animation player will always be rendered on top of your screen, so any leftovers will always be rendered (you should notice right away if this is the case) + +Add your animations to this `AnimationPlayer` and then use their names in the options of any SceneManager transition function as `animation_name`, `animation_name_enter` or `animation_name_leave` + +Please check the demo files in this repository for a complete example. + ### `func get_entity(entity_name: String)` Get a reference to a named entity (node) in your scene. To define entity names go to the desired node in the editor inspector and you'll see two new properties: `Singleton entity` and `Entity name`. Check the `Singleton entity` checkbox to have this node saved to the SceneManager entity dictionary and write a friendly `Entity name` to be used in this function. Afterwards, you'll be able to access it within the scene. diff --git a/addons/scene_manager/SceneManager.gd b/addons/scene_manager/SceneManager.gd index b8e968d..eea444e 100644 --- a/addons/scene_manager/SceneManager.gd +++ b/addons/scene_manager/SceneManager.gd @@ -11,6 +11,7 @@ onready var _root := _tree.get_root() onready var _current_scene := _tree.current_scene onready var _animation_player := $AnimationPlayer onready var _shader_blend_rect := $CanvasLayer/ColorRect +var _user_animation_player: AnimationPlayer enum FadeTypes { Fade, ShaderFade } @@ -25,12 +26,15 @@ var default_options := { "skip_scene_change": false, "skip_fade_out": false, "skip_fade_in": false, + "animation_name": null } # extra_options = { # "pattern_enter": DEFAULT_IMAGE, # "pattern_leave": DEFAULT_IMAGE, # "ease_enter": 1.0, # "ease_leave": 1.0, +# "animation_name_enter": null, +# "animation_name_leave": null, # } var new_names := { @@ -114,6 +118,10 @@ func _get_final_options(initial_options: Dictionary) -> Dictionary: if not ease_key in options: options[ease_key] = options["ease"] + for animation_name_key in ["animation_name_enter", "animation_name_leave"]: + if not animation_name_key in options: + options[animation_name_key] = options["animation_name"] + return options @@ -136,17 +144,56 @@ func _reload_scene() -> void: func _replace_scene(scene) -> void: _current_scene.queue_free() emit_signal("scene_unloaded") - var following_scene = _load_scene_resource(scene) + var following_scene = _load_resource(scene) _current_scene = following_scene.instance() yield(_tree.create_timer(0.0), "timeout") _root.add_child(_current_scene) _tree.set_current_scene(_current_scene) -func _load_scene_resource(scene) -> Resource: - if scene is PackedScene: - return scene - return ResourceLoader.load(scene) +func _load_resource(resource) -> Resource: + if resource is PackedScene: + return resource + return ResourceLoader.load(resource) + + +func _user_fade_out(options: Dictionary): + assert(_user_animation_player is AnimationPlayer, "No animation player was set.") + _user_animation_player.playback_speed = options["speed"] + _user_animation_player.play(options["animation_name_enter"]) + yield(_user_animation_player, "animation_finished") + + +func _user_fade_in(options: Dictionary): + assert(_user_animation_player is AnimationPlayer, "No animation player was set.") + _user_animation_player.playback_speed = options["speed"] + _user_animation_player.play_backwards(options["animation_name_leave"]) + yield(_user_animation_player, "animation_finished") + + +func _plugin_fade_out(options: Dictionary): + _animation_player.playback_speed = options["speed"] + _shader_blend_rect.material.set_shader_param("dissolve_texture", options["pattern_enter"]) + _shader_blend_rect.material.set_shader_param("fade", not options["pattern_enter"]) + _shader_blend_rect.material.set_shader_param("fade_color", options["color"]) + _shader_blend_rect.material.set_shader_param("inverted", options["invert"]) + var animation = _animation_player.get_animation("ShaderFade") + animation.track_set_key_transition(0, 0, options["ease_enter"]) + _animation_player.play("ShaderFade") + yield(_animation_player, "animation_finished") + + +func _plugin_fade_in(options: Dictionary): + _animation_player.playback_speed = options["speed"] + _shader_blend_rect.material.set_shader_param("dissolve_texture", options["pattern_leave"]) + _shader_blend_rect.material.set_shader_param("fade", not options["pattern_leave"]) + _shader_blend_rect.material.set_shader_param( + "inverted", not options["invert"] if options["invert_on_leave"] else options["invert"] + ) + var animation = _animation_player.get_animation("ShaderFade") + animation.track_set_key_transition(0, 0, options["ease_leave"]) + _animation_player.play_backwards("ShaderFade") + yield(_animation_player, "animation_finished") #region Public API @@ -178,36 +225,47 @@ func fade_in_place(setted_options: Dictionary = {}) -> void: func fade_out(setted_options: Dictionary = {}) -> void: var options = _get_final_options(setted_options) is_transitioning = true - _animation_player.playback_speed = options["speed"] - - _shader_blend_rect.material.set_shader_param("dissolve_texture", options["pattern_enter"]) - _shader_blend_rect.material.set_shader_param("fade", not options["pattern_enter"]) - _shader_blend_rect.material.set_shader_param("fade_color", options["color"]) - _shader_blend_rect.material.set_shader_param("inverted", options["invert"]) - var animation = _animation_player.get_animation("ShaderFade") - animation.track_set_key_transition(0, 0, options["ease_enter"]) - _animation_player.play("ShaderFade") - - yield(_animation_player, "animation_finished") + if options["animation_name_enter"]: + yield(_user_fade_out(options), "completed") + else: + yield(_plugin_fade_out(options), "completed") emit_signal("fade_complete") func fade_in(setted_options: Dictionary = {}) -> void: var options = _get_final_options(setted_options) - _shader_blend_rect.material.set_shader_param("dissolve_texture", options["pattern_leave"]) - _shader_blend_rect.material.set_shader_param("fade", not options["pattern_leave"]) - _shader_blend_rect.material.set_shader_param( - "inverted", not options["invert"] if options["invert_on_leave"] else options["invert"] - ) - var animation = _animation_player.get_animation("ShaderFade") - animation.track_set_key_transition(0, 0, options["ease_leave"]) - _animation_player.play_backwards("ShaderFade") - - yield(_animation_player, "animation_finished") + if options["animation_name_leave"]: + if not options["animation_name_enter"]: + _animation_player.play("RESET") + yield(_user_fade_in(options), "completed") + else: + if options["animation_name_enter"]: + _user_animation_player.play("RESET") + yield(_plugin_fade_in(options), "completed") is_transitioning = false emit_signal("transition_finished") +func set_animation_player(animation_player) -> void: + assert( + animation_player is String or animation_player is PackedScene, + "set_animation_player() must receive a string (path to AnimationPlayer.tscn) or a PackedScene" + ) + var loaded_animation_player = _load_resource(animation_player).instance() + assert( + loaded_animation_player is AnimationPlayer, + ( + "The scene loaded from set_animation_player() (%s) must receive an AnimationPlayer" + % _user_animation_player + ) + ) + if _user_animation_player is AnimationPlayer: + _user_animation_player.queue_free() + _user_animation_player = loaded_animation_player + $CanvasLayer.add_child(_user_animation_player) + _user_animation_player.play("RESET") + + func get_entity(entity_name: String) -> Node: assert( singleton_entities.has(entity_name), diff --git a/addons/scene_manager/SceneManager.tscn b/addons/scene_manager/SceneManager.tscn index 44e99f3..a82423f 100644 --- a/addons/scene_manager/SceneManager.tscn +++ b/addons/scene_manager/SceneManager.tscn @@ -1,6 +1,5 @@ [gd_scene load_steps=7 format=2] -[ext_resource path="res://addons/scene_manager/ColorFade.tres" type="Animation" id=1] [ext_resource path="res://addons/scene_manager/SceneManager.gd" type="Script" id=2] [ext_resource path="res://addons/scene_manager/Dissolve2d.shader" type="Shader" id=3] [ext_resource path="res://addons/scene_manager/shader_patterns/squares.png" type="Texture" id=4] @@ -14,6 +13,21 @@ shader_param/fade = false shader_param/inverted = false shader_param/dissolve_texture = ExtResource( 4 ) +[sub_resource type="Animation" id=2] +length = 0.001 +tracks/0/type = "value" +tracks/0/path = NodePath("CanvasLayer/ColorRect:material:shader_param/dissolve_amount") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/keys = { +"times": PoolRealArray( 0 ), +"transitions": PoolRealArray( 1 ), +"update": 0, +"values": [ 0.0 ] +} + [node name="SceneManager" type="Node2D"] pause_mode = 2 script = ExtResource( 2 ) @@ -28,5 +42,6 @@ anchor_bottom = 1.0 mouse_filter = 2 [node name="AnimationPlayer" type="AnimationPlayer" parent="."] -anims/ColorFade = ExtResource( 1 ) +method_call_mode = 1 +anims/RESET = SubResource( 2 ) anims/ShaderFade = ExtResource( 5 ) diff --git a/custom_animation_setup.gif b/custom_animation_setup.gif new file mode 100644 index 0000000..44f5f1d Binary files /dev/null and b/custom_animation_setup.gif differ diff --git a/custom_animation_showcase.gif b/custom_animation_showcase.gif new file mode 100644 index 0000000..8b56b3a Binary files /dev/null and b/custom_animation_showcase.gif differ diff --git a/demo/animation_player.tscn b/demo/animation_player.tscn new file mode 100644 index 0000000..9570378 --- /dev/null +++ b/demo/animation_player.tscn @@ -0,0 +1,43 @@ +[gd_scene load_steps=3 format=2] + +[sub_resource type="Animation" id=1] +length = 0.001 +tracks/0/type = "value" +tracks/0/path = NodePath("ColorRect:rect_rotation") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/keys = { +"times": PoolRealArray( 0 ), +"transitions": PoolRealArray( 1 ), +"update": 0, +"values": [ -90.0 ] +} + +[sub_resource type="Animation" id=2] +resource_name = "roll" +length = 0.5 +tracks/0/type = "value" +tracks/0/path = NodePath("ColorRect:rect_rotation") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/keys = { +"times": PoolRealArray( 0, 0.5 ), +"transitions": PoolRealArray( 1, 1 ), +"update": 0, +"values": [ -90.0, 0.0 ] +} + +[node name="AnimationPlayer" type="AnimationPlayer"] +root_node = NodePath(".") +anims/RESET = SubResource( 1 ) +anims/roll = SubResource( 2 ) + +[node name="ColorRect" type="ColorRect" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +rect_rotation = -90.0 +color = Color( 0, 0, 0, 1 ) diff --git a/demo/test.gd b/demo/test.gd index a67855b..c4bbd41 100644 --- a/demo/test.gd +++ b/demo/test.gd @@ -1,9 +1,14 @@ extends Node +const animation_player = preload('res://demo/animation_player.tscn') func _ready(): + SceneManager.set_animation_player(animation_player) yield(SceneManager, "scene_loaded") SceneManager.get_entity("Button").connect("button_down", self, "_on_Button_button_down") + SceneManager.get_entity("CustomButton").connect( + "button_down", self, "_on_CustomButton_button_down" + ) func _on_Button_button_down(): @@ -11,3 +16,10 @@ func _on_Button_button_down(): SceneManager.change_scene( 'res://demo/test2.tscn', {"pattern_enter": "fade", "pattern_leave": "squares"} ) + + +func _on_CustomButton_button_down(): + if not SceneManager.is_transitioning: + SceneManager.change_scene( + 'res://demo/test2.tscn', {"animation_name_enter": "roll", "pattern_leave": "squares"} + ) diff --git a/demo/test.tscn b/demo/test.tscn index 52b95a9..de3d0d0 100644 --- a/demo/test.tscn +++ b/demo/test.tscn @@ -11,17 +11,30 @@ script = ExtResource( 1 ) anchor_right = 1.0 anchor_bottom = 1.0 color = Color( 0.298039, 0.14902, 0.14902, 1 ) -__meta__ = { -"_edit_use_anchors_": false -} -[node name="Button" type="Button" parent="CanvasLayer/ColorRect" groups=["scene_manager_entity_nodes", "scene_manager_singleton_nodes"]] -anchor_left = 0.4 -anchor_top = 0.4 -anchor_right = 0.6 -anchor_bottom = 0.6 +[node name="CenterContainer" type="CenterContainer" parent="CanvasLayer/ColorRect"] +anchor_right = 1.0 +anchor_bottom = 1.0 + +[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/ColorRect/CenterContainer"] +margin_left = 435.0 +margin_top = 278.0 +margin_right = 588.0 +margin_bottom = 322.0 + +[node name="Button" type="Button" parent="CanvasLayer/ColorRect/CenterContainer/VBoxContainer" groups=["scene_manager_entity_nodes", "scene_manager_singleton_nodes"]] +margin_right = 153.0 +margin_bottom = 20.0 text = "Change Scene" __meta__ = { -"_edit_use_anchors_": false, "entity_name": "Button" } + +[node name="Button2" type="Button" parent="CanvasLayer/ColorRect/CenterContainer/VBoxContainer" groups=["scene_manager_entity_nodes", "scene_manager_singleton_nodes"]] +margin_top = 24.0 +margin_right = 153.0 +margin_bottom = 44.0 +text = "Custom Change Scene" +__meta__ = { +"entity_name": "CustomButton" +}