Initial commit
This commit is contained in:
BIN
src/assets/gfx/ui/spellbook_cover_small.png
Normal file
BIN
src/assets/gfx/ui/spellbook_cover_small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 91 KiB |
40
src/assets/gfx/ui/spellbook_cover_small.png.import
Normal file
40
src/assets/gfx/ui/spellbook_cover_small.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bpk136b7ld4rm"
|
||||
path="res://.godot/imported/spellbook_cover_small.png-c8007902877b53ad13e98c79605b99d6.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/gfx/ui/spellbook_cover_small.png"
|
||||
dest_files=["res://.godot/imported/spellbook_cover_small.png-c8007902877b53ad13e98c79605b99d6.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
src/assets/gfx/ui/spine_inner_binding.png
Normal file
BIN
src/assets/gfx/ui/spine_inner_binding.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 MiB |
40
src/assets/gfx/ui/spine_inner_binding.png.import
Normal file
40
src/assets/gfx/ui/spine_inner_binding.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ckrthw12d1gwb"
|
||||
path="res://.godot/imported/spine_inner_binding.png-fa5d560b7cfc1892b8181ab9e269f289.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/gfx/ui/spine_inner_binding.png"
|
||||
dest_files=["res://.godot/imported/spine_inner_binding.png-fa5d560b7cfc1892b8181ab9e269f289.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
src/assets/gfx/ui/spine_inner_binding_small.png
Normal file
BIN
src/assets/gfx/ui/spine_inner_binding_small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 109 KiB |
40
src/assets/gfx/ui/spine_inner_binding_small.png.import
Normal file
40
src/assets/gfx/ui/spine_inner_binding_small.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://wcpely4edb4a"
|
||||
path="res://.godot/imported/spine_inner_binding_small.png-1afe3288b0162c03127f1d9584faddba.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/gfx/ui/spine_inner_binding_small.png"
|
||||
dest_files=["res://.godot/imported/spine_inner_binding_small.png-1afe3288b0162c03127f1d9584faddba.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
src/assets/gfx/ui/spine_leather_small.png
Normal file
BIN
src/assets/gfx/ui/spine_leather_small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
40
src/assets/gfx/ui/spine_leather_small.png.import
Normal file
40
src/assets/gfx/ui/spine_leather_small.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bgq3fup6clg8y"
|
||||
path="res://.godot/imported/spine_leather_small.png-188cfce5b6aa1942b496990b84d5db37.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/gfx/ui/spine_leather_small.png"
|
||||
dest_files=["res://.godot/imported/spine_leather_small.png-188cfce5b6aa1942b496990b84d5db37.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
|
||||
@@ -5,11 +5,6 @@ resource_name = "Reverb"
|
||||
room_size = 0.51
|
||||
wet = 0.28
|
||||
|
||||
[sub_resource type="AudioEffectLowPassFilter" id="AudioEffectLowPassFilter_j3pel"]
|
||||
resource_name = "LowPassFilter"
|
||||
cutoff_hz = 958.0
|
||||
resonance = 0.75
|
||||
|
||||
[resource]
|
||||
bus/1/name = &"Sfx"
|
||||
bus/1/solo = false
|
||||
@@ -25,5 +20,3 @@ bus/2/mute = false
|
||||
bus/2/bypass_fx = false
|
||||
bus/2/volume_db = 0.0
|
||||
bus/2/send = &"Sfx"
|
||||
bus/2/effect/0/effect = SubResource("AudioEffectLowPassFilter_j3pel")
|
||||
bus/2/effect/0/enabled = true
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
[gd_scene format=3 uid="uid://copkx0vek0okx"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://gwy6pqap2ye6" path="res://assets/gfx/ui/page_parchment_small.png" id="1_ttuta"]
|
||||
[ext_resource type="Script" uid="uid://b53v027falxj8" path="res://scripts/book_paper.gd" id="2_script"]
|
||||
|
||||
[sub_resource type="Animation" id="Animation_ttuta"]
|
||||
resource_name = "turn_page"
|
||||
[sub_resource type="Animation" id="Animation_reset"]
|
||||
resource_name = "RESET"
|
||||
length = 0.01
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
@@ -11,22 +13,44 @@ tracks/0/path = NodePath("PagePolygon:polygon")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(),
|
||||
"transitions": PackedFloat32Array(),
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": []
|
||||
"values": [PackedVector2Array(138, 0, 182, 0, 182, 256, 138, 256, 92, 256, 42, 256, 0, 256, 0, 0, 42, 0, 92, 0)]
|
||||
}
|
||||
|
||||
[sub_resource type="Animation" id="Animation_ttuta"]
|
||||
resource_name = "turn_page"
|
||||
length = 0.6
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("PagePolygon:polygon")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(0, 0.075, 0.15, 0.225, 0.3, 0.4, 0.5, 0.6),
|
||||
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1),
|
||||
"update": 0,
|
||||
"values": [PackedVector2Array(138, 0, 182, 0, 182, 256, 138, 256, 92, 256, 42, 256, 0, 256, 0, 0, 42, 0, 92, 0), PackedVector2Array(117, 0, 155, 0, 155, 264, 117, 276, 78, 282, 36, 266, 0, 256, 0, 0, 36, 0, 78, 0), PackedVector2Array(76, 0, 100, 0, 100, 278, 76, 311, 51, 328, 23, 284, 0, 256, 0, 0, 23, 0, 51, 0), PackedVector2Array(28, 0, 36, 0, 36, 292, 28, 346, 18, 373, 8, 301, 0, 256, 0, 0, 8, 0, 18, 0), PackedVector2Array(-28, 0, -36, 0, -36, 292, -28, 346, -18, 373, -8, 301, 0, 256, 0, 0, -8, 0, -18, 0), PackedVector2Array(-83, 0, -109, 0, -109, 280, -83, 316, -55, 334, -25, 286, 0, 256, 0, 0, -25, 0, -55, 0), PackedVector2Array(-124, 0, -164, 0, -164, 264, -124, 276, -83, 282, -38, 266, 0, 256, 0, 0, -38, 0, -83, 0), PackedVector2Array(-138, 0, -182, 0, -182, 256, -138, 256, -92, 256, -42, 256, 0, 256, 0, 0, -42, 0, -92, 0)]
|
||||
}
|
||||
|
||||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_i033y"]
|
||||
_data = {
|
||||
&"RESET": SubResource("Animation_reset"),
|
||||
&"turn_page": SubResource("Animation_ttuta")
|
||||
}
|
||||
|
||||
[node name="BookPaper" type="Node2D" unique_id=723859984]
|
||||
script = ExtResource("2_script")
|
||||
|
||||
[node name="PagePolygon" type="Polygon2D" parent="." unique_id=1476858006]
|
||||
texture = ExtResource("1_ttuta")
|
||||
polygon = PackedVector2Array(138, 0, 182, 0, 182, 256, 138, 256, 92, 256, 42, 256, 0, 256, 0, 0, 42, 0, 92, 0)
|
||||
uv = PackedVector2Array(138, 0, 182, 0, 182, 256, 138, 256, 92, 256, 42, 256, 0, 256, 0, 0, 42, 0, 92, 0)
|
||||
polygons = [PackedInt32Array(7, 8, 5, 6), PackedInt32Array(8, 9, 4, 5), PackedInt32Array(9, 0, 3, 4), PackedInt32Array(0, 1, 2, 3)]
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="." unique_id=2024937129]
|
||||
libraries/ = SubResource("AnimationLibrary_i033y")
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="." unique_id=225599989]
|
||||
|
||||
60
src/scenes/paper.tscn
Normal file
60
src/scenes/paper.tscn
Normal file
@@ -0,0 +1,60 @@
|
||||
[gd_scene load_steps=6 format=3]
|
||||
|
||||
[ext_resource type="Shader" path="res://shaders/page_turn.gdshader" id="2_shader"]
|
||||
[ext_resource type="Script" path="res://scripts/paper.gd" id="3_script"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_paper"]
|
||||
shader = ExtResource("2_shader")
|
||||
shader_parameter/progress = 0.0
|
||||
shader_parameter/page_width = 182.0
|
||||
shader_parameter/page_height = 256.0
|
||||
shader_parameter/arch_amount = 80.0
|
||||
|
||||
[sub_resource type="Animation" id="Animation_reset"]
|
||||
resource_name = "RESET"
|
||||
length = 0.01
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("PagePoly:material:shader_parameter/progress")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(0),
|
||||
"transitions": PackedFloat32Array(1),
|
||||
"update": 0,
|
||||
"values": [0.0]
|
||||
}
|
||||
|
||||
[sub_resource type="Animation" id="Animation_turn"]
|
||||
resource_name = "turn_page"
|
||||
length = 2.0
|
||||
tracks/0/type = "value"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("PagePoly:material:shader_parameter/progress")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"times": PackedFloat32Array(0, 2.0),
|
||||
"transitions": PackedFloat32Array(1, 1),
|
||||
"update": 0,
|
||||
"values": [0.0, 1.0]
|
||||
}
|
||||
|
||||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_paper"]
|
||||
_data = {
|
||||
&"RESET": SubResource("Animation_reset"),
|
||||
&"turn_page": SubResource("Animation_turn")
|
||||
}
|
||||
|
||||
[node name="Paper" type="Node2D"]
|
||||
script = ExtResource("3_script")
|
||||
|
||||
[node name="PagePoly" type="Polygon2D" parent="."]
|
||||
material = SubResource("ShaderMaterial_paper")
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
||||
libraries/ = SubResource("AnimationLibrary_paper")
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="."]
|
||||
31
src/scenes/test_spellbook.tscn
Normal file
31
src/scenes/test_spellbook.tscn
Normal file
@@ -0,0 +1,31 @@
|
||||
[gd_scene format=3 uid="uid://dce5khb0f44px"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c75u55osgapks" path="res://scripts/spell_book_ui.gd" id="1_sb"]
|
||||
[ext_resource type="Script" uid="uid://cyqdk71cnqipt" path="res://scripts/test_spellbook.gd" id="2_test"]
|
||||
|
||||
[node name="TestSpellbook" type="Control" unique_id=1924210192]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("2_test")
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="." unique_id=1627853997]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
color = Color(0.12, 0.1, 0.08, 1)
|
||||
|
||||
[node name="SpellBookPanel" type="Control" parent="." unique_id=1024751319]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_sb")
|
||||
29
src/scripts/book_paper.gd
Normal file
29
src/scripts/book_paper.gd
Normal file
@@ -0,0 +1,29 @@
|
||||
extends Node2D
|
||||
## Polygon2D page-turn driven by AnimationPlayer.
|
||||
## The "turn_page" animation directly keyframes PagePolygon:polygon vertices.
|
||||
## Points 7,6 (spine edge at x=0) never move. All other column pairs fold right-to-left.
|
||||
##
|
||||
## Column pairs (top, bottom):
|
||||
## Spine (FIXED): pts 7, 6 (x=0)
|
||||
## Col 1: pts 8, 5 (x=42)
|
||||
## Col 2: pts 9, 4 (x=92)
|
||||
## Col 3: pts 0, 3 (x=138)
|
||||
## Col 4: pts 1, 2 (x=182, leading edge)
|
||||
|
||||
@onready var _anim: AnimationPlayer = $AnimationPlayer
|
||||
|
||||
|
||||
## Play the page turn animation (right-to-left fold).
|
||||
func play_turn() -> void:
|
||||
if _anim:
|
||||
_anim.play("turn_page")
|
||||
|
||||
## Play the page turn in reverse (left-to-right unfold).
|
||||
func play_turn_reverse() -> void:
|
||||
if _anim:
|
||||
_anim.play_backwards("turn_page")
|
||||
|
||||
## Reset to flat (unturned) state immediately.
|
||||
func reset_flat() -> void:
|
||||
if _anim:
|
||||
_anim.play("RESET")
|
||||
1
src/scripts/book_paper.gd.uid
Normal file
1
src/scripts/book_paper.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b53v027falxj8
|
||||
192
src/scripts/paper.gd
Normal file
192
src/scripts/paper.gd
Normal file
@@ -0,0 +1,192 @@
|
||||
extends Node2D
|
||||
## Polygon2D page turn with vertex-shader deformation and front/back side support.
|
||||
## Two SubViewports render the front and back page content. The shader switches
|
||||
## between them at the midpoint of the turn. Add your content (spell icons,
|
||||
## descriptions, etc.) to the returned Control nodes from get_front_content()
|
||||
## and get_back_content().
|
||||
##
|
||||
## Test controls (standalone):
|
||||
## SPACE / ENTER = play turn
|
||||
## BACKSPACE = play reverse
|
||||
## R = reset flat
|
||||
|
||||
const GRID_COLS: int = 10
|
||||
const GRID_ROWS: int = 8
|
||||
|
||||
@onready var _poly: Polygon2D = $PagePoly
|
||||
@onready var _anim: AnimationPlayer = $AnimationPlayer
|
||||
|
||||
var _front_viewport: SubViewport
|
||||
var _back_viewport: SubViewport
|
||||
var _front_content: Control
|
||||
var _back_content: Control
|
||||
var _page_size: Vector2
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
# Get page size from the parchment texture.
|
||||
var parchment: Texture2D = preload("res://assets/gfx/ui/page_parchment_small.png")
|
||||
_page_size = parchment.get_size()
|
||||
|
||||
_setup_viewports(parchment)
|
||||
_build_grid()
|
||||
_setup_test_content()
|
||||
print("Paper scene ready. Press SPACE to turn, BACKSPACE to reverse, R to reset.")
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventKey and event.pressed and not event.echo:
|
||||
match event.keycode:
|
||||
KEY_SPACE, KEY_ENTER:
|
||||
play_turn()
|
||||
KEY_BACKSPACE:
|
||||
play_turn_reverse()
|
||||
KEY_R:
|
||||
reset_flat()
|
||||
|
||||
|
||||
## Play the page turn animation (right-to-left).
|
||||
func play_turn() -> void:
|
||||
if _anim:
|
||||
_anim.play("turn_page")
|
||||
|
||||
|
||||
## Play the page turn in reverse (left-to-right).
|
||||
func play_turn_reverse() -> void:
|
||||
if _anim:
|
||||
_anim.play_backwards("turn_page")
|
||||
|
||||
|
||||
## Reset to flat (unturned) state immediately.
|
||||
func reset_flat() -> void:
|
||||
if _anim:
|
||||
_anim.play("RESET")
|
||||
|
||||
|
||||
## Returns the Control node for the FRONT side. Add children here for front content.
|
||||
func get_front_content() -> Control:
|
||||
return _front_content
|
||||
|
||||
|
||||
## Returns the Control node for the BACK side. Add children here for back content.
|
||||
func get_back_content() -> Control:
|
||||
return _back_content
|
||||
|
||||
|
||||
## Create SubViewports for front and back, each with a parchment background.
|
||||
func _setup_viewports(parchment: Texture2D) -> void:
|
||||
var w: int = int(_page_size.x)
|
||||
var h: int = int(_page_size.y)
|
||||
|
||||
# --- Front viewport ---
|
||||
_front_viewport = SubViewport.new()
|
||||
_front_viewport.name = "FrontViewport"
|
||||
_front_viewport.size = Vector2i(w, h)
|
||||
_front_viewport.transparent_bg = false
|
||||
_front_viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
|
||||
add_child(_front_viewport)
|
||||
|
||||
var front_bg := TextureRect.new()
|
||||
front_bg.texture = parchment
|
||||
front_bg.stretch_mode = TextureRect.STRETCH_SCALE
|
||||
front_bg.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_front_viewport.add_child(front_bg)
|
||||
|
||||
_front_content = Control.new()
|
||||
_front_content.name = "FrontContent"
|
||||
_front_content.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_front_viewport.add_child(_front_content)
|
||||
|
||||
# --- Back viewport ---
|
||||
_back_viewport = SubViewport.new()
|
||||
_back_viewport.name = "BackViewport"
|
||||
_back_viewport.size = Vector2i(w, h)
|
||||
_back_viewport.transparent_bg = false
|
||||
_back_viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
|
||||
add_child(_back_viewport)
|
||||
|
||||
var back_bg := TextureRect.new()
|
||||
back_bg.texture = parchment
|
||||
back_bg.stretch_mode = TextureRect.STRETCH_SCALE
|
||||
back_bg.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_back_viewport.add_child(back_bg)
|
||||
|
||||
_back_content = Control.new()
|
||||
_back_content.name = "BackContent"
|
||||
_back_content.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_back_viewport.add_child(_back_content)
|
||||
|
||||
# --- Assign textures to the polygon ---
|
||||
_poly.texture = _front_viewport.get_texture()
|
||||
|
||||
var mat: ShaderMaterial = _poly.material as ShaderMaterial
|
||||
if mat:
|
||||
mat.set_shader_parameter("back_texture", _back_viewport.get_texture())
|
||||
|
||||
|
||||
## Build a dense polygon grid for smooth vertex-shader deformation.
|
||||
func _build_grid() -> void:
|
||||
var page_w: float = _page_size.x
|
||||
var page_h: float = _page_size.y
|
||||
|
||||
# Tell the shader the page dimensions.
|
||||
var mat: ShaderMaterial = _poly.material as ShaderMaterial
|
||||
if mat:
|
||||
mat.set_shader_parameter("page_width", page_w)
|
||||
mat.set_shader_parameter("page_height", page_h)
|
||||
|
||||
var cols: int = GRID_COLS
|
||||
var rows: int = GRID_ROWS
|
||||
var num_verts_x: int = cols + 1
|
||||
var num_verts_y: int = rows + 1
|
||||
|
||||
var verts := PackedVector2Array()
|
||||
var uvs := PackedVector2Array()
|
||||
verts.resize(num_verts_x * num_verts_y)
|
||||
uvs.resize(num_verts_x * num_verts_y)
|
||||
|
||||
for row_i in range(num_verts_y):
|
||||
for col_i in range(num_verts_x):
|
||||
var x: float = (float(col_i) / float(cols)) * page_w
|
||||
var y: float = (float(row_i) / float(rows)) * page_h
|
||||
var idx: int = row_i * num_verts_x + col_i
|
||||
verts[idx] = Vector2(x, y)
|
||||
uvs[idx] = Vector2(x, y)
|
||||
|
||||
_poly.polygon = verts
|
||||
_poly.uv = uvs
|
||||
|
||||
var polys: Array[PackedInt32Array] = []
|
||||
for row_i in range(rows):
|
||||
for col_i in range(cols):
|
||||
var tl: int = row_i * num_verts_x + col_i
|
||||
var tr: int = tl + 1
|
||||
var bl: int = (row_i + 1) * num_verts_x + col_i
|
||||
var br: int = bl + 1
|
||||
polys.append(PackedInt32Array([tl, tr, br, bl]))
|
||||
|
||||
_poly.polygons = polys
|
||||
|
||||
# Center on screen for standalone testing.
|
||||
_poly.position = Vector2(-page_w / 2.0, -page_h / 2.0)
|
||||
|
||||
|
||||
## Add test labels so front/back are clearly visible when testing.
|
||||
func _setup_test_content() -> void:
|
||||
var front_label := Label.new()
|
||||
front_label.text = "FRONT SIDE"
|
||||
front_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
front_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
front_label.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
front_label.add_theme_font_size_override("font_size", 20)
|
||||
front_label.add_theme_color_override("font_color", Color(0.3, 0.15, 0.05))
|
||||
_front_content.add_child(front_label)
|
||||
|
||||
var back_label := Label.new()
|
||||
back_label.text = "BACK SIDE"
|
||||
back_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
back_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
back_label.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
back_label.add_theme_font_size_override("font_size", 20)
|
||||
back_label.add_theme_color_override("font_color", Color(0.5, 0.1, 0.1))
|
||||
_back_content.add_child(back_label)
|
||||
1
src/scripts/paper.gd.uid
Normal file
1
src/scripts/paper.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://y8cle55rlpg2
|
||||
File diff suppressed because it is too large
Load Diff
17
src/scripts/test_spellbook.gd
Normal file
17
src/scripts/test_spellbook.gd
Normal file
@@ -0,0 +1,17 @@
|
||||
extends Control
|
||||
## Standalone test harness for the SpellBook UI.
|
||||
## Run this scene directly (F6) to see the spellbook open and turn pages.
|
||||
|
||||
@onready var spell_book: Control = $SpellBookPanel
|
||||
|
||||
|
||||
func _ready():
|
||||
# Create a mock CharacterStats with all five spells learnt.
|
||||
var stats := CharacterStats.new()
|
||||
stats.learnt_spells = ["flames", "frostspike", "healing", "water_bubble", "earth_spike"]
|
||||
stats.spell_hotkeys = {"1": "", "2": "", "3": ""}
|
||||
|
||||
# Give the UI one frame to finish _ready(), then feed it the stats.
|
||||
await get_tree().process_frame
|
||||
spell_book.visible = true
|
||||
spell_book.set_character_stats(stats)
|
||||
1
src/scripts/test_spellbook.gd.uid
Normal file
1
src/scripts/test_spellbook.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cyqdk71cnqipt
|
||||
44
src/shaders/page_turn.gdshader
Normal file
44
src/shaders/page_turn.gdshader
Normal file
@@ -0,0 +1,44 @@
|
||||
shader_type canvas_item;
|
||||
|
||||
// How far the page turn has progressed (0 = flat on right, 1 = flat on left).
|
||||
uniform float progress : hint_range(0.0, 1.0) = 0.0;
|
||||
|
||||
// Page dimensions in pixels (set from script).
|
||||
uniform float page_width = 182.0;
|
||||
uniform float page_height = 256.0;
|
||||
|
||||
// How many pixels the free edge lifts upward during the turn.
|
||||
uniform float arch_amount = 80.0;
|
||||
|
||||
// Back side texture (from SubViewport).
|
||||
uniform sampler2D back_texture : hint_default_white;
|
||||
|
||||
// Pass normalized UV from vertex to fragment for back-texture sampling.
|
||||
varying vec2 page_uv;
|
||||
|
||||
void vertex() {
|
||||
float nx = VERTEX.x / page_width;
|
||||
|
||||
// Store normalized UV before deformation for texture lookups.
|
||||
page_uv = vec2(VERTEX.x / page_width, VERTEX.y / page_height);
|
||||
|
||||
// --- Horizontal: uniform sweep from right to left ---
|
||||
VERTEX.x *= cos(PI * progress);
|
||||
|
||||
// --- Vertical: page bends upward as it sweeps ---
|
||||
float arch_envelope = sin(PI * progress);
|
||||
float arch_shape = sqrt(nx);
|
||||
float arch = arch_amount * arch_shape * arch_envelope;
|
||||
VERTEX.y -= arch;
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
if (progress <= 0.5) {
|
||||
// Front side visible.
|
||||
COLOR = texture(TEXTURE, UV);
|
||||
} else {
|
||||
// Page has flipped past mid-point: show back side with mirrored UV.
|
||||
vec2 back_uv = vec2(1.0 - page_uv.x, page_uv.y);
|
||||
COLOR = texture(back_texture, back_uv);
|
||||
}
|
||||
}
|
||||
1
src/shaders/page_turn.gdshader.uid
Normal file
1
src/shaders/page_turn.gdshader.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bdnrvvdqoocwn
|
||||
Reference in New Issue
Block a user