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