# Godot 4 (GDScript) > version: 4.3 [TOC] ## Animation - 使用 AnimationTree 並透過程式來控制 主要使用 `AnimationTree` 的 `StateMachine` 和 `BlendTree` 架構整體如下 StateMachine: 圖片待補 BlendTree: 圖片待補 - 開頭定義 ```python @onready var animation_player: AnimationPlayer = $CharacterModel/AnimationPlayer @onready var animation_tree: AnimationTree = $CharacterModel/AnimationTree @onready var skeleton: Skeleton3D = $CharacterModel/man_ao/Skeleton3D # 第一個動畫節點名稱 var start_node_name # 骨骼的根名稱 var fst_root_bone_name: String = "bone_root" # 過渡時間 var transition_time: float = 0.2 ``` ### 取得動畫 - 從 `AnimationPlayer` 取得動畫 1. 預先將動畫添加到 `AnimationPlayer` 節點:直接取得 2. 手動添加動畫:需先將動畫添加到 `AnimationLibrary`,接著再將其和 `AnimationPlater` 關聯 ```python # animation = 動畫實例 var animation_name: String = "動畫名稱" var animations = Animation.new() # 建立動畫庫並加入至播放器內 var animation_library = AnimationLibrary.new() var animation_library: String = "動畫庫名稱" animation_player.add_animation_library(animation_library_name, animation_library) # 將動畫加入到動畫庫 animation_library.add_animation(animation_name, animation) # 取得播放器動畫列表 var animations = animation_player.get_animation_list() ``` ### AnimationTree - 建立狀態機 `AnimationNodeStateMachine`/`StateMachine` 並添加動畫節點 預先將 `StateMachine` 加到 `AnimationTree` 節點的 `Tree root` 屬性下(點選 `AnimationTree` 節點將 `Tree root` 選擇 `AnimationNodeStateMachine`) ```python # 取的 tree 的根結點(此時是狀態機) var state_machine_node: AnimationNodeStateMachine = animation_tree.tree_root # 遍歷動畫列表將動畫添加到 tree for i in range(animations.size()): var animation = animations[i] # 建立動畫節點 var animation_node = AnimationNodeAnimation.new() var animation_node_name = "動畫節點名稱" # 添加動畫到 AnimationNode # animation 是一個 string 但他對應的是播放器裡動畫的名稱,所以必須要一致 animation_node.animation = animation # 在狀態機上添加動畫節點 state_machine_node.add_node(animation_node_name, animation_node) if i == 0: start_node_name = animation_node_name # 播放的時候直接 start() 第一個節點名稱 state_machine_node.start(start_node_name) ``` - 建立混合樹 `AnimationNodeBlendTree`/`BlendTree` (本處範例是將混合樹當作一個節點添加到狀態機裡,並且加入 TimeScale 節點來控制動畫播放速度) ```python # 遍歷動畫列將每一個動畫都加到混合樹並添加到狀態機 for i in range(animations.size()): var blend_node = AnimationNodeBlendTree.new() var animation_node = AnimationNodeAnimation.new() var time_scale_node = AnimationNodeTimeScale.new() animation_node.animation = animation blend_node_name = "混合樹節點名稱" animation_node_name = "動畫節點名稱" time_scale_name = "TimeScale 節點名稱" blend_node.add_node(animation_node_name, animation_node) blend_node.add_node(time_scale_name, time_scale_node) # 將動畫節點和 TimeScale 節點連接 blend_node.connect_node(time_scale_name, 0, blend_node_name) # 連接至輸出 blend_node.connect_node("output", 0, time_scale_name) # 如果每個動畫的播放速度會不一樣可以從 tree 中設定個別的播放速度 # 參數名:"parameters/{BlendTree 節點名稱}/{TimeScale 節點名稱}/scale" var time_scale = 1.2 animation_tree.set("parameters/%s/%s/scale" % [blend_node_name, time_scale_name], time_scale) state_machine_node.add_node(blend_node_name, blend_node) if i == 0: start_node_name = blend_node_name # 播放第一個節點名稱 state_machine_node.start() ``` :::danger :bulb: 文檔寫 `Node.connect_node(input_node: StringName, input_index: int, output_node: StringName)` 不確定是否我理解有問題,因為對我來說是 `Node.connect_node("輸出", 0 , "輸入")`,從右到左的感覺;input_index 是當你的節點是多個輸入的時候才會需要改 ::: ### Transition (過渡動畫) 主要是指狀態機的過渡動畫,兩個動畫交叉淡入淡出的效果 ```python var transition = AnimationNodeStateMachineTransition.new() transition.xfade_time = transition_time # ...設置其他屬性就接著 # 將兩個節點添加過渡(就是連接起來) state_machine.add_transition( previous_animation_node_name, animation_node_name, transition ) ``` ### 交叉淡化且並不縮短總動畫時間 當 xfade 設定不為 0 的時候,動畫之間過渡就會有交叉淡入淡出的效果,但這個會導致總動畫時間減少 ![image](https://hackmd.io/_uploads/B1cw2Wh2C.png) 我的處理方式是,用動畫A(或動畫B)的最後一幀(用動畫B則是第一幀)另外做出一個凍結動畫(即插入一個 n 秒但不會動的動畫),將時間長度設為過渡的時間,這樣就可以和過渡時間做抵銷 - 建立凍結畫面(以動畫B做凍結畫面為例) ```python # 創建過渡用的凍結畫面 func create_freeze_animation(next_animation_name: String): var transition_time = 0.2 # transition.xfade(交叉淡化)的時間 var next_animation = animation_library.get_animation(next_animation_name) var animation = Animation.new() # 複製下個動畫的第一幀到新動畫 for track_index in range(next_animation.get_track_count()): var track_type = next_animation.track_get_type(track_index) var key_count = next_animation.track_get_key_count(track_index) if key_count > 0: var key_value = next_animation.track_get_key_value(track_index, 0) var new_track_index = animation.add_track(track_type) animation.track_set_path(new_track_index, next_animation.track_get_path(track_index)) animation.track_set_interpolation_type(new_track_index, next_animation.track_get_interpolation_type(track_index)) animation.track_insert_key(new_track_index, 0.0, key_value) animation.track_insert_key(new_track_index, transition_time, key_value) animation.length = transition_time return animation ``` - 插入到動畫結束後(以狀態機為例) ```python for i in range(animations.size()): . . . # 另外建立一個動畫庫存放凍結動畫 var freeze_library = AnimationLibrary.new() var freeze_library_name = "Freeze" # animation_name 正在遍歷的動畫名稱 # 如果不是第一個動畫則添加凍結畫面 if previous_blend_name: freeze_node_name = "Freeze_%s" % animation_name freeze_library.add_animation(animation_name, create_freeze_animation(animation_name)) freeze_node.animation = "%s/%s" % [freeze_library_name, animation_name] state_machine.add_node(freeze_node_name, freeze_node) # 連接動畫:動畫A -> 動畫B的凍結畫面 -> 動畫B if previous_blend_name: # 動畫A 連接到 動畫B的凍結畫面 state_machine.add_transition( previous_blend_name, freeze_node_name, create_transition() ) print("連接至凍結畫面:%s -> %s" % [previous_blend_name, freeze_node_name]) state_machine.add_transition( freeze_node_name, blend_node_name, create_transition("freeze") ) # 動畫B的凍結畫面 連接到 動畫B print("添加過渡:%s -> %s" % [freeze_node_name, blend_node_name]) ``` ## 物件導向 OOP 父類別 ```python extends node3D # 繼承原先的 node3D 類別 class_name testNode # 類別名稱 var test_position: Dictionary # 骨架當前位置 func _ready(): test() # 子類別繼承後也會有這個方法 func _process(): # 繼承後 self 會指向子類別 test_position = self.transform.origin func test(): print("This is parent.") ``` 子類別 - 繼承 ```python extends testNode # 繼承父類別 func _ready(): test() ''' 執行後會印出 This is parent. 這裡的 test() 是父類別的方法 ''' ``` - 子類別重寫父類別方法 ```python extends testNode # 繼承父類別 func _ready(): test() func test(): print("This is child.") ''' 執行後印出 This is parent. 子類別重寫了 test() 方法 ''' ``` - 子類別重寫父類別方法並保留原先父類別方法 ```python extends testNode # 繼承父類別 func _ready(): test() func test(): super.test() # 執行父類別的 test() print("This is child.") ''' 執行後印出 This is parent. This is child. 子類別重寫 test() 也保留原先父類別的 test() ''' ``` ## 著色器 Shader >[color=red]https://hackmd.io/jkjd0kR8QoGxaT7X3gL6YA