From 2ad1391ca19b6d3422b18b568490f265ed07b1fa Mon Sep 17 00:00:00 2001 From: Variable <77773850+Variable-ind@users.noreply.github.com> Date: Thu, 23 Mar 2023 18:25:33 +0500 Subject: [PATCH] Spritesheet Animation Canvas Preview (#835) * spritesheet preview * formatting --- src/UI/Canvas/CanvasPreview.gd | 137 ++++++++---- src/UI/Canvas/CanvasPreview.tscn | 1 + .../CanvasPreviewContainer.gd | 61 +++++- .../CanvasPreviewContainer.tscn | 201 ++++++++++++++---- 4 files changed, 320 insertions(+), 80 deletions(-) diff --git a/src/UI/Canvas/CanvasPreview.gd b/src/UI/Canvas/CanvasPreview.gd index 954f79b3d..872b5b01a 100644 --- a/src/UI/Canvas/CanvasPreview.gd +++ b/src/UI/Canvas/CanvasPreview.gd @@ -1,58 +1,119 @@ extends Node2D +enum Mode { TIMELINE, SPRITESHEET } +var mode = Mode.TIMELINE + +var h_frames: int = 1 +var v_frames: int = 1 +var start_sprite_sheet_frame: int = 1 +var end_sprite_sheet_frame: int = 1 +var sprite_frames = [] + var frame: int = 0 + onready var animation_timer: Timer = $AnimationTimer func _draw() -> void: var current_project: Project = Global.current_project - if frame >= current_project.frames.size(): - frame = current_project.current_frame + var texture_to_draw := ImageTexture.new() + var modulate_color := Color.white + match mode: + Mode.TIMELINE: + if frame >= current_project.frames.size(): + frame = current_project.current_frame - $AnimationTimer.wait_time = ( - current_project.frames[frame].duration - * (1 / Global.current_project.fps) - ) + $AnimationTimer.wait_time = ( + current_project.frames[frame].duration + * (1 / Global.current_project.fps) + ) - if animation_timer.is_stopped(): - frame = current_project.current_frame - var current_cels: Array = current_project.frames[frame].cels + if animation_timer.is_stopped(): + frame = current_project.current_frame + var current_cels: Array = current_project.frames[frame].cels - # Draw current frame layers - for i in range(current_cels.size()): - if current_cels[i] is GroupCel: - continue - var modulate_color := Color(1, 1, 1, current_cels[i].opacity) - if ( - i < current_project.layers.size() - and current_project.layers[i].is_visible_in_hierarchy() - ): - draw_texture(current_cels[i].image_texture, Vector2.ZERO, modulate_color) + # Draw current frame layers + for i in range(current_cels.size()): + if current_cels[i] is GroupCel: + continue + modulate_color = Color(1, 1, 1, current_cels[i].opacity) + if ( + i < current_project.layers.size() + and current_project.layers[i].is_visible_in_hierarchy() + ): + texture_to_draw = current_cels[i].image_texture + Mode.SPRITESHEET: + var target_frame = current_project.frames[current_project.current_frame] + var frame_image = Image.new() + frame_image.create( + current_project.size.x, current_project.size.y, false, Image.FORMAT_RGBA8 + ) + Export.blend_all_layers(frame_image, target_frame) + sprite_frames = split_spritesheet(frame_image, h_frames, v_frames) + + # limit start and end + if end_sprite_sheet_frame > sprite_frames.size(): + end_sprite_sheet_frame = sprite_frames.size() + if start_sprite_sheet_frame < 0: + start_sprite_sheet_frame = 0 + # reset frame if required + if frame >= end_sprite_sheet_frame: + frame = start_sprite_sheet_frame - 1 + texture_to_draw = sprite_frames[frame] + + var rect = Rect2(Vector2.ZERO, texture_to_draw.get_data().get_size()) + get_parent().get_node("TransparentChecker").fit_rect(rect) + draw_texture(texture_to_draw, Vector2.ZERO, modulate_color) func _on_AnimationTimer_timeout() -> void: - var first_frame := 0 - var last_frame: int = Global.current_project.frames.size() - 1 - var current_project: Project = Global.current_project + match mode: + Mode.TIMELINE: + var first_frame := 0 + var last_frame: int = Global.current_project.frames.size() - 1 + var current_project: Project = Global.current_project - if Global.play_only_tags: - for tag in current_project.animation_tags: - if ( - current_project.current_frame + 1 >= tag.from - && current_project.current_frame + 1 <= tag.to - ): - first_frame = tag.from - 1 - last_frame = min(current_project.frames.size() - 1, tag.to - 1) + if Global.play_only_tags: + for tag in current_project.animation_tags: + if ( + current_project.current_frame + 1 >= tag.from + && current_project.current_frame + 1 <= tag.to + ): + first_frame = tag.from - 1 + last_frame = min(current_project.frames.size() - 1, tag.to - 1) - if frame < last_frame: - frame += 1 - else: - frame = first_frame + if frame < last_frame: + frame += 1 + else: + frame = first_frame + $AnimationTimer.wait_time = ( + Global.current_project.frames[frame].duration + * (1 / Global.current_project.fps) + ) + + Mode.SPRITESHEET: + frame += 1 + $AnimationTimer.wait_time = (1 / Global.current_project.fps) $AnimationTimer.set_one_shot(true) - $AnimationTimer.wait_time = ( - Global.current_project.frames[frame].duration - * (1 / Global.current_project.fps) - ) $AnimationTimer.start() update() + + +func split_spritesheet(image: Image, horiz: int, vert: int) -> Array: + var result = [] + horiz = min(horiz, image.get_size().x) + vert = min(vert, image.get_size().y) + var frame_width := image.get_size().x / horiz + var frame_height := image.get_size().y / vert + for yy in range(vert): + for xx in range(horiz): + var tex := ImageTexture.new() + var cropped_image := Image.new() + cropped_image = image.get_rect( + Rect2(frame_width * xx, frame_height * yy, frame_width, frame_height) + ) + cropped_image.convert(Image.FORMAT_RGBA8) + tex.create_from_image(cropped_image, 0) + result.append(tex) + return result diff --git a/src/UI/Canvas/CanvasPreview.tscn b/src/UI/Canvas/CanvasPreview.tscn index 7d057882c..04a302e2c 100644 --- a/src/UI/Canvas/CanvasPreview.tscn +++ b/src/UI/Canvas/CanvasPreview.tscn @@ -6,4 +6,5 @@ script = ExtResource( 1 ) [node name="AnimationTimer" type="Timer" parent="."] + [connection signal="timeout" from="AnimationTimer" to="." method="_on_AnimationTimer_timeout"] diff --git a/src/UI/CanvasPreviewContainer/CanvasPreviewContainer.gd b/src/UI/CanvasPreviewContainer/CanvasPreviewContainer.gd index 90211ab1e..c19ae013b 100644 --- a/src/UI/CanvasPreviewContainer/CanvasPreviewContainer.gd +++ b/src/UI/CanvasPreviewContainer/CanvasPreviewContainer.gd @@ -1,8 +1,13 @@ extends PanelContainer -onready var canvas_preview = $HBoxContainer/PreviewViewportContainer/Viewport/CanvasPreview -onready var camera: Camera2D = $HBoxContainer/PreviewViewportContainer/Viewport/CameraPreview -onready var play_button: Button = $HBoxContainer/VBoxContainer/PlayButton +onready var canvas_preview: Node2D = $"%CanvasPreview" +onready var camera: Camera2D = $"%CameraPreview" +onready var play_button: Button = $"%PlayButton" + +onready var h_frames: SpinBox = $"%HFrames" +onready var v_frames: SpinBox = $"%VFrames" +onready var start: SpinBox = $"%Start" +onready var end: SpinBox = $"%End" func _on_PreviewZoomSlider_value_changed(value: float) -> void: @@ -13,11 +18,55 @@ func _on_PreviewZoomSlider_value_changed(value: float) -> void: func _on_PlayButton_toggled(button_pressed: bool) -> void: if button_pressed: - if Global.current_project.frames.size() <= 1: - play_button.pressed = false - return + if canvas_preview.mode == canvas_preview.Mode.TIMELINE: + if Global.current_project.frames.size() <= 1: + play_button.pressed = false + return + else: + if start.value == end.value: + play_button.pressed = false + return canvas_preview.animation_timer.start() Global.change_button_texturerect(play_button.get_child(0), "pause.png") else: canvas_preview.animation_timer.stop() Global.change_button_texturerect(play_button.get_child(0), "play.png") + + +func _on_OptionButton_item_selected(index: int) -> void: + play_button.pressed = false + canvas_preview.mode = index + $VBox/Animation/VBoxContainer/Options.visible = bool(index == 1) + canvas_preview.update() + + +func _on_HFrames_value_changed(value: float) -> void: + canvas_preview.h_frames = value + var frames = canvas_preview.h_frames * canvas_preview.v_frames + start.max_value = frames + end.max_value = frames + canvas_preview.update() + + +func _on_VFrames_value_changed(value: float) -> void: + canvas_preview.v_frames = value + var frames = canvas_preview.h_frames * canvas_preview.v_frames + start.max_value = frames + end.max_value = frames + canvas_preview.update() + + +func _on_Start_value_changed(value: float) -> void: + canvas_preview.frame = value - 1 + canvas_preview.start_sprite_sheet_frame = value + if end.value < value: + end.value = value + canvas_preview.update() + + +func _on_End_value_changed(value: float) -> void: + canvas_preview.end_sprite_sheet_frame = value + if start.value > value: + start.value = value + canvas_preview.frame = value - 1 + canvas_preview.update() diff --git a/src/UI/CanvasPreviewContainer/CanvasPreviewContainer.tscn b/src/UI/CanvasPreviewContainer/CanvasPreviewContainer.tscn index 3cf69ee02..7ed8c97fd 100644 --- a/src/UI/CanvasPreviewContainer/CanvasPreviewContainer.tscn +++ b/src/UI/CanvasPreviewContainer/CanvasPreviewContainer.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=8 format=2] +[gd_scene load_steps=9 format=2] [ext_resource path="res://src/UI/Nodes/TransparentChecker.tscn" type="PackedScene" id=1] [ext_resource path="res://src/Shaders/TransparentChecker.shader" type="Shader" id=2] @@ -6,6 +6,7 @@ [ext_resource path="res://src/UI/CanvasPreviewContainer/CanvasPreviewContainer.gd" type="Script" id=4] [ext_resource path="res://src/UI/Canvas/CanvasPreview.tscn" type="PackedScene" id=5] [ext_resource path="res://assets/graphics/timeline/play.png" type="Texture" id=6] +[ext_resource path="res://src/UI/Nodes/CollapsibleContainer.tscn" type="PackedScene" id=7] [sub_resource type="ShaderMaterial" id=1] shader = ExtResource( 2 ) @@ -25,52 +26,89 @@ margin_bottom = 174.0 rect_min_size = Vector2( 0, 90 ) script = ExtResource( 4 ) -[node name="HBoxContainer" type="HBoxContainer" parent="."] +[node name="VBox" type="VBoxContainer" parent="."] margin_left = 7.0 margin_top = 7.0 margin_right = 321.0 margin_bottom = 167.0 -[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer"] -margin_right = 20.0 -margin_bottom = 160.0 +[node name="HBox" type="HBoxContainer" parent="VBox"] +margin_right = 314.0 +margin_bottom = 136.0 +size_flags_vertical = 3 -[node name="Label" type="Label" parent="HBoxContainer/VBoxContainer"] -margin_left = 6.0 -margin_right = 14.0 +[node name="VBoxContainer" type="VBoxContainer" parent="VBox/HBox"] +margin_right = 16.0 +margin_bottom = 136.0 + +[node name="Label" type="Label" parent="VBox/HBox/VBoxContainer"] +margin_left = 4.0 +margin_right = 12.0 margin_bottom = 14.0 size_flags_horizontal = 4 text = "+" align = 1 -[node name="PreviewZoomSlider" type="VSlider" parent="HBoxContainer/VBoxContainer"] -margin_left = 2.0 +[node name="PreviewZoomSlider" type="VSlider" parent="VBox/HBox/VBoxContainer"] margin_top = 18.0 -margin_right = 18.0 +margin_right = 16.0 margin_bottom = 118.0 mouse_default_cursor_shape = 2 size_flags_horizontal = 4 size_flags_vertical = 3 step = 0.01 -[node name="Label2" type="Label" parent="HBoxContainer/VBoxContainer"] -margin_left = 7.0 +[node name="Label2" type="Label" parent="VBox/HBox/VBoxContainer"] +margin_left = 5.0 margin_top = 122.0 -margin_right = 12.0 +margin_right = 10.0 margin_bottom = 136.0 size_flags_horizontal = 4 text = "-" align = 1 -[node name="PlayButton" type="Button" parent="HBoxContainer/VBoxContainer" groups=["UIButtons"]] +[node name="PreviewViewportContainer" type="ViewportContainer" parent="VBox/HBox"] +margin_left = 20.0 +margin_right = 314.0 +margin_bottom = 136.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +stretch = true + +[node name="Viewport" type="Viewport" parent="VBox/HBox/PreviewViewportContainer"] +size = Vector2( 294, 136 ) +transparent_bg = true +handle_input_locally = false +render_target_update_mode = 3 + +[node name="TransparentChecker" parent="VBox/HBox/PreviewViewportContainer/Viewport" instance=ExtResource( 1 )] +material = SubResource( 1 ) + +[node name="CanvasPreview" parent="VBox/HBox/PreviewViewportContainer/Viewport" instance=ExtResource( 5 )] +unique_name_in_owner = true + +[node name="CameraPreview" type="Camera2D" parent="VBox/HBox/PreviewViewportContainer/Viewport"] +unique_name_in_owner = true +offset = Vector2( 32, 32 ) +current = true +zoom = Vector2( 0.15, 0.15 ) +script = ExtResource( 3 ) + +[node name="Animation" type="HBoxContainer" parent="VBox"] margin_top = 140.0 -margin_right = 20.0 +margin_right = 314.0 margin_bottom = 160.0 + +[node name="PlayButton" type="Button" parent="VBox/Animation" groups=["UIButtons"]] +unique_name_in_owner = true +margin_right = 20.0 +margin_bottom = 20.0 rect_min_size = Vector2( 20, 0 ) mouse_default_cursor_shape = 2 +size_flags_vertical = 0 toggle_mode = true -[node name="TextureRect" type="TextureRect" parent="HBoxContainer/VBoxContainer/PlayButton"] +[node name="TextureRect" type="TextureRect" parent="VBox/Animation/PlayButton"] anchor_left = 0.5 anchor_top = 0.5 anchor_right = 0.5 @@ -85,30 +123,121 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="PreviewViewportContainer" type="ViewportContainer" parent="HBoxContainer"] +[node name="VBoxContainer" type="VBoxContainer" parent="VBox/Animation"] margin_left = 24.0 margin_right = 314.0 -margin_bottom = 160.0 +margin_bottom = 20.0 size_flags_horizontal = 3 -size_flags_vertical = 3 -stretch = true -[node name="Viewport" type="Viewport" parent="HBoxContainer/PreviewViewportContainer"] -size = Vector2( 290, 160 ) -transparent_bg = true -handle_input_locally = false -render_target_update_mode = 3 +[node name="Mode" type="HBoxContainer" parent="VBox/Animation/VBoxContainer"] +margin_right = 290.0 +margin_bottom = 20.0 -[node name="TransparentChecker" parent="HBoxContainer/PreviewViewportContainer/Viewport" instance=ExtResource( 1 )] -material = SubResource( 1 ) +[node name="Label" type="Label" parent="VBox/Animation/VBoxContainer/Mode"] +margin_top = 3.0 +margin_right = 114.0 +margin_bottom = 17.0 +text = "Animation Mode: " -[node name="CanvasPreview" parent="HBoxContainer/PreviewViewportContainer/Viewport" instance=ExtResource( 5 )] +[node name="OptionButton" type="OptionButton" parent="VBox/Animation/VBoxContainer/Mode"] +margin_left = 118.0 +margin_right = 290.0 +margin_bottom = 20.0 +size_flags_horizontal = 3 +text = "Timeline Frames" +clip_text = true +align = 1 +items = [ "Timeline Frames", null, false, 0, null, "Current Frame (As Spritesheet)", null, false, 1, null ] +selected = 0 -[node name="CameraPreview" type="Camera2D" parent="HBoxContainer/PreviewViewportContainer/Viewport"] -offset = Vector2( 32, 32 ) -current = true -zoom = Vector2( 0.15, 0.15 ) -script = ExtResource( 3 ) +[node name="Options" parent="VBox/Animation/VBoxContainer" instance=ExtResource( 7 )] +visible = false +margin_top = 24.0 +margin_right = 290.0 +margin_bottom = 44.0 +text = "Spritesheet Options" -[connection signal="value_changed" from="HBoxContainer/VBoxContainer/PreviewZoomSlider" to="." method="_on_PreviewZoomSlider_value_changed"] -[connection signal="toggled" from="HBoxContainer/VBoxContainer/PlayButton" to="." method="_on_PlayButton_toggled"] +[node name="GridContainer" type="GridContainer" parent="VBox/Animation/VBoxContainer/Options"] +visible = false +margin_right = 290.0 +margin_bottom = 52.0 +columns = 4 + +[node name="Label" type="Label" parent="VBox/Animation/VBoxContainer/Options/GridContainer"] +margin_top = 5.0 +margin_right = 65.0 +margin_bottom = 19.0 +text = "HFrames: " +align = 2 + +[node name="HFrames" type="SpinBox" parent="VBox/Animation/VBoxContainer/Options/GridContainer"] +unique_name_in_owner = true +margin_left = 69.0 +margin_right = 143.0 +margin_bottom = 24.0 +min_value = 1.0 +value = 1.0 +allow_greater = true + +[node name="Label2" type="Label" parent="VBox/Animation/VBoxContainer/Options/GridContainer"] +margin_left = 147.0 +margin_top = 5.0 +margin_right = 210.0 +margin_bottom = 19.0 +text = "VFrames: " +align = 2 + +[node name="VFrames" type="SpinBox" parent="VBox/Animation/VBoxContainer/Options/GridContainer"] +unique_name_in_owner = true +margin_left = 214.0 +margin_right = 288.0 +margin_bottom = 24.0 +min_value = 1.0 +value = 1.0 +allow_greater = true +__meta__ = { +"_editor_description_": "" +} + +[node name="Label3" type="Label" parent="VBox/Animation/VBoxContainer/Options/GridContainer"] +margin_top = 33.0 +margin_right = 65.0 +margin_bottom = 47.0 +text = "Start: " +align = 2 + +[node name="Start" type="SpinBox" parent="VBox/Animation/VBoxContainer/Options/GridContainer"] +unique_name_in_owner = true +margin_left = 69.0 +margin_top = 28.0 +margin_right = 143.0 +margin_bottom = 52.0 +min_value = 1.0 +max_value = 1.0 +value = 1.0 + +[node name="Label4" type="Label" parent="VBox/Animation/VBoxContainer/Options/GridContainer"] +margin_left = 147.0 +margin_top = 33.0 +margin_right = 210.0 +margin_bottom = 47.0 +text = "End: " +align = 2 + +[node name="End" type="SpinBox" parent="VBox/Animation/VBoxContainer/Options/GridContainer"] +unique_name_in_owner = true +margin_left = 214.0 +margin_top = 28.0 +margin_right = 288.0 +margin_bottom = 52.0 +min_value = 1.0 +max_value = 1.0 +value = 1.0 + +[connection signal="value_changed" from="VBox/HBox/VBoxContainer/PreviewZoomSlider" to="." method="_on_PreviewZoomSlider_value_changed"] +[connection signal="toggled" from="VBox/Animation/PlayButton" to="." method="_on_PlayButton_toggled"] +[connection signal="item_selected" from="VBox/Animation/VBoxContainer/Mode/OptionButton" to="." method="_on_OptionButton_item_selected"] +[connection signal="value_changed" from="VBox/Animation/VBoxContainer/Options/GridContainer/HFrames" to="." method="_on_HFrames_value_changed"] +[connection signal="value_changed" from="VBox/Animation/VBoxContainer/Options/GridContainer/VFrames" to="." method="_on_VFrames_value_changed"] +[connection signal="value_changed" from="VBox/Animation/VBoxContainer/Options/GridContainer/Start" to="." method="_on_Start_value_changed"] +[connection signal="value_changed" from="VBox/Animation/VBoxContainer/Options/GridContainer/End" to="." method="_on_End_value_changed"]