# 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`