Compare commits

...

42 Commits

Author SHA1 Message Date
c2440b5272 Merge remote-tracking branch 'origin/demo' 2025-07-21 17:56:17 +08:00
187a86c915 sfx update 2025-07-21 17:55:48 +08:00
e97a70faac Merge remote-tracking branch 'origin/demo' 2025-07-21 17:33:07 +08:00
08cc9c643b 默认普通话,其次上海话 2025-07-21 17:32:54 +08:00
b42f6e510e Merge remote-tracking branch 'origin/demo' 2025-07-21 17:25:21 +08:00
6fc8efd46d sfx update 2025-07-21 17:25:04 +08:00
b0ba970651 merge 2025-07-21 17:23:53 +08:00
ab5b7ecf4f Merge remote-tracking branch 'origin/demo' 2025-07-20 20:13:12 +08:00
85475ff549 VibeSfx 替换为 Sfx 2025-07-20 20:05:59 +08:00
aaa7dc26bb 优化 sfx_config_panel 2025-07-19 15:02:32 +08:00
301ee98c0f 增加 sfx_config_panel 2025-07-18 19:52:49 +08:00
cce0a02fb8 Merge remote-tracking branch 'origin/demo' 2025-07-17 20:52:00 +08:00
86b6bc733b 优化院子纸钱z透视层级 2025-07-17 20:35:41 +08:00
658fd923d5 Merge remote-tracking branch 'origin/demo' 2025-07-17 16:53:49 +08:00
bbd_pc
350184821d hotfix 2025-07-17 16:51:04 +08:00
4d4ab68f70 Merge remote-tracking branch 'origin/demo' 2025-07-17 16:49:13 +08:00
644d8bcc39 理发店演出优化 2025-07-17 16:48:40 +08:00
d465857737 优化 npc hook speaking 表现 2025-07-17 16:43:30 +08:00
72f00c821f 理发店癞子理发演出 2025-07-17 05:44:00 +08:00
b4d34bccb2 更新 readme 代码说明 2025-07-17 00:27:43 +08:00
571c6ab74c index 无需章节入口(已有存档管理器) 2025-07-17 00:02:25 +08:00
850900ad5f Merge remote-tracking branch 'origin/demo' 2025-07-17 00:01:33 +08:00
4e3183b013 鸡毛掸子操作优化 2025-07-16 23:58:41 +08:00
2f431cff52 Merge remote-tracking branch 'origin/demo' 2025-07-16 23:26:58 +08:00
a60beeea1d 增加 debug panel(连按五次「`」开启debug,下次进入时重置) 2025-07-16 23:20:55 +08:00
87da4d2e73 Merge remote-tracking branch 'origin/demo' 2025-07-16 20:02:01 +08:00
e42b434bed archive、config、ground 等重要代码优化;trailer 读取本地化配置。 2025-07-16 19:56:21 +08:00
746a25fd5b compression_method=4 2025-07-16 18:48:16 +08:00
c720a78161 Merge remote-tracking branch 'origin/demo' 2025-07-16 18:12:56 +08:00
ffac8f61ef 刮刮乐贴图存储优化;麻绳 mesh 优化 2025-07-16 18:07:09 +08:00
7f8136784f Merge remote-tracking branch 'origin/demo' 2025-07-16 17:30:13 +08:00
f2c2af8c57 第二章部分进度 2025-07-16 17:20:45 +08:00
c7528dd0cc 修复 纯黑启动界面 棕色斑点 2025-07-16 17:19:34 +08:00
bbd_pc
3f3a1f73c4 quit game 前先取消 pause 状态 2025-07-16 17:02:03 +08:00
bbd_pc
10272f6e45 书店外监督小孩说话时,hold玩家操作 2025-07-16 16:54:55 +08:00
f267aecfe5 demo0.5.3: 修复弹珠游戏后看小猫会跳过霸凌bug;优化dialog layer问题;增加内测反馈问卷;优化 prop hud逻辑;优化序章部分对白文案;书店从书架掉落后回忆 2025-07-16 16:44:40 +08:00
214918423d 优化balloon在游戏/回忆的不同layer 2025-07-16 16:42:59 +08:00
740620a1d4 优化 prop hud 逻辑 2025-07-16 16:32:40 +08:00
3be55f41e7 增加内侧反馈问卷链接 2025-07-16 16:05:22 +08:00
4063434c26 部分对话优化;书店摔倒后回忆效果 2025-07-16 16:01:09 +08:00
70f9d35d07 优化NPC2D,增加 hook_speaking&unhook_speaking;孤儿院第一次与大胖对话使用该功能 2025-07-16 15:23:00 +08:00
9c3c470f3e 院子判断 c02_ball_game_stage >= 3 2025-07-16 14:24:10 +08:00
187 changed files with 5426 additions and 2188 deletions

View File

@ -68,7 +68,7 @@ GroundLoader 加载/切换 Ground 时,分为上下两段转场:
1. ambient光照
2. general音效等
3. partical粒子效果
4. ux用户交互
4. ux用户交互相关
主要类型说明:
@ -77,17 +77,37 @@ GroundLoader 加载/切换 Ground 时,分为上下两段转场:
2. 可配置触发方式enter, area_enter, interact
3. 可在场景加载时触发
4. 触发效果有播放对话期间锁定玩家播放动画回调方法AnimationPlayer 的方法注意“_”开头的方法会被忽略
2. inspectable可在检阅窗口进行审视的物品可以附加文案
2. 【已废弃】inspectable可在检阅窗口进行审视的物品可以附加文案
3. local_inspectable运镜+检阅,无需检阅窗口
4. note显示文案
1. 显示方式os玩家头顶气泡ballon下方字幕可播放配音
5. npc
1. hook/unhook speaking控制 NPC 在玩家未交互时显示对话气泡,配合触发播放 Dialogue 的情景enable==false时也可生效
6. portal传送门
1. 名称有: left默认, right默认, 1, 2, 3, ...
1. 名称有: left默认, right默认, 1, 2, 3, ... 其中 left/right 在玩家进入时会改变朝向left 进入时朝右right 进入时朝左)
2. 关键参数targer_scene 与 target_portaltarget_portal 为 none 时不启用)
3. 三种模式default通道opened打开的门locked锁定的门对应不同图标与操作音效
4. 锁定的门可以配置启用钥匙prop_key可在使用后自动消耗该钥匙
7. pickable: 可用于拾取道具可作为重要物品as_important_prop
8. closeup: 可配置内部 PackedScenedisplay 在 child 下
1. 可设置是否 exit_on_cancel
2. 读取并连接 PackedScene 的 exit 信号PackedScene exit 可传递参数(可选)
特殊 UX 类型:
1. 刮刮乐:可以按进度刮开其中内容
2. Sign Snapper 拉动机制:
1. 自动锁定玩家操作,调用 walk_to 指定位置
2. 自动优化范围:忽略小范围移动以防抽动,最低活动距离
3. Draggable (目录: scene/little_game/general/)
1. hover 时 sprite 自动描白边(需配置子节点 CollisionShape
2. pick & drag & drop
3. drag 活动范围
4. 可启用 as_button 效果picked signal
4. wheel: 检测鼠标按住后,围绕中心旋转操作
5. HoverLightClickArea
1. hover 时高亮(需配置子节点 PointLight 与 CollisionShape
### AimationPlayer Tool Button 说明
#### 存档 Tool Button
@ -170,9 +190,6 @@ current_scene 是通过 GroundLoader 加载的,在 ground loader 加载 ground
- PropHud 切换与点击展示
- 左键点击展开 Hud
- 右键点击检阅道具
- SignSnapper 拉动机制
- 自动锁定玩家操作,调用 walk_to 指定位置
- 自动优化范围:忽略小范围移动以防抽动,最低活动距离
- ReenterLock 机制
- 可重入锁
- Editor 中运行可 Debug 输出调用位置(调用 lock 的代码文件&代码行)
@ -185,4 +202,5 @@ current_scene 是通过 GroundLoader 加载的,在 ground loader 加载 ground
- 比 Event2D 更轻量灵活的 EventBinder内有 updater 与 trigger 两种绑定
- updater 由 event 驱动更新父节点状态
- trigger 由父节点 signal 驱动更新 event
- SavingsPanel 存档管理器:可便捷地增、删、加载存档
- Vibe Control 控制氛围情绪音乐

View File

@ -18,20 +18,9 @@ func _ready():
# Normally you can just call DialogueManager directly but doing so before the plugin has been
# enabled in settings will throw a compiler error here so I'm using `get_singleton` instead.
var dialogue_manager = Engine.get_singleton("DialogueManager")
dialogue_manager.dialogue_ended.connect(_on_dialogue_ended)
dialogue_manager.show_dialogue_balloon(resource, title if not title.is_empty() else resource.first_title)
DialogueManager.show_dialogue_balloon(resource, title if not title.is_empty() else resource.first_title)
func _enter_tree() -> void:
DialogueSettings.set_user_value("is_running_test_scene", false)
#region Signals
func _on_dialogue_ended(_resource: DialogueResource):
get_tree().quit()
#endregion

View File

@ -48,7 +48,7 @@ static func new_action_config() -> Dictionary:
return ACTION_CONFIG.duplicate()
@onready var debug_mov_onion_sprite2d = $DebugMovOnionSkinSprite2D as Sprite2D
var debug_mov_onion_sprite2d: Sprite2D
# 从 intro 到 next 的配置信息
var auto_checkout_dict = {
@ -75,12 +75,16 @@ func _ready() -> void:
animation_finished.connect(_on_animation_finished)
animation_looped.connect(_on_animation_finished)
# 处理 debug_mov_onion_sprite2d
debug_mov_onion_sprite2d = get_node_or_null("DebugMovOnionSkinSprite2D")
if Engine.is_editor_hint():
# stop()
# frame = 0
if not debug_mov_onion_sprite2d:
debug_mov_onion_sprite2d = Sprite2D.new()
add_child(debug_mov_onion_sprite2d)
debug_mov_onion_sprite2d.name = "DebugMovOnionSkinSprite2D"
debug_mov_onion_sprite2d.modulate.a = 0.5
debug_playing = false
_debug_mov_projection()
else:
elif debug_mov_onion_sprite2d:
debug_mov_onion_sprite2d.queue_free()
# autoplay 会自己 play, 只有自定义的 autostart 手动调用 play
if not Engine.is_editor_hint() and autostart:
@ -93,8 +97,8 @@ func _debug_mov_projection():
var mov_config = animation_mov_dict[debug_mov_animation]
# 展示 accumulated animation 的目标位置
debug_mov_onion_sprite2d.position.x = (
mov_config.movement_x * (-1 if flip_h else 1) / scale.x
) * sign(mov_config.velocity.x)
(mov_config.movement_x * (-1 if flip_h else 1) / scale.x) * sign(mov_config.velocity.x)
)
debug_mov_onion_sprite2d.texture = sprite_frames.get_frame_texture(debug_mov_animation, 0)
debug_mov_onion_sprite2d.flip_h = flip_h
elif debug_mov_animation:

View File

@ -1,17 +1,80 @@
[gd_resource type="SpriteFrames" load_steps=9 format=3 uid="uid://cmvr3lbwe3h7p"]
[gd_resource type="SpriteFrames" load_steps=21 format=3 uid="uid://cmvr3lbwe3h7p"]
[ext_resource type="Texture2D" uid="uid://dcwbe6hb3gdcp" path="res://asset/art/gif/c03_特写与游戏动画/父亲抱小孩/0.png" id="1_fctjd"]
[ext_resource type="Texture2D" uid="uid://cdyq7y562gsxp" path="res://asset/art/gif/c03_特写与游戏动画/绞肉机特写动画/0.png" id="1_uknvx"]
[ext_resource type="Texture2D" uid="uid://bhrahd7u0yoba" path="res://asset/art/gif/c03_特写与游戏动画/父亲抱小孩/1.png" id="2_g03au"]
[ext_resource type="Texture2D" uid="uid://botg6n14al2eu" path="res://asset/art/gif/c03_特写与游戏动画/绞肉机特写动画/1.png" id="2_up5wq"]
[ext_resource type="Texture2D" uid="uid://dweny6ivmkanl" path="res://asset/art/gif/c03_特写与游戏动画/父亲抱小孩/2.png" id="3_5wyv8"]
[ext_resource type="Texture2D" uid="uid://cvhquuvipetio" path="res://asset/art/gif/c03_特写与游戏动画/绞肉机特写动画/2.png" id="3_oilu3"]
[ext_resource type="Texture2D" uid="uid://nvddvjnphsqx" path="res://asset/art/gif/c03_特写与游戏动画/父亲抱小孩/3.png" id="4_ag85t"]
[ext_resource type="Texture2D" uid="uid://bn3x37ya65ixm" path="res://asset/art/gif/c03_特写与游戏动画/绞肉机特写动画/3.png" id="4_uidoe"]
[ext_resource type="Texture2D" uid="uid://bhhjsj1dqep40" path="res://asset/art/gif/c03_特写与游戏动画/癞子背坐呼吸/0.png" id="5_5m4aa"]
[ext_resource type="Texture2D" uid="uid://c5sc5ctss08qc" path="res://asset/art/gif/c03_特写与游戏动画/鬼母子神/0.png" id="5_uidoe"]
[ext_resource type="Texture2D" uid="uid://lop0mxjb5y71" path="res://asset/art/gif/c03_特写与游戏动画/癞子背坐呼吸/1.png" id="6_0df74"]
[ext_resource type="Texture2D" uid="uid://dx2v7bicpg7b4" path="res://asset/art/gif/c03_特写与游戏动画/鬼母子神/1.png" id="6_yp83y"]
[ext_resource type="Texture2D" uid="uid://bkh3cc2fg486c" path="res://asset/art/gif/c03_特写与游戏动画/鬼母子神/2.png" id="7_axfhb"]
[ext_resource type="Texture2D" uid="uid://c08v3b4prnqyt" path="res://asset/art/gif/c03_特写与游戏动画/癞子背坐呼吸/2.png" id="7_xhhcn"]
[ext_resource type="Texture2D" uid="uid://4m0ix5nhdmdf" path="res://asset/art/gif/c03_特写与游戏动画/癞子背坐呼吸/3.png" id="8_dspyd"]
[ext_resource type="Texture2D" uid="uid://d0ad1s3sfsejp" path="res://asset/art/gif/c03_特写与游戏动画/鬼母子神/3.png" id="8_yp83y"]
[ext_resource type="Texture2D" uid="uid://c6gpicmu30026" path="res://asset/art/gif/c03_特写与游戏动画/癞子背对侧头呼吸/0.png" id="9_naxgo"]
[ext_resource type="Texture2D" uid="uid://b7ufyuiusqmh7" path="res://asset/art/gif/c03_特写与游戏动画/癞子背对侧头呼吸/1.png" id="10_qcrrr"]
[ext_resource type="Texture2D" uid="uid://c21g54sgebjti" path="res://asset/art/gif/c03_特写与游戏动画/癞子背对侧头呼吸/2.png" id="11_buon4"]
[ext_resource type="Texture2D" uid="uid://c7vjtmjhv7v7f" path="res://asset/art/gif/c03_特写与游戏动画/癞子背对侧头呼吸/3.png" id="12_cx8xa"]
[resource]
animations = [{
"frames": [{
"duration": 20.0,
"texture": ExtResource("1_fctjd")
}, {
"duration": 8.0,
"texture": ExtResource("2_g03au")
}, {
"duration": 30.0,
"texture": ExtResource("3_5wyv8")
}, {
"duration": 15.0,
"texture": ExtResource("4_ag85t")
}],
"loop": true,
"name": &"父亲抱小孩",
"speed": 30.0
}, {
"frames": [{
"duration": 20.0,
"texture": ExtResource("5_5m4aa")
}, {
"duration": 20.0,
"texture": ExtResource("6_0df74")
}, {
"duration": 20.0,
"texture": ExtResource("7_xhhcn")
}, {
"duration": 20.0,
"texture": ExtResource("8_dspyd")
}],
"loop": true,
"name": &"癞子背坐呼吸",
"speed": 30.0
}, {
"frames": [{
"duration": 20.0,
"texture": ExtResource("9_naxgo")
}, {
"duration": 20.0,
"texture": ExtResource("10_qcrrr")
}, {
"duration": 20.0,
"texture": ExtResource("11_buon4")
}, {
"duration": 20.0,
"texture": ExtResource("12_cx8xa")
}],
"loop": true,
"name": &"癞子背对侧头呼吸",
"speed": 30.0
}, {
"frames": [{
"duration": 6.0,
"texture": ExtResource("1_uknvx")
}, {

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dcwbe6hb3gdcp"
path="res://.godot/imported/0.png-a3269686a9806970c0dfd8799afcb5ce.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://asset/art/gif/c03_特写与游戏动画/父亲抱小孩/0.png"
dest_files=["res://.godot/imported/0.png-a3269686a9806970c0dfd8799afcb5ce.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bhrahd7u0yoba"
path="res://.godot/imported/1.png-2461ac2fae16cf03e4832dc2fe3f2555.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://asset/art/gif/c03_特写与游戏动画/父亲抱小孩/1.png"
dest_files=["res://.godot/imported/1.png-2461ac2fae16cf03e4832dc2fe3f2555.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dweny6ivmkanl"
path="res://.godot/imported/2.png-afafb5161ae4bca2cba94e19f92d32ed.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://asset/art/gif/c03_特写与游戏动画/父亲抱小孩/2.png"
dest_files=["res://.godot/imported/2.png-afafb5161ae4bca2cba94e19f92d32ed.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://nvddvjnphsqx"
path="res://.godot/imported/3.png-f1b878a4ae5a1352052eb9c3414b1c64.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://asset/art/gif/c03_特写与游戏动画/父亲抱小孩/3.png"
dest_files=["res://.godot/imported/3.png-f1b878a4ae5a1352052eb9c3414b1c64.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bhhjsj1dqep40"
path="res://.godot/imported/0.png-29a92c9e2d863c26da560ecf9d7f55e9.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://asset/art/gif/c03_特写与游戏动画/癞子背坐呼吸/0.png"
dest_files=["res://.godot/imported/0.png-29a92c9e2d863c26da560ecf9d7f55e9.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://lop0mxjb5y71"
path="res://.godot/imported/1.png-9504242d789560b7aa7bd583e9c9ee9b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://asset/art/gif/c03_特写与游戏动画/癞子背坐呼吸/1.png"
dest_files=["res://.godot/imported/1.png-9504242d789560b7aa7bd583e9c9ee9b.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 B

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c08v3b4prnqyt"
path="res://.godot/imported/2.png-771674e6060afe77d45d1ae84d22c7c2.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://asset/art/gif/c03_特写与游戏动画/癞子背坐呼吸/2.png"
dest_files=["res://.godot/imported/2.png-771674e6060afe77d45d1ae84d22c7c2.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://4m0ix5nhdmdf"
path="res://.godot/imported/3.png-4b276625cb8a4697e2e33c8df223b9b9.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://asset/art/gif/c03_特写与游戏动画/癞子背坐呼吸/3.png"
dest_files=["res://.godot/imported/3.png-4b276625cb8a4697e2e33c8df223b9b9.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 B

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c6gpicmu30026"
path="res://.godot/imported/0.png-218e6fe7dc6cd000a213f3a006d18933.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://asset/art/gif/c03_特写与游戏动画/癞子背对侧头呼吸/0.png"
dest_files=["res://.godot/imported/0.png-218e6fe7dc6cd000a213f3a006d18933.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 B

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b7ufyuiusqmh7"
path="res://.godot/imported/1.png-d46a9f066213228516ec78730973b1b1.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://asset/art/gif/c03_特写与游戏动画/癞子背对侧头呼吸/1.png"
dest_files=["res://.godot/imported/1.png-d46a9f066213228516ec78730973b1b1.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 B

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c21g54sgebjti"
path="res://.godot/imported/2.png-65316c99b267f6b126a0514cf04f68d3.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://asset/art/gif/c03_特写与游戏动画/癞子背对侧头呼吸/2.png"
dest_files=["res://.godot/imported/2.png-65316c99b267f6b126a0514cf04f68d3.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 B

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c7vjtmjhv7v7f"
path="res://.godot/imported/3.png-77114853f7248e4f7711c27bfb05bb12.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://asset/art/gif/c03_特写与游戏动画/癞子背对侧头呼吸/3.png"
dest_files=["res://.godot/imported/3.png-77114853f7248e4f7711c27bfb05bb12.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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dr51e8ell86"
path="res://.godot/imported/伤口血迹.png-09a80deddc6822152bc5759dfa5b43c4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://asset/art/scene/c03/s03_瞎子理发店/癞子特写/伤口血迹.png"
dest_files=["res://.godot/imported/伤口血迹.png-09a80deddc6822152bc5759dfa5b43c4.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://k4eplss3bx5g"
path="res://.godot/imported/刀劈特效.png-303e36c2e802d04a4dbed5e046db47cf.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://asset/art/scene/c03/s03_瞎子理发店/癞子特写/刀劈特效.png"
dest_files=["res://.godot/imported/刀劈特效.png-303e36c2e802d04a4dbed5e046db47cf.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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -4,7 +4,7 @@ extends CanvasLayer
var root_dir = "res://config/audio/"
func _ready():
func _ready() -> void:
var dir_access = DirAccess.open(root_dir) as DirAccess
for dir in dir_access.get_directories():
container.add_child(HSeparator.new())

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://ba2wk1ox76efe"
path="res://.godot/imported/c03_理发店_小小蝶_5.ogg-66a9a4612eefec52289193483440da3f.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_小小蝶_5.ogg"
dest_files=["res://.godot/imported/c03_理发店_小小蝶_5.ogg-66a9a4612eefec52289193483440da3f.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://cun811u2e7345"
path="res://.godot/imported/c03_理发店_小小蝶_6.ogg-38c66f064187bd8dba9d0e151e32d032.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_小小蝶_6.ogg"
dest_files=["res://.godot/imported/c03_理发店_小小蝶_6.ogg-38c66f064187bd8dba9d0e151e32d032.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://b0jpnl74h0r5j"
path="res://.godot/imported/c03_理发店_小小蝶_7.ogg-7278d678564f0d8cfb1baab6ce4a8912.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_小小蝶_7.ogg"
dest_files=["res://.godot/imported/c03_理发店_小小蝶_7.ogg-7278d678564f0d8cfb1baab6ce4a8912.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://dpmc3ku12hvgg"
path="res://.godot/imported/c03_理发店_癞子_10.ogg-8654894b5ed36318642f4dd8c25d8a89.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_癞子_10.ogg"
dest_files=["res://.godot/imported/c03_理发店_癞子_10.ogg-8654894b5ed36318642f4dd8c25d8a89.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://duj1rj5pqaypw"
path="res://.godot/imported/c03_理发店_癞子_11.ogg-3cb72d9b6ab549acd5cc78b162889624.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_癞子_11.ogg"
dest_files=["res://.godot/imported/c03_理发店_癞子_11.ogg-3cb72d9b6ab549acd5cc78b162889624.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://b0sauuigs8y0p"
path="res://.godot/imported/c03_理发店_癞子_12.ogg-c35d34415f87cc409e4eba30b914bb4a.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_癞子_12.ogg"
dest_files=["res://.godot/imported/c03_理发店_癞子_12.ogg-c35d34415f87cc409e4eba30b914bb4a.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://8wtlftvqn5gp"
path="res://.godot/imported/c03_理发店_癞子_13.ogg-ad4f488cd4278b06727a566379a9cca8.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_癞子_13.ogg"
dest_files=["res://.godot/imported/c03_理发店_癞子_13.ogg-ad4f488cd4278b06727a566379a9cca8.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://bplaj6uidsias"
path="res://.godot/imported/c03_理发店_癞子_14.ogg-0ae7c592462185954fbc2fc44ff8161b.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_癞子_14.ogg"
dest_files=["res://.godot/imported/c03_理发店_癞子_14.ogg-0ae7c592462185954fbc2fc44ff8161b.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://dqyythgj4b2a7"
path="res://.godot/imported/c03_理发店_癞子_15.ogg-13ac10d8375509b479ea2e94d96f3cfa.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_癞子_15.ogg"
dest_files=["res://.godot/imported/c03_理发店_癞子_15.ogg-13ac10d8375509b479ea2e94d96f3cfa.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://bylaoxlh2bii"
path="res://.godot/imported/c03_理发店_癞子_8.ogg-5cd1c86dea61b02ba8a86260dc6c73d9.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_癞子_8.ogg"
dest_files=["res://.godot/imported/c03_理发店_癞子_8.ogg-5cd1c86dea61b02ba8a86260dc6c73d9.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://d4mihdw45ed6x"
path="res://.godot/imported/c03_理发店_癞子_9.ogg-502658df3b170c4f123c76ea08ed87ec.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_癞子_9.ogg"
dest_files=["res://.godot/imported/c03_理发店_癞子_9.ogg-502658df3b170c4f123c76ea08ed87ec.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://tmsyvdoklrid"
path="res://.godot/imported/c03_理发店_瞎子_15.ogg-993c9e658b98f80d58aa324105579201.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_瞎子_15.ogg"
dest_files=["res://.godot/imported/c03_理发店_瞎子_15.ogg-993c9e658b98f80d58aa324105579201.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://cyfi6r567yo1u"
path="res://.godot/imported/c03_理发店_瞎子_16.ogg-cf3255fb045aae07a6e320b54f000c3f.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_瞎子_16.ogg"
dest_files=["res://.godot/imported/c03_理发店_瞎子_16.ogg-cf3255fb045aae07a6e320b54f000c3f.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://d0tfvpokjdcp1"
path="res://.godot/imported/c03_理发店_瞎子_17.ogg-77d1e04338e47990fbcecedf8301c49d.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_瞎子_17.ogg"
dest_files=["res://.godot/imported/c03_理发店_瞎子_17.ogg-77d1e04338e47990fbcecedf8301c49d.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://dtbb5vi4ry85b"
path="res://.godot/imported/c03_理发店_瞎子_18.ogg-4e672df3b198ee52c88f0ce7cfef4c3a.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_瞎子_18.ogg"
dest_files=["res://.godot/imported/c03_理发店_瞎子_18.ogg-4e672df3b198ee52c88f0ce7cfef4c3a.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://ckl6eyb6fn6ui"
path="res://.godot/imported/c03_理发店_瞎子_19.ogg-f6fd7591a238ead0b34e2d58608527ea.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_瞎子_19.ogg"
dest_files=["res://.godot/imported/c03_理发店_瞎子_19.ogg-f6fd7591a238ead0b34e2d58608527ea.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://bvrkiuimnlh5w"
path="res://.godot/imported/c03_理发店_瞎子_20.ogg-02426bd5c2b1e5597e2b937a0c0940eb.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_瞎子_20.ogg"
dest_files=["res://.godot/imported/c03_理发店_瞎子_20.ogg-02426bd5c2b1e5597e2b937a0c0940eb.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://dnctmpsv0xrad"
path="res://.godot/imported/c03_理发店_瞎子_21.ogg-620878f56dfc42101cca76bc6de2b9ec.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_瞎子_21.ogg"
dest_files=["res://.godot/imported/c03_理发店_瞎子_21.ogg-620878f56dfc42101cca76bc6de2b9ec.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://uf14ji1pkqaf"
path="res://.godot/imported/c03_理发店_瞎子_22.ogg-50207038a1cfd50caa472f5ab22a8778.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_瞎子_22.ogg"
dest_files=["res://.godot/imported/c03_理发店_瞎子_22.ogg-50207038a1cfd50caa472f5ab22a8778.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://dsymgeq6i02mj"
path="res://.godot/imported/c03_理发店_瞎子_23.ogg-9721e730eb715d8a9090aee47d6b7171.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_瞎子_23.ogg"
dest_files=["res://.godot/imported/c03_理发店_瞎子_23.ogg-9721e730eb715d8a9090aee47d6b7171.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

Binary file not shown.

View File

@ -0,0 +1,19 @@
[remap]
importer="oggvorbisstr"
type="AudioStreamOggVorbis"
uid="uid://bqhqxmf5hsed0"
path="res://.godot/imported/c03_理发店_陆仁.ogg-5b28d2fa4f1ffe8f2ecd2c7ee6f218e2.oggvorbisstr"
[deps]
source_file="res://asset/audio/peiyin/c03/c03_理发店_陆仁.ogg"
dest_files=["res://.godot/imported/c03_理发店_陆仁.ogg-5b28d2fa4f1ffe8f2ecd2c7ee6f218e2.oggvorbisstr"]
[params]
loop=false
loop_offset=0
bpm=0
beat_count=0
bar_beats=4

View File

@ -8,3 +8,84 @@ Second one,Second one,(response),,
Start again,Start again,(response),,
End the conversation,End the conversation,(response),,
For more information see the online documentation.,For more information see the online documentation.,Nathan,,
(看病费用涨价,为了攒钱让小蝶上学,),(看病费用涨价,为了攒钱让小蝶上学,),陆仁,,
(小蝶想请小蝉来家里吃饭,写了请柬),(小蝶想请小蝉来家里吃饭,写了请柬),小蝶,,
(父母让小蝶去送请柬,顺便带肉回家做饭),(父母让小蝶去送请柬,顺便带肉回家做饭),陆仁,,
那个...方叔叔,小蝉在吗? [ID:],那个...方叔叔,小蝉在吗? [ID:],小小蝶,,
咳咳,她,咳咳,不在。 [ID:],咳咳,她,咳咳,不在。 [ID:],瞎子,,
她是生病了吗,好几天没见她了。 [ID:],她是生病了吗,好几天没见她了。 [ID:],小小蝶,,
她... [ID:],她... [ID:],瞎子,,
我挨家挨户地问啊,都说没看见她。 [ID:],我挨家挨户地问啊,都说没看见她。 [ID:],瞎子,,
一个活生生的小孩子,就这么找不见了... [ID:],一个活生生的小孩子,就这么找不见了... [ID:],瞎子,,
c03_理发店_癞子_8,我家那个昨天想拿推子给我剃头,差点给我开了瓢!,癞子,,
c03_理发店_癞子_9,你瞅瞅,这么长一道血印子!唉呀...,癞子,,
c03_理发店_瞎子_15,可不能这么说。,瞎子,,
c03_理发店_瞎子_16,弟妹这一手,没准是变相消了你的血光灾呢。,瞎子,,
c03_理发店_瞎子_17,逢凶化吉,灾消晦退,好日子还在后头。,瞎子,,
c03_理发店_癞子_10,哼,听你这么一说,好像确实有那么几分道理。,癞子,,
c03_理发店_癞子_11,不过这辫子留了这么多年,乍一找人剪了,心里还有点怪不是滋味儿。,癞子,,
c03_理发店_瞎子_18,怎么舍得剪了?,瞎子,,
c03_理发店_癞子_12,妇人家家的,不知道从哪打听到用男人的辫子入药能治病,一直念叨个不停。,癞子,,
c03_理发店_癞子_13,...你听过这说法没有?,癞子,,
c03_理发店_瞎子_19,我不懂医,不好乱说。,瞎子,,
c03_理发店_癞子_14,最近街上也是彻底乱了套,那些人看见你留了“阴阳头”,就跟那闻着味儿的狗一样凑过来...,癞子,,
c03_理发店_癞子_15,哦,瞧我这记性,忘了你眼神不好,不爱出门。,癞子,,
c03_理发店_小小蝶_5,方叔叔,我来找小蝉玩儿,她在屋里吗?,小小蝶,,
c03_理发店_瞎子_20,她摔了一跤,还睡着呢,我喊她起来?,瞎子,,
c03_理发店_小小蝶_6,不用不用,我在这等一会儿就行。,小小蝶,,
c03_理发店_陆仁,方老弟,你正好给我家这小皮猴儿修修毛,成天跟个假小子似的,没个小姑娘样子。,陆仁,,
c03_理发店_瞎子_21,小孩子嘛,高高兴兴就好。,瞎子,,
c03_理发店_瞎子_22,你们也别在这干等,我去给你们拿些“洋点心”。,瞎子,,
c03_理发店_瞎子_23,说是什么舶来品,高级货,你们拿出尝尝,甜个嘴。,瞎子,,
c03_理发店_小小蝶_7,谢谢方叔叔。,小小蝶,,
记得当年这箱麻将还是我弟弟背回来的。,记得当年这箱麻将还是我弟弟背回来的。,李氏,,
那天下了雨,他光着膀子回来,用衣裳包着,说木头怕水他不怕,就当免费洗了个澡...,那天下了雨,他光着膀子回来,用衣裳包着,说木头怕水他不怕,就当免费洗了个澡...,李氏,,
(轻笑一声)后来发现少了张牌,这小崽子又背着我们敲敲打打,硬是做了张一模一样的补上。,(轻笑一声)后来发现少了张牌,这小崽子又背着我们敲敲打打,硬是做了张一模一样的补上。,癞子,,
(苦笑)他呀,就是个死心眼,犟种!,(苦笑)他呀,就是个死心眼,犟种!,李氏,,
得了病就一直忍着,等到实在瞒不住了才告诉咱们!,得了病就一直忍着,等到实在瞒不住了才告诉咱们!,李氏,,
他认了命等死,咱们不能认,只要今晚赢了钱...,他认了命等死,咱们不能认,只要今晚赢了钱...,癞子,,
哟,人都来齐了怎么还在理麻将?今晚大家可得好好地打一场!,哟,人都来齐了怎么还在理麻将?今晚大家可得好好地打一场!,胖子,,
我观张老弟面色晦暗、嘴唇发白,难不成是这些日子手气不佳,输多了牌才...,我观张老弟面色晦暗、嘴唇发白,难不成是这些日子手气不佳,输多了牌才...,瞎子,,
(哼)以前可不这样,不说牌桌通杀,也是赢多输少,偏偏最近...,(哼)以前可不这样,不说牌桌通杀,也是赢多输少,偏偏最近...,胖子,,
最近怎么了?,最近怎么了?,李氏,,
碰。,碰。,瞎子,,
自从那三楼的婆娘出了名以后,我这手气就臭得不行...怕不是让人给“借运”了?,自从那三楼的婆娘出了名以后,我这手气就臭得不行...怕不是让人给“借运”了?,胖子,,
还真不好说。运气这玩意玄乎得很。,还真不好说。运气这玩意玄乎得很。,癞子,,
我以前走南闯北的时候,见过一个会使邪门功夫的老爷子。,我以前走南闯北的时候,见过一个会使邪门功夫的老爷子。,癞子,,
说是能“借”人家的命来延长自己的寿命...,说是能“借”人家的命来延长自己的寿命...,癞子,,
听着怪瘆人的。,听着怪瘆人的。,胖子,,
人外有人,天外有天。,人外有人,天外有天。,瞎子,,
天外还有什么...咱们这些闲杂人等也说了不算。,天外还有什么...咱们这些闲杂人等也说了不算。,瞎子,,
六万。,六万。,李氏,,
话又说回来,你们喝过洋酒没有?用葡萄发酵成的,红得发黑,度数还高。,话又说回来,你们喝过洋酒没有?用葡萄发酵成的,红得发黑,度数还高。,李氏,,
那叫香槟,洋人就喜欢整那些面子货,不适口。,那叫香槟,洋人就喜欢整那些面子货,不适口。,癞子,,
(啧)见多识广还得看王兄啊。,(啧)见多识广还得看王兄啊。,胖子,,
瞧这架势,以前没准多风光...,瞧这架势,以前没准多风光...,胖子,,
英雄不问出处。,英雄不问出处。,瞎子,,
都过去了,都过去了。,都过去了,都过去了。,癞子,,
为了跑出来,我们还睡过桥洞呢!,为了跑出来,我们还睡过桥洞呢!,李氏,,
(此处响起癞子的咳嗽声)那阵子我老是梦见让人剁了手指头,八成就是因为那桥是洋人建的...,(此处响起癞子的咳嗽声)那阵子我老是梦见让人剁了手指头,八成就是因为那桥是洋人建的...,李氏,,
你说的是外摆渡桥?,你说的是外摆渡桥?,胖子,,
应该是吧,我们再没回去过,也不知道后来成了什么样...,应该是吧,我们再没回去过,也不知道后来成了什么样...,癞子,,
(脏话)怎么一张牌都不来!,(脏话)怎么一张牌都不来!,胖子,,
八条,八条,瞎子,,
对了,小蝉那孩子有消息了吗?,对了,小蝉那孩子有消息了吗?,癞子,,
要是有消息,我也不会跟你们在这打牌,虚度光阴。,要是有消息,我也不会跟你们在这打牌,虚度光阴。,瞎子,,
方大哥,你这话说得不妥。,方大哥,你这话说得不妥。,李氏,,
咱们哪个心里头不苦的?不都是在这牌桌上苦中作乐呢。,咱们哪个心里头不苦的?不都是在这牌桌上苦中作乐呢。,李氏,,
找点乐子,何必想那么多。,找点乐子,何必想那么多。,胖子,,
我这头生意都不做了,帮你满大街的又找又问,不也是白忙活一场。,我这头生意都不做了,帮你满大街的又找又问,不也是白忙活一场。,李氏,,
要我说,还是得从身边的人下手。,要我说,还是得从身边的人下手。,李氏,,
指不定是被楼里的那个邻居拐了呢,就那么一丁点的小女孩,谁都能敲昏了抱回家去。,指不定是被楼里的那个邻居拐了呢,就那么一丁点的小女孩,谁都能敲昏了抱回家去。,癞子,,
...我看她跟三楼那个叫什么小蝶的走得很近,老是手拉手出去,成天凑在一块说悄悄话。,...我看她跟三楼那个叫什么小蝶的走得很近,老是手拉手出去,成天凑在一块说悄悄话。,胖子,,
他们一家人最近都不怎么出门,我看就是心里有鬼。,他们一家人最近都不怎么出门,我看就是心里有鬼。,胖子,,
把小孩骗走了换钱换药,搞邪术,什么都干得出来。,把小孩骗走了换钱换药,搞邪术,什么都干得出来。,胖子,,
这么一说,好像确实有点道理。,这么一说,好像确实有点道理。,癞子,,
我小弟去看过病了,那药方邪门得很,待会我拿给你们瞧瞧。,我小弟去看过病了,那药方邪门得很,待会我拿给你们瞧瞧。,李氏,,
我活了这么大岁数,也是第一次见。,我活了这么大岁数,也是第一次见。,李氏,,
道听途说而已。,道听途说而已。,瞎子,,
自摸!(笑),自摸!(笑),癞子,,
(脏话)不玩了不玩了,今天一把都没胡过!,(脏话)不玩了不玩了,今天一把都没胡过!,胖子,,
我也得早点回去,没准小蝉今儿个能回来,我总惦记着回去看看。,我也得早点回去,没准小蝉今儿个能回来,我总惦记着回去看看。,瞎子,,
天黑了,你们回去都小心着点。,天黑了,你们回去都小心着点。,李氏,,
嘶...,嘶...,癞子,,

1 keys zh_CN _character _notes _tags
8 Start again Start again (response)
9 End the conversation End the conversation (response)
10 For more information see the online documentation. For more information see the online documentation. Nathan
11 (看病费用涨价,为了攒钱让小蝶上学,) (看病费用涨价,为了攒钱让小蝶上学,) 陆仁
12 (小蝶想请小蝉来家里吃饭,写了请柬) (小蝶想请小蝉来家里吃饭,写了请柬) 小蝶
13 (父母让小蝶去送请柬,顺便带肉回家做饭) (父母让小蝶去送请柬,顺便带肉回家做饭) 陆仁
14 那个...方叔叔,小蝉在吗? [ID:] 那个...方叔叔,小蝉在吗? [ID:] 小小蝶
15 咳咳,她,咳咳,不在。 [ID:] 咳咳,她,咳咳,不在。 [ID:] 瞎子
16 她是生病了吗,好几天没见她了。 [ID:] 她是生病了吗,好几天没见她了。 [ID:] 小小蝶
17 她... [ID:] 她... [ID:] 瞎子
18 我挨家挨户地问啊,都说没看见她。 [ID:] 我挨家挨户地问啊,都说没看见她。 [ID:] 瞎子
19 一个活生生的小孩子,就这么找不见了... [ID:] 一个活生生的小孩子,就这么找不见了... [ID:] 瞎子
20 c03_理发店_癞子_8 我家那个昨天想拿推子给我剃头,差点给我开了瓢! 癞子
21 c03_理发店_癞子_9 你瞅瞅,这么长一道血印子!唉呀... 癞子
22 c03_理发店_瞎子_15 可不能这么说。 瞎子
23 c03_理发店_瞎子_16 弟妹这一手,没准是变相消了你的血光灾呢。 瞎子
24 c03_理发店_瞎子_17 逢凶化吉,灾消晦退,好日子还在后头。 瞎子
25 c03_理发店_癞子_10 哼,听你这么一说,好像确实有那么几分道理。 癞子
26 c03_理发店_癞子_11 不过这辫子留了这么多年,乍一找人剪了,心里还有点怪不是滋味儿。 癞子
27 c03_理发店_瞎子_18 怎么舍得剪了? 瞎子
28 c03_理发店_癞子_12 妇人家家的,不知道从哪打听到用男人的辫子入药能治病,一直念叨个不停。 癞子
29 c03_理发店_癞子_13 ...你听过这说法没有? 癞子
30 c03_理发店_瞎子_19 我不懂医,不好乱说。 瞎子
31 c03_理发店_癞子_14 最近街上也是彻底乱了套,那些人看见你留了“阴阳头”,就跟那闻着味儿的狗一样凑过来... 癞子
32 c03_理发店_癞子_15 哦,瞧我这记性,忘了你眼神不好,不爱出门。 癞子
33 c03_理发店_小小蝶_5 方叔叔,我来找小蝉玩儿,她在屋里吗? 小小蝶
34 c03_理发店_瞎子_20 她摔了一跤,还睡着呢,我喊她起来? 瞎子
35 c03_理发店_小小蝶_6 不用不用,我在这等一会儿就行。 小小蝶
36 c03_理发店_陆仁 方老弟,你正好给我家这小皮猴儿修修毛,成天跟个假小子似的,没个小姑娘样子。 陆仁
37 c03_理发店_瞎子_21 小孩子嘛,高高兴兴就好。 瞎子
38 c03_理发店_瞎子_22 你们也别在这干等,我去给你们拿些“洋点心”。 瞎子
39 c03_理发店_瞎子_23 说是什么舶来品,高级货,你们拿出尝尝,甜个嘴。 瞎子
40 c03_理发店_小小蝶_7 谢谢方叔叔。 小小蝶
41 记得当年这箱麻将还是我弟弟背回来的。 记得当年这箱麻将还是我弟弟背回来的。 李氏
42 那天下了雨,他光着膀子回来,用衣裳包着,说木头怕水他不怕,就当免费洗了个澡... 那天下了雨,他光着膀子回来,用衣裳包着,说木头怕水他不怕,就当免费洗了个澡... 李氏
43 (轻笑一声)后来发现少了张牌,这小崽子又背着我们敲敲打打,硬是做了张一模一样的补上。 (轻笑一声)后来发现少了张牌,这小崽子又背着我们敲敲打打,硬是做了张一模一样的补上。 癞子
44 (苦笑)他呀,就是个死心眼,犟种! (苦笑)他呀,就是个死心眼,犟种! 李氏
45 得了病就一直忍着,等到实在瞒不住了才告诉咱们! 得了病就一直忍着,等到实在瞒不住了才告诉咱们! 李氏
46 他认了命等死,咱们不能认,只要今晚赢了钱... 他认了命等死,咱们不能认,只要今晚赢了钱... 癞子
47 哟,人都来齐了怎么还在理麻将?今晚大家可得好好地打一场! 哟,人都来齐了怎么还在理麻将?今晚大家可得好好地打一场! 胖子
48 我观张老弟面色晦暗、嘴唇发白,难不成是这些日子手气不佳,输多了牌才... 我观张老弟面色晦暗、嘴唇发白,难不成是这些日子手气不佳,输多了牌才... 瞎子
49 (哼)以前可不这样,不说牌桌通杀,也是赢多输少,偏偏最近... (哼)以前可不这样,不说牌桌通杀,也是赢多输少,偏偏最近... 胖子
50 最近怎么了? 最近怎么了? 李氏
51 碰。 碰。 瞎子
52 自从那三楼的婆娘出了名以后,我这手气就臭得不行...怕不是让人给“借运”了? 自从那三楼的婆娘出了名以后,我这手气就臭得不行...怕不是让人给“借运”了? 胖子
53 还真不好说。运气这玩意玄乎得很。 还真不好说。运气这玩意玄乎得很。 癞子
54 我以前走南闯北的时候,见过一个会使邪门功夫的老爷子。 我以前走南闯北的时候,见过一个会使邪门功夫的老爷子。 癞子
55 说是能“借”人家的命来延长自己的寿命... 说是能“借”人家的命来延长自己的寿命... 癞子
56 听着怪瘆人的。 听着怪瘆人的。 胖子
57 人外有人,天外有天。 人外有人,天外有天。 瞎子
58 天外还有什么...咱们这些闲杂人等也说了不算。 天外还有什么...咱们这些闲杂人等也说了不算。 瞎子
59 六万。 六万。 李氏
60 话又说回来,你们喝过洋酒没有?用葡萄发酵成的,红得发黑,度数还高。 话又说回来,你们喝过洋酒没有?用葡萄发酵成的,红得发黑,度数还高。 李氏
61 那叫香槟,洋人就喜欢整那些面子货,不适口。 那叫香槟,洋人就喜欢整那些面子货,不适口。 癞子
62 (啧)见多识广还得看王兄啊。 (啧)见多识广还得看王兄啊。 胖子
63 瞧这架势,以前没准多风光... 瞧这架势,以前没准多风光... 胖子
64 英雄不问出处。 英雄不问出处。 瞎子
65 都过去了,都过去了。 都过去了,都过去了。 癞子
66 为了跑出来,我们还睡过桥洞呢! 为了跑出来,我们还睡过桥洞呢! 李氏
67 (此处响起癞子的咳嗽声)那阵子我老是梦见让人剁了手指头,八成就是因为那桥是洋人建的... (此处响起癞子的咳嗽声)那阵子我老是梦见让人剁了手指头,八成就是因为那桥是洋人建的... 李氏
68 你说的是外摆渡桥? 你说的是外摆渡桥? 胖子
69 应该是吧,我们再没回去过,也不知道后来成了什么样... 应该是吧,我们再没回去过,也不知道后来成了什么样... 癞子
70 (脏话)怎么一张牌都不来! (脏话)怎么一张牌都不来! 胖子
71 八条 八条 瞎子
72 对了,小蝉那孩子有消息了吗? 对了,小蝉那孩子有消息了吗? 癞子
73 要是有消息,我也不会跟你们在这打牌,虚度光阴。 要是有消息,我也不会跟你们在这打牌,虚度光阴。 瞎子
74 方大哥,你这话说得不妥。 方大哥,你这话说得不妥。 李氏
75 咱们哪个心里头不苦的?不都是在这牌桌上苦中作乐呢。 咱们哪个心里头不苦的?不都是在这牌桌上苦中作乐呢。 李氏
76 找点乐子,何必想那么多。 找点乐子,何必想那么多。 胖子
77 我这头生意都不做了,帮你满大街的又找又问,不也是白忙活一场。 我这头生意都不做了,帮你满大街的又找又问,不也是白忙活一场。 李氏
78 要我说,还是得从身边的人下手。 要我说,还是得从身边的人下手。 李氏
79 指不定是被楼里的那个邻居拐了呢,就那么一丁点的小女孩,谁都能敲昏了抱回家去。 指不定是被楼里的那个邻居拐了呢,就那么一丁点的小女孩,谁都能敲昏了抱回家去。 癞子
80 ...我看她跟三楼那个叫什么小蝶的走得很近,老是手拉手出去,成天凑在一块说悄悄话。 ...我看她跟三楼那个叫什么小蝶的走得很近,老是手拉手出去,成天凑在一块说悄悄话。 胖子
81 他们一家人最近都不怎么出门,我看就是心里有鬼。 他们一家人最近都不怎么出门,我看就是心里有鬼。 胖子
82 把小孩骗走了换钱换药,搞邪术,什么都干得出来。 把小孩骗走了换钱换药,搞邪术,什么都干得出来。 胖子
83 这么一说,好像确实有点道理。 这么一说,好像确实有点道理。 癞子
84 我小弟去看过病了,那药方邪门得很,待会我拿给你们瞧瞧。 我小弟去看过病了,那药方邪门得很,待会我拿给你们瞧瞧。 李氏
85 我活了这么大岁数,也是第一次见。 我活了这么大岁数,也是第一次见。 李氏
86 道听途说而已。 道听途说而已。 瞎子
87 自摸!(笑) 自摸!(笑) 癞子
88 (脏话)不玩了不玩了,今天一把都没胡过! (脏话)不玩了不玩了,今天一把都没胡过! 胖子
89 我也得早点回去,没准小蝉今儿个能回来,我总惦记着回去看看。 我也得早点回去,没准小蝉今儿个能回来,我总惦记着回去看看。 瞎子
90 天黑了,你们回去都小心着点。 天黑了,你们回去都小心着点。 李氏
91 嘶... 嘶... 癞子

View File

@ -5,17 +5,46 @@
=> END
~ c03_s02_邀请小蝉与瞎子对话1
小小蝶: 那个...方叔叔,小蝉在吗?
瞎子: 咳咳,她,咳咳,不在。
小小蝶: 她是生病了吗,好几天没见她了。
小小蝶: 那个...方叔叔,小蝉在吗? [ID:]
瞎子: 咳咳,她,咳咳,不在。 [ID:]
小小蝶: 她是生病了吗,好几天没见她了。 [ID:]
=> END
~ c03_s02_邀请小蝉与瞎子对话2
瞎子: 她...
瞎子: 我挨家挨户地问啊,都说没看见她。
瞎子: 一个活生生的小孩子,就这么找不见了...
瞎子: 她... [ID:]
瞎子: 我挨家挨户地问啊,都说没看见她。 [ID:]
瞎子: 一个活生生的小孩子,就这么找不见了... [ID:]
=> END
~ c03_s03_理发店演出1
癞子: 我家那个昨天想拿推子给我剃头,差点给我开了瓢! [ID:c03_理发店_癞子_8]
癞子: 你瞅瞅,这么长一道血印子!唉呀... [ID:c03_理发店_癞子_9]
瞎子: 可不能这么说。 [ID:c03_理发店_瞎子_15]
瞎子: 弟妹这一手,没准是变相消了你的血光灾呢。 [ID:c03_理发店_瞎子_16]
瞎子: 逢凶化吉,灾消晦退,好日子还在后头。 [ID:c03_理发店_瞎子_17]
癞子: 哼,听你这么一说,好像确实有那么几分道理。 [ID:c03_理发店_癞子_10]
癞子: 不过这辫子留了这么多年,乍一找人剪了,心里还有点怪不是滋味儿。 [ID:c03_理发店_癞子_11]
瞎子: 怎么舍得剪了? [ID:c03_理发店_瞎子_18]
癞子: 妇人家家的,不知道从哪打听到用男人的辫子入药能治病,一直念叨个不停。 [ID:c03_理发店_癞子_12]
癞子: ...你听过这说法没有? [ID:c03_理发店_癞子_13]
瞎子: 我不懂医,不好乱说。 [ID:c03_理发店_瞎子_19]
癞子: 最近街上也是彻底乱了套,那些人看见你留了“阴阳头”,就跟那闻着味儿的狗一样凑过来... [ID:c03_理发店_癞子_14]
癞子: 哦,瞧我这记性,忘了你眼神不好,不爱出门。 [#wait=1.5] [ID:c03_理发店_癞子_15]
小小蝶: 方叔叔,我来找小蝉玩儿,她在屋里吗? [ID:c03_理发店_小小蝶_5]
=> END
~ c03_s03_理发店演出2
瞎子: 她摔了一跤,还睡着呢,我喊她起来? [ID:c03_理发店_瞎子_20]
小小蝶: 不用不用,我在这等一会儿就行。 [ID:c03_理发店_小小蝶_6]
陆仁: 方老弟,你正好给我家这小皮猴儿修修毛,成天跟个假小子似的,没个小姑娘样子。 [ID:c03_理发店_陆仁]
瞎子: 小孩子嘛,高高兴兴就好。 [ID:c03_理发店_瞎子_21]
瞎子: 你们也别在这干等,我去给你们拿些“洋点心”。 [ID:c03_理发店_瞎子_22]
=> END
~ c03_s03_理发店演出3
瞎子: 说是什么舶来品,高级货,你们拿出尝尝,甜个嘴。 [ID:c03_理发店_瞎子_23]
小小蝶: 谢谢方叔叔。 [ID:c03_理发店_小小蝶_7]
=> END
~ c03_s04_整理麻将游戏0
# 打牌准备,整理麻将盒游戏成功(演出至麻将搬到桌上):

View File

@ -1,15 +1 @@
keys,zh_CN,_character,_notes,_tags
c03_invite_xchan_supper=2 & c03_s03_laizi_braid=2 ~ c03_f2_madman_runaway=2,c03_invite_xchan_supper=2 & c03_s03_laizi_braid=2 ~ c03_f2_madman_runaway=2,,,
c03_invite_xchan_supper=2 & c03_s03_laizi_braid=2 -> c03_f2_madman_runaway=2,c03_invite_xchan_supper=2 & c03_s03_laizi_braid=2 -> c03_f2_madman_runaway=2,,,
0:demo 1:release,0:demo 1:release,release_stage,,
1:序章 2:第一章 3:第二章 4:第三章 5:第四章 6:尾声,1:序章 2:第一章 3:第二章 4:第三章 5:第四章 6:尾声,current_chapter_stage,,
0:初始化_关闭 1:打开 2:放入小蝉人偶 3:全部放置正确_可摇手柄 4:已播放完成,0:初始化_关闭 1:打开 2:放入小蝉人偶 3:全部放置正确_可摇手柄 4:已播放完成,c02_musicbox_stage,,
0:初始化 1:已交互疯子 2小鞋已掉落,0:初始化 1:已交互疯子 2小鞋已掉落,c02_madman_interacted_stage,,
0:初始化 1:寻找弹珠_老虎钳可以换弹珠 2:给出弹珠 3:游戏结束_小猫纸片 4:游戏结束_小猫离开,0:初始化 1:寻找弹珠_老虎钳可以换弹珠 2:给出弹珠 3:游戏结束_小猫纸片 4:游戏结束_小猫离开,c02_ball_game_stage,,
0:初始化 1:已放肉,0:初始化 1:已放肉,c03_s01_meat_put,,
0:初始化 1:已偷听_需邀请 2:完成邀请,0:初始化 1:已偷听_需邀请 2:完成邀请,c03_invite_xchan_supper,,
0:初始化 1:已使用剪刀 2:已剪下,0:初始化 1:已使用剪刀 2:已剪下,c03_s03_laizi_braid,,
0:初始化 1:跑开_纸人挡路 2:消除纸人,0:初始化 1:跑开_纸人挡路 2:消除纸人,c03_f2_madman_runaway,,
c03_invite_xchan_supper=2 & c03_s03_laizi_braid=2 & c03_mahjong_game=1 -> c03_f2_madman_runaway=2,c03_invite_xchan_supper=2 & c03_s03_laizi_braid=2 & c03_mahjong_game=1 -> c03_f2_madman_runaway=2,,,
0:初始化 1:粘舌头和刀把 2:给药 4:准备好进入游戏,0:初始化 1:粘舌头和刀把 2:给药 4:准备好进入游戏,c03_before_mahjong_game,,
0:麻将理牌 1:麻将出千 2:麻将结束 3:演出结束,0:麻将理牌 1:麻将出千 2:麻将结束 3:演出结束,c03_mahjong_game,,

1 keys zh_CN _character _notes _tags
c03_invite_xchan_supper=2 & c03_s03_laizi_braid=2 ~ c03_f2_madman_runaway=2 c03_invite_xchan_supper=2 & c03_s03_laizi_braid=2 ~ c03_f2_madman_runaway=2
c03_invite_xchan_supper=2 & c03_s03_laizi_braid=2 -> c03_f2_madman_runaway=2 c03_invite_xchan_supper=2 & c03_s03_laizi_braid=2 -> c03_f2_madman_runaway=2
0:demo 1:release 0:demo 1:release release_stage
1:序章 2:第一章 3:第二章 4:第三章 5:第四章 6:尾声 1:序章 2:第一章 3:第二章 4:第三章 5:第四章 6:尾声 current_chapter_stage
0:初始化_关闭 1:打开 2:放入小蝉人偶 3:全部放置正确_可摇手柄 4:已播放完成 0:初始化_关闭 1:打开 2:放入小蝉人偶 3:全部放置正确_可摇手柄 4:已播放完成 c02_musicbox_stage
0:初始化 1:已交互疯子 2:小鞋已掉落 0:初始化 1:已交互疯子 2:小鞋已掉落 c02_madman_interacted_stage
0:初始化 1:寻找弹珠_老虎钳可以换弹珠 2:给出弹珠 3:游戏结束_小猫纸片 4:游戏结束_小猫离开 0:初始化 1:寻找弹珠_老虎钳可以换弹珠 2:给出弹珠 3:游戏结束_小猫纸片 4:游戏结束_小猫离开 c02_ball_game_stage
0:初始化 1:已放肉 0:初始化 1:已放肉 c03_s01_meat_put
0:初始化 1:已偷听_需邀请 2:完成邀请 0:初始化 1:已偷听_需邀请 2:完成邀请 c03_invite_xchan_supper
0:初始化 1:已使用剪刀 2:已剪下 0:初始化 1:已使用剪刀 2:已剪下 c03_s03_laizi_braid
0:初始化 1:跑开_纸人挡路 2:消除纸人 0:初始化 1:跑开_纸人挡路 2:消除纸人 c03_f2_madman_runaway
c03_invite_xchan_supper=2 & c03_s03_laizi_braid=2 & c03_mahjong_game=1 -> c03_f2_madman_runaway=2 c03_invite_xchan_supper=2 & c03_s03_laizi_braid=2 & c03_mahjong_game=1 -> c03_f2_madman_runaway=2
0:初始化 1:粘舌头和刀把 2:给药 4:准备好进入游戏 0:初始化 1:粘舌头和刀把 2:给药 4:准备好进入游戏 c03_before_mahjong_game
0:麻将理牌 1:麻将出千 2:麻将结束 3:演出结束 0:麻将理牌 1:麻将出千 2:麻将结束 3:演出结束 c03_mahjong_game

View File

@ -12,7 +12,7 @@ c02_ball_game_stage: 0:初始化 1:寻找弹珠_老虎钳可以换弹珠 2:给
~ EventStage_c03
c03_s01_meat_put: 0:初始化 1:已放肉
c03_invite_xchan_supper: 0:初始化 1:已偷听_需邀请 2:完成邀请
c03_s03_laizi_braid: 0:初始化 1:已使用剪刀 2:已剪下
c03_s03_laizi_braid: 0:初始化 1:已剪下辫子 2:演出结束
c03_f2_madman_runaway: 0:初始化 1:跑开_纸人挡路 2:消除纸人
c03_invite_xchan_supper=2 & c03_s03_laizi_braid=2 -> c03_f2_madman_runaway=2
c03_before_mahjong_game: 0:初始化 1:粘舌头和刀把 2:给药 4:准备好进入游戏

View File

@ -147,19 +147,20 @@ c01_s08_书店工钱,这个月的工钱还没拿。,,,,,I haven't collected this
mem_偷听对话,偷听对话,,,,,Eavesdropping
mem_疯子看井,疯子看井,,,,,Madman Guards Well
c01_鸡毛掸子,这是鸡毛掸子吗?,,,,,Is this a feather duster?
c01_院长书桌,桌上放着一本《圣经》。{br}「得著生命的,將要失喪生命...」,,,,,There's a Bible on the desk.{br}'Whoever finds their life will lose it...'
c01_院长书桌,桌上放着一本《圣经》。{br}「凡要救自己生命的,必丧掉生命...」,,,,,"There's a Bible on the table.{br}'Whoever wants to save their life will lose it, but whoever loses their life for me will find it...'"
c01_院长床,这本书已经看过了。{br}...那些句子是什么意思呢?,,,,,I've already read this book.{br}...What do those sentences mean?
c01_院长座钟,这西洋钟没坏的时候,走针会咔哒、咔哒地响...{br}停在未时一刻不动了。,,,,,"When this Western clock wasn't broken, the hands would tick and tock...{br}It's stopped at a quarter past one in the afternoon."
c01_院长座钟,咦?这西洋钟为什么倒着走?,,,,,Huh? Why is this Western clock running backwards?
c01_倾斜的洋相片,哇啊,这张洋相片要掉下来了!{br}我得做点什么...,,,,,"Oh no, this Western photograph is about to fall!{br}I need to do something..."
c01_摆正的洋相片,这是院长的儿子吗?,,,,,Is this the director's son?
c01_s06_院长房间,这是院长的房间,,,,,This is the director's room
c01_s06_小朋友房间,这是其他小朋友的房间,,,,,This is the other children's room
c01_s06_熟悉的墙画,墙上的画看起来好熟悉。{br}过去问问看吧,没准他们几个知道些什么。,,,,,The painting on the wall looks so familiar.{br}Let me go ask them. Maybe they know something.
c01_s06_四小孩对话结束,怪人、花、门...{br}他在找什么东西,或是什么人?,,,,,"Strange man, flowers, door...{br}What is he looking for, or who?"
c01_s06_四小孩对话结束,怪人、花...{br}有时候真搞不懂他们在说些什么。,,,,,"Strange people, flowers...{br}Sometimes I really don't understand what they're talking about."
c01_s07_钱碗,碗里只有一枚铜钱。,,,,,There's only one copper coin in the bowl.
c01_s07_获得报纸,这是什么?,,,,,What is this?
c01_s07_书店展柜,院长说,读一百本书,就可以成为无所不能的大人。{br}如果我再大一些,没准可以求店长把我留下,我会干很多活,也能吃苦...,,,,,"The director said that reading a hundred books would make me an all-capable adult.{br}If I were a bit older, maybe I could ask the shop owner to keep me. I'd work hard and endure hardship..."
c01_s08_书架游戏完成,这些书都被老鼠啃坏了,连木头架子都没放过。,,,,,"These books have all been gnawed by mice, even the wooden shelves weren't spared."
c01_s08_书架游戏完成,{br}刚才那是什么?,,,,,?!{br}What was that just now?
c01_s08_书架游戏恢复记忆,...{br}这是...我之前工作的地方。{br}...{br}现在老板不在了,我也该走了...,,,,,"...{br}This is... where I used to work.{br}...{br}Now that the boss is gone, I should leave too..."
c01_s08_获得袁大头后,工钱还在老地方。,,,,,The wages are still in the usual place.
c02_海报_剪辫子侦探,剪辫悬梁上侦探奇闻,,,,,Detective Tales of the Queue-Cutting Mystery
c02_海报_戏法班,朱连魁全班戏法——「各有幻女...演技新奇」,,,,,Zhu Liankui's Magic Troupe—'Each with enchanting women... performances most novel'
@ -239,3 +240,4 @@ c03_s02_小蝉寻人启事,?小蝉寻人启事,,,,,
c03_s03_获得剪刀纸舌头,?✂️纸人,,,,,
c03_s03_桌子,?桌子,,,,,
c03_s03_洗头盆,?洗头盆,,,,,
c03_s03_演出结束,?演出结束,刚刚他们?,,,,,

1 keys zh_CN _character _notes _tags zh_SH en
147 mem_偷听对话 偷听对话 Eavesdropping
148 mem_疯子看井 疯子看井 Madman Guards Well
149 c01_鸡毛掸子 这是鸡毛掸子吗? Is this a feather duster?
150 c01_院长书桌 桌上放着一本《圣经》。{br}「得著生命的,將要失喪生命...」 桌上放着一本《圣经》。{br}「凡要救自己生命的,必丧掉生命...」 There's a Bible on the desk.{br}'Whoever finds their life will lose it...' There's a Bible on the table.{br}'Whoever wants to save their life will lose it, but whoever loses their life for me will find it...'
151 c01_院长床 这本书已经看过了。{br}...那些句子是什么意思呢? I've already read this book.{br}...What do those sentences mean?
152 c01_院长座钟 这西洋钟没坏的时候,走针会咔哒、咔哒地响...{br}停在未时一刻不动了。 咦?这西洋钟为什么倒着走? When this Western clock wasn't broken, the hands would tick and tock...{br}It's stopped at a quarter past one in the afternoon. Huh? Why is this Western clock running backwards?
153 c01_倾斜的洋相片 哇啊,这张洋相片要掉下来了!{br}我得做点什么... Oh no, this Western photograph is about to fall!{br}I need to do something...
154 c01_摆正的洋相片 这是院长的儿子吗? Is this the director's son?
155 c01_s06_院长房间 这是院长的房间 This is the director's room
156 c01_s06_小朋友房间 这是其他小朋友的房间 This is the other children's room
157 c01_s06_熟悉的墙画 墙上的画看起来好熟悉。{br}过去问问看吧,没准他们几个知道些什么。 The painting on the wall looks so familiar.{br}Let me go ask them. Maybe they know something.
158 c01_s06_四小孩对话结束 怪人、花、门...{br}他在找什么东西,或是什么人? 怪人、花...{br}有时候真搞不懂他们在说些什么。 Strange man, flowers, door...{br}What is he looking for, or who? Strange people, flowers...{br}Sometimes I really don't understand what they're talking about.
159 c01_s07_钱碗 碗里只有一枚铜钱。 There's only one copper coin in the bowl.
160 c01_s07_获得报纸 这是什么? What is this?
161 c01_s07_书店展柜 院长说,读一百本书,就可以成为无所不能的大人。{br}如果我再大一些,没准可以求店长把我留下,我会干很多活,也能吃苦... The director said that reading a hundred books would make me an all-capable adult.{br}If I were a bit older, maybe I could ask the shop owner to keep me. I'd work hard and endure hardship...
162 c01_s08_书架游戏完成 这些书都被老鼠啃坏了,连木头架子都没放过。 ?!{br}刚才那是什么? These books have all been gnawed by mice, even the wooden shelves weren't spared. ?!{br}What was that just now?
163 c01_s08_书架游戏恢复记忆 ...{br}这是...我之前工作的地方。{br}...{br}现在老板不在了,我也该走了... ...{br}This is... where I used to work.{br}...{br}Now that the boss is gone, I should leave too...
164 c01_s08_获得袁大头后 工钱还在老地方。 The wages are still in the usual place.
165 c02_海报_剪辫子侦探 剪辫悬梁上侦探奇闻 Detective Tales of the Queue-Cutting Mystery
166 c02_海报_戏法班 朱连魁全班戏法——「各有幻女...演技新奇」 Zhu Liankui's Magic Troupe—'Each with enchanting women... performances most novel'
240 c03_s03_获得剪刀纸舌头 ?✂️纸人
241 c03_s03_桌子 ?桌子
242 c03_s03_洗头盆 ?洗头盆
243 c03_s03_演出结束 ?演出结束,刚刚他们?

View File

@ -204,9 +204,9 @@
~ Notes_c01
# c01-s05 院长房间
这是鸡毛掸子吗? [ID:c01_鸡毛掸子]
桌上放着一本《圣经》。{br}「得著生命的,將要失喪生命...」 [ID:c01_院长书桌]
桌上放着一本《圣经》。{br}「凡要救自己生命的,必丧掉生命...」 [ID:c01_院长书桌]
这本书已经看过了。{br}...那些句子是什么意思呢? [ID:c01_院长床]
这西洋钟没坏的时候,走针会咔哒、咔哒地响...{br}停在未时一刻不动了。 [ID:c01_院长座钟]
咦?这西洋钟为什么倒着走? [ID:c01_院长座钟]
哇啊,这张洋相片要掉下来了!{br}我得做点什么... [ID:c01_倾斜的洋相片]
这是院长的儿子吗? [ID:c01_摆正的洋相片]
# c01-s06 院子
@ -214,13 +214,14 @@
这是其他小朋友的房间 [ID:c01_s06_小朋友房间]
# 院子里四个小孩交谈结束后
墙上的画看起来好熟悉。{br}过去问问看吧,没准他们几个知道些什么。 [ID:c01_s06_熟悉的墙画]
怪人、花、门...{br}他在找什么东西,或是什么人? [ID:c01_s06_四小孩对话结束]
怪人、花...{br}有时候真搞不懂他们在说些什么。 [ID:c01_s06_四小孩对话结束]
# c01-s07 书店外
碗里只有一枚铜钱。 [ID:c01_s07_钱碗]
这是什么? [ID:c01_s07_获得报纸]
院长说,读一百本书,就可以成为无所不能的大人。{br}如果我再大一些,没准可以求店长把我留下,我会干很多活,也能吃苦... [ID:c01_s07_书店展柜]
# c01-s08 书店
这些书都被老鼠啃坏了,连木头架子都没放过。 [ID:c01_s08_书架游戏完成]
{br}刚才那是什么? [ID:c01_s08_书架游戏完成]
...{br}这是...我之前工作的地方。{br}...{br}现在老板不在了,我也该走了... [ID:c01_s08_书架游戏恢复记忆]
工钱还在老地方。 [ID:c01_s08_获得袁大头后]
=> END
@ -323,6 +324,7 @@
?✂️纸人 [ID:c03_s03_获得剪刀纸舌头]
?桌子 [ID:c03_s03_桌子]
?洗头盆 [ID:c03_s03_洗头盆]
?演出结束,刚刚他们? [ID:]
# s04 李癞房间
# s05 肉铺

View File

@ -54,7 +54,7 @@ func _ready() -> void:
next_btn.pressed.connect(_on_next_btn_pressed)
func _validate_json():
func _validate_json() -> void:
# first_frame_mapping
if !original_config.data.has("first_frame_mapping"):
original_config.data["first_frame_mapping"] = {}
@ -69,7 +69,7 @@ func _validate_json():
original_config.data["frames_per_second"] = {}
func _scan():
func _scan() -> void:
var dir = DirAccess.open(scan_path)
if !dir:
printerr("Failed to open directory:", scan_path)
@ -109,7 +109,7 @@ func _scan_subdir_frames(path: String) -> Dictionary:
return frames
func load_and_display_pages(search_text):
func load_and_display_pages(search_text) -> void:
all_keys = original_config.data.dirs.keys()
all_keys.sort_custom(func(a, b): return a < b)
if search_edit.text:
@ -126,7 +126,7 @@ func load_and_display_pages(search_text):
_load_current_page()
func _on_prev_btn_pressed():
func _on_prev_btn_pressed() -> void:
if current_page <= 1:
current_page = all_pages
else:
@ -134,7 +134,7 @@ func _on_prev_btn_pressed():
_load_current_page()
func _on_next_btn_pressed():
func _on_next_btn_pressed() -> void:
if current_page >= all_pages:
current_page = 1
else:
@ -142,7 +142,7 @@ func _on_next_btn_pressed():
_load_current_page()
func _load_current_page():
func _load_current_page() -> void:
page_label.text = str(current_page) + "/" + str(all_pages)
var start = (current_page - 1) * PAGE_SIZE
var end = min(start + PAGE_SIZE, matched_keys.size())
@ -155,7 +155,7 @@ func _load_current_page():
config_edit.text = text
func _display_cards(current_keys: Array):
func _display_cards(current_keys: Array) -> void:
# clear frames_display_grid
for child in frames_display_grid.get_children():
child.queue_free()

View File

@ -34,7 +34,7 @@ func _ready() -> void:
var reload_lock := Mutex.new()
func display_frames():
func display_frames() -> void:
if mapping_name and animated_sprite.sprite_frames.has_animation(mapping_name):
animated_sprite.play(mapping_name)
# scale down if the frame is too big
@ -47,7 +47,7 @@ func display_frames():
reload_frames()
func reload_frames():
func reload_frames() -> void:
# 暂不启用,使用手动调整
# return
var sprite_frames = animated_sprite.sprite_frames as SpriteFrames
@ -85,7 +85,7 @@ func reload_frames():
display_frames()
func _on_mapping_submitted(new_text: String):
func _on_mapping_submitted(new_text: String) -> void:
# clear the old mapping
var sprite_frames = animated_sprite.sprite_frames as SpriteFrames
if sprite_frames.has_animation(mapping_name):
@ -97,7 +97,7 @@ func _on_mapping_submitted(new_text: String):
_save_and_update_frames_and_config()
func _on_frames_speed_submitted(new_text: String):
func _on_frames_speed_submitted(new_text: String) -> void:
frames_per_sec = int(new_text)
if frames_per_sec < 1:
frames_per_sec = 1
@ -108,7 +108,7 @@ func _on_frames_speed_submitted(new_text: String):
_save_and_update_frames_and_config()
func _on_first_frame_mapping_submitted(new_text: String):
func _on_first_frame_mapping_submitted(new_text: String) -> void:
# clear the old first frame mapping
if first_frame_mapping != "":
var sprite_frames = animated_sprite.sprite_frames as SpriteFrames
@ -120,7 +120,7 @@ func _on_first_frame_mapping_submitted(new_text: String):
_save_and_update_frames_and_config()
func _on_mirror_mapping_submitted(new_text: String):
func _on_mirror_mapping_submitted(new_text: String) -> void:
# clear the old mirror mapping
if mirror_mapping != "":
var sprite_frames = animated_sprite.sprite_frames as SpriteFrames
@ -132,7 +132,7 @@ func _on_mirror_mapping_submitted(new_text: String):
_save_and_update_frames_and_config()
func _create_mirror():
func _create_mirror() -> void:
if not mirror_mapping:
return
var mirror_dir_path = "res://asset/art/animation/" + mirror_mapping
@ -159,7 +159,7 @@ func _create_mirror():
sprite_frames.set_animation_speed(mapping_name, frames_per_sec)
func _save_and_update_frames_and_config():
func _save_and_update_frames_and_config() -> void:
# save the sprite_frames
if animated_sprite:
ResourceSaver.save(animated_sprite.sprite_frames)

View File

@ -4,7 +4,7 @@ var gif_path = "res://asset/art/gif/"
var sprite_frames = preload("res://config/animation/entity_sprite_frames.tres")
func _ready():
func _ready() -> void:
var gif_files = []
for file in DirAccess.open(gif_path).get_files():
if file.get_extension() == "gif":

View File

@ -11,7 +11,7 @@ func _ready() -> void:
load_btn.pressed.connect(_reload_all)
func _reload_all():
func _reload_all() -> void:
static_frames.clear_all()
var dir = DirAccess.open(path) as DirAccess
static_frames.add_animation("placeholder")

View File

@ -9,7 +9,7 @@ func _init() -> void:
bus = "game_sfx"
func play_random():
func play_random() -> void:
if audio_collections == null:
push_warning("empty audio_collections")
return

View File

@ -9,7 +9,7 @@ custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path="../demo0.5.2/xiandie.exe"
export_path="../demo0.5.3/xiandie.exe"
patches=PackedStringArray()
encryption_include_filters=""
encryption_exclude_filters=""
@ -37,8 +37,8 @@ application/modify_resources=true
application/icon="uid://cxgwspjv16j7m"
application/console_wrapper_icon="uid://cxgwspjv16j7m"
application/icon_interpolation=4
application/file_version="0.5.2.0"
application/product_version="0.5.2.0"
application/file_version="0.5.3.0"
application/product_version="0.5.3.0"
application/company_name="包包丁"
application/product_name="衔蝶"
application/file_description="衔蝶"
@ -97,8 +97,8 @@ application/icon_interpolation=4
application/bundle_identifier="com.baobaoding.xiandie"
application/signature=""
application/app_category="Games"
application/short_version="0.2.0"
application/version="0.2.0"
application/short_version="0.5.3"
application/version="0.5.3"
application/copyright="爆爆叮"
application/copyright_localized={}
application/min_macos_version_x86_64="10.12"
@ -586,7 +586,7 @@ storyboard/custom_bg_color=Color(0, 0, 0, 1)
name="Android"
platform="Android"
runnable=true
advanced_options=false
advanced_options=true
dedicated_server=false
custom_features=""
export_filter="all_resources"

View File

@ -3,24 +3,30 @@ extends Node
signal archive_loaded
# Constants
const CURRENT_VERSION = 6
const ARCHIVE_ID_MIN = 0
const ARCHIVE_ID_MAX = 99
const ARCHIVE_ID_DIGITS = 3
# Static paths
static var user_data_root_dir := "user://data/" # must end with "/"
static var user_archives_dir := "user://data/archives/"
static var archive_prefix := "save"
# Archive management
var archive: AssembledArchive:
set(val):
archive = val
if archive:
# emit signal
archive_loaded.emit()
GlobalConfigManager.print_global_info()
print("use archive ", archive.resource_path)
archive.event_stage["release_stage"] = GlobalConfig.RELEASE_STAGE
print_rich("[color=brown] release_stage = %s[/color]" % GlobalConfig.RELEASE_STAGE)
# current archive
static var user_root_dir := "user://data/" # must end with "/"
static var archive_dir := "user://data/archives/"
static var archive_prefix := "save"
const CURRENT_VERSION = 6
var archives_dict: Dictionary[int, AssembledArchive] = {}
var archives_notes_dict: Dictionary[int, String] = {}
var autosave_timer := Timer.new()
@ -32,24 +38,22 @@ func _ready() -> void:
# 禁用默认退出行为,在 _notification 处理 NOTIFICATION_WM_CLOSE_REQUEST 时保存数据
get_tree().set_auto_accept_quit(false)
process_mode = Node.PROCESS_MODE_ALWAYS
if not _check_dirs_and_archives():
_handle_load_error("存档目录", "读写")
return
autosave_timer.timeout.connect(_try_auto_save)
autosave_timer.stop()
add_child(autosave_timer)
_setup_autosave_timer()
# config should be loaded first
load_config()
# 在 debug or editor 模式下,直接保证有 archive
if GlobalConfig.DEBUG or Engine.is_editor_hint():
if archives_dict.size() == 0:
create_and_use_new_archive(0)
else:
# debug 模式下默认使用 0 号存档
GlobalConfigManager.config.current_selected_archive_id = 0
_ensure_debug_archive()
func _notification(what):
func _notification(what: int) -> void:
# handle window close request
if what == NOTIFICATION_WM_CLOSE_REQUEST:
save_all()
@ -59,14 +63,27 @@ func _notification(what):
SceneManager.quit_game()
func _on_archive_id_changed():
func _setup_autosave_timer() -> void:
autosave_timer.timeout.connect(_try_auto_save)
autosave_timer.stop()
add_child(autosave_timer)
func _ensure_debug_archive() -> void:
if archives_dict.is_empty():
create_and_use_new_archive(0)
else:
# debug 模式下默认使用 0 号存档
GlobalConfigManager.config.current_selected_archive_id = 0
func _on_archive_id_changed() -> void:
var selected_id = GlobalConfigManager.config.current_selected_archive_id
if selected_id < 0:
return
# if archive and selected_id == archive.archive_id:
# print("_on_archive_id_changed same id=", selected_id)
# return
print("_on_archive_id_changed id=", selected_id)
if not archives_dict.has(selected_id):
print("新建存档 ", selected_id)
create_and_use_new_archive(selected_id)
@ -76,16 +93,19 @@ func _on_archive_id_changed():
load_archive()
func check_autosave_options():
if (
GlobalConfigManager.config.auto_save_enabled
and archive
and GlobalConfigManager.config.auto_save_seconds > 1
):
func check_autosave_options() -> void:
var config = GlobalConfigManager.config
var should_enable_autosave = (
config.auto_save_enabled
and archive
and config.auto_save_seconds > 1
)
if should_enable_autosave:
# reset left time
autosave_timer.stop()
autosave_timer.one_shot = false
autosave_timer.wait_time = GlobalConfigManager.config.auto_save_seconds
autosave_timer.wait_time = config.auto_save_seconds
autosave_timer.start()
else:
autosave_timer.stop()
@ -93,15 +113,16 @@ func check_autosave_options():
if GlobalConfig.DEBUG:
print(
"check_autosave_option: ",
GlobalConfigManager.config.auto_save_enabled,
config.auto_save_enabled,
" wait_time=",
autosave_timer.wait_time
)
func _try_auto_save():
func _try_auto_save() -> void:
if GlobalConfig.DEBUG:
print("Auto save")
if archive and GlobalConfigManager.config.auto_save_seconds > 1:
save_all()
# 自动保存成功 [ID:ui_auto_saved]
@ -109,72 +130,109 @@ func _try_auto_save():
func _check_dirs_and_archives() -> bool:
if !DirAccess.dir_exists_absolute(user_root_dir):
DirAccess.make_dir_recursive_absolute(user_root_dir)
print("Create user_root_dir:", user_root_dir)
# Ensure directories exist
_ensure_directory_exists(user_data_root_dir)
_ensure_directory_exists(user_archives_dir)
# Check if the archive directory is accessible
if !DirAccess.dir_exists_absolute(archive_dir):
DirAccess.make_dir_recursive_absolute(archive_dir)
print("Create archive_dir:", archive_dir)
var archive_dir_access = DirAccess.open(archive_dir)
if !archive_dir_access:
var archive_dir_access = DirAccess.open(user_archives_dir)
if not archive_dir_access:
_handle_load_error("存档目录", "读取")
# TODO pop up a dialog to inform the user
return false
var files = archive_dir_access.get_files()
files.sort()
# get archive number
for file in files:
if file.begins_with(archive_prefix) and file.ends_with(GlobalConfig.RES_FILE_FORMAT):
# format: save012_xxxxx; save000
var id_and_note = file.get_basename().substr(archive_prefix.length()).strip_escapes().split("_", true, 1)
var id_str = id_and_note[0]
var note_str = id_and_note[1] if id_and_note.size() >= 2 else (archive_prefix + id_str)
# 非三位数的 id 会被忽略
if id_str.length() != 3:
continue
var id = int(id_str)
# 读取范围是 0-99
if id < 0 or id > 99:
continue
archives_notes_dict[id] = note_str
var path = archive_dir + file
if not archives_dict.has(id):
var res = ResourceLoader.load(
path, "AssembledArchive", ResourceLoader.CACHE_MODE_REPLACE_DEEP
)
if is_instance_valid(res) and res.version >= CURRENT_VERSION:
archives_dict[id] = res
else:
printerr("SKIP INVALID ARCHIVE! file=", file)
# Load existing archives
_load_existing_archives(archive_dir_access)
return true
func _ensure_directory_exists(dir_path: String) -> void:
if not DirAccess.dir_exists_absolute(dir_path):
DirAccess.make_dir_recursive_absolute(dir_path)
print("Create directory:", dir_path)
func _load_existing_archives(dir_access: DirAccess) -> void:
var files = dir_access.get_files()
files.sort()
for file in files:
if not _is_valid_archive_filename(file):
continue
var archive_info = _parse_archive_filename(file)
if not archive_info:
continue
var id = archive_info.id
var note = archive_info.note
archives_notes_dict[id] = note
if not archives_dict.has(id):
var archive_resource = _load_archive_resource(user_archives_dir + file)
if archive_resource:
archives_dict[id] = archive_resource
func _is_valid_archive_filename(filename: String) -> bool:
return filename.begins_with(archive_prefix) and filename.ends_with(GlobalConfig.RES_FILE_FORMAT)
func _parse_archive_filename(filename: String) -> Dictionary:
# format: save012_xxxxx; save000
var basename = filename.get_basename()
var id_and_note = basename.substr(archive_prefix.length()).strip_escapes().split("_", true, 1)
var id_str = id_and_note[0]
# 非三位数的 id 会被忽略
if id_str.length() != ARCHIVE_ID_DIGITS:
return {}
var id = int(id_str)
# 读取范围是 0-99
if id < ARCHIVE_ID_MIN or id > ARCHIVE_ID_MAX:
return {}
var note_str = id_and_note[1] if id_and_note.size() >= 2 else (archive_prefix + id_str)
return {
"id": id,
"note": note_str
}
func _load_archive_resource(path: String) -> AssembledArchive:
var res = ResourceLoader.load(
path, "AssembledArchive", ResourceLoader.CACHE_MODE_REPLACE_DEEP
)
if is_instance_valid(res) and res.version >= CURRENT_VERSION:
return res
else:
printerr("SKIP INVALID ARCHIVE! path=", path)
return null
# id = -1 means create a new archive, otherwise create an archive with the given id
func create_and_use_new_archive(id := -1) -> void:
_check_dirs_and_archives()
var archive_path = _get_archive_path(id)
if id < 0:
# 如果 id 小于 0找到一个新的 id创建新存档
id = 0
# find a new id
archive_path = _get_archive_path(id)
while FileAccess.file_exists(archive_path):
id += 1
archive_path = _get_archive_path(id)
id = _find_next_available_id()
_create_and_save_new_archive_resoure(id)
else:
# 如果 id 大于等于 0创建指定 id 的存档
if FileAccess.file_exists(archive_path):
_create_and_save_new_archive_resoure(id, true)
else:
_create_and_save_new_archive_resoure(id)
var archive_path = get_archive_path(id)
var take_over_path = FileAccess.file_exists(archive_path)
_create_and_save_new_archive_resoure(id, take_over_path)
# this will auto trigger signal and load the new archive
GlobalConfigManager.config.current_selected_archive_id = id
func _create_and_save_new_archive_resoure(id, take_over_path = false) -> void:
var archive_path = _get_archive_path(id)
func _find_next_available_id() -> int:
var id = 0
var archive_path = get_archive_path(id)
while FileAccess.file_exists(archive_path) and id <= ARCHIVE_ID_MAX:
id += 1
archive_path = get_archive_path(id)
return id
func _create_and_save_new_archive_resoure(id: int, take_over_path := false) -> void:
var archive_path = get_archive_path(id)
archive = AssembledArchive.new() as Resource
archive.version = CURRENT_VERSION
if take_over_path:
@ -188,15 +246,9 @@ func _create_and_save_new_archive_resoure(id, take_over_path = false) -> void:
# 超过 999 个存档会出问题;不过这个游戏不会有这么多存档
func _get_archive_path(id: int) -> String:
var id_str := ""
if id < 10:
id_str = "00" + str(id)
elif id < 100:
id_str = "0" + str(id)
else:
id_str = str(id)
return archive_dir + archive_prefix + id_str + GlobalConfig.RES_FILE_FORMAT
func get_archive_path(id: int) -> String:
var id_str := str(id).pad_zeros(ARCHIVE_ID_DIGITS)
return user_archives_dir + archive_prefix + id_str + GlobalConfig.RES_FILE_FORMAT
func allow_resume(id := 1) -> bool:
@ -208,41 +260,54 @@ func save_all() -> void:
var config = GlobalConfigManager.config
if config:
ResourceSaver.save(config)
# player_global_position
var player = SceneManager.get_player() as MainPlayer
# 在此处保存 player 的位置信息
if archive and player:
archive.player_global_position_x = player.global_position.x
archive.player_direction = player.facing_direction
# save player state
_save_player_state()
# save archive
if archive:
ResourceSaver.save(archive)
# reset autosave timer
check_autosave_options()
func _save_player_state() -> void:
if not archive:
return
var player = SceneManager.get_player() as MainPlayer
if player:
archive.player_global_position_x = player.global_position.x
archive.player_direction = player.facing_direction
func load_config() -> void:
if GlobalConfigManager.config:
return
var path = user_root_dir + "config" + GlobalConfig.RES_FILE_FORMAT
var path = user_data_root_dir + "config" + GlobalConfig.RES_FILE_FORMAT
if FileAccess.file_exists(path):
var config = ResourceLoader.load(path)
if is_instance_valid(config) and config.version >= CURRENT_VERSION:
GlobalConfigManager.config = config
var loaded_config = ResourceLoader.load(path)
if is_instance_valid(loaded_config) and loaded_config.version >= CURRENT_VERSION:
GlobalConfigManager.config = loaded_config
else:
printerr("SKIP INVALID CONFIG!")
if GlobalConfigManager.config == null:
var config = GlobalConfig.new()
config.version = CURRENT_VERSION
GlobalConfigManager.config = config
ResourceSaver.save(config, path)
GlobalConfigManager.config.resource_path = path
if Engine.is_editor_hint():
return
# connect signals
GlobalConfigManager.config.current_selected_archive_id_changed.connect(_on_archive_id_changed)
GlobalConfigManager.config.auto_save_seconds_changed.connect(check_autosave_options)
GlobalConfigManager.config.auto_save_enabled_changed.connect(check_autosave_options)
if not GlobalConfigManager.config:
_create_default_config(path)
if not Engine.is_editor_hint():
_connect_config_signals()
func _create_default_config(path: String) -> void:
var config = GlobalConfig.new()
config.version = CURRENT_VERSION
config.resource_path = path
GlobalConfigManager.config = config
ResourceSaver.save(config, path)
func _connect_config_signals() -> void:
var config = GlobalConfigManager.config
config.current_selected_archive_id_changed.connect(_on_archive_id_changed)
config.auto_save_seconds_changed.connect(check_autosave_options)
config.auto_save_enabled_changed.connect(check_autosave_options)
func load_archive() -> void:
@ -250,19 +315,16 @@ func load_archive() -> void:
var selected_id = 0
if GlobalConfigManager.config:
selected_id = GlobalConfigManager.config.current_selected_archive_id
# if archive and selected_id == archive.archive_id:
# return
print("load_archive ", selected_id)
if not archives_dict.has(selected_id):
_handle_load_error(str(selected_id) + " 号存档", "查找")
return
archive = archives_dict[selected_id]
# emit signal
archive_loaded.emit()
check_autosave_options()
func _handle_load_error(target, action) -> void:
func _handle_load_error(target: String, action: String) -> void:
var msg = str(target) + " " + str(action) + " failed. Permission Error."
SceneManager.pop_notification(msg)
printerr(msg)
@ -281,9 +343,7 @@ func get_global_value(property: StringName, default = null) -> Variant:
printerr("Archive is null, cannot get global value")
return default
var val = archive.global_data_dict.get(property)
if val == null:
return default
return val
return default if val == null else val
func set_chapter_if_greater(c: int) -> void:
@ -291,7 +351,9 @@ func set_chapter_if_greater(c: int) -> void:
printerr("Archive is null, cannot set chapter")
return
# 1:序章2-5:一四章6:结尾
if c < 1 or c > 6:
const MIN_CHAPTER = 1
const MAX_CHAPTER = 6
if c < MIN_CHAPTER or c > MAX_CHAPTER:
printerr("[ArchiveManager] set_chapter_if_greater: invalid chapter value: " + str(c))
return
if EventManager.get_chapter_stage() >= c:
@ -302,14 +364,14 @@ func set_chapter_if_greater(c: int) -> void:
func unlock_memory(id: int) -> void:
if archive:
if archive.mem_display_dict.get(id):
print("memory already unlocked. id=", id)
return
archive.mem_display_dict[id] = true
SceneManager.pop_notification("ui_notify_mem_update")
else:
if not archive:
printerr("Archive is null, cannot unlock memory. id=", id)
return
if archive.mem_display_dict.get(id):
print("memory already unlocked. id=", id)
return
archive.mem_display_dict[id] = true
SceneManager.pop_notification("ui_notify_mem_update")
# 供运行时缓存跨场景数据

View File

@ -11,8 +11,8 @@ class_name AssembledArchive extends Resource
printerr("[AssembledArchive] current_scene is not valid: " + val)
return
# 尝试后台预先加载该场景
if GroundLoader.GROUND_SCENE_PATH_DICT.has(val):
var path = GroundLoader.GROUND_SCENE_PATH_DICT[val]
var path = GroundLoader.get_ground_scene_uid(val)
if path:
if GlobalConfig.DEBUG:
print("[AssembledArchive] current_scene: " + current_scene)
ResourceLoader.load_threaded_request(path, "PackedScene")

View File

@ -0,0 +1,43 @@
extends CanvasLayer
@onready var quit_debug_button: Button = %QuitDebugModeButton
func _ready() -> void:
AudioManager.process_mode = Node.PROCESS_MODE_PAUSABLE
SceneManager.toggle_pause_counter(true, "debugging")
layer = GlobalConfig.CANVAS_LAYER_SETTINGS
quit_debug_button.pressed.connect(_on_quit_debug_button_pressed)
# 恢复上次打开的 tab
var tab = ArchiveManager.get_global_value(&"debug_panel_tab", 0)
%TabContainer.current_tab = tab
func _input(event: InputEvent) -> void:
if (
event.is_action_pressed("debugging")
or event.is_action_pressed("cancel")
or event.is_action_pressed("escape")
):
get_viewport().set_input_as_handled()
quit()
func _on_quit_debug_button_pressed() -> void:
# 不写入配置
GlobalConfig.DEBUG = false
quit()
func _exit_tree() -> void:
AudioManager.process_mode = Node.PROCESS_MODE_ALWAYS
SceneManager.toggle_pause_counter(false, "debugging")
func quit() -> void:
queue_free()
func _on_tab_container_tab_changed(tab: int) -> void:
ArchiveManager.set_global_entry(&"debug_panel_tab", tab)

View File

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

View File

@ -0,0 +1,60 @@
[gd_scene load_steps=5 format=3 uid="uid://d4jeeteyq8kk3"]
[ext_resource type="PackedScene" uid="uid://bo4f3sdubdp61" path="res://manager/archive_manager/savings_panel.tscn" id="1_fbybo"]
[ext_resource type="Script" uid="uid://d3aitlnh5hrfc" path="res://manager/archive_manager/debug_panel.gd" id="1_k0pa6"]
[ext_resource type="PackedScene" uid="uid://b6ogrp5ec5nr3" path="res://manager/archive_manager/sfx_config_panel.tscn" id="2_t7mby"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k0pa6"]
bg_color = Color(0, 0, 0, 1)
[node name="DebugLayer" type="CanvasLayer"]
process_mode = 3
scale = Vector2(0.5, 0.5)
transform = Transform2D(0.5, 0, 0, 0.5, 0, 0)
script = ExtResource("1_k0pa6")
[node name="PanelContainer" type="PanelContainer" parent="."]
offset_left = 68.0
offset_top = 28.0
offset_right = 1068.0
offset_bottom = 606.0
theme_override_styles/panel = SubResource("StyleBoxFlat_k0pa6")
[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="PanelContainer/VBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="PanelContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 18
text = "Debug 面板"
horizontal_alignment = 1
[node name="QuitDebugModeButton" type="Button" parent="PanelContainer/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "关闭 Debug 模式"
[node name="TabContainer" type="TabContainer" parent="PanelContainer/VBoxContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(1000, 0)
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
mouse_filter = 0
current_tab = 0
[node name="存档控制" parent="PanelContainer/VBoxContainer/TabContainer" instance=ExtResource("1_fbybo")]
unique_name_in_owner = true
layout_mode = 2
[node name="音效控制" parent="PanelContainer/VBoxContainer/TabContainer" instance=ExtResource("2_t7mby")]
unique_name_in_owner = true
visible = false
layout_mode = 2
metadata/_tab_index = 1
[connection signal="tab_changed" from="PanelContainer/VBoxContainer/TabContainer" to="." method="_on_tab_container_tab_changed"]

View File

@ -0,0 +1,282 @@
extends Control
# UI References
@onready var archive_grid: GridContainer = %ArchiveGrid
@onready var scroll_container: ScrollContainer = %ScrollContainer
@onready var current_archive_label: Label = %CurrentArchiveLabel
@onready var name_input: LineEdit = %NameInput
@onready var save_button: Button = %SaveButton
@onready var refresh_button: Button = %RefreshButton
# Constants
const MAX_MANUAL_ARCHIVES = 99
const GRID_COLUMNS = 4
# Variables
var manual_archives: Dictionary = {} # {id: {name: String, path: String, time: String}}
var next_available_id: int = 2 # Start from 2 since 1 is reserved for main archive
func _ready() -> void:
# Setup UI
archive_grid.columns = GRID_COLUMNS
# Connect signals
save_button.pressed.connect(_on_save_button_pressed)
refresh_button.pressed.connect(_refresh_archive_list)
name_input.text_submitted.connect(_on_name_submitted)
# Set default name
_update_default_name()
# Initial load
_refresh_archive_list()
_update_current_archive_label()
func _update_current_archive_label() -> void:
current_archive_label.text = "当前使用存档1号存档主存档"
func _update_default_name() -> void:
name_input.placeholder_text = "输入存档名称"
var chapter_name = EventManager.get_chapter_stage()
if chapter_name == 1:
chapter_name = "序章"
elif chapter_name <= 5:
chapter_name = "%s" % (chapter_name - 1)
elif chapter_name == 6:
chapter_name = "结尾"
else:
chapter_name = "未知"
var scene_name = SceneManager.get_current_scene_name()
var saving_name = chapter_name + "_" + scene_name
name_input.text = _get_unique_archive_name(saving_name)
func _on_name_submitted(_text: String) -> void:
_on_save_button_pressed()
func _on_save_button_pressed() -> void:
# Check limit
if manual_archives.size() >= MAX_MANUAL_ARCHIVES:
_show_notification("已达到最大存档数量限制99个")
return
# Get and validate name
var archive_name = name_input.text.strip_edges()
if archive_name.is_empty():
archive_name = "未命名存档_" + Time.get_datetime_string_from_system()
# Save current progress
ArchiveManager.save_all()
# Get unique name
archive_name = _get_unique_archive_name(archive_name)
# Copy current archive
_copy_current_archive(archive_name)
# Reset input field
_update_default_name()
name_input.select_all()
func _get_unique_archive_name(base_name: String) -> String:
var final_name = base_name
var counter = 1
# Check if name already exists
var name_exists = true
while name_exists:
name_exists = false
for data in manual_archives.values():
if data.name == final_name:
name_exists = true
final_name = base_name + "_" + str(counter)
counter += 1
break
return final_name
func _copy_current_archive(archive_name: String) -> void:
# Get current archive path
var current_archive = ArchiveManager.archive
if not current_archive:
_show_notification("当前没有活动存档")
return
# Find next available ID
while manual_archives.has(next_available_id) and next_available_id <= MAX_MANUAL_ARCHIVES + 1:
next_available_id += 1
if next_available_id > MAX_MANUAL_ARCHIVES + 1:
_show_notification("无法创建更多存档")
return
# Create new archive path
var new_archive_path = (
ArchiveManager.user_archives_dir
+ "manual_"
+ str(next_available_id)
+ "_"
+ archive_name.validate_filename()
+ GlobalConfig.RES_FILE_FORMAT
)
# Copy the archive file
var source_path = current_archive.resource_path
var dir = DirAccess.open(ArchiveManager.user_archives_dir)
if dir:
var error = dir.copy(source_path, new_archive_path)
print("Copying archive from: ", source_path, " to: ", new_archive_path)
if error == OK:
# Save manual archive info
manual_archives[next_available_id] = {
"name": archive_name,
"path": new_archive_path,
"time": Time.get_datetime_string_from_system(false, true)
}
# Save manual archives data
_save_manual_archives_data()
# Refresh UI
_refresh_archive_list()
_show_notification("存档已保存:" + archive_name)
next_available_id += 1
else:
_show_notification("存档复制失败:" + error_string(error))
else:
_show_notification("无法访问存档目录")
func _refresh_archive_list() -> void:
# refresh debugging name
_update_default_name()
# Clear existing items
for child in archive_grid.get_children():
child.queue_free()
# Load manual archives data
_load_manual_archives_data()
# Create UI items for each manual archive
var sorted_ids = manual_archives.keys()
sorted_ids.sort()
for id in sorted_ids:
var data = manual_archives[id]
_create_archive_item(id, data)
func _create_archive_item(id: int, data: Dictionary) -> void:
# Create container for the archive item
var item_container = PanelContainer.new()
item_container.custom_minimum_size = Vector2(200, 60)
var vbox = VBoxContainer.new()
vbox.add_theme_constant_override("separation", 4)
item_container.add_child(vbox)
# Archive name (editable)
var name_edit = LineEdit.new()
name_edit.text = data.name
name_edit.tooltip_text = "创建时间:" + data.time
vbox.add_child(name_edit)
# Time label
var time_label = Label.new()
time_label.text = data.time
time_label.add_theme_font_size_override("font_size", 12)
time_label.modulate.a = 0.7
vbox.add_child(time_label)
# Action buttons container
var button_container = HBoxContainer.new()
button_container.add_theme_constant_override("separation", 4)
# Load button
var load_btn = Button.new()
load_btn.text = "加载"
load_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
button_container.add_child(load_btn)
# Delete button
var delete_btn = Button.new()
delete_btn.text = "删除"
delete_btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
button_container.add_child(delete_btn)
vbox.add_child(button_container)
# Connect signals
name_edit.text_changed.connect(func(new_text): _on_archive_renamed(id, new_text))
load_btn.pressed.connect(func(): _load_manual_archive(id, data))
delete_btn.pressed.connect(func(): _delete_manual_archive(id))
archive_grid.add_child(item_container)
func _on_archive_renamed(id: int, new_name: String) -> void:
if new_name.strip_edges().is_empty():
return
# Update name in data
manual_archives[id].name = new_name.strip_edges()
# Save changes
_save_manual_archives_data()
func _load_manual_archive(_id: int, data: Dictionary) -> void:
# Save current state first
ArchiveManager.save_all()
# Copy manual archive to archive 1
var dir = DirAccess.open(ArchiveManager.user_archives_dir)
if dir:
var saving_archive = load(data.path)
if saving_archive:
var target_path = ArchiveManager.get_archive_path(1)
print("Loading archive from: ", data.path, " to: ", target_path)
GlobalConfigManager.config.current_selected_archive_id = 1
if ArchiveManager.archive:
saving_archive.take_over_path(target_path)
else:
saving_archive.resource_path = target_path
ArchiveManager.archives_dict[1] = saving_archive
ArchiveManager.archive = saving_archive
print("Loading archive from: ", data.path, " to: ", target_path)
# Reload current scene
SceneManager.enter_main_scene()
else:
_show_notification("加载存档失败:" + data.name)
func _delete_manual_archive(id: int) -> void:
var data = manual_archives[id]
# Delete file
var dir = DirAccess.open(ArchiveManager.user_archives_dir)
if dir:
dir.remove(data.path)
# Remove from dictionary
manual_archives.erase(id)
# Save changes
_save_manual_archives_data()
# Refresh UI
_refresh_archive_list()
_show_notification("已删除存档:" + data.name)
func _save_manual_archives_data() -> void:
var save_path = ArchiveManager.user_data_root_dir + "test_manual_archives.dat"
var file = FileAccess.open(save_path, FileAccess.WRITE)
if file:
file.store_var(manual_archives)
file.close()
func _load_manual_archives_data() -> void:
var save_path = ArchiveManager.user_data_root_dir + "test_manual_archives.dat"
if FileAccess.file_exists(save_path):
var file = FileAccess.open(save_path, FileAccess.READ)
if file:
manual_archives = file.get_var()
file.close()
# Find next available ID
next_available_id = 2
for id in manual_archives.keys():
if id >= next_available_id:
next_available_id = id + 1
func _show_notification(message: String) -> void:
SceneManager.pop_notification(message)

View File

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

View File

@ -0,0 +1,83 @@
[gd_scene load_steps=2 format=3 uid="uid://bo4f3sdubdp61"]
[ext_resource type="Script" uid="uid://cirf1nw72l315" path="res://manager/archive_manager/savings_panel.gd" id="1_oo2ip"]
[node name="存档控制" type="PanelContainer"]
offset_right = 405.0
offset_bottom = 531.0
mouse_filter = 1
script = ExtResource("1_oo2ip")
metadata/_tab_index = 0
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="Title" type="Label" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 24
text = "存档测试管理器(测试专用)"
horizontal_alignment = 1
[node name="存档管理" type="VBoxContainer" parent="VBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="CurrentArchiveLabel" type="Label" parent="VBoxContainer/存档管理"]
unique_name_in_owner = true
layout_mode = 2
theme_override_colors/font_color = Color(0.7, 0.9, 0.7, 1)
text = "当前使用存档1号存档主存档"
[node name="HSeparator" type="HSeparator" parent="VBoxContainer/存档管理"]
layout_mode = 2
[node name="SaveContainer" type="HBoxContainer" parent="VBoxContainer/存档管理"]
layout_mode = 2
theme_override_constants/separation = 10
[node name="NameInput" type="LineEdit" parent="VBoxContainer/存档管理/SaveContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "输入存档名称"
[node name="SaveButton" type="Button" parent="VBoxContainer/存档管理/SaveContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(120, 0)
layout_mode = 2
text = "保存当前进度"
[node name="RefreshButton" type="Button" parent="VBoxContainer/存档管理/SaveContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
text = "刷新"
[node name="HSeparator2" type="HSeparator" parent="VBoxContainer/存档管理"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/存档管理"]
layout_mode = 2
theme_override_colors/font_color = Color(0.8, 0.8, 0.8, 1)
text = "手动存档列表:"
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer/存档管理"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 380)
layout_mode = 2
size_flags_vertical = 3
horizontal_scroll_mode = 0
vertical_scroll_mode = 2
[node name="ArchiveGrid" type="GridContainer" parent="VBoxContainer/存档管理/ScrollContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/h_separation = 10
theme_override_constants/v_separation = 10
columns = 4

View File

@ -0,0 +1,526 @@
class_name SfxConfigPanel
extends Control
# signal config_changed(node_name: String, property: String, value)
# 面板相关
@onready var scroll_container: ScrollContainer = %ScrollContainer
@onready var vbox_container: VBoxContainer = %VBoxContainer
@onready var reset_button: Button = %ResetButton
@onready var import_button: Button = %ImportButton
@onready var export_button: Button = %ExportButton
# 音频预览播放器
@onready var preview_player: AudioStreamPlayer = %AudioStreamPlayer
# 文件对话框
@onready var file_dialog: FileDialog = %FileDialog
# 数据存储
var sfx_nodes: Array[Node] = []
var config_data: Dictionary = {}
var current_scene_name: String = ""
func _ready():
file_dialog.file_selected.connect(_on_file_selected)
reset_button.pressed.connect(_on_reset_pressed)
import_button.pressed.connect(_on_import_pressed)
export_button.pressed.connect(_on_export_pressed)
# 初始加载
refresh_sfx_list(SceneManager.get_ground())
# 1. 检测 current_scene 下 Sfx 节点
func refresh_sfx_list(ground: Ground2D, headless := false):
sfx_nodes.clear()
current_scene_name = GroundLoader.get_ground_scene_readable_name(ground.scene_name)
find_sfx_nodes(ground)
load_config()
if not headless:
refresh_ui()
var ignore_class_list = ["Portal2D", "Interactable2D", "Note2D", "Inspectable2D", "Pickable2D", "Npc2D", "Ambush2D"]
func find_sfx_nodes(node: Node):
var script = node.get_script()
if script:
if script.get_global_name() in ignore_class_list:
return
# if node is Sfx or node is VibeSfx:
if node is Sfx:
if node.has_method("play") and node.get("volume_db") != null and node.get("stream") != null:
sfx_nodes.append(node)
# 递归检查子节点
for child in node.get_children():
find_sfx_nodes(child)
func clear_ui():
for child in vbox_container.get_children():
child.queue_free()
# 2. 在面板生成对应条目,双向绑定
func create_ui_items():
for sfx_node in sfx_nodes:
create_sfx_item(sfx_node)
func refresh_ui():
clear_ui()
create_ui_items()
func _build_sfx_name(sfx: Sfx) -> String:
var parent_name = ""
var parent = sfx.get_parent()
while not parent_name and parent:
if parent and parent.get_script() and parent.get_script().get_global_name() in ignore_class_list:
parent_name = parent.name
else:
parent = parent.get_parent()
if parent_name:
return parent_name + " > " + sfx.name
else:
return sfx.name
func create_sfx_item(sfx_node: Node):
var item_container = VBoxContainer.new()
item_container.add_theme_constant_override("separation", 8)
vbox_container.add_child(item_container)
# 节点名称标签
var name_label = Label.new()
name_label.text = _build_sfx_name(sfx_node)
name_label.add_theme_font_size_override("font_size", 14)
item_container.add_child(name_label)
var controls_hbox = HBoxContainer.new()
controls_hbox.add_theme_constant_override("separation", 10)
item_container.add_child(controls_hbox)
# 音量滑块
var volume_vbox = VBoxContainer.new()
controls_hbox.add_child(volume_vbox)
var volume_title_hbox = HBoxContainer.new()
volume_vbox.add_child(volume_title_hbox)
var volume_label = Label.new()
volume_label.text = "音量 (dB)"
volume_title_hbox.add_child(volume_label)
# 重置音量按钮
var reset_volumn_button = Button.new()
reset_volumn_button.text = "重置音量"
reset_volumn_button.pressed.connect(reset_volumn.bind(sfx_node))
volume_title_hbox.add_child(reset_volumn_button)
var volume_slider_hbox = HBoxContainer.new()
volume_vbox.add_child(volume_slider_hbox)
var volume_slider = HSlider.new()
volume_slider.min_value = -60.0
volume_slider.max_value = 20.0
volume_slider.step = 0.1
volume_slider.value = sfx_node.volume_db
volume_slider.custom_minimum_size.x = 200
volume_slider_hbox.add_child(volume_slider)
var volume_value_label = Label.new()
volume_value_label.text = str(snapped(sfx_node.volume_db, 0.1))
volume_value_label.custom_minimum_size.x = 50
volume_slider_hbox.add_child(volume_value_label)
# 双向绑定音量
volume_slider.value_changed.connect(func(value):
sfx_node.volume_db = value
volume_value_label.text = str(snapped(value, 0.1))
save_node_config(sfx_node.get_path(), "volume_db", value)
)
# Stream 信息和控制
var stream_vbox = VBoxContainer.new()
controls_hbox.add_child(stream_vbox)
var stream_label = Label.new()
stream_label.text = "音频文件"
stream_vbox.add_child(stream_label)
var stream_hbox = HBoxContainer.new()
stream_vbox.add_child(stream_hbox)
var stream_name_label = Label.new()
stream_name_label.custom_minimum_size.x = 150
_set_stream_name_label(sfx_node, stream_name_label)
stream_hbox.add_child(stream_name_label)
# 预览播放按钮
var preview_button = Button.new()
preview_button.text = ""
preview_button.custom_minimum_size = Vector2(30, 30)
preview_button.pressed.connect(preview_audio.bind(sfx_node))
stream_hbox.add_child(preview_button)
# 恢复默认按钮
if sfx_node.stream_was_replaced():
var reset_btn = Button.new()
reset_btn.text = "恢复默认音频"
reset_btn.custom_minimum_size = Vector2(30, 30)
reset_btn.pressed.connect(reset_audio.bind(sfx_node))
stream_hbox.add_child(reset_btn)
# 3. 上传文件按钮
var upload_button = Button.new()
upload_button.text = "上传音频"
upload_button.pressed.connect(open_file_dialog.bind(sfx_node, stream_name_label))
stream_hbox.add_child(upload_button)
# 分割线
var separator = HSeparator.new()
item_container.add_child(separator)
# 存储节点引用用于后续操作
item_container.set_meta("sfx_node", sfx_node)
item_container.set_meta("stream_label", stream_name_label)
func _set_stream_name_label(sfx_node:Sfx, stream_name_label:Label) -> void:
if sfx_node.stream:
stream_name_label.text = sfx_node.stream.resource_path.get_file()
else:
stream_name_label.text = "无音频文件"
func reset_volumn(sfx_node: Sfx):
sfx_node.volume_db = sfx_node.default_db
save_config()
refresh_ui()
func preview_audio(sfx_node: Sfx):
if sfx_node.stream:
preview_player.stream = sfx_node.stream
preview_player.volume_db = sfx_node.volume_db
preview_player.play()
func reset_audio(sfx_node: Sfx):
sfx_node.reset_original_stream()
config_data.erase(sfx_node.get_path())
save_config()
refresh_ui()
var current_upload_node: Node
var current_stream_label: Label
func open_file_dialog(sfx_node: Node, stream_label: Label):
current_upload_node = sfx_node
current_stream_label = stream_label
file_dialog.popup_centered()
func _on_file_selected(path: String):
if not current_upload_node:
return
copy_and_load_audio_file(path, current_upload_node, current_stream_label)
# 3. 复制文件并加载
func copy_and_load_audio_file(source_path: String, sfx_node: Node, stream_label: Label):
var file_name = source_path.get_file()
var audio_dir = "user://audio/" + current_scene_name + "/"
var target_path = audio_dir + file_name
# 确保目录存在
DirAccess.open("user://").make_dir_recursive("audio/" + current_scene_name)
# 复制文件
var source_file = FileAccess.open(source_path, FileAccess.READ)
if not source_file:
push_error("无法读取源文件: " + source_path)
return
var target_file = FileAccess.open(target_path, FileAccess.WRITE)
if not target_file:
push_error("无法创建目标文件: " + target_path)
source_file.close()
return
target_file.store_buffer(source_file.get_buffer(source_file.get_length()))
source_file.close()
target_file.close()
# 加载新的音频流
var new_stream = AudioLoader.new().loadfile(target_path)
if new_stream:
sfx_node.replace_stream(new_stream)
stream_label.text = file_name
save_node_config(sfx_node.get_path(), "stream", target_path)
print("音频文件已更新: ", sfx_node.name, " -> ", file_name)
else:
push_error("无法加载音频文件: " + target_path)
refresh_ui()
# 4. 配置保存与加载
func save_node_config(node_path: String, property: String, value):
if not config_data.has(node_path):
config_data[node_path] = {}
config_data[node_path][property] = value
save_config()
func save_config():
var config_dir = "user://audio/" + current_scene_name + "/"
DirAccess.open("user://").make_dir_recursive("audio/" + current_scene_name)
var config_path = config_dir + "audio_config.dat"
var file = FileAccess.open(config_path, FileAccess.WRITE)
if file:
file.store_string(var_to_str(config_data))
file.close()
func load_config():
var config_path = "user://audio/" + current_scene_name + "/audio_config.dat"
var file = FileAccess.open(config_path, FileAccess.READ)
if file:
var config_str = file.get_as_text()
file.close()
config_data = str_to_var(config_str)
if not config_data:
config_data = {}
apply_config()
else:
config_data = {}
func apply_config():
for sfx_node in sfx_nodes:
var sfx_path = sfx_node.get_path()
if config_data.has(sfx_path):
var node_config = config_data[sfx_path]
if node_config.has("volume_db"):
sfx_node.volume_db = node_config["volume_db"]
if node_config.has("stream"):
var stream_path = node_config["stream"]
if FileAccess.file_exists(stream_path):
var new_stream = AudioLoader.new().loadfile(stream_path)
# var new_stream = load(stream_path)
if new_stream:
sfx_node.replace_stream(new_stream)
func _on_reset_pressed() -> void:
for sfx_node in sfx_nodes:
sfx_node.reset_original_stream()
var config_file_path = "user://audio/" + current_scene_name + "/" + "/audio_config.dat"
if FileAccess.file_exists(config_file_path):
DirAccess.remove_absolute(config_file_path)
SceneManager.enter_main_scene()
# 5. Import/Export 功能
func _on_import_pressed():
# 打开文件对话框选择导入文件夹或配置文件
var import_dialog = FileDialog.new()
import_dialog.size = Vector2(1400, 800)
import_dialog.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
import_dialog.content_scale_factor = 3
import_dialog.file_mode = FileDialog.FILE_MODE_OPEN_DIR
import_dialog.access = FileDialog.ACCESS_FILESYSTEM
import_dialog.dir_selected.connect(_on_import_folder_selected)
add_child(import_dialog)
import_dialog.popup_centered()
func _on_import_folder_selected(folder_path: String):
var config_file_path = folder_path + "/audio_config.dat"
if not FileAccess.file_exists(config_file_path):
push_error("所选文件夹中没有找到 audio_config.dat 配置文件")
return
# 确保目标目录存在
var target_dir = "user://audio/" + current_scene_name + "/"
DirAccess.open("user://").make_dir_recursive("audio/" + current_scene_name)
# 复制配置文件
copy_file(config_file_path, target_dir + "audio_config.dat")
# 复制所有音频文件
var dir = DirAccess.open(folder_path)
if dir:
dir.list_dir_begin()
var file_name = dir.get_next()
while file_name != "":
if not dir.current_is_dir():
var extension = file_name.get_extension().to_lower()
if extension in ["wav", "ogg", "mp3"]:
var source_path = folder_path + "/" + file_name
var target_path = target_dir + file_name
copy_file(source_path, target_path)
file_name = dir.get_next()
dir.list_dir_end()
# 重新加载配置
refresh_sfx_list(SceneManager.get_ground())
print("配置和音频文件已导入")
func copy_file(source_path: String, target_path: String) -> bool:
var source_file = FileAccess.open(source_path, FileAccess.READ)
if not source_file:
push_error("无法读取文件: " + source_path)
return false
var target_file = FileAccess.open(target_path, FileAccess.WRITE)
if not target_file:
push_error("无法创建文件: " + target_path)
source_file.close()
return false
target_file.store_buffer(source_file.get_buffer(source_file.get_length()))
source_file.close()
target_file.close()
return true
func _on_export_pressed():
var desktop_path = OS.get_system_dir(OS.SYSTEM_DIR_DESKTOP)
var export_folder = desktop_path + "/" + current_scene_name + "_audio_export"
var config_path = "user://audio/" + current_scene_name + "/audio_config.dat"
# 创建导出文件夹
var dir = DirAccess.open(desktop_path)
if not dir:
push_error("无法访问桌面目录")
return
# 如果文件夹已存在,先删除
if dir.dir_exists(export_folder):
remove_directory_recursive(export_folder)
dir.make_dir(current_scene_name + "_audio_export")
var exported_files = []
var failed_files = []
# 1. 导出配置文件
if FileAccess.file_exists(config_path):
var source_file = FileAccess.open(config_path, FileAccess.READ)
var target_file = FileAccess.open(export_folder + "/audio_config.dat", FileAccess.WRITE)
if source_file and target_file:
target_file.store_buffer(source_file.get_buffer(source_file.get_length()))
source_file.close()
target_file.close()
exported_files.append("audio_config.dat")
else:
failed_files.append("audio_config.dat")
push_error("配置文件导出失败")
else:
push_error("没有找到配置文件")
return
# 2. 导出所有关联的音频文件
var audio_dir = "user://audio/" + current_scene_name + "/"
# 从配置数据中收集所有音频文件路径
var audio_files_to_export = []
# 收集配置文件中引用的音频文件
for node_name in config_data.keys():
var node_config = config_data[node_name]
if node_config.has("stream"):
var stream_path = node_config["stream"]
if stream_path.begins_with("user://audio/" + current_scene_name + "/"):
audio_files_to_export.append(stream_path)
# 收集当前目录下的所有音频文件
var audio_dir_access = DirAccess.open(audio_dir)
if audio_dir_access:
audio_dir_access.list_dir_begin()
var file_name = audio_dir_access.get_next()
while file_name != "":
if not audio_dir_access.current_is_dir():
var full_path = audio_dir + file_name
var extension = file_name.get_extension().to_lower()
# 检查是否是音频文件
if extension in ["wav", "ogg", "mp3"]:
if not full_path in audio_files_to_export:
audio_files_to_export.append(full_path)
file_name = audio_dir_access.get_next()
audio_dir_access.list_dir_end()
# 导出音频文件
for audio_path in audio_files_to_export:
if FileAccess.file_exists(audio_path):
var file_name = audio_path.get_file()
var source_file = FileAccess.open(audio_path, FileAccess.READ)
var target_file = FileAccess.open(export_folder + "/" + file_name, FileAccess.WRITE)
if source_file and target_file:
target_file.store_buffer(source_file.get_buffer(source_file.get_length()))
source_file.close()
target_file.close()
exported_files.append(file_name)
else:
failed_files.append(file_name)
if source_file:
source_file.close()
if target_file:
target_file.close()
else:
failed_files.append(audio_path.get_file() + " (文件不存在)")
# 3. 创建导出说明文件
create_export_readme(export_folder, exported_files, failed_files)
# 显示导出结果
var message = "导出完成!\n导出位置: " + export_folder + "\n"
message += "成功导出 " + str(exported_files.size()) + " 个文件"
if failed_files.size() > 0:
message += "\n失败 " + str(failed_files.size()) + " 个文件: " + str(failed_files)
print(message)
# 可选:打开导出文件夹
OS.shell_open(export_folder)
func create_export_readme(export_folder: String, exported_files: Array, failed_files: Array):
var readme_path = export_folder + "/README.txt"
var readme_file = FileAccess.open(readme_path, FileAccess.WRITE)
if readme_file:
var content = "音效配置导出说明\n"
content += "===================\n\n"
content += "场景名称: " + current_scene_name + "\n"
content += "导出时间: " + Time.get_datetime_string_from_system() + "\n\n"
content += "导出文件清单:\n"
content += "-----------------\n"
for file_name in exported_files:
content += "" + file_name + "\n"
if failed_files.size() > 0:
content += "\n失败文件:\n"
content += "-----------------\n"
for file_name in failed_files:
content += "" + file_name + "\n"
content += "\n使用说明:\n"
content += "-----------------\n"
content += "1. audio_config.dat 是配置文件,包含音量和文件关联信息\n"
content += "2. 其他 .wav/.ogg/.mp3 文件是对应的音频资源\n"
content += "3. 导入时请将所有文件放在同一目录下\n"
readme_file.store_string(content)
readme_file.close()
func remove_directory_recursive(path: String):
var dir = DirAccess.open(path)
if dir:
dir.list_dir_begin()
var file_name = dir.get_next()
while file_name != "":
var full_path = path + "/" + file_name
if dir.current_is_dir():
remove_directory_recursive(full_path)
else:
dir.remove(file_name)
file_name = dir.get_next()
dir.list_dir_end()
dir.remove(path)

View File

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

View File

@ -0,0 +1,68 @@
[gd_scene load_steps=2 format=3 uid="uid://b6ogrp5ec5nr3"]
[ext_resource type="Script" uid="uid://dtwvx0vs0uoun" path="res://manager/archive_manager/sfx_config_panel.gd" id="1_386ty"]
[node name="SfxConfigPanel" type="PanelContainer"]
custom_minimum_size = Vector2(500, 0)
offset_right = 508.0
offset_bottom = 335.0
mouse_filter = 1
script = ExtResource("1_386ty")
[node name="MainVBox" type="VBoxContainer" parent="."]
layout_mode = 2
[node name="HeaderHBox" type="HBoxContainer" parent="MainVBox"]
layout_mode = 2
[node name="Label" type="Label" parent="MainVBox/HeaderHBox"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 18
text = "音效配置面板"
horizontal_alignment = 1
[node name="ResetButton" type="Button" parent="MainVBox/HeaderHBox"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 8
text = "重置所有配置"
[node name="ImportButton" type="Button" parent="MainVBox/HeaderHBox"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 8
text = "导入配置"
[node name="ExportButton" type="Button" parent="MainVBox/HeaderHBox"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 8
text = "导出配置"
[node name="ScrollContainer" type="ScrollContainer" parent="MainVBox"]
unique_name_in_owner = true
custom_minimum_size = Vector2(500, 400)
layout_mode = 2
size_flags_vertical = 0
horizontal_scroll_mode = 0
vertical_scroll_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="MainVBox/ScrollContainer"]
unique_name_in_owner = true
layout_mode = 2
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
unique_name_in_owner = true
[node name="FileDialog" type="FileDialog" parent="."]
unique_name_in_owner = true
title = "Open a File"
initial_position = 1
size = Vector2i(1500, 1000)
content_scale_mode = 1
content_scale_factor = 3.0
ok_button_text = "Open"
file_mode = 0
access = 2
filters = PackedStringArray("*.wav", "*.ogg", "*.mp3")

Some files were not shown because too many files have changed in this diff Show More