# 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 的時候,動畫之間過渡就會有交叉淡入淡出的效果,但這個會導致總動畫時間減少

我的處理方式是,用動畫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