mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-19 01:29:49 +00:00
Spritesheet Animation Canvas Preview (#835)
* spritesheet preview * formatting
This commit is contained in:
parent
4e7d5d34cf
commit
2ad1391ca1
|
@ -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
|
||||
|
|
|
@ -6,4 +6,5 @@
|
|||
script = ExtResource( 1 )
|
||||
|
||||
[node name="AnimationTimer" type="Timer" parent="."]
|
||||
|
||||
[connection signal="timeout" from="AnimationTimer" to="." method="_on_AnimationTimer_timeout"]
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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"]
|
||||
|
|
Loading…
Reference in a new issue