Godot

Godot

——All in godot

图片处理

图片切割

==直接切割==:在ps之类的软件裁剪了再传进godot

==间接切割==:拖入所需图片素材–>在右侧边栏的Region下的Enabled勾选上(以切割形式打开)–>在下边栏的纹理区域选择所需要的部分(在吸附模式中选择鼠标选取或自动选取),==注==:间接切割并非真正的切割,只是显示隐藏其他部分,在设置中随时可以关闭这个切割或是重新切割,在间接切割后右边栏的Rect记录的是在图片中的相对位置,x\y\w\h分别表示 x相对坐标\y相对坐标\截取宽度\截取长度

图片合成

在左下角文件区域复数选择需要合成的图片

在左上角合成为:TextureAtlas->Atlas Files 选择新合成的文件名->重新载入即可

节点

新建节点

在左下文件系统中创建一个脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 继承Node2D节点
# 如果不写会默认继承 Reference,当没有引用时会被回收
# 如果没有直接或间接继承 Reference则需要手动释放(free)
extends Node2D

# 对应的class名,后面的图片对应节点的图标
class_name myNode,"res://goland.png"

# export 可设置属性
export var a:int
export var b:String

func _ready():
	print(b)
	pass # Replace with function body.

保存脚本后在新建该节点即可看到该节点的设置,运行后会打印设置的字符串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) 每帧调用,用于游戏视窗的实时更新

==其他函数及执行时间==

godot_lifecycle

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 inta是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使用

updatedupdated2023-02-072023-02-07