---
# System prepended metadata

title: Godot 4.3
tags: [Godot 4]

---

# 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