update game_menu
This commit was merged in pull request #40.
This commit is contained in:
BIN
addons/PageFlip/Assets/back.png
Normal file
BIN
addons/PageFlip/Assets/back.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 766 KiB |
40
addons/PageFlip/Assets/back.png.import
Normal file
40
addons/PageFlip/Assets/back.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bjowpx1ap4uxt"
|
||||
path="res://.godot/imported/back.png-bddbf3418b746eb53bc79fd4d0989d41.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/PageFlip/Assets/back.png"
|
||||
dest_files=["res://.godot/imported/back.png-bddbf3418b746eb53bc79fd4d0989d41.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
BIN
addons/PageFlip/Assets/blank_page.png
Normal file
BIN
addons/PageFlip/Assets/blank_page.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 300 KiB |
40
addons/PageFlip/Assets/blank_page.png.import
Normal file
40
addons/PageFlip/Assets/blank_page.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cen51wqc15b14"
|
||||
path="res://.godot/imported/blank_page.png-589b6dfce0585742cc1d305c391c9cd9.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/PageFlip/Assets/blank_page.png"
|
||||
dest_files=["res://.godot/imported/blank_page.png-589b6dfce0585742cc1d305c391c9cd9.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
BIN
addons/PageFlip/Assets/book_impact.ogg
Normal file
BIN
addons/PageFlip/Assets/book_impact.ogg
Normal file
Binary file not shown.
19
addons/PageFlip/Assets/book_impact.ogg.import
Normal file
19
addons/PageFlip/Assets/book_impact.ogg.import
Normal file
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="oggvorbisstr"
|
||||
type="AudioStreamOggVorbis"
|
||||
uid="uid://bhitebdghyhua"
|
||||
path="res://.godot/imported/book_impact.ogg-ae2f194921fcd1bc549a3ddb37fab4ba.oggvorbisstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/PageFlip/Assets/book_impact.ogg"
|
||||
dest_files=["res://.godot/imported/book_impact.ogg-ae2f194921fcd1bc549a3ddb37fab4ba.oggvorbisstr"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
BIN
addons/PageFlip/Assets/front.png
Normal file
BIN
addons/PageFlip/Assets/front.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 765 KiB |
40
addons/PageFlip/Assets/front.png.import
Normal file
40
addons/PageFlip/Assets/front.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dbdwbowx32d3v"
|
||||
path="res://.godot/imported/front.png-76dff7071666496195badceef4e0ce67.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/PageFlip/Assets/front.png"
|
||||
dest_files=["res://.godot/imported/front.png-76dff7071666496195badceef4e0ce67.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
BIN
addons/PageFlip/Assets/interior.png
Normal file
BIN
addons/PageFlip/Assets/interior.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 772 KiB |
40
addons/PageFlip/Assets/interior.png.import
Normal file
40
addons/PageFlip/Assets/interior.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dt1tiecgw5rip"
|
||||
path="res://.godot/imported/interior.png-d4b06daf084fdb8a90836ec57d2dd53f.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/PageFlip/Assets/interior.png"
|
||||
dest_files=["res://.godot/imported/interior.png-d4b06daf084fdb8a90836ec57d2dd53f.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
BIN
addons/PageFlip/Assets/page_flip.ogg
Normal file
BIN
addons/PageFlip/Assets/page_flip.ogg
Normal file
Binary file not shown.
19
addons/PageFlip/Assets/page_flip.ogg.import
Normal file
19
addons/PageFlip/Assets/page_flip.ogg.import
Normal file
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="oggvorbisstr"
|
||||
type="AudioStreamOggVorbis"
|
||||
uid="uid://bylfc3b5pmbij"
|
||||
path="res://.godot/imported/page_flip.ogg-aabb6ae51ff161ae6afa52da11fe75de.oggvorbisstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/PageFlip/Assets/page_flip.ogg"
|
||||
dest_files=["res://.godot/imported/page_flip.ogg-aabb6ae51ff161ae6afa52da11fe75de.oggvorbisstr"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
49
addons/PageFlip/Assets/page_flip_final.gdshader
Normal file
49
addons/PageFlip/Assets/page_flip_final.gdshader
Normal file
@@ -0,0 +1,49 @@
|
||||
shader_type canvas_item;
|
||||
|
||||
uniform sampler2D front_texture : source_color, filter_linear_mipmap;
|
||||
uniform sampler2D back_texture : source_color, filter_linear_mipmap;
|
||||
|
||||
uniform sampler2D spine_shadow_gradient : source_color, filter_linear, repeat_disable;
|
||||
uniform float shadow_intensity : hint_range(0.0, 1.0) = 0.0;
|
||||
uniform float max_shadow_spread : hint_range(0.0, 1.0) = 0.35;
|
||||
|
||||
uniform bool debug_mode = false;
|
||||
uniform bool is_backward = false;
|
||||
|
||||
void fragment() {
|
||||
vec2 uv_dx = dFdx(UV);
|
||||
vec2 uv_dy = dFdy(UV);
|
||||
float face_dir = uv_dx.x * uv_dy.y - uv_dx.y * uv_dy.x;
|
||||
bool is_front = face_dir > 0.0;
|
||||
|
||||
vec4 base_color;
|
||||
|
||||
if (is_front) {
|
||||
if (is_backward) {
|
||||
// Let's try flipping Y just in case they meant upside down!
|
||||
// And keep X normal, since UV goes 0 to 1.
|
||||
base_color = texture(front_texture, UV);
|
||||
} else {
|
||||
base_color = texture(front_texture, UV);
|
||||
}
|
||||
} else {
|
||||
if (is_backward) {
|
||||
base_color = texture(back_texture, vec2(1.0 - UV.x, UV.y));
|
||||
} else {
|
||||
base_color = texture(back_texture, vec2(1.0 - UV.x, UV.y));
|
||||
}
|
||||
}
|
||||
|
||||
float current_spread = max_shadow_spread * (shadow_intensity + 0.001);
|
||||
float sample_x = UV.x / current_spread;
|
||||
float shadow_alpha = texture(spine_shadow_gradient, vec2(sample_x, 0.0)).a;
|
||||
|
||||
if (sample_x > 1.0) {
|
||||
shadow_alpha = 0.0;
|
||||
}
|
||||
|
||||
shadow_alpha *= shadow_intensity;
|
||||
|
||||
vec3 final_rgb = mix(base_color.rgb, vec3(0.0, 0.0, 0.0), shadow_alpha);
|
||||
COLOR = vec4(final_rgb, base_color.a);
|
||||
}
|
||||
1
addons/PageFlip/Assets/page_flip_final.gdshader.uid
Normal file
1
addons/PageFlip/Assets/page_flip_final.gdshader.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b57sdbj15qx37
|
||||
9
addons/PageFlip/Assets/page_flip_shader_material.tres
Normal file
9
addons/PageFlip/Assets/page_flip_shader_material.tres
Normal file
@@ -0,0 +1,9 @@
|
||||
[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://ddw2ie8wnrnre"]
|
||||
|
||||
[ext_resource type="Shader" uid="uid://b57sdbj15qx37" path="res://addons/PageFlip/Assets/page_flip_final.gdshader" id="1_cl0rr"]
|
||||
|
||||
[resource]
|
||||
shader = ExtResource("1_cl0rr")
|
||||
shader_parameter/shadow_intensity = 0.0
|
||||
shader_parameter/max_shadow_spread = 0.35
|
||||
shader_parameter/debug_mode = false
|
||||
8
addons/PageFlip/Assets/shadow_gradient.tres
Normal file
8
addons/PageFlip/Assets/shadow_gradient.tres
Normal file
@@ -0,0 +1,8 @@
|
||||
[gd_resource type="GradientTexture2D" format=3 uid="uid://rm7bporlv3cf"]
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_3i6i7"]
|
||||
offsets = PackedFloat32Array(0, 0.2731183, 0.44731182, 1)
|
||||
colors = PackedColorArray(0, 0, 0, 0.521, 0, 0, 0, 0.492, 0, 0, 0, 0.358, 0, 0, 0, 0)
|
||||
|
||||
[resource]
|
||||
gradient = SubResource("Gradient_3i6i7")
|
||||
BIN
addons/PageFlip/Assets/spine.png
Normal file
BIN
addons/PageFlip/Assets/spine.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
40
addons/PageFlip/Assets/spine.png.import
Normal file
40
addons/PageFlip/Assets/spine.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cit41jypw2sy1"
|
||||
path="res://.godot/imported/spine.png-c2fa10bef4996b84a954651c908149a7.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/PageFlip/Assets/spine.png"
|
||||
dest_files=["res://.godot/imported/spine.png-c2fa10bef4996b84a954651c908149a7.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
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/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
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
|
||||
229
addons/PageFlip/book_api.gd
Normal file
229
addons/PageFlip/book_api.gd
Normal file
@@ -0,0 +1,229 @@
|
||||
@tool
|
||||
class_name BookAPI
|
||||
extends RefCounted
|
||||
|
||||
## Static utility class for the PageFlip2D system by Newold.
|
||||
## Provides helper methods to configure the book and facilitate interaction
|
||||
## from embedded scenes (UI, puzzles, maps).
|
||||
##
|
||||
## [b]How to use:[/b][br]
|
||||
## This class is designed to be used statically. You do not need to instance it.
|
||||
## Simply call [code]BookAPI.function_name()[/code] from anywhere in your project.
|
||||
## It automatically tracks the active [PageFlip2D] instance.
|
||||
## (When a PageFlip scene enters the tree, it registers itself as the current_book.)
|
||||
|
||||
# ==============================================================================
|
||||
# ENUMS (Mirrored for easy access)
|
||||
# ==============================================================================
|
||||
|
||||
enum JumpTarget {
|
||||
## Jump to the closed state showing the Front Cover.
|
||||
FRONT_COVER,
|
||||
## Jump to the closed state showing the Back Cover.
|
||||
BACK_COVER,
|
||||
## Jump to a specific content page number.
|
||||
CONTENT_PAGE
|
||||
}
|
||||
|
||||
# Internal reference to the currently active book.
|
||||
static var _current_book: PageFlip2D
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# SETUP & REFERENCES
|
||||
# ==============================================================================
|
||||
|
||||
## Registers a book instance as the currently active one.
|
||||
## Automatically called by [PageFlip2D] in its [method Node._ready].
|
||||
static func set_current_book(book: PageFlip2D) -> void:
|
||||
_current_book = book
|
||||
|
||||
|
||||
## Returns the currently active book instance, or null if none is registered.
|
||||
static func get_current_book() -> PageFlip2D:
|
||||
return _current_book
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# NAVIGATION CONTROLS
|
||||
# ==============================================================================
|
||||
|
||||
## Turns the page forward (to the next page/spread).
|
||||
## Does nothing if the book is animating or at the end.
|
||||
static func next_page() -> void:
|
||||
if is_instance_valid(_current_book):
|
||||
force_release_control(_current_book)
|
||||
_current_book.next_page()
|
||||
|
||||
|
||||
## Turns the page backward (to the previous page/spread).
|
||||
## Does nothing if the book is animating or at the beginning.
|
||||
static func prev_page() -> void:
|
||||
if is_instance_valid(_current_book):
|
||||
force_release_control(_current_book)
|
||||
_current_book.prev_page()
|
||||
|
||||
|
||||
## Forces the active book to close towards a specific cover.
|
||||
## [param to_front_cover]: If true, closes to the Front Cover (Right to Left). If false, closes to Back Cover.
|
||||
static func force_close_book(to_front_cover: bool) -> void:
|
||||
if is_instance_valid(_current_book):
|
||||
force_release_control(_current_book)
|
||||
_current_book.force_close_book(to_front_cover)
|
||||
|
||||
|
||||
## Navigates to a specific page number (1-based index).
|
||||
## Acts as a wrapper for go_to_spread, calculating the correct index automatically.
|
||||
## [b]ASYNC:[/b] Must be called with 'await' if animated is true.
|
||||
## [param page_num]: The 1-based page number (1 = first texture in pages_paths).
|
||||
## [param target]: Specifies if the target is a content page or a cover.
|
||||
static func go_to_page(page_num: int = 1, target: JumpTarget = JumpTarget.CONTENT_PAGE, animated: bool = true) -> void:
|
||||
var book = _current_book
|
||||
if not is_instance_valid(book): return
|
||||
|
||||
var target_spread_idx: int = 0
|
||||
|
||||
match target:
|
||||
JumpTarget.FRONT_COVER:
|
||||
target_spread_idx = -1
|
||||
JumpTarget.BACK_COVER:
|
||||
target_spread_idx = book.total_spreads
|
||||
JumpTarget.CONTENT_PAGE:
|
||||
# Calculate spread index: 1-2 -> Spread 0, 3-4 -> Spread 1, etc.
|
||||
var safe_page = max(1, page_num)
|
||||
target_spread_idx = int(safe_page / 2.0)
|
||||
target_spread_idx = clampi(target_spread_idx, 0, book.total_spreads - 1)
|
||||
|
||||
await go_to_spread(book, target_spread_idx, animated)
|
||||
|
||||
|
||||
## Navigates to a specific spread index directly.
|
||||
## [b]ASYNC:[/b] Must be called with 'await' if animated is true.
|
||||
## - Animated: Fast-forwards through pages with dynamic speed.
|
||||
## - Instant: Snaps to page and manually triggers the scene activation handshake.
|
||||
static func go_to_spread(book: PageFlip2D, target_spread: int, animated: bool = true) -> void:
|
||||
if not is_instance_valid(book): return
|
||||
|
||||
# Clamp target
|
||||
var final_target = clampi(target_spread, -1, book.total_spreads)
|
||||
var diff = final_target - book.current_spread
|
||||
|
||||
if diff == 0:
|
||||
return
|
||||
|
||||
force_release_control(book)
|
||||
|
||||
if not animated:
|
||||
# INSTANT TELEPORT
|
||||
book.current_spread = final_target
|
||||
book.call("_update_static_visuals_immediate")
|
||||
book.call("_update_volume_visuals")
|
||||
# Force check for interactive scenes after teleport
|
||||
book.call("_check_scene_activation")
|
||||
|
||||
else:
|
||||
# ANIMATED FAST-FORWARD
|
||||
if book.is_animating: return # Don't interrupt an existing animation
|
||||
|
||||
var original_speed = book.anim_player.speed_scale
|
||||
|
||||
# --- DYNAMIC SPEED CALCULATION ---
|
||||
var steps = abs(diff)
|
||||
# Base speed: 1.5x -> Max speed: 10.0x
|
||||
var dynamic_speed = remap(float(steps), 0.0, float(book.total_spreads), 1.5, 10.0)
|
||||
book.anim_player.speed_scale = dynamic_speed
|
||||
|
||||
var going_forward = diff > 0
|
||||
|
||||
for i in range(steps):
|
||||
if not is_instance_valid(book): break
|
||||
|
||||
if going_forward: book.next_page()
|
||||
else: book.prev_page()
|
||||
|
||||
# Wait for the physical page turn to finish before starting the next one.
|
||||
if book.anim_player.is_playing():
|
||||
await book.anim_player.animation_finished
|
||||
else:
|
||||
await book.get_tree().process_frame
|
||||
|
||||
# RESTORE STATE
|
||||
if is_instance_valid(book):
|
||||
book.anim_player.speed_scale = original_speed
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# STATE & INTERACTION HELPERS
|
||||
# ==============================================================================
|
||||
|
||||
## Returns true if the book (active or specific) is currently animating.
|
||||
static func is_busy(book_instance: PageFlip2D = null) -> bool:
|
||||
var book = book_instance if book_instance else _current_book
|
||||
if not is_instance_valid(book): return false
|
||||
return book.is_animating
|
||||
|
||||
|
||||
## Locates the PageFlip2D controller ancestor from any node inside an interactive page.
|
||||
static func find_book_controller(caller_node: Node) -> PageFlip2D:
|
||||
var current = caller_node
|
||||
while current:
|
||||
if current is PageFlip2D:
|
||||
return current
|
||||
current = current.get_parent()
|
||||
return null
|
||||
|
||||
|
||||
## Safely locks or unlocks the book's ability to turn pages manually.
|
||||
## WARNING: If locked, the interactive scene MUST be responsible for unlocking it later.
|
||||
static func set_interaction_lock(book: PageFlip2D, locked: bool) -> void:
|
||||
if not is_instance_valid(book): return
|
||||
book.call("_pageflip_set_input_enabled", not locked)
|
||||
|
||||
|
||||
## Forces the book to regain input control immediately.
|
||||
## Useful as a failsafe if an interactive scene closes unexpectedly.
|
||||
static func force_release_control(book: PageFlip2D) -> void:
|
||||
if not is_instance_valid(book): return
|
||||
book.call("_pageflip_set_input_enabled", true)
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION HELPERS
|
||||
# ==============================================================================
|
||||
|
||||
## Configures the visual properties of a Book instance via a dictionary.
|
||||
static func configure_visuals(book: PageFlip2D, data: Dictionary) -> void:
|
||||
if not is_instance_valid(book): return
|
||||
|
||||
if "pages" in data:
|
||||
book.pages_paths = data["pages"]
|
||||
book.call("_prepare_book_content")
|
||||
|
||||
if "cover_front_out" in data: book.tex_cover_front_out = data["cover_front_out"]
|
||||
if "cover_front_in" in data: book.tex_cover_front_in = data["cover_front_in"]
|
||||
if "cover_back_in" in data: book.tex_cover_back_in = data["cover_back_in"]
|
||||
if "cover_back_out" in data: book.tex_cover_back_out = data["cover_back_out"]
|
||||
|
||||
if "spine_col" in data: book.spine_color = data["spine_col"]
|
||||
if "spine_width" in data:
|
||||
book.spine_width = data["spine_width"]
|
||||
book.call("_build_spine")
|
||||
|
||||
if "size" in data:
|
||||
book.target_page_size = data["size"]
|
||||
book.call("_apply_new_size")
|
||||
|
||||
book.call("_update_static_visuals_immediate")
|
||||
book.call("_update_volume_visuals")
|
||||
|
||||
|
||||
## Configures the physics simulation of the page turning effect.
|
||||
static func configure_physics(book: PageFlip2D, data: Dictionary) -> void:
|
||||
if not is_instance_valid(book) or not book.dynamic_poly: return
|
||||
|
||||
var rigger = book.dynamic_poly
|
||||
for key in data.keys():
|
||||
rigger.set(key, data[key])
|
||||
|
||||
if rigger.has_method("rebuild"):
|
||||
rigger.rebuild(book.target_page_size)
|
||||
1
addons/PageFlip/book_api.gd.uid
Normal file
1
addons/PageFlip/book_api.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c2dx1dksgeey6
|
||||
1416
addons/PageFlip/page_flip.gd
Normal file
1416
addons/PageFlip/page_flip.gd
Normal file
File diff suppressed because it is too large
Load Diff
1
addons/PageFlip/page_flip.gd.uid
Normal file
1
addons/PageFlip/page_flip.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ctw83iqgqxjjt
|
||||
1980
addons/PageFlip/page_flip.tscn
Normal file
1980
addons/PageFlip/page_flip.tscn
Normal file
File diff suppressed because one or more lines are too long
587
addons/PageFlip/page_rigger.gd
Normal file
587
addons/PageFlip/page_rigger.gd
Normal file
@@ -0,0 +1,587 @@
|
||||
@tool
|
||||
class_name DynamicPage
|
||||
extends Polygon2D
|
||||
|
||||
# ==============================================================================
|
||||
# ENUMS & CONSTANTS
|
||||
# ==============================================================================
|
||||
|
||||
## Defines the material physical properties for the page simulation.
|
||||
enum PagePreset {
|
||||
CUSTOM,
|
||||
DEFAULT,
|
||||
STANDARD_PAPER,
|
||||
HEAVY_GRIMOIRE,
|
||||
LIGHT_MAGAZINE,
|
||||
OLD_SCROLL,
|
||||
RIGID_BOARD,
|
||||
WET_CLOTH,
|
||||
PLASTIC_SHEET,
|
||||
METAL_PLATE
|
||||
}
|
||||
|
||||
## Determines the direction of the page turn curve.
|
||||
enum CurlMode {
|
||||
STRAIGHT,
|
||||
TOP_CORNER_FIRST,
|
||||
BOTTOM_CORNER_FIRST
|
||||
}
|
||||
|
||||
const SKELETON_NAME = "AutoSkeleton"
|
||||
const SHADOW_NAME = "AutoShadow"
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# SIGNALS
|
||||
# ==============================================================================
|
||||
|
||||
signal change_page_requested
|
||||
signal end_animation
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# INTERNAL CACHE
|
||||
# ==============================================================================
|
||||
|
||||
var _custom_cache: Dictionary = {}
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================
|
||||
|
||||
@export_category("Rig Generator")
|
||||
|
||||
## Horizontal mesh subdivision. Higher values create smoother bends.
|
||||
@export_range(1, 20) var subdivision_x: int = 8
|
||||
## Vertical mesh subdivision. Higher values allow for better corner curling.
|
||||
@export_range(1, 10) var subdivision_y: int = 5
|
||||
## Generates the mesh, skeleton, weights and animations.
|
||||
@export var rebuild_all: bool = false : set = _on_generate_pressed
|
||||
|
||||
|
||||
@export_category("Animation Generator")
|
||||
|
||||
@export_group("Quick Presets")
|
||||
## Select a material style to auto-configure physics.
|
||||
@export var animation_preset: PagePreset = PagePreset.CUSTOM : set = _on_preset_changed
|
||||
|
||||
|
||||
@export_group("Manual Configuration")
|
||||
## The AnimationPlayer node to store the generated animations.
|
||||
@export var anim_player: AnimationPlayer
|
||||
## Duration of the page turn animation in seconds.
|
||||
@export var anim_duration: float = 0.75
|
||||
|
||||
|
||||
@export_subgroup("Paper Physics")
|
||||
## Stiffness of the material. 0.5 = Rubber, 5.0 = Wood.
|
||||
@export_range(0.5, 5.0, 0.1) var paper_stiffness: float = 2.0
|
||||
## Bend angle during the lift phase (Negative = Tip drags down).
|
||||
@export_range(-180.0, 180.0) var lift_bend: float = -10.0
|
||||
## Bend angle during the landing phase (Negative = Tip floats up).
|
||||
@export_range(-180.0, 180.0) var land_bend: float = -8.0
|
||||
|
||||
|
||||
@export_subgroup("Curl Effect")
|
||||
## Which part of the page initiates the movement.
|
||||
@export var curl_mode: CurlMode = CurlMode.TOP_CORNER_FIRST
|
||||
## Time delay for the trailing corner (0.0 = None, 1.0 = High lag).
|
||||
@export_range(0.0, 1.0, 0.05) var curl_lag: float = 0.8
|
||||
|
||||
|
||||
@export_subgroup("Shadow FX")
|
||||
## Create a dynamic shadow behind the page (Background Shadow).
|
||||
@export var enable_shadow: bool = false
|
||||
## The gradient texture used for the back shadow.
|
||||
@export var shadow_gradient: GradientTexture2D
|
||||
|
||||
|
||||
@export_subgroup("Fine Timing")
|
||||
## Normalized time (0.0 - 1.0) for the peak lift position.
|
||||
@export_range(0.05, 0.45, 0.01) var timing_peak_lift: float = 0.15
|
||||
## Normalized time (0.0 - 1.0) for the landing contact.
|
||||
@export_range(0.55, 0.95, 0.01) var timing_peak_land: float = 0.85
|
||||
|
||||
@export var generate_anims_btn: bool = false : set = _on_anim_pressed
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# RUNTIME INITIALIZATION
|
||||
# ==============================================================================
|
||||
|
||||
func _ready():
|
||||
self.z_index = 10
|
||||
if animation_preset == PagePreset.CUSTOM:
|
||||
_save_state_to_cache()
|
||||
|
||||
|
||||
func rebuild(current_page_size: Vector2 = Vector2.ZERO) -> void:
|
||||
if not anim_player: return
|
||||
_clean_previous_rig()
|
||||
_create_rig_logic(current_page_size)
|
||||
_generate_animations_logic()
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# PRESET LOGIC
|
||||
# ==============================================================================
|
||||
|
||||
func _on_preset_changed(val):
|
||||
if animation_preset == PagePreset.CUSTOM:
|
||||
_save_state_to_cache()
|
||||
|
||||
animation_preset = val
|
||||
|
||||
if val == PagePreset.CUSTOM:
|
||||
if not _custom_cache.is_empty():
|
||||
_load_state_from_cache()
|
||||
print("[PageRigger] Restored Custom Settings.")
|
||||
notify_property_list_changed()
|
||||
return
|
||||
|
||||
# Defaults
|
||||
curl_mode = CurlMode.BOTTOM_CORNER_FIRST
|
||||
curl_lag = 0.3
|
||||
enable_shadow = false
|
||||
anim_duration = 0.75
|
||||
|
||||
match val:
|
||||
PagePreset.DEFAULT:
|
||||
paper_stiffness = 2.0; lift_bend = -15.0; land_bend = 5.0
|
||||
curl_mode = CurlMode.TOP_CORNER_FIRST; curl_lag = 0.5
|
||||
anim_duration = 0.75; timing_peak_lift = 0.15; timing_peak_land = 0.85
|
||||
|
||||
PagePreset.STANDARD_PAPER:
|
||||
paper_stiffness = 2.5; lift_bend = -12.0; land_bend = 3.0
|
||||
curl_mode = CurlMode.TOP_CORNER_FIRST; curl_lag = 0.4
|
||||
anim_duration = 0.65; timing_peak_lift = 0.12; timing_peak_land = 0.88
|
||||
|
||||
PagePreset.HEAVY_GRIMOIRE:
|
||||
paper_stiffness = 4.0; lift_bend = -3.0; land_bend = 1.0
|
||||
curl_mode = CurlMode.STRAIGHT; curl_lag = 0.1
|
||||
anim_duration = 1.1; timing_peak_lift = 0.25; timing_peak_land = 0.75
|
||||
|
||||
PagePreset.LIGHT_MAGAZINE:
|
||||
paper_stiffness = 0.8; lift_bend = -16.0; land_bend = 0.0
|
||||
curl_mode = CurlMode.BOTTOM_CORNER_FIRST; curl_lag = 0.6
|
||||
anim_duration = 0.8; timing_peak_lift = 0.10; timing_peak_land = 0.92
|
||||
|
||||
PagePreset.OLD_SCROLL:
|
||||
paper_stiffness = 1.8; lift_bend = -20.0; land_bend = 8.0
|
||||
curl_mode = CurlMode.TOP_CORNER_FIRST; curl_lag = 0.9
|
||||
anim_duration = 0.9; timing_peak_lift = 0.18; timing_peak_land = 0.82
|
||||
|
||||
PagePreset.RIGID_BOARD:
|
||||
paper_stiffness = 5.0; lift_bend = 0.0; land_bend = 0.0
|
||||
curl_mode = CurlMode.STRAIGHT; curl_lag = 0.0
|
||||
anim_duration = 0.8; timing_peak_lift = 0.2; timing_peak_land = 0.8
|
||||
|
||||
PagePreset.WET_CLOTH:
|
||||
paper_stiffness = 0.3; lift_bend = -28.0; land_bend = -0.0
|
||||
curl_mode = CurlMode.BOTTOM_CORNER_FIRST; curl_lag = 0.6
|
||||
anim_duration = 1.3; timing_peak_lift = 0.2; timing_peak_land = 0.95
|
||||
|
||||
PagePreset.PLASTIC_SHEET:
|
||||
paper_stiffness = 3.5; lift_bend = -12.0; land_bend = 12.0
|
||||
curl_mode = CurlMode.TOP_CORNER_FIRST; curl_lag = 0.2
|
||||
anim_duration = 0.5; timing_peak_lift = 0.08; timing_peak_land = 0.92
|
||||
|
||||
PagePreset.METAL_PLATE:
|
||||
paper_stiffness = 5.0; lift_bend = 0.0; land_bend = 0.0
|
||||
curl_mode = CurlMode.STRAIGHT; curl_lag = 0.0
|
||||
anim_duration = 1.4; timing_peak_lift = 0.15; timing_peak_land = 0.98
|
||||
|
||||
notify_property_list_changed()
|
||||
if anim_player and skeleton != NodePath(""):
|
||||
_generate_animations_logic()
|
||||
print("[PageRigger] Preset Applied: ", PagePreset.keys()[val])
|
||||
|
||||
|
||||
func _save_state_to_cache():
|
||||
_custom_cache = {
|
||||
"stiffness": paper_stiffness, "lift": lift_bend, "land": land_bend,
|
||||
"curl_m": curl_mode, "curl_l": curl_lag,
|
||||
"t_lift": timing_peak_lift, "t_land": timing_peak_land,
|
||||
"dur": anim_duration, "shadow": enable_shadow
|
||||
}
|
||||
|
||||
|
||||
func _load_state_from_cache():
|
||||
paper_stiffness = _custom_cache.get("stiffness", 1.5)
|
||||
lift_bend = _custom_cache.get("lift", -30.0)
|
||||
land_bend = _custom_cache.get("land", -15.0)
|
||||
curl_mode = _custom_cache.get("curl_m", CurlMode.BOTTOM_CORNER_FIRST)
|
||||
curl_lag = _custom_cache.get("curl_l", 0.3)
|
||||
timing_peak_lift = _custom_cache.get("t_lift", 0.15)
|
||||
timing_peak_land = _custom_cache.get("t_land", 0.85)
|
||||
anim_duration = _custom_cache.get("dur", 1.0)
|
||||
enable_shadow = false # Force disable shadow
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# RIGGING LOGIC
|
||||
# ==============================================================================
|
||||
|
||||
func _calculate_polygon_rect() -> Rect2:
|
||||
if polygon.size() == 0:
|
||||
return Rect2(0,0,0,0)
|
||||
|
||||
var min_v = polygon[0]
|
||||
var max_v = polygon[0]
|
||||
|
||||
for v in polygon:
|
||||
min_v.x = min(min_v.x, v.x)
|
||||
min_v.y = min(min_v.y, v.y)
|
||||
max_v.x = max(max_v.x, v.x)
|
||||
max_v.y = max(max_v.y, v.y)
|
||||
|
||||
return Rect2(min_v, max_v - min_v)
|
||||
|
||||
|
||||
func _create_rig_logic(current_page_size: Vector2 = Vector2.ZERO):
|
||||
self.z_index = 10
|
||||
|
||||
var original_size = Vector2(512, 820)
|
||||
var tex_size: Vector2 = Vector2.ZERO
|
||||
if current_page_size != Vector2.ZERO:
|
||||
tex_size = current_page_size
|
||||
elif texture:
|
||||
tex_size = texture.get_size()
|
||||
if tex_size == Vector2.ZERO:
|
||||
tex_size = original_size
|
||||
|
||||
var step_x = tex_size.x / subdivision_x
|
||||
var step_y = tex_size.y / subdivision_y
|
||||
|
||||
# Prepare Gradient Texture for Background Shadow
|
||||
var applied_shadow_tex: Texture2D = null
|
||||
if not shadow_gradient:
|
||||
shadow_gradient = preload("uid://rm7bporlv3cf")
|
||||
var dup = shadow_gradient.duplicate()
|
||||
if dup is GradientTexture2D:
|
||||
dup.width = int(tex_size.x)
|
||||
applied_shadow_tex = dup
|
||||
|
||||
# --- 0. BACKGROUND SHADOW ---
|
||||
if enable_shadow:
|
||||
var shadow = Polygon2D.new()
|
||||
shadow.name = SHADOW_NAME
|
||||
shadow.z_index = -1
|
||||
|
||||
if applied_shadow_tex:
|
||||
shadow.texture = applied_shadow_tex
|
||||
shadow.color = Color.WHITE
|
||||
else:
|
||||
shadow.color = Color(0, 0, 0, 0.5)
|
||||
|
||||
var margin = 4.0
|
||||
shadow.polygon = PackedVector2Array([
|
||||
Vector2(margin, margin), Vector2(tex_size.x - margin, margin),
|
||||
Vector2(tex_size.x - margin, tex_size.y - margin), Vector2(margin, tex_size.y - margin)
|
||||
])
|
||||
shadow.uv = shadow.polygon
|
||||
add_child(shadow)
|
||||
|
||||
# --- 1. MESH & UV ---
|
||||
var new_uvs = PackedVector2Array()
|
||||
for y in range(subdivision_y + 1):
|
||||
for x in range(subdivision_x + 1):
|
||||
new_uvs.append(Vector2(x * step_x, y * step_y))
|
||||
self.uv = new_uvs
|
||||
self.polygon = new_uvs
|
||||
|
||||
var new_polygons = []
|
||||
var rc = subdivision_x + 1
|
||||
for y in range(subdivision_y):
|
||||
for x in range(subdivision_x):
|
||||
var i = y * rc + x
|
||||
new_polygons.append(PackedInt32Array([i, i + 1, i + rc + 1, i + rc]))
|
||||
self.polygons = new_polygons
|
||||
|
||||
# --- 2. SKELETON ---
|
||||
var sk = Skeleton2D.new()
|
||||
sk.name = SKELETON_NAME
|
||||
add_child(sk)
|
||||
self.skeleton = NodePath(SKELETON_NAME)
|
||||
|
||||
# --- 3. BONES (Structured Hierarchy) ---
|
||||
for y in range(subdivision_y + 1):
|
||||
var parent_bone: Bone2D = null
|
||||
|
||||
for x in range(subdivision_x + 1):
|
||||
var b = Bone2D.new()
|
||||
b.name = "Bone_%d_%d" % [x, y]
|
||||
b.set_autocalculate_length_and_angle(false)
|
||||
b.set_length(step_x)
|
||||
|
||||
if x == 0:
|
||||
# Root bone (Spine)
|
||||
sk.add_child(b)
|
||||
b.position = Vector2(0, y * step_y)
|
||||
else:
|
||||
# Child bone
|
||||
if parent_bone:
|
||||
parent_bone.add_child(b)
|
||||
b.position = Vector2(step_x, 0)
|
||||
|
||||
b.set_rest(b.transform)
|
||||
parent_bone = b
|
||||
|
||||
# --- 4. WEIGHTS (Gradient Skinning) ---
|
||||
_apply_weights_to_polygon(self, sk)
|
||||
|
||||
queue_redraw()
|
||||
notify_property_list_changed()
|
||||
|
||||
|
||||
func _apply_weights_to_polygon(poly: Polygon2D, sk: Skeleton2D):
|
||||
poly.clear_bones()
|
||||
|
||||
var bone_map = {}
|
||||
for y in range(subdivision_y + 1):
|
||||
for x in range(subdivision_x + 1):
|
||||
var bname = "Bone_%d_%d" % [x, y]
|
||||
var bone_node = sk.find_child(bname, true, false)
|
||||
if bone_node:
|
||||
bone_map[bname] = bone_node.get_path()
|
||||
|
||||
var vertex_count = poly.polygon.size()
|
||||
|
||||
var weights_per_bone = {}
|
||||
|
||||
for bpath in bone_map.values():
|
||||
var w_arr = PackedFloat32Array()
|
||||
w_arr.resize(vertex_count)
|
||||
w_arr.fill(0.0)
|
||||
weights_per_bone[bpath] = w_arr
|
||||
|
||||
var vert_idx = 0
|
||||
for y in range(subdivision_y + 1):
|
||||
for x in range(subdivision_x + 1):
|
||||
var primary_name = "Bone_%d_%d" % [x, y]
|
||||
var prev_name = "Bone_%d_%d" % [x - 1, y]
|
||||
var next_name = "Bone_%d_%d" % [x + 1, y]
|
||||
|
||||
var primary_path = bone_map.get(primary_name)
|
||||
var prev_path = bone_map.get(prev_name)
|
||||
var next_path = bone_map.get(next_name)
|
||||
|
||||
var w_center = 0.7
|
||||
var w_neighbor = 0.15
|
||||
|
||||
if primary_path: weights_per_bone[primary_path][vert_idx] += w_center
|
||||
if prev_path: weights_per_bone[prev_path][vert_idx] += w_neighbor
|
||||
else: weights_per_bone[primary_path][vert_idx] += w_neighbor
|
||||
if next_path: weights_per_bone[next_path][vert_idx] += w_neighbor
|
||||
else: weights_per_bone[primary_path][vert_idx] += w_neighbor
|
||||
|
||||
vert_idx += 1
|
||||
|
||||
for bpath in weights_per_bone:
|
||||
poly.add_bone(bpath, weights_per_bone[bpath])
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# ANIMATION LOGIC
|
||||
# ==============================================================================
|
||||
|
||||
func _generate_animations_logic():
|
||||
var library: AnimationLibrary
|
||||
if anim_player.has_animation_library(""): library = anim_player.get_animation_library("")
|
||||
else: library = AnimationLibrary.new(); anim_player.add_animation_library("", library)
|
||||
|
||||
_create_single_anim(library, "turn_flexible_page", false, false)
|
||||
_create_single_anim(library, "turn_rigid_page", true, false)
|
||||
_create_single_anim(library, "turn_flexible_page_mirror", false, true)
|
||||
_create_single_anim(library, "turn_rigid_page_mirror", true, true)
|
||||
notify_property_list_changed()
|
||||
|
||||
|
||||
func _create_single_anim(library: AnimationLibrary, anim_name: String, is_rigid: bool, is_mirror: bool):
|
||||
var anim = Animation.new()
|
||||
anim.length = anim_duration
|
||||
anim.step = 0.01
|
||||
|
||||
if library.has_animation(anim_name): library.remove_animation(anim_name)
|
||||
library.add_animation(anim_name, anim)
|
||||
|
||||
var sk_node = find_child(SKELETON_NAME, true, false)
|
||||
if not sk_node: return
|
||||
|
||||
var my_path = anim_player.get_node(anim_player.root_node).get_path_to(self)
|
||||
|
||||
var visible_col = Color(1, 1, 1, 1)
|
||||
var invisible_col = Color(1, 1, 1, 0)
|
||||
var faded_col = Color(1, 1, 1, 0.6)
|
||||
|
||||
var t_mid = anim_duration * 0.5
|
||||
var t_end_snap = anim_duration * 0.95
|
||||
|
||||
# ==========================================================================
|
||||
# 1. VISIBILITY & Z-INDEX
|
||||
# ==========================================================================
|
||||
var z_track = anim.add_track(Animation.TYPE_VALUE)
|
||||
anim.track_set_path(z_track, str(my_path) + ":z_index")
|
||||
anim.track_set_interpolation_type(z_track, Animation.INTERPOLATION_NEAREST)
|
||||
anim.track_insert_key(z_track, 0.0, 10)
|
||||
anim.track_insert_key(z_track, anim_duration * 0.15, 25)
|
||||
anim.track_insert_key(z_track, anim_duration * 0.65, 10)
|
||||
|
||||
# ==========================================================================
|
||||
# 2. BACKGROUND SHADOW
|
||||
# ==========================================================================
|
||||
var shadow_node = find_child(SHADOW_NAME, true, false)
|
||||
if shadow_node and enable_shadow:
|
||||
var shadow_path = anim_player.get_node(anim_player.root_node).get_path_to(shadow_node)
|
||||
var t_scale = anim.add_track(Animation.TYPE_VALUE)
|
||||
anim.track_set_path(t_scale, str(shadow_path) + ":scale")
|
||||
anim.track_set_interpolation_type(t_scale, Animation.INTERPOLATION_CUBIC)
|
||||
var t_mod = anim.add_track(Animation.TYPE_VALUE)
|
||||
anim.track_set_path(t_mod, str(shadow_path) + ":modulate")
|
||||
|
||||
var s_start = Vector2(-1.0, 1.0) if is_mirror else Vector2(1.0, 1.0)
|
||||
var s_end = Vector2(1.0, 1.0) if is_mirror else Vector2(-1.0, 1.0)
|
||||
|
||||
anim.track_insert_key(t_scale, 0.0, s_start)
|
||||
anim.track_insert_key(t_mod, 0.0, invisible_col)
|
||||
anim.track_insert_key(t_mod, anim_duration * 0.1, visible_col)
|
||||
|
||||
anim.track_insert_key(t_scale, t_mid, Vector2(0.01, 1.0))
|
||||
anim.track_insert_key(t_mod, t_mid, faded_col)
|
||||
|
||||
anim.track_insert_key(t_scale, t_end_snap, s_end)
|
||||
anim.track_insert_key(t_scale, anim_duration, s_end)
|
||||
anim.track_insert_key(t_mod, t_end_snap, invisible_col)
|
||||
anim.track_insert_key(t_mod, anim_duration, invisible_col)
|
||||
|
||||
# ==========================================================================
|
||||
# 3. BONE ANIMATION
|
||||
# ==========================================================================
|
||||
for y in range(subdivision_y + 1):
|
||||
var row_factor = 1.0
|
||||
if not is_rigid and curl_mode != CurlMode.STRAIGHT:
|
||||
var y_ratio = float(y) / float(max(1, subdivision_y))
|
||||
if curl_mode == CurlMode.TOP_CORNER_FIRST:
|
||||
row_factor = lerp(1.0, 1.0 - curl_lag, y_ratio)
|
||||
elif curl_mode == CurlMode.BOTTOM_CORNER_FIRST:
|
||||
row_factor = lerp(1.0 - curl_lag, 1.0, y_ratio)
|
||||
|
||||
var time_offset = (1.0 - row_factor) * (anim_duration * 0.1)
|
||||
|
||||
for x in range(subdivision_x + 1):
|
||||
var bone_name = "Bone_%d_%d" % [x, y]
|
||||
var bone = sk_node.find_child(bone_name, true, false)
|
||||
if not bone: continue
|
||||
|
||||
var t_idx = anim.add_track(Animation.TYPE_VALUE)
|
||||
var path = anim_player.get_node(anim_player.root_node).get_path_to(bone)
|
||||
anim.track_set_path(t_idx, str(path) + ":rotation_degrees")
|
||||
anim.track_set_interpolation_type(t_idx, Animation.INTERPOLATION_CUBIC)
|
||||
|
||||
var deg_flat_right = 0.0; var deg_flat_left = -179.9; var deg_mid = -90.0
|
||||
|
||||
if is_rigid:
|
||||
if x == 0:
|
||||
var s = deg_flat_left if is_mirror else deg_flat_right
|
||||
var e = deg_flat_right if is_mirror else deg_flat_left
|
||||
anim.track_insert_key(t_idx, 0.0, s)
|
||||
anim.track_insert_key(t_idx, anim_duration * 0.5, deg_mid)
|
||||
anim.track_insert_key(t_idx, anim_duration, e)
|
||||
else:
|
||||
anim.track_insert_key(t_idx, 0.0, 0.0)
|
||||
anim.track_insert_key(t_idx, anim_duration, 0.0)
|
||||
else:
|
||||
var x_ratio = float(x) / float(subdivision_x)
|
||||
var influence = pow(x_ratio, paper_stiffness)
|
||||
var t_lift = clamp((anim_duration * timing_peak_lift) + time_offset, 0.0, (anim_duration * 0.5) - 0.05)
|
||||
var t_mid_bone = anim_duration * 0.5
|
||||
var t_land = clamp((anim_duration * timing_peak_land) - time_offset, t_mid_bone + 0.05, anim_duration)
|
||||
|
||||
if x == 0:
|
||||
# ROOT BONE
|
||||
var s = deg_flat_left if is_mirror else deg_flat_right
|
||||
var e = deg_flat_right if is_mirror else deg_flat_left
|
||||
var sl = -15.0 * row_factor
|
||||
if is_mirror: sl = deg_flat_left - sl
|
||||
var slt = lerp(-90.0, -180.0, timing_peak_land)
|
||||
if is_mirror: slt = lerp(-90.0, 0.0, timing_peak_land)
|
||||
|
||||
anim.track_insert_key(t_idx, 0.0, s)
|
||||
anim.track_insert_key(t_idx, t_lift, sl)
|
||||
anim.track_insert_key(t_idx, t_mid_bone, deg_mid)
|
||||
anim.track_insert_key(t_idx, t_land, slt)
|
||||
|
||||
var t_settle = lerp(t_land, anim_duration, 0.7)
|
||||
var s_settle = lerp(slt, e, 0.85)
|
||||
anim.track_insert_key(t_idx, t_settle, s_settle)
|
||||
|
||||
anim.track_insert_key(t_idx, anim_duration, e)
|
||||
else:
|
||||
# SURFACE BONES (CURVED)
|
||||
var bm = -1.0 if is_mirror else 1.0
|
||||
|
||||
anim.track_insert_key(t_idx, 0.0, 0.0)
|
||||
anim.track_insert_key(t_idx, t_lift, lift_bend * row_factor * bm * influence)
|
||||
# REMOVED FORCED 0.0 AT MIDPOINT FOR ORGANIC CURVE
|
||||
|
||||
var val_land = land_bend * row_factor * bm * influence
|
||||
anim.track_insert_key(t_idx, t_land, val_land)
|
||||
|
||||
var t_settle = lerp(t_land, anim_duration, 0.75)
|
||||
anim.track_insert_key(t_idx, t_settle, val_land * 0.15)
|
||||
|
||||
anim.track_insert_key(t_idx, anim_duration, 0.0)
|
||||
|
||||
# ==========================================================================
|
||||
# 4. MAIN POLYGON SCALE ("SQUASH & STRETCH")
|
||||
# ==========================================================================
|
||||
if not is_rigid:
|
||||
var t_main_scale = anim.add_track(Animation.TYPE_VALUE)
|
||||
anim.track_set_path(t_main_scale, str(my_path) + ":scale")
|
||||
anim.track_set_interpolation_type(t_main_scale, Animation.INTERPOLATION_LINEAR)
|
||||
var squash_x = 0.88
|
||||
|
||||
anim.track_insert_key(t_main_scale, 0.0, Vector2(1, 1))
|
||||
anim.track_insert_key(t_main_scale, t_mid, Vector2(1, 1))
|
||||
anim.track_insert_key(t_main_scale, t_mid + (anim_duration * 0.1), Vector2(squash_x, 1))
|
||||
anim.track_insert_key(t_main_scale, anim_duration * 0.85, Vector2(1, 1))
|
||||
anim.track_insert_key(t_main_scale, anim_duration, Vector2(1, 1))
|
||||
|
||||
var method_track = anim.add_track(Animation.TYPE_METHOD)
|
||||
anim.track_set_path(method_track, str(my_path))
|
||||
anim.track_insert_key(method_track, anim_duration * 0.5, {"method": "_trigger_midpoint", "args": []})
|
||||
anim.track_insert_key(method_track, anim_duration, {"method": "_trigger_end", "args": []})
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# ==============================================================================
|
||||
|
||||
func _trigger_midpoint(): emit_signal("change_page_requested")
|
||||
func _trigger_end(): emit_signal("end_animation")
|
||||
|
||||
|
||||
func _on_generate_pressed(val):
|
||||
if val:
|
||||
rebuild_all=false
|
||||
_clean_previous_rig()
|
||||
_create_rig_logic()
|
||||
_generate_animations_logic()
|
||||
|
||||
|
||||
func _on_anim_pressed(val):
|
||||
if val:
|
||||
generate_anims_btn=false
|
||||
if anim_player and skeleton!=NodePath(""):
|
||||
_generate_animations_logic()
|
||||
|
||||
|
||||
func _clean_previous_rig():
|
||||
for c in get_children():
|
||||
remove_child(c)
|
||||
c.free()
|
||||
var old_shadow = find_child(SHADOW_NAME, true, false)
|
||||
if old_shadow: old_shadow.free()
|
||||
clear_bones()
|
||||
skeleton = NodePath("")
|
||||
1
addons/PageFlip/page_rigger.gd.uid
Normal file
1
addons/PageFlip/page_rigger.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ce5ib2f4qff24
|
||||
Binary file not shown.
Reference in New Issue
Block a user