---
# System prepended metadata

title: Godot Q&A
tags: [godot]

---

# Godot Q&A

:::spoiler 如何製作 prefab ?
:::success
當成場景儲存: 物件右鍵選擇 Save branch as scene
:::

<br>

:::spoiler 如何載入 prefab ?

:::success
* 4.x
```gdscript=
var prefab = preload("路徑")
var obj = prefab.instantiate()
obj.transform.origin = 位置座標
add_child(obj)
```

* 3.x
```gdscript=
var prefab = load("路徑")
var obj = prefab.instance()
# 以下略...
```
:::

<br>

:::spoiler 執行 3D 相關 API 會發生錯誤: attempt to call function XXX in base null instance on a null instance
:::success
4.x 才有支援

* 單執行緒:
Project setting -> Physics -> 3D -> 不勾選 Run on Separate Thread

* 多執行緒:
<font color=#880000> [Using multiple threads](https://docs.godotengine.org/en/stable/tutorials/performance/threads/using_multiple_threads.html)</font>
:::

<br>

:::spoiler 如何設定 rigidbody ?

:::success
物件階層:
```
RigidDynamicBody3D
  |-- CollisionShape3D;
  |-- Shape
```
:::

<br>

:::spoiler 如何設定透明碰撞區?
:::success
物件階層:
```
Area3D
  |-- CollisionShape3D
```
:::

<br>


:::spoiler 如何打射線?

:::success
```gdscript=
var length = 1000
var camera = get_viewport().get_camera_3d()
var from = camera.project_ray_origin(globalPosition)
var to = from + camera.project_ray_normal(globalPosition) * length

var args = PhysicsRayQueryParameters3D.new()
args.from = from
args.to = to
# 只偵測第 3 個 layer -> 二進制 0b100, 十六進制 0x4
args.collision_mask = 0b100;
# 偵測對象是 Area3D
args.collide_with_areas = true;

var spaceState = get_world_3d().direct_space_state
var result = spaceState.intersect_ray(args)

if result:
    # do something
```
:::

<br>

:::spoiler 如何偵測滑鼠左鍵點擊?

:::success
```gdscript=
func _input(event):
    if event is InputEventMouseButton:
        var mouseEvent = event as InputEventMouseButton;
        if mouseEvent.button_index == MOUSE_BUTTON_LEFT && !mouseEvent.pressed:
            # do something
```
:::

<br>

:::spoiler 如何使用自訂 MainLoop ?
:::success
```gdscript=
extends SceneTree

class_name CustomLoop

func _initialize():
	print("initialize")
```
Project setting -> Run -> Main Loop Type -> 設定 class name
:::

<br>

::: spoiler 如何抓取物件的資訊?
```gdscript=
# 物件方向
transform.basis

# 物件座標
transform.origin
```
:::

<br>


:::spoiler 如何使用全域變數?
:::info
文件 [Singletons (AutoLoad)](https://docs.godotengine.org/en/stable/tutorials/scripting/singletons_autoload.html)

:::success
1. 建立 .gd
2. 開頭至少需要加上繼承 Node
3. 不要在 script 內定義 class_name
4. Project setting -> AutoLoad -> 設定 script 路徑後按加入
5. 直接呼叫 AutoLoad 設定的名稱, 可直接存取 script 底下的內容

:::warning
陷阱:
* AutoLoad 選好檔案會自動設定 Name
* Name 的 <font color=#880000>大小寫</font> 可能和檔名有差異

e.g.
檔名 WSGlobal.gd
Name 會變成 W<font color=#880000>s</font>Global
:::

<br>

:::spoiler 如何在 class 建構子傳入參數?
:::success
```gdscript=
class_name ABC

var name: String

func _init(__name: String):
    name = __name
    print(name)
```

```gdscript!
var obj = ABC.new("hi")
```
:::

<br>


:::spoiler class_name OOO could not be fully loaded (script error or cyclic inheritance)
在沒有 script 編譯錯誤的情況下, 可能是循環呼而叫導致無窮迴圈

可能的原因:
:::warning
**Parse Error: Using own name in class file is not allowed (create a cyclic reference)**

<font color=#880000>禁止在 class 內呼叫到自己的建構子</font>
```gdscript!
class_name ABC

var _list: Array

# 建構子
func _init(__list: Array):
    _list = __list
    
func concat(__target: Array) -> ABC:
    var __newList: Array = []
    __newList.append_array(_list)
    __newList.append_array(__target)

    # 此段無法通過編譯, 需要透過其他外部 class 來建立實體
    return ABC.new(__newList)
    
```

<br>

**class OOO is already being loaded. Cyclic reference**

目前已知 class 內含的 static function 呼叫到自定義 class 會造成問題

```gdscript!
class_name Car
# more detail
```
```gdscript!
class_name Utils

static func getObj() -> Car:
    return Car.new()
```

<font color=green>移除 class_name 和 static 關鍵字</font>
<font color=green>改從 Project setting -> AutoLoad -> 設定成 singleton class</font>
```gdscript!
extends Node

func getObj() -> Car
    return Car.new()
```

:::info
目前已存在的相關討論, 大多都跟場景載入相關...但此範例只有單場景 
:::

<br>

:::spoiler 如何實作 JS 的 **>>>** (Unsigned right shift)?
int 在二進制的最大位元是用來表示正負號, 0=正, 1=負
JS 的 [>>>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Unsigned_right_shift) 進行 bit 位移之後, 原本在最大位元的負號會變成 0, 數值會變成正數
而 [>>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Right_shift) 在位移之後, 原本最大位元是負號則會補 1
:::success
GDScript 僅支援 **>>**
```gdscript=
# 32-bit
func unsignedRightShift(__value: int, __places: int) -> int:
    # 正數直接用 >> 位移
    if __value >= 0:
        return __value >> __places

    # 移除最大位元的負號, 0x7FFFFFFF = (1 << 31) - 1
    var __tmp: int = __value & 0x7FFFFFFF
    __tmp = __tmp >> __places

    # 補上負號位移後的結果
    return __tmp | (1 << (31 - __places))
    
```

:::info
以上參考自 [python](https://realpython.com/python-bitwise-operators/#arithmetic-vs-logical-shift) 的解法
:::warning
此範例只在 python 能正常運作
```python!
>>> def logical_rshift(signed_integer, places, num_bits=32):
...     unsigned_integer = signed_integer % (1 << num_bits)
...     return unsigned_integer >> places
...
>>> logical_rshift(-100, 1)
```

原因在於 <font color=#AA0000>**%**</font> 的差異
[參考文章](https://www.twblogs.net/a/5d415170bd9eee5174233912)
* JS
```javascript=
-300 % 4294967296 = -300
```

* Python
```python=
-300 % 4294967296 = 4294966996
```

* GDScript
```gdscript=
-300 % 4294967296 = -300
```
:::

<br>

:::spoiler log 太長, 出現 output overflow, print less text
:::success
Project setting -> Network -> Limit
調整 **Max Chars Per Second** 的上限
:::

<br>

:::spoiler 如何將 function 當作參數儲存?
:::success
使用 [FuncRef](https://docs.godotengine.org/en/stable/classes/class_funcref.html)

```gdscript=
class_name Xyz

func _init():
    var __aaa: FuncRef = funcref(self, "aaa")   
    __aaa.call_func()
    
    var __bbb: FuncRef = funcref(self, "bbb")
    __bbb.call_func("hi")
    
func aaa():
    print("this is aaa")
    
func bbb(__msg: String):
    print("bbb: " + __msg)
```
:::

<br>

:::spoiler 如何儲存 class 的 reference ?
```gdscript=
class_name Xyz

func _init(__a: int):
    print("init: " + str(__a))
```

```gdscript=
var __ref: Reference = Xyz
__ref.new(1)
```
:::

<br>

:::spoiler 如何透過字串建立 class 實體 ?
:::success
* 內建 API 透過 [ClassDB](https://docs.godotengine.org/en/stable/classes/class_classdb.html)
    ```gdscript=
    # 建立空的 Object
    var __obj: Object = ClassDB.instance("Object")
    ```

* 自定義 class 只能透過 load() 載入
    [參考來源](https://godotengine.org/qa/54129/is-there-way-to-instance-class-based-on-string-enum-gdscript)
    ```gdscript=
    var __inst: Aaa = load("res://Scripts/Aaa.gd").new()
    ```
    
    取得所有 class 清單, 但需要自行解析內容來取得 class 檔案路徑
    ```gdscript=
    ProjectSettings.get_setting("_global_script_classes")
    ```
    ```
    [{base:Object, class:Aaa, language:GDScript, path:res://Scripts/Aaa.gd},
    {base:Reference, class:Bbb, language:GDScript, path:res://Scripts/Bbb.gd}]
    ```
:::

<br>

:::spoiler 如何透過字串來更新物件實體的屬性?
:::success
透過 [set()](https://docs.godotengine.org/en/stable/classes/class_object.html#id4) 更新

```gdscript=
class_name Aaa

var a: int
var b: String
```

```gdscript=
var ref: Reference = Aaa

func test(__ref: Reference) -> void:
    var __inst = __ref.new()
    __inst.set("a", 1)
    __inst.set("b", "hi")
    
    # 此屬性不存在, 無任何作用
    __inst.set("c", "hi")
```

:::warning
屬性名稱需要存在於 class, 如果不存在則不會有任何作用
:::

<br>

:::spoiler 如何使用 inner class ?
```gdscript=
class_name CustomObj

var name: String

func _init(__name: String):
    name = __name
```

:::success
```gdscript=
class_name Main

var aaa: AAA

func _init():
    aaa = AAA.new("abc")
    

class AAA:
    extend CustomObj
    
    func _init(__name: String).(__name):
        pass
```
:::

<br>

:::spoiler 如何動態產生 class ?
:::success
[參考來源](https://www.reddit.com/r/godot/comments/vo40ya/comment/ieawibm/)

適合搭配 [string format](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_format_string.html) 一起使用

```gdscript=
func newClass(__className: String, __vars: PoolStringArray) -> Struct:
    # Struct 是自定義 class
    var __raw: String = """
extends Struct
class_name Struct_{className}
{vars}
func _init(__arg).(__arg):
	pass
"""

    var __finalFormat: String = {"className": __className}
    var __varFormat: String = ""
    
    # 設定變數
    for i in __vars.size():
        var __varName: String = __vars[i]
        var __tmpFormat: String = "\nvar {varName}".format({"varName": __varName})
        __varFormat += __tmpFormat
        
    __finalFormat["vars"] = __varFormat
    
    var __source: String = __raw.format(__finalFormat)
    var __script = GDScript.new()
    __script.set_source_code(__source)
    __script.reload()
    
    # 只回傳 class reference, 並未建立 class 實體
    return __script
```

:::warning
動態建立的 class, 原始碼縮排需要符合 GDScript 的規範
:::

<br>

:::spoiler 如何引用HTML5 JS ?
:::success
[參考來源](http://man.hubwiz.com/docset/Godot.docset/Contents/Resources/Documents/classes/class_javascript.html)
查看 URL parameters 為例
```gdscript=
func get_JSparameter(target, parameter):
    if OS.has_feature('JavaScript'):
        var result = JavaScript.eval("""
        var url_string = window.location.href;
        var url = new URL(url_string);
        console.log('{target}&{parameter}')
        url.searchParams.get'{parameter}'
        """.format({"target":target, "parameter":parameter}))
        )
        return result;
    return target
```
:::

<br>

:::spoiler 如何掃描資料夾資源 ?
:::success
[參考來源](https://docs.godotengine.org/zh_TW/stable/classes/class_directory.html)

```gdscript=
func dir_contents(path):
    var dir = Directory.new()
    if dir.open(path) == OK:
        dir.list_dir_begin()
        var file_name = dir.get_next()
        while file_name != "":
            if dir.current_is_dir():
                print("Found directory: " + file_name)
            else:
                print("Found file: " + file_name)
            file_name = dir.get_next()
    else:
        print("An error occurred when trying to access the path.")
```
:::

---

###### tags: `godot`