Godot
——All in godot
图片处理
图片切割
==直接切割==:在ps之类的软件裁剪了再传进godot
==间接切割==:拖入所需图片素材–>在右侧边栏的Region
下的Enabled
勾选上(以切割形式打开)–>在下边栏的纹理区域选择所需要的部分(在吸附模式中选择鼠标选取或自动选取),==注==:间接切割并非真正的切割,只是显示隐藏其他部分,在设置中随时可以关闭这个切割或是重新切割,在间接切割后右边栏的Rect
记录的是在图片中的相对位置,x\y\w\h
分别表示 x相对坐标\y相对坐标\截取宽度\截取长度
图片合成
在左下角文件区域复数选择需要合成的图片
在左上角合成为:TextureAtlas->Atlas Files 选择新合成的文件名->重新载入即可
节点
新建节点
在左下文件系统中创建一个脚本
|
|
保存脚本后在新建该节点即可看到该节点的设置,运行后会打印设置的字符串b
物理节点
之前写了 没保存 简单写一点 详细看视频吧 https://www.bilibili.com/video/BV14Y411h7Po?p=71这几节
所有 物理节点 都需要挂载形状子节点 该形状不可视 想要可视就再挂一个图片节点
在 PhysicsBody2D下面
StaticBody2D 静态节点 有形状 不可推动 只能通过改坐标来移动
RigidBody2D 物理节点 有形状有质量
KinematicBody2D 运动节点 可以挂载移动脚本后运动
移动脚本
extends KinematicBody2D
var speed : int = 300
var screenSize
func _ready():
screenSize = get_viewport_rect().size
pass
# 逐帧移动
func _process(delta):
# 向量 表示移动
var velocity = Vector2.ZERO
# 监听键盘输入移动
if Input.is_action_pressed("ui_left"):
velocity.x -= 1
if Input.is_action_pressed("ui_right"):
velocity.x += 1
if Input.is_action_pressed("ui_up"):
velocity.y -= 1
if Input.is_action_pressed("ui_down"):
velocity.y += 1
if velocity.length() <= 0:
return
velocity = velocity.normalized()*speed
# position += velocity*delta
## clamp 越界取边界值边界
# position.x = clamp(position.x,0,screenSize.x)
# position.y = clamp(position.y,0,screenSize.y)
# 也可以使用 move_and_collide()方法直接导入向量
# 第二个参数是 testOnly 设置为false后这次 move_and_collide将不再施加力
var info : KinematicCollision2D = move_and_collide(velocity*delta,false)
# 碰撞时打印碰到的东西
if info !=null:
print(info.collider.name)
pass
line2D
直线节点
可以直接左键点画线
Width Curve是 粗细变化
使用line2D实现拖尾
结构
Node2D
->Line2D
->icon(一个sprite节点,挂载了下面的移动脚本)
拖尾脚本
extends Line2D
func _ready():
# 初始化时移除起始节点 避免第一条线
remove_point(0)
pass # Replace with function body.
func _process(delta):
# 添加一个点 在父节点下的 icon节点 的坐标上
add_point(get_parent().find_node("icon").position)
# 只保留20个节点 多余20个就删除掉前面的
if (get_point_count() >= 20):
remove_point(0)
pass
RemoteTransform(代理节点)
选择一个节点后 RemoteTransform上的脚本可以被被选择的节点读取 注意更改 extends
Path2D
由多个点设置 路径 ctrl+左键添加点 右键删除点,或者在上方工具栏选择添加\删除模式
它只提供路径 挂载节点需要添加 PathFollow2D子节点 并在 PathFollow2D节点下添加指向节点的 RemoteTransform2D子节点
Path2D->PathFollow2D->RemoteTransform2D
在 PathFollow2D上挂载脚本控制 offset以移动
extends PathFollow2D
func _process(delta):
offset+=2
pass
TileMap
图片块
GD脚本
gdscript是一种弱类型语言
==常用函数==
_init() 在脚本初始化时调用
_ready() 在运行时调用,ready运行在init之后
_process(delta) 每帧调用,用于游戏视窗的实时更新
==其他函数及执行时间==
process和physics_process
- 平时我们看到的动画,实际上是由很多静止的画面连续切换组成的
- 其中每个静止的画面,我们都称为一帧,比如60帧的动画,就是一秒播放60个静止的画面,组成的动画
- godot 的 _process 相当于 unity 的 Update
内部对代码就会在每一帧之前被执行,也就是引擎每渲染一幅的画面之前,都会执行它里面的代码
- godot 的 _physics_process 相当于 unity 的 FixedUpdate
内部的代码会在每个物理帧之前被执行,
因为godot的物理模拟是单独进行的,每次进行物理模拟的时候,如计算一个刚体小球的运动轨迹,每进行一次计算,我们就称为是一进行了一个物理帧,
而每次进行物理模拟之前,都会执行_physics_process中的代码
(按钮的)信号量
方法1:
在右边栏的节点处选择自己需要使用的事件,右键连接,选择挂载的节点,它会自动生成一个 _on_Button_***()
方法在事件触发时执行
这是godot提供的基础方法
方法2:
# button2节点.连接(将 button_up事件,连接到 self节点触发事件时执行self中的onButten2函数)
$Button2.connect("button_up",self,"onButton2")
第二种方法相对更自由一些
自定义信号量
新建欲定义信号量的button的脚本
extends Button
# 新建信号量
signal mySignal(a,b)
func _ready():
# 将自己连接到自己的onMysignalCallback()方法上
self.connect("mySignal",self,"onMysignalCallback")
self.connect("button_up",self,"onButton")
# 断开连接
# disconnect("mySignal",self,"onMysignalCallback")
pass
func onMysignalCallback(a,b):
print(a+b)
pass
func onButton():
# 发送信号
# emit_signal("mySignal",a,b)
emit_signal("mySignal",1,2)
pass
异步回调(yield)
yield()的作用是暂时挂起当前函数,直达收到某个信号位置
extends Button
signal mySignal(a,b)
func _ready():
self.connect("button_up",self,"onButton")
# 断开连接
# disconnect("mySignal",self,"onMysignalCallback")
pass
func doSomething1():
print("yielding")
# 被挂起之后没有再激活,所以 `yielded`没有被打印
yield()
print("yielded")
pass
func doSomething2():
print(1)
# 等待 1s,然后返回 timeout信号
yield(get_tree().create_timer(1),"timeout")
print(2)
yield(get_tree().create_timer(1),"timeout")
print(3)
yield(get_tree().create_timer(1),"timeout")
print("2end")
pass
# 执行结果
# yielding
# 1
# all end
# 2
# 3
# 2end
func onButton():
doSomething1()
# *** 函数执行完后会返回 completed信号,可以使用该信号让外面的函数 yield直到 doSometing2(),执行完毕
# *** yield(doSomething2(),"completed")
# 如果匿名函数不方便也可以 var f = doSomething2() yield(f,"completed")
doSomething2()
# 2个 doSomething都被挂起了,所以顺延下来执行了 `all end`打印语句,执行完后 doSomething2接收了 timeout信号继续打印 `2`和`3`
# 如果 `all end`需要在最后执行,参照前面!!!标记的两行注释代码
print("all end")
pass
全局与相对坐标的转化
extends Sprite
const StringUtils = preload("res://zfoo/util/StringUtils.gd")
var gameWidth: int
var gameHeight: int
var spriteWidth: int
var spriteHeight: int
# 当节点进入SceneTree时调用
func _enter_tree():
windowPositionTest()
positionTest()
# textureTest()
pass
func _physics_process(delta):
setupGameWindow()
setupSprite()
# positionTopCenter()
# positionCenter()
pass
# 打印屏幕位置
func windowPositionTest():
# 设置游戏屏幕的位置
# OS.window_position = Vector2(0, 0)
# 全屏幕
# OS.window_fullscreen = true
print(StringUtils.format("屏幕大小[{}]", [OS.window_size]))
print(StringUtils.format("屏幕位置[{}]", [OS.window_position]))
pass
# 坐标点测试用例
func positionTest():
print(position)
print(global_position)
# position的值是自己相对父节点的相对坐标的值 但to_global()是相对自己的,所以在数值上是自己的全局坐标加上与父节点的相对坐标
print(to_global(position))
# 把全局坐标转化为相对于本节点的坐标所以,只能是 0,0
print(to_local(global_position))
pass
# 将窗口大小读入变量
func setupGameWindow():
gameWidth = OS.window_size.x
gameHeight = OS.window_size.y
# 将图片大小读入变量
func setupSprite():
spriteWidth = texture.get_width()
spriteHeight = texture.get_height()
# 相对坐标 置顶居中
func positionTopCenter() -> void:
#get_parent().position.x = 0
#get_parent().position.y = 0
#self.global_position
# position是以上级节点为0,0的相对位置
self.position.x = gameWidth/2
self.position.y = spriteHeight/2
# 全局居中,下面类似
func positionCenter() -> void:
# global_position是全局坐标
self.global_position.x = gameWidth/2
self.global_position.y = gameHeight/2
func positionBottomCenter() -> void:
self.global_position.x = gameWidth/2
self.global_position.y = gameHeight-spriteHeight/2
func positionLeftCenter() -> void:
self.global_position.x = spriteWidth/2
self.global_position.y = gameHeight/2
移动
extends Sprite
var speed : int = 1
func _ready():
pass
# 逐帧移动
# ui_left这些是编译器里预先定义的东西
func _process(delta):
# 监听键盘输入移动
if Input.is_action_pressed("ui_left"):
position.x -= speed
if Input.is_action_pressed("ui_right"):
position.x += speed
# godot的y轴正方向在下面这点请注意
if Input.is_action_pressed("ui_up"):
position.y -= speed
if Input.is_action_pressed("ui_down"):
position.y += speed
pass
并发
extends Node2D
func _ready():
# 创建线程
var myThread = Thread.new()
# 让线程去执行函数 self.f1("aaa") 执行优先级为 0
# 特别的此处传值可以传 myThread线程变量 使其可以操作自己
myThread.start(self,"f1","aaa",0)
# 打印id
print("myThead ID:",myThread.get_id())
print("active:",myThread.is_active())
# v是myThread执行的函数的返回值
var v = myThread.wait_to_finish()
print(v)
print("active:",myThread.is_active())
pass # Replace with function body.
func f1(k):
print("hello "+k)
return "bye"
extends Object
const atomicInt: Array = [0]
const mutex = [false, null]
static func getMutex() -> Mutex:
if mutex[0]:
return mutex[1]
var mutexInstance = Mutex.new()
mutex[1] = mutexInstance
return mutexInstance
# 获取本地int的唯一id,如果达到最大值则重新从最小值重新计算,线程安全
static func getLocalIntId() -> int:
var mutexInstance = getMutex()
mutexInstance.lock()
var id = atomicInt[0]
atomicInt[0] = id + 1
mutexInstance.unlock()
return id
数据类型
bool
一个字节,默认为false
int
(同C++和Java long)8个字节,默认为0
float
(同C++和Java double)8个字节,默认为0
String
默认为null,字符串可以存储一系列字符,如 “John Doe”。
Array
数组
var c=[5,"aa",'a']
var d:Array=["12",3,4]
var e=c+["hello"]+d #数组可以直接用'+'拼接
class
对象,从:
开始到pass
结束,里面可以放变量或者方法,总之基本上和java没差
class 对象名:
pass
dictionary
字典var d={0:"a",'a':1}
就是map,’}‘分行需要写最后一个’,’ 否则可以不写
null
,变量没有被赋值,则默认为null
关键字
==所有关键字[https://www.cnblogs.com/empist/p/10198531.html]==
tool
:在编辑器中执行脚本
self
:代指对象自己,类似于java的this
is
: 类型判断a is int
a是int吗,继承的也会返回true
列如self is Node
任何节点都继承Node
as
:a as class
如果可能,将值转换为给定类型
export
:定义变量时的前缀,表示可以在 gd engine里设置
match
:类似于switch
函数
func f1():
#执行代码
pass
#传参时不指定x,y的类型
func f2(x,y):
#执行代码
pass
func f3():
#执行代码
return 123#不用返回值类型,直接返回即可,有return可以不pass
-------------------------------------
#想要指定返回类型也可以
func f3() -> int:
#执行代码
return 321
if…elif…else…结构
if a==1:
a=2
elif a==2:
a=3
else:
a=4
pass
#match 可以不分行,不用写pass
match a:
1:
a=2
2:
a=3
continue#continue表示如果满足了2分支则继续向下匹配
3:
a=4
a=5
# _等价于default
_:
a=7
a=6
#没有pass它是根据缩进来读取的,如此处a=5在match的分支里面,a=6是在match外执行的
循环
while a<2 :
a+=1
print(a)
#后面也可以写数组,取数组的值
#等价于 for i:=10;i<20;i+=2
#只写一个数字(a)的话默认 (0,a,1)
#写了(a,b)的话默认(a,b,1)
for i in range(10,20,2):
print(i)
内存管理(free)
# 创建 节点.new()
# 在继承(extends)时
# 如果不写会默认继承 Reference,当没有引用时会被回收,Reference对象不能手动释放,只能通过 unreference()方法使godot中的引用计数-1,来通知godot来回收
# 如果没有直接或间接继承 Reference(如 Node2D)则需要手动释放(free()或queue_free() free()是立刻free而queue_free()是在帧结束时安全删除)
没有使用且没有 free的节点可以在调试器->监视->object->Orphan Nodes中看到
循环引用
工具类
# A.gd
var b
# B.gd
var a
extends Node2D
const A = preload("res://A.gd")
const B = preload("res://B.gd")
func _ready():
# 此时A\B类中的a,b变量均为 null,没有循环调用所以此时可以正常回收
var a = A.new()
var b = B.new()
# 赋值后a调用了b,b调用了a,所有a,b均无法自动回收
a.b = b
b.a = a
# 此时手动回收掉a,b.a被释放 所以b也可以被回收了
a.unreference()
# b.unreference()#当然多写一句手动回收b也没有问题
在脚本中创建节点
使用之前新建节点中的 myNode为例
extends Node2D
func _ready():
var sprite = Sprite.new()
sprite.set_texture(preload("res://goland.png")) # 载入图片,preload()预加载
sprite.name = "myNode" # 设置 sprite的节点名称为 myNode
sprite.position.x = 200 # 设置坐标
sprite.position.y = 200
add_child(sprite) # 挂载节点
pass
func _process(delta):
var sprite = $myNode # '$'取按名称运行中的节点
sprite.rotate(0.1) # 旋转 0.1个弧度
pass
帧数
设置为100帧 Engine.target_fps = 100
deltaTime:与上一帧的时间间隔,常用于平均化移动
获取节点
# 获取当前节点
var currentNode1 = $"."
var currentNode2 = self
# 获取父节点
var parentNode1 = get_parent()
var parentNode2 = $"../"
# 获取子节点
var subNode1 = $SubNode2
var subNode2 = $"SubNode2"
var subNode3 = get_node("SubNode2")
# 根节点查找法,会返回节点树从上到下找到的第一个节点
var subNode4 = get_tree().root.find_node("SubNode2", true, false)
print(currentNode1.name)
print(currentNode2.name)
print(parentNode1.name)
print(parentNode2.name)
print(subNode1.name)
print(subNode2.name)
print(subNode3.name)
print(subNode4.name)
Owner和Parent
Parent是父节点 使用get_parent()获取
Owner是第一个父节点,如果是根则是null 可以直接Owner使用