event_manager formula_dict 解析与运行功能

This commit is contained in:
cakipaul 2025-07-15 15:13:14 +08:00
parent 9ca51cde97
commit 28b067c4f6
5 changed files with 173 additions and 2 deletions

View File

@ -181,6 +181,7 @@ current_scene 是通过 GroundLoader 加载的,在 ground loader 加载 ground
- 影响 SignSnapper 的等待时长(如 Boss 战时加快节奏)
- 转场 process 机制优化:暂停 & AnimationPlayer 保持运行
- EventManager 控制事件,使用 Event2D 控制绑定关系
- formula_dict: 前置事件推动后续。示例(条件数>=1: `xx=2 & yy=1 & xy=3 -> zz=1`
- 比 Event2D 更轻量灵活的 EventBinder内有 updater 与 trigger 两种绑定
- updater 由 event 驱动更新父节点状态
- trigger 由父节点 signal 驱动更新 event

View File

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

View File

@ -0,0 +1,17 @@
[remap]
importer="csv_translation"
type="Translation"
uid="uid://biw8l6b4d3v3j"
[deps]
files=["res://asset/dialogue/event_stage.zh_CN.translation"]
source_file="res://asset/dialogue/event_stage.csv"
dest_files=["res://asset/dialogue/event_stage.zh_CN.translation"]
[params]
compress=true
delimiter=0

View File

@ -13,8 +13,10 @@ c02_ball_game_stage: 0:初始化 1:寻找弹珠_老虎钳可以换弹珠 2:给
c03_s01_meat_put: 0:初始化 1:已放肉
c03_invite_xchan_supper: 0:初始化 1:已偷听_需邀请 2:完成邀请
c03_laizi_braid: 0:初始化 1:已使用剪刀 2:已剪下
c03_f2_madman_runaway: 0:初始化 1:跑开_纸人挡路 2:消除纸人
c03_invite_xchan_supper=2 & c03_laizi_braid=2 -> c03_f2_madman_runaway=2
c03_before_mahjong_game: 0:初始化 1:粘舌头和刀把 2:给药 4:准备好进入游戏
c03_mahjong_game: 0::麻将理牌 1:麻将出千 2:麻将结束 3:演出结束
c03_mahjong_game: 0:麻将理牌 1:麻将出千 2:麻将结束 3:演出结束
=> END
~ EventStage_c04

View File

@ -5,12 +5,142 @@ extends Node
## 其他系统如UI、角色状态机等可以监听此信号来做出反应。
signal stage_updated(event_name: StringName, stage: int)
# 存储公式的字典。键是事件名称StringName值是当该事件更新时需要被评估的公式Callable数组。
# {event_name: [formula_callable_1, formula_callable_2, ...]}
var formula_dict: Dictionary[StringName, Array] = {}
# 节点初始化时,异步加载并解析事件规则。
func _ready() -> void:
# 异步加载,避免阻塞主线程。
# 使用 await a, b 语法等待协程完成。
await _load_and_parse_formulas()
stage_updated.connect(_on_stage_updated_for_handnote)
# 将加载和解析逻辑封装到一个独立的异步函数中,使 _ready 更清晰。
func _load_and_parse_formulas() -> void:
var event_stage_resource = load("uid://dohpsb4jttuv1") as DialogueResource
if not event_stage_resource:
printerr("[EventManager] Failed to load event stage resource (uid://dohpsb4jttuv1).")
return
print("[EventManager] Refreshing event formulas...")
for title in event_stage_resource.get_titles():
# 使用 await 等待异步函数 get_lines 完成
var lines = await Util.get_lines(event_stage_resource, title)
for line in lines:
var line_text: String = line.text.strip_edges()
# 跳过注释行和不包含"->"的行
if line_text.begins_with("#") or not "->" in line_text:
continue
_build_and_add_formula(line_text)
print("[EventManager] Formula refreshing completed.")
# 解析单行规则,构建公式并添加到字典中。
# 示例: "c03_invite_xchan_supper=2 & c03_laizi_braid=2 -> c03_f2_madman_runaway=2"
func _build_and_add_formula(line_text: String) -> void:
var parts: PackedStringArray = line_text.split("->")
if parts.size() != 2:
printerr("[EventManager] Invalid formula line (must contain one '->'): %s" % line_text)
return
var condition_events: Array[StringName] = []
var condition_callable := _build_condition(parts[0], condition_events)
var execution_callable := _build_execution(parts[1])
# 关键的健壮性检查:确保条件和执行都成功构建
if not condition_callable.is_valid() or not execution_callable.is_valid():
printerr("[EventManager] Failed to build formula due to invalid parts from line: %s" % line_text)
return
var formula := func():
if condition_callable.call():
if Engine.is_editor_hint() or GlobalConfig.DEBUG:
print("[EventManager] Formula condition met, executing: ", line_text)
execution_callable.call()
if Engine.is_editor_hint() or GlobalConfig.DEBUG:
print("[EventManager] Built formula from: ", line_text)
for event_name in condition_events:
if not formula_dict.has(event_name):
formula_dict[event_name] = []
formula_dict[event_name].append(formula)
if Engine.is_editor_hint() or GlobalConfig.DEBUG:
print("[EventManager] Added formula for event: ", event_name)
# 构建条件 Callable。重构为迭代而不是递归以提高清晰度和健壮性。
# 示例: "c03_invite_xchan_supper=2 & c03_laizi_braid=2"
func _build_condition(text: String, r_condition_events: Array[StringName]) -> Callable:
var condition_text := text.strip_edges()
if condition_text.is_empty():
return func(): return true # 空条件始终为真
var sub_conditions: Array[Callable] = []
# 按 "&" 分割所有子条件
for part in condition_text.split("&"):
var single_condition_text := part.strip_edges()
if single_condition_text.is_empty():
continue # 忽略由 "&&" 或末尾 "&" 产生的空部分
var args := single_condition_text.split("=", false, 1)
if args.size() != 2 or args[0].strip_edges().is_empty():
printerr("[EventManager] Invalid condition part (format: 'event=value'): %s" % single_condition_text)
continue # 跳过此错误部分,继续解析下一个
var event_name_str := args[0].strip_edges()
var value_str := args[1].strip_edges()
if not value_str.is_valid_int():
printerr("[EventManager] Invalid integer value in condition: %s" % single_condition_text)
continue # 跳过
var event_name := StringName(event_name_str)
var target_value := value_str.to_int()
if not r_condition_events.has(event_name):
r_condition_events.append(event_name)
sub_conditions.append(func(): return get_stage(event_name) == target_value)
if sub_conditions.is_empty():
printerr("[EventManager] No valid conditions were parsed from text: ", text)
return Callable() # 返回无效 Callable表示构建失败
# 返回一个复合 Callable它会检查所有子条件是否都为真
return func():
for sub_condition in sub_conditions:
if not sub_condition.call():
return false # 短路:任何一个为假,则整个条件为假
return true # 所有子条件都为真
# 构建执行 Callable。增加了健壮性检查。
# 示例: "c03_f2_madman_runaway=2"
func _build_execution(text: String) -> Callable:
var execution_text := text.strip_edges()
var parts := execution_text.split("=", false, 1)
if parts.size() != 2 or parts[0].strip_edges().is_empty():
printerr("[EventManager] Invalid execution format (format: 'event=value'): %s" % execution_text)
return Callable() # 返回无效 Callable
var event_name_str := parts[0].strip_edges()
var value_str := parts[1].strip_edges()
if not value_str.is_valid_int():
printerr("[EventManager] Invalid integer value in execution: %s" % execution_text)
return Callable() # 返回无效 Callable
var event_name := StringName(event_name_str)
var target_value := value_str.to_int()
# 使用 .bind() 是创建带参数 Callable 的首选方式,比手动包装一个 lambda 更清晰高效。
return set_stage_if_greater.bind(event_name, target_value)
# --- 公共 API ---
func get_chapter_stage() -> int:
@ -32,7 +162,12 @@ func set_stage(event_name: StringName, stage := 1) -> void:
return
ArchiveManager.archive.event_stage[event_name] = stage
print("[EventManager] Stage updated: %s -> %s" % [event_name, stage])
# 1. 发出通用信号
stage_updated.emit(event_name, stage)
# 2. 触发关联的公式
if formula_dict.has(event_name):
for formula in formula_dict.get(event_name, []):
formula.call()
# 仅当设置的 stage > 当前 stage 时更新
@ -96,12 +231,13 @@ func get_event_stage_map_array(event_name: StringName) -> PackedStringArray:
return current_dict["stages"]
# 文件: res://asset/dialogue/event_stage.dialogue
var event_stage_resource = load("uid://dohpsb4jttuv1") as DialogueResource
print("[EventManager] get_event_stage_map_array refreshing...")
for title in event_stage_resource.get_titles():
var lines = await Util.get_lines(event_stage_resource, title)
for line in lines:
var line_text = line.text.strip_edges()
var e_name = line.character
if line_text.begins_with("#"):
if line_text.begins_with("#") or line_text.find("->") >= 0 or not e_name:
continue
#xxxx: 0:未开始 1:已偷听,需邀请 2:已完成邀请
var parts = line_text.split(" ")