godot-plateformer/addons/reedscene/prop/PropComponent.gd

237 lines
6.7 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'''此类为Prop的管理组件类任何的Prop其至少需要在此类的最上层的子层添加该组件
该管理器在添加入Tree时会自动的添加一个States节点作为其所有state的root
并且添加一个默认的state作为其子节点。
'''
@tool
@icon("res://addons/reedscene/prop/icon/prop_icon.svg")
class_name PropComponent extends Node
## ==============================
## Export
## ==============================
##此物件的描述ID无法主动修改由PropManager发信
@export_custom(PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY) var prop_id: int = -1
##初始的默认state_id
@export var initial_state_id: int = 0
##如果为真则无论ActManger的配置为任何在Inital的时候都会使用自己定义的initial state
@export var overwrite_init_state: bool = false
##是否需要输出错误
@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
const STATES_ROOT_NAME := "States"
const STATES_ROOT_SCRIPT := preload("res://addons/reedscene/prop/StateManager.gd")
const DEFAULT_STATE_ID := 0
func _enter_tree() -> void:
if Engine.is_editor_hint():
_editor_ensure_states_root()
func _ready() -> void:
if Engine.is_editor_hint():
_editor_ready()
_build_state_cache()
## 只在編輯器模式的Ready裏Call的函數
func _editor_ready() -> void:
child_exiting_tree.connect(_on_child_exiting_tree)
## 用于初始化状态
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
})
## 在構造時映射State到stateID
func _build_state_cache() -> void:
_state_map.clear()
_states_root = get_node_or_null(STATES_ROOT_NAME)
if _states_root == null:
push_warning("[PropComponent:%s] States root not found" % prop_id)
return
## 判斷子節點是否是StatesRoot節點此節點的脚本後續會輕量化所以不設置為一個單獨的類
if not _states_root.IS_PROP_STATES_ROOT:
push_error("[PropComponent:%s] States root missing PropStatesRoot script" % prop_id)
return
for c in _states_root.get_children():
if c is ReedPropState:
var s := c as ReedPropState
if _state_map.has(s.state_id):
push_error("[PropComponent:%s] Duplicate state_id: %d"
% [prop_id, s.state_id])
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])
## 引擎層預知一個工具節點給予到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:
if child.name == STATES_ROOT_NAME:
push_error("[PropComponent] 'States' node is required and cannot be removed.")
call_deferred("_editor_ensure_states_root")
## ==============================
## 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
## 切换状态,如果 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
## 获取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