226 lines
7.4 KiB
Markdown
226 lines
7.4 KiB
Markdown
### 组合与继承
|
||
|
||
### 用键盘代替鼠标来进行游戏操作(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, 简单来说就是不依赖引擎自带的信号流程
|
||
注: 以上方法并不局限, 可以组合在不同的地方使用
|
||
``` gdscript
|
||
## 事件中心, 无视版本的信号小杀器 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)
|
||
```
|
||
推荐使用样例
|
||
![推荐使用样例](./Aseets/exapmle.png)
|
||
|
||
|
||
|
||
### 类似 C# 的 `Linq` 语句
|
||
``` python
|
||
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实现逐秒的异步操作
|
||
``` python
|
||
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的分支语句
|
||
|
||
基础样例
|
||
``` python
|
||
extends Node
|
||
|
||
func _ready():
|
||
var x = 3
|
||
match x:
|
||
1:
|
||
print("1")
|
||
2:
|
||
print("2")
|
||
"test":
|
||
print("test")
|
||
_:
|
||
print("others")
|
||
```
|
||
match多类型 与 match方法样例
|
||
``` python
|
||
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 数组与字典样例
|
||
```python
|
||
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中使用继承与组合
|
||
- [How You can Easily Make Your Code Simpler in Godot4](https://www.youtube.com/watch?v=74y6zWZfQKk&t=2s)
|
||
- 这个视频挺好的 完美阐述了如何在godot中使用组合的方式替代继承 非常妙 子物体即是组件
|
||
|
||
## Godot中的坑
|
||
![](Aseets/Pasted%20image%2020231113190838.png)
|
||
上述脚本的25行就是个坑, 我在编写编辑器的时候, 在静态方法中调用一个空引用回导致引擎崩溃, 而且没有日志. 这里的初始化我猜测是失败了, 或者像是Unity一样把静态属性置空了.
|
||
|
||
|
||
## Get Set Property
|
||
```python
|
||
|
||
var my_property : int = 0:
|
||
get:
|
||
return my_property + 1
|
||
set(value):
|
||
my_property = value
|
||
|
||
``` |