2025-12-30 17:22:29 +08:00
|
|
|
|
'''此类为Prop的管理组件类,任何的Prop,其至少需要在此类的最上层的子层添加该组件
|
|
|
|
|
|
该管理器,在添加入Tree时,会自动的添加一个States节点作为其所有state的root,
|
|
|
|
|
|
并且添加一个默认的state作为其子节点。
|
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
@tool
|
|
|
|
|
|
@icon("res://addons/reedscene/prop/icon/prop_icon.svg")
|
|
|
|
|
|
class_name PropComponent extends Node
|
|
|
|
|
|
|
|
|
|
|
|
## ==============================
|
|
|
|
|
|
## Export
|
|
|
|
|
|
## ==============================
|
|
|
|
|
|
|
2025-12-31 13:07:31 +08:00
|
|
|
|
##此物件的描述ID,无法主动修改,由PropManager发信
|
|
|
|
|
|
@export_custom(PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY) var prop_id: int = -1
|
2025-12-30 17:22:29 +08:00
|
|
|
|
##初始的默认state_id
|
|
|
|
|
|
@export var initial_state_id: int = 0
|
2026-01-02 23:56:22 +08:00
|
|
|
|
##如果为真,则无论ActManger的配置为任何,在Inital的时候都会使用自己定义的initial state
|
|
|
|
|
|
@export var overwrite_init_state: bool = false
|
2025-12-30 17:22:29 +08:00
|
|
|
|
##是否需要输出错误
|
|
|
|
|
|
@export var debug_log := false
|
|
|
|
|
|
##是否等待
|
|
|
|
|
|
@export var should_wait_owner_ready :bool = true
|
|
|
|
|
|
##状态切换的信号
|
|
|
|
|
|
signal state_changed(from_state: int, to_state: int, ctx: Dictionary)
|
|
|
|
|
|
|
|
|
|
|
|
##state的根节点,所有的state都需要连在根节点上
|
|
|
|
|
|
var _states_root: Node
|
|
|
|
|
|
var _state_map: Dictionary = {}
|
|
|
|
|
|
var _current: ReedPropState = null
|
|
|
|
|
|
|
2026-01-03 23:21:04 +08:00
|
|
|
|
const STATES_ROOT_NAME := "States"
|
|
|
|
|
|
const STATES_ROOT_SCRIPT := preload("res://addons/reedscene/prop/StateManager.gd")
|
2025-12-30 17:22:29 +08:00
|
|
|
|
const DEFAULT_STATE_ID := 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _enter_tree() -> void:
|
|
|
|
|
|
if Engine.is_editor_hint():
|
|
|
|
|
|
_editor_ensure_states_root()
|
|
|
|
|
|
|
|
|
|
|
|
func _ready() -> void:
|
2026-01-03 23:21:04 +08:00
|
|
|
|
if Engine.is_editor_hint():
|
|
|
|
|
|
_editor_ready()
|
|
|
|
|
|
|
2025-12-30 17:22:29 +08:00
|
|
|
|
_build_state_cache()
|
|
|
|
|
|
|
2026-01-03 23:21:04 +08:00
|
|
|
|
## 只在編輯器模式的Ready裏Call的函數
|
|
|
|
|
|
func _editor_ready() -> void:
|
|
|
|
|
|
child_exiting_tree.connect(_on_child_exiting_tree)
|
2026-01-02 23:56:22 +08:00
|
|
|
|
|
|
|
|
|
|
## 用于初始化状态
|
|
|
|
|
|
func init() -> void:
|
|
|
|
|
|
_init_states()
|
|
|
|
|
|
|
|
|
|
|
|
## 用于检查初始状态复写的Check
|
|
|
|
|
|
func _init_state_check() ->void:
|
|
|
|
|
|
if _current == null and initial_state_id >= 0 and overwrite_init_state:
|
|
|
|
|
|
change_state(initial_state_id, false, {
|
|
|
|
|
|
"reason": "INIT",
|
|
|
|
|
|
"instant": true
|
|
|
|
|
|
})
|
2025-12-30 17:22:29 +08:00
|
|
|
|
|
2026-01-03 23:21:04 +08:00
|
|
|
|
## 在構造時,映射State到stateID
|
2025-12-30 17:22:29 +08:00
|
|
|
|
func _build_state_cache() -> void:
|
|
|
|
|
|
_state_map.clear()
|
|
|
|
|
|
|
2026-01-03 23:21:04 +08:00
|
|
|
|
_states_root = get_node_or_null(STATES_ROOT_NAME)
|
2025-12-30 17:22:29 +08:00
|
|
|
|
if _states_root == null:
|
|
|
|
|
|
push_warning("[PropComponent:%s] States root not found" % prop_id)
|
|
|
|
|
|
return
|
2026-01-03 23:21:04 +08:00
|
|
|
|
|
|
|
|
|
|
## 判斷子節點是否是StatesRoot節點,此節點的脚本後續會輕量化,所以不設置為一個單獨的類
|
|
|
|
|
|
if not _states_root.IS_PROP_STATES_ROOT:
|
|
|
|
|
|
push_error("[PropComponent:%s] States root missing PropStatesRoot script" % prop_id)
|
2025-12-31 13:07:31 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
2025-12-30 17:22:29 +08:00
|
|
|
|
for c in _states_root.get_children():
|
|
|
|
|
|
if c is ReedPropState:
|
|
|
|
|
|
var s := c as ReedPropState
|
|
|
|
|
|
if _state_map.has(s.state_id):
|
2025-12-31 13:07:31 +08:00
|
|
|
|
push_error("[PropComponent:%s] Duplicate state_id: %d"
|
|
|
|
|
|
% [prop_id, s.state_id])
|
2025-12-30 17:22:29 +08:00
|
|
|
|
continue
|
|
|
|
|
|
_state_map[s.state_id] = s
|
|
|
|
|
|
|
|
|
|
|
|
if debug_log:
|
|
|
|
|
|
print("[PropComponent:%s] States:", _state_map.keys())
|
|
|
|
|
|
|
|
|
|
|
|
##初始化状态
|
|
|
|
|
|
func _init_states() -> void:
|
|
|
|
|
|
var owner := get_parent()
|
|
|
|
|
|
if owner == null:
|
|
|
|
|
|
push_error("[PropComponent:%s] Owner node not found." % prop_id)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
for s in _state_map.values():
|
|
|
|
|
|
var ok :bool = s.init(owner, self)
|
|
|
|
|
|
if not ok:
|
|
|
|
|
|
push_error("[PropComponent:%s] State init failed: %s"
|
|
|
|
|
|
% [prop_id, s.name])
|
|
|
|
|
|
|
2026-01-03 23:21:04 +08:00
|
|
|
|
## 引擎層預知一個工具節點給予到State及其子節點。
|
|
|
|
|
|
func _editor_ensure_states_root() -> void:
|
|
|
|
|
|
var states := get_node_or_null(STATES_ROOT_NAME)
|
|
|
|
|
|
if states:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
states = Node.new()
|
|
|
|
|
|
states.name = STATES_ROOT_NAME
|
|
|
|
|
|
states.set_script(STATES_ROOT_SCRIPT)
|
|
|
|
|
|
add_child(states)
|
|
|
|
|
|
states.owner = get_tree().edited_scene_root
|
|
|
|
|
|
|
|
|
|
|
|
var default_state := ReedPropState.new()
|
|
|
|
|
|
default_state.name = "Default"
|
|
|
|
|
|
states.add_child(default_state)
|
|
|
|
|
|
default_state.owner = get_tree().edited_scene_root
|
|
|
|
|
|
|
|
|
|
|
|
## 不允许StatesNode被删除,如果删了会自动补一个
|
|
|
|
|
|
func _on_child_exiting_tree(child: Node) -> void:
|
2026-01-09 15:53:45 +08:00
|
|
|
|
if child.name != STATES_ROOT_NAME:
|
|
|
|
|
|
return
|
|
|
|
|
|
# 不要立刻报错,先延后一帧再确认
|
|
|
|
|
|
call_deferred("_editor_verify_states_root_missing")
|
|
|
|
|
|
|
|
|
|
|
|
func _editor_verify_states_root_missing() -> void:
|
|
|
|
|
|
if not Engine.is_editor_hint():
|
|
|
|
|
|
return
|
|
|
|
|
|
if not is_inside_tree():
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 如果下一帧 States 还在,说明只是编辑器重建/移动过程,不算删除
|
|
|
|
|
|
if get_node_or_null(STATES_ROOT_NAME) != null:
|
|
|
|
|
|
return
|
2026-01-03 23:21:04 +08:00
|
|
|
|
|
2026-01-09 15:53:45 +08:00
|
|
|
|
# 到这里才算真的没了
|
|
|
|
|
|
push_error("[PropComponent] 'States' node is required and cannot be removed.")
|
|
|
|
|
|
_editor_ensure_states_root()
|
2026-01-03 23:21:04 +08:00
|
|
|
|
## ==============================
|
|
|
|
|
|
## External API
|
|
|
|
|
|
## ==============================
|
|
|
|
|
|
#region 外部接口函数
|
|
|
|
|
|
## 使用PropComp獲取PropName,直接返回ParentName
|
|
|
|
|
|
func get_prop_name() -> String:
|
|
|
|
|
|
return get_parent().name
|
|
|
|
|
|
|
|
|
|
|
|
## 獲取StatesRoot,返回緩存的StatesRoot
|
|
|
|
|
|
func get_states_root() -> Node:
|
|
|
|
|
|
return _states_root
|
|
|
|
|
|
|
|
|
|
|
|
## 通過ID獲取PropState
|
|
|
|
|
|
func get_state_by_id(id: int) -> ReedPropState:
|
|
|
|
|
|
return _state_map.get(id)
|
|
|
|
|
|
|
|
|
|
|
|
## 返回State的Size
|
|
|
|
|
|
func get_state_size() -> int:
|
|
|
|
|
|
return _state_map.keys().size()
|
|
|
|
|
|
|
|
|
|
|
|
## 返回所有的States
|
|
|
|
|
|
func get_all_states() -> Array[ReedPropState]:
|
|
|
|
|
|
return _state_map.values()
|
|
|
|
|
|
|
|
|
|
|
|
##检查是否存在Id的State
|
|
|
|
|
|
func has_state(state_id: int) -> bool:
|
|
|
|
|
|
return _state_map.has(state_id)
|
|
|
|
|
|
|
|
|
|
|
|
##获取当前的状态ID
|
|
|
|
|
|
func get_current_state_id() -> int:
|
|
|
|
|
|
return _current.state_id if _current else -1
|
|
|
|
|
|
|
2025-12-30 17:22:29 +08:00
|
|
|
|
## 切换状态,如果 use_trans 为 true,则会优先检查 next state 下是否存在可用的 Transition
|
|
|
|
|
|
func change_state(state_id: int, use_trans: bool, ctx: Dictionary = {}) -> bool:
|
|
|
|
|
|
if not _state_map.has(state_id):
|
|
|
|
|
|
push_warning("[PropComponent:%s] State not found: %d" % [prop_id, state_id])
|
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
|
|
var next: ReedPropState = _state_map[state_id]
|
|
|
|
|
|
|
|
|
|
|
|
if _current == next:
|
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
|
|
if not next.can_enter(_current, ctx):
|
|
|
|
|
|
if debug_log:
|
|
|
|
|
|
print("[PropComponent:%s] can_enter rejected: %d" % [prop_id, state_id])
|
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
|
|
var from_state_id := get_current_state_id()
|
|
|
|
|
|
var prev := _current
|
|
|
|
|
|
|
|
|
|
|
|
# ---------- EXIT ----------
|
|
|
|
|
|
if _current:
|
|
|
|
|
|
_current.on_exit(next, ctx)
|
|
|
|
|
|
|
|
|
|
|
|
# ---------- SWITCH CURRENT ----------
|
|
|
|
|
|
_current = next
|
|
|
|
|
|
|
|
|
|
|
|
# ---------- TRANSITION CHECK ----------
|
|
|
|
|
|
var transition_handled := false
|
|
|
|
|
|
|
|
|
|
|
|
if use_trans:
|
|
|
|
|
|
for child in _current.get_children():
|
|
|
|
|
|
if child is ReedTransition:
|
|
|
|
|
|
if child.can_trigger(prev, ctx):
|
|
|
|
|
|
if debug_log:
|
|
|
|
|
|
print("[PropComponent:%s] Transition triggered: %s"
|
|
|
|
|
|
% [prop_id, child.name])
|
|
|
|
|
|
|
|
|
|
|
|
transition_handled = child.execute(prev, _current, ctx)
|
|
|
|
|
|
if transition_handled:
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
# ---------- ENTER ----------
|
|
|
|
|
|
if not transition_handled:
|
|
|
|
|
|
_current.on_enter(prev, ctx)
|
|
|
|
|
|
else:
|
|
|
|
|
|
if debug_log:
|
|
|
|
|
|
print("[PropComponent:%s] StateEnter skipped by Transition: %d"
|
|
|
|
|
|
% [prop_id, state_id])
|
|
|
|
|
|
|
|
|
|
|
|
if debug_log:
|
|
|
|
|
|
print("[PropComponent:%s] State %d -> %d | ctx=%s"
|
|
|
|
|
|
% [prop_id, from_state_id, state_id, ctx])
|
|
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
|
|
2026-01-03 23:21:04 +08:00
|
|
|
|
## 获取ID的State的Name
|
|
|
|
|
|
func get_state_name_by_id(id: int) -> String:
|
|
|
|
|
|
var s : ReedPropState = get_state_by_id(id)
|
|
|
|
|
|
if not s:
|
|
|
|
|
|
return ""
|
|
|
|
|
|
return get_state_name(s)
|
|
|
|
|
|
|
|
|
|
|
|
## 获取State的Name
|
|
|
|
|
|
func get_state_name(state: ReedPropState) -> String:
|
|
|
|
|
|
if not state:
|
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
var n: String = state.name
|
|
|
|
|
|
var extract_raw_name := func(name: String) -> String:
|
|
|
|
|
|
return (
|
|
|
|
|
|
name.substr(name.find("]") + 2)
|
|
|
|
|
|
if name.begins_with("[ID:")
|
|
|
|
|
|
and name.find("]") != -1
|
|
|
|
|
|
and name.find("]") + 2 < name.length()
|
|
|
|
|
|
else name
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return extract_raw_name.call(n)
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|