Initial commit

This commit is contained in:
2026-02-24 22:02:05 +01:00
parent 82219474ec
commit 888849b298
21 changed files with 1501 additions and 675 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View 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

View File

@@ -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

View File

@@ -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
View 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="."]

View 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
View 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")

View File

@@ -0,0 +1 @@
uid://b53v027falxj8

192
src/scripts/paper.gd Normal file
View 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
View File

@@ -0,0 +1 @@
uid://y8cle55rlpg2

File diff suppressed because it is too large Load Diff

View 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)

View File

@@ -0,0 +1 @@
uid://cyqdk71cnqipt

View 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);
}
}

View File

@@ -0,0 +1 @@
uid://bdnrvvdqoocwn