Skip to content

Commit

Permalink
feat: allow shifting the shapes along the sprite frames
Browse files Browse the repository at this point in the history
This helps when adding new sprite frames to the animation,
after you've already configured the shape frames.

Fixes #4

/spend 6h
  • Loading branch information
Goutte committed Jan 4, 2024
1 parent 84edbb8 commit 26df773
Show file tree
Hide file tree
Showing 11 changed files with 283 additions and 30 deletions.
14 changes: 9 additions & 5 deletions addons/goutte.animated_shape_2d/animated_shape_2d.gd
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
@tool
extends Node
## Animates a Shape2D for each frame of an AnimatedSprite.
## You can put this pretty much anywhere you want in your scene.
class_name AnimatedShape2D

# _ _ _ _____ _
Expand All @@ -11,7 +9,9 @@ class_name AnimatedShape2D
# / ____ \| | | | | | | | | | (_| | || __/ (_| |____) | | | | (_| | |_) | __/
# /_/ \_\_| |_|_|_| |_| |_|\__,_|\__\___|\__,_|_____/|_| |_|\__,_| .__/ \___|
# | |
# v0.1.2-20231229 |_|
# v0.1.3-20231231 |_|
## Animates a CollisionShape2D for each frame of an AnimatedSprite2D.
## You can put this pretty much anywhere you want in your scene.


## Animated sprite we're going to watch to figure out which shape we want.
Expand Down Expand Up @@ -80,13 +80,15 @@ func setup():
self.collision_shape_parent = self.collision_shape.get_parent()
if self.collision_shape_parent != null:
self.initial_scale = self.collision_shape_parent.scale

self.animated_sprite.frame_changed.connect(update_shape)


func update_shape():
var animation_name := self.animated_sprite.get_animation()
var frame := self.animated_sprite.get_frame()
var shape_frame := self.shape_frames.get_shape_frame(animation_name, frame)

var shape: Shape2D = null
if shape_frame != null:
shape = shape_frame.get_shape()
Expand All @@ -96,20 +98,22 @@ func update_shape():
position = shape_frame.position
disabled = shape_frame.disabled
if shape == null and self.use_previous_as_fallback:
# Improvement idea: allow flipping in this case as well
return
if shape == null and self.use_initial_as_fallback:
shape = self.fallback_shape
position = self.fallback_position
disabled = self.fallback_disabled

self.collision_shape.shape = shape
self.collision_shape.position = position
self.collision_shape.disabled = disabled
if self.handle_flip_h and is_collision_shape_parent_flippable():
# Improvement idea: flip the CollisionBody2D itself and mirror its x pos
if self.animated_sprite.flip_h:
self.collision_shape_parent.scale.x = -1.0 * self.initial_scale.x
self.collision_shape_parent.scale.x = -self.initial_scale.x
else:
self.collision_shape_parent.scale.x = 1.0 * self.initial_scale.x
self.collision_shape_parent.scale.x = self.initial_scale.x


## We don't want to flip PhysicsBodies because it creates odd behaviors.
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions addons/goutte.animated_shape_2d/editor/icons/shift_left.png.import
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://btv6bx8bipqrm"
path="res://.godot/imported/shift_left.png-2904efcb7f02a4fa2b265a34ae875c4f.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://addons/goutte.animated_shape_2d/editor/icons/shift_left.png"
dest_files=["res://.godot/imported/shift_left.png-2904efcb7f02a4fa2b265a34ae875c4f.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://q30fj4bowepc"
path="res://.godot/imported/shift_right.png-de4924e407dbbfc32773a9a30221b8a0.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://addons/goutte.animated_shape_2d/editor/icons/shift_right.png"
dest_files=["res://.godot/imported/shift_right.png-de4924e407dbbfc32773a9a30221b8a0.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
39 changes: 36 additions & 3 deletions addons/goutte.animated_shape_2d/editor/shape_frame_editor.gd
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
@tool
extends Control
class_name ShapeFrameEditor

## Editor GUI for a single ShapeFrame2D.
## Shows a preview of the sprite and the shape, as well as action buttons.
class_name ShapeFrameEditor


const SHAPE_PREVIEW_SCRIPT := preload("./shape_preview.gd")
Expand All @@ -24,6 +25,10 @@ var background_color := Color.WEB_GRAY
var undo_redo: EditorUndoRedoManager


signal frame_selected
signal frame_deselected


## Mandatory dependency injection, since it's best to leave _init() alone.
func configure(
animated_shape: AnimatedShape2D,
Expand Down Expand Up @@ -55,6 +60,8 @@ func _enter_tree():
func _exit_tree():
disconnect_from_shape_frame()
remove_preview_of_shape_frame()
if is_selected():
frame_deselected.emit()


func build(button_group: ButtonGroup):
Expand All @@ -73,6 +80,19 @@ func get_shape_frame() -> ShapeFrame2D:
)


func set_shape_frame(value: ShapeFrame2D):
if self.animated_shape == null:
return
if self.animated_shape.shape_frames == null:
return
disconnect_from_shape_frame()
self.animated_shape.shape_frames.set_shape_frame(
self.animation_name, self.frame_index, value,
)
connect_to_shape_frame()
update()


## Connect to the edited Resource, in order to update the GUI in real time.
func connect_to_shape_frame():
var shape_frame := get_shape_frame()
Expand All @@ -88,6 +108,14 @@ func disconnect_from_shape_frame():
shape_frame.changed.disconnect(on_shape_frame_changed)


func is_selected() -> bool:
return %SpriteButton.button_pressed


func select():
%SpriteButton.button_pressed = true


## The crux of the matter ; update the scene according to the data.
func update():
if self.animated_shape == null:
Expand Down Expand Up @@ -377,15 +405,17 @@ func on_shape_frame_changed():

func _on_sprite_button_toggled(toggled_on: bool):
if toggled_on:
frame_selected.emit()
preview_shape_frame()
#inspect_shape_frame() # nope, the preview has priority somehow
#inspect_shape_frame.call_deferred() # nope too
# So, this horrendous await that will create bugs
# So, we use this horrendous await that will create bugs:
get_tree().create_timer(0.064).timeout.connect(
func():
inspect_shape_frame()
)
else:
frame_deselected.emit()
remove_preview_of_shape_frame()


Expand All @@ -399,7 +429,10 @@ func _on_create_button_pressed():
shape_frame = ShapeFrame2D.new()
shape_frame.disabled = self.animated_shape.collision_shape.disabled
shape_frame.position = self.animated_shape.collision_shape.position
shape_frame.shape = self.animated_shape.collision_shape.shape.duplicate(true)
if self.animated_shape.collision_shape.shape:
shape_frame.shape = self.animated_shape.collision_shape.shape.duplicate(true)
else:
shape_frame.shape = RectangleShape2D.new()
self.animated_shape.shape_frames.set_shape_frame(
self.animation_name, self.frame_index, shape_frame,
)
Expand Down
Loading

0 comments on commit 26df773

Please sign in to comment.