ObsidianNotes/Godot GDScript Trick.md

10 KiB
Raw Permalink Blame History

GDScript reference — Godot Engine (stable) documentation in English

组合与继承

  • extends 关键字就是继承的方式
  • 子脚本中调用 super. 就是调用父类 类似C#中的parent.
  • 子类方法与父类方法同名时, 会重写方法,
    • 一般都会写一个空的方法作为 abstruct 方法
    • 如果想搞一个 virtual 方法, 那么就在子类重写方法后, 调用super.原方法实现

用键盘代替鼠标来进行游戏操作Vimlike

  • 关键点Control.focus

Signal up Call down Solution

此解决方案主要目标就是分析Godot中的信息传递问题, 因为信号本质上就是一个观察者模式. 此文主要面向工程, 为信号传递提供解决方案

关联信号的方式(名词定义)

  • BindA(s,m):引擎内直接使用编辑器信号绑定信号s对于脚本上的方法m
  • BindB(s,m):在脚本中暴露export Node 然后用s.connect(m)的方式绑定

夸级别检索或绑定方式(Godot小技巧)

一般来说, 直接把资源文件夹内的场景资源实例化在其他场景中时, 无法看到和修改子场景中的子物体信息.

  • 通过右键子物体可以选择 Editable Children 就可以实现子物体可编辑了
  • 基于上述方式, 进行检索和帮顶就可以实现更多方式信息交换

实例

背景介绍: 当前有一个主场景M场景内包含一个UI面板场景S面板场景内拥有一个按钮B。 现在需要实现B点击,触发M的一个方法A

  • 方法1夸级冒泡:直接把S设置成可编辑子物体的模式,然后直接在Godot内进行信号链接B=>A即可
  • 方法2逐级冒泡:为S写一个脚本, 然后用B关联S从而生成一个新方法,然后在S中定义一个新的信号G并在新方法中进行触发,最后再把S的G与M的方法A进行链接
  • 方法3夸级向下查找:直接吧S设置成可编辑子物体模式, 然后用BindB的方式绑定即可
  • 方法4逐级向下查找:通过 BindB 绑定方式: M找S,S找B, 他们一对一进行BindB操作
  • 方法5全局单例信号:全局单例中进行查找与触发没啥好讲,全脚本绑定与发送
  • 方法6事件中心:类似方法5但是完全不存在信号,使用的是方法的委托与key关联的方式. 主场景M的脚本和场景S的脚本都持有一个事件中心, 然后S的子物体B信号绑定到S的事件中心去触发, S的事件再绑定到M的事件中心去触发

总结

可以使用的方式非常多.

  • 因素1: 引擎升级后, 概率因为引擎和脚本大改, 导致脚本资源.tscn 损坏
  • 因素2: 考虑场景复用性, 如果一个场景资源在多个地方拥有实例那么意味着这个资源被复用了. 这时候最好做好子物体相对自身引用而不是全局引用 基于上述因素与方法,总结以下:
  • 不考虑因素1, 考虑因素2, 使用方法2最好, 因为非常的'面对对象'
  • 不考虑因素1, 不考虑因素2, 使用方法1最好, 因为比较方便
  • 考虑因素1, 不考虑因素2, 使用方法5无脑选, 写起来非常快, 就是维护成本有点高
  • 考虑因素1, 考虑因素2, 使用方法4结合方法6, 简单来说就是不依赖引擎自带的信号流程 注: 以上方法并不局限, 可以组合在不同的地方使用
## 事件中心, 无视版本的信号小杀器 script only Signal Solution
class_name EventCenter
extends RefCounted

var _all_events : Dictionary
## 使用样例 注:实际项目一般是使用常量字符串与实例方法,这里是为了方便才写死了字符串与匿名方法
static func example():
	var event_center = EventCenter.new()

	var f = func(): print("ss")
	event_center.add("print",f)
	event_center.erase("print",f)
	event_center.trigger("print")

	var f2 = func(): print("f2")
	event_center.add("print2",f2)
	event_center.trigger("print2")

	var f3 = func(input_value): print(input_value)
	event_center.add("print3",f3)
	event_center.trigger_data("print3","haha this is print3")
	
	
func _has(key:String)->bool:
	return _all_events.has(key)
func _get_actions(key:String) -> Variant:
	return _all_events[key]
	
func add(key:String,action:Callable):
	if !_has(key):
		var empty  : Array[Callable] = []
		_all_events[key] = empty
	_get_actions(key).append(action)

func erase(key:String, action:Callable):
	if _has(key):
		_get_actions(key).erase(action)

func trigger(event_key:String):
	if _has(event_key):
		for event in _get_actions(event_key):
			event.call()

func trigger_data(event_key:String, data):
	if _has(event_key):
		for event in _get_actions(event_key):
			event.call(data)

推荐使用样例 推荐使用样例

格式化字符串的不同方式

#reference: https://docs.godotengine.org/en/4.2/tutorials/scripting/gdscript/gdscript_format_string.html
func test_format_string():
	print("测试格式化字符串的最佳方式")
	var pa := Vector2(2,3)
	var pb := PI
	var pc := "aha"
	# format的方式:
	print("f0 this is pb:{1},pa:{0},pc:{2}".format([pa,pb,pc])) #可以乱序
	print("f1 this is pb:{},pa:{},pc:{}".format([pb,pa,pc],"{}")) #顺序
	print("f2 this is pb:$0,pa:$1,pc:$2".format([pb,pa,pc],"$_")) #乱序 数组对应 自定义解析格式
	print("f3 this is pb:{pb},pa:{pa},pc:{pc}".format({"pb"=pb,"pa"=pa,"pc"=pc})) #键值对_字典
	print("f4 this is pb:{pb},pa:{pa},pc:{pc}".format([["pb",pb],["pa",pa],["pc",pc]])) #键值对_数组套数组
	print("f5 this is pb:{0},pa:{pa},pc:{pc}".format([pb,["pa",pa],["pc",pc]])) #键值对_混合
	##以上所有方式输出都是这个:fn this is pb:3.14159265358979,pa:(2, 3),pc:aha
	
	# % 的方式
	print("%%1 this is pa:%v,pb_d:%d,pb_f:%f,pc:%s" % [pa,pb,pb,pc]) #顺序
	##%1 this is pa:(2.000000, 3.000000),pb_d:3,pb_f:3.141593,pc:aha
	# 直接累加
	print("%2 this is pa:"+ str(pa) +",pb:"+str(pb)+",pc:"+str(pc)) #乱序
	##%2 this is pa:(2, 3),pb:3.14159265358979,pc:aha
  • 最推荐的是f0,因为可以乱序, 填写的内容也比较少
  • 其次推荐f2, 因为解析数据的格式可以自定义
  • 如果和本地化相关的话, 推荐使用f3, 因为可以在本地化脚本内实现字符串对应, 方便做本地化

类似 C# 的 Linq 语句

static func square(x):
	return x * x

static func is_even(x):
	return x%2 == 0

static func sum(accum, y):
	return accum + y


static func test():
	var arr : Array
	var boolean : bool
	var item : int
	var obj;
	var number = [1,2,3,4,5,6]
	
	arr = number.map(square) # 映射
	arr = number.filter(is_even) # 过滤
	obj = number.reduce(sum,0) # 合并/合并/组合 元素 初始值为0
	number.sort()#排序
	number.sort_custom(func(a,b):return a>b) #自定义排序
	item = number.find(func(item): return item == 5, 0)#获得 其中一个==5 的值, 从 0 开始查询
	boolean = number.all(func(item):return item == 5)#所有==5
	boolean = number.any(func(item):return item == 5)#任何一个==5
	number.shuffle()#乱序

单线程异步

这里的异步本质是等待信号, 完全可以等待一帧的信号来实现逐帧的异步方法, 或者等待Timer实现逐秒的异步操作

func asyns_tween_property(scene_tree : SceneTree, object:Object, tween_property:String, value:Variant, move_time:float):
	var tween = scene_tree.create_tween()
	tween.tween_property(object,tween_property, value, move_time)
	await tween.finished

使用类似switch的分支语句

基础样例

extends Node

func _ready():
    var x = 3
    match x:
        1:
            print("1")
        2:
            print("2")
        "test":
            print("test")
        _:
            print("others")

match多类型 与 match方法样例

extends Node

func _ready():
    var x = "ss"

    match x:
        1, 2, 3:
            print("numbers")
        "String1", "String2", "String3":
            print("strings")
        var default:
            print(default)

    match test(x):
        TYPE_INT:
            print("numbers")
        TYPE_STRING:
            print("strings")
        TYPE_FLOAT:
            print("floats")

func test(a):
    return typeof(a)

match 数组与字典样例

extends Node

func _ready():
    var x = [1, 0, 3, 4, null, "test"]
    match x:
        []:
            print("empty")
        [1, 2, 3, 4, null, "test"]:
            print("case1")
        [1, 2, ..]:
            print("case2")
        [var start, _, 3, ..]:
            print(start)

    x = {
        "name" : "TsaiShuku",
        "hobby" : "Sing, dance, rap and dance",
        "test" : ""
    }

    match x:
        {}:
            print("case1")
        {"name" : "TsaiShuk", ..}:
            print("case2")
        {"name", "hobby"}:
            print("case3")
        {"test", "hobby": var hobby, ..}:
            print(hobby)
        {"test": _,"name"}:
            print("case4")

在Godot中使用继承与组合

Godot中的坑

上述脚本的25行就是个坑, 我在编写编辑器的时候, 在静态方法中调用一个空引用回导致引擎崩溃, 而且没有日志. 这里的初始化我猜测是失败了, 或者像是Unity一样把静态属性置空了.

Get Set Property


var my_property : int = 0:
	get:
		return my_property + 1
	set(value):
		my_property = value

RichTextLabel

RichTextLabel 中的 BBCode — Godot Engine (4.x) 简体中文文档

这个控件可以通过富文本修改文本的样式字体等, 并且还能加入图片, 加入动态文字效果, 自定义效果功能, 是一个非常实用的控件

脚本注释

结合BBCode与部分注释有关的语法可以实现脚本手册的自定义 GDScript 文档注释 — Godot Engine (4.x) 简体中文文档