Godot Q&A

如何製作 prefab ?

當成場景儲存: 物件右鍵選擇 Save branch as scene


如何載入 prefab ?
  • 4.x
var prefab = preload("路徑") var obj = prefab.instantiate() obj.transform.origin = 位置座標 add_child(obj)
  • 3.x
var prefab = load("路徑") var obj = prefab.instance() # 以下略...

執行 3D 相關 API 會發生錯誤: attempt to call function XXX in base null instance on a null instance

4.x 才有支援

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

  • 多執行緒:
    Using multiple threads


如何設定 rigidbody ?

物件階層:

RigidDynamicBody3D
  |-- CollisionShape3D;
  |-- Shape

如何設定透明碰撞區?

物件階層:

Area3D
  |-- CollisionShape3D

如何打射線?
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

如何偵測滑鼠左鍵點擊?
func _input(event): if event is InputEventMouseButton: var mouseEvent = event as InputEventMouseButton; if mouseEvent.button_index == MOUSE_BUTTON_LEFT && !mouseEvent.pressed: # do something

如何使用自訂 MainLoop ?
extends SceneTree class_name CustomLoop func _initialize(): print("initialize")

Project setting -> Run -> Main Loop Type -> 設定 class name


如何抓取物件的資訊?
# 物件方向 transform.basis # 物件座標 transform.origin

如何使用全域變數?

文件 Singletons (AutoLoad)

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

陷阱:

  • AutoLoad 選好檔案會自動設定 Name
  • Name 的 大小寫 可能和檔名有差異

e.g.
檔名 WSGlobal.gd
Name 會變成 WsGlobal


如何在 class 建構子傳入參數?
class_name ABC var name: String func _init(__name: String): name = __name print(name)
var obj = ABC.new("hi")

class_name OOO could not be fully loaded (script error or cyclic inheritance)

在沒有 script 編譯錯誤的情況下, 可能是循環呼而叫導致無窮迴圈

可能的原因:

Parse Error: Using own name in class file is not allowed (create a cyclic reference)

禁止在 class 內呼叫到自己的建構子

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)
    

class OOO is already being loaded. Cyclic reference

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

class_name Car
# more detail
class_name Utils

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

移除 class_name 和 static 關鍵字
改從 Project setting -> AutoLoad -> 設定成 singleton class

extends Node

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

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


如何實作 JS 的 >>> (Unsigned right shift)?

int 在二進制的最大位元是用來表示正負號, 0=正, 1=負
JS 的 >>> 進行 bit 位移之後, 原本在最大位元的負號會變成 0, 數值會變成正數
>> 在位移之後, 原本最大位元是負號則會補 1

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))

以上參考自 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)

原因在於 % 的差異
參考文章

  • JS
-300 % 4294967296 = -300
  • Python
-300 % 4294967296 = 4294966996
  • GDScript
-300 % 4294967296 = -300

log 太長, 出現 output overflow, print less text

Project setting -> Network -> Limit
調整 Max Chars Per Second 的上限


如何將 function 當作參數儲存?

使用 FuncRef

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)

如何儲存 class 的 reference ?
class_name Xyz func _init(__a: int): print("init: " + str(__a))
var __ref: Reference = Xyz __ref.new(1)

如何透過字串建立 class 實體 ?
  • 內建 API 透過 ClassDB

    ​​​​# 建立空的 Object ​​​​var __obj: Object = ClassDB.instance("Object")
  • 自定義 class 只能透過 load() 載入
    參考來源

    ​​​​var __inst: Aaa = load("res://Scripts/Aaa.gd").new()

    取得所有 class 清單, 但需要自行解析內容來取得 class 檔案路徑

    ​​​​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}]
    

如何透過字串來更新物件實體的屬性?

透過 set() 更新

class_name Aaa var a: int var b: String
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")

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


如何使用 inner class ?
class_name CustomObj var name: String func _init(__name: String): name = __name
class_name Main var aaa: AAA func _init(): aaa = AAA.new("abc") class AAA: extend CustomObj func _init(__name: String).(__name): pass

如何動態產生 class ?

參考來源

適合搭配 string format 一起使用

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

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


如何引用HTML5 JS ?

參考來源
查看 URL parameters 為例

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

如何掃描資料夾資源 ?

參考來源

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