Godot 开源游戏引擎

本贴最后更新于 370 天前,其中的信息可能已经时移世异

image

基础操作

场景和节点

每当创建一个脚本,就要将它添加到一个节点,节点是一个具有属性和方法的类。

内置函数都是下划线开头:_ready()初始化​,_process()每一帧运行一次

编辑器界面

窗口右上角调整窗口位置。右侧窗口是节点属性

场景和资源

场景:Scene,每一个节点树都可以是一个场景

资源:Asset,直接拖入

场景视图和游戏视图

鼠标滚轮​放大缩小场景,中键​平移场景

网格 shift+G打开网格吸附​:每一个大网格包括 8*8​的小网格,每个小网格包括 8*8​的像素

设置运行长宽比:项目设置 > 窗口

游戏对象(节点)

把图片拖到场景中,会自动创建一个节点,同级节点名称不能重复

坐标系

原点在视图左上角,向右为 +x 轴,向下为 +y 轴

一个像素表示多大可以自己定,比如一个像素代表一米

相对坐标系:节点的初始位置始终在父节点,godot 都是相对坐标系,就算是根节点坐标也是相对于全局坐标(世界原点)

节点的基础操作

添加节点:ctrl+A

移动:alt+左键​,按住 shift​可以限定轴向

缩放:ctrl+alt+左键

旋转:ctrl+左键

轴心点工具设置所选节点轴心位置:v

多节点选择工具:alt+右键​当几个节点重合时选择想要的节点

平移模式:G

尺子工具:R​测量角度长度等

智能对齐和锁定节点

智能吸附:shift+S​以轴心点对齐

网格对齐:shift+G

锁定工具:ctrl+L​,但是父节点移动还是会影响

子节点不可选中:ctrl+G​,取消 ctrl+shift+G

节点的父子关系

父对象移动时子对象随之移动,也随之旋转

节点的显示和隐藏

小眼睛

游戏节点的显示顺序

在 2d 画面中,重叠的节点显示顺序时按照节点的顺序。在数据结构上的专业术语叫树的广度优先遍历,广度优先遍历的顺序就是最后显示的顺序(下面的节点显示在最上面)

轴心点 Pivot

也叫中心点,以中心点来操作物体。默认在对象的几何中心

锚点 Anchor

是一个点,锚点描述的是一个对象的 Margin,相对与锚点的坐标,主要用在 GUI 中描述子节点相对于父节点的位置是。

图片的切割

属性 > 区域 > 启用 > 编辑区域

图片的合并

在导入模块下,选择合并模式

节点和功能的关系

不同的节点表示不同的功能,子节点是继承于父节点,拥有父节点的功能

比如 Sprite​节点的功能就是显示图片

Node 节点

注释,可以挂载脚本。

模式:控制节点在暂停的时候 process 的表现。默认是继承,脚本运行会从上到下遍历执行。还有其它模式

process 优先级:数值越小优先级越大

Canvasltem 节点

  • Canvasltem 节点,Canvasltem -> Node
  • Canvas 是画布的意思,所以 Canvasltem 代表了就是可以被绘制节点,可以设置可视化界面和材质的颜色
  • 所有的 2D 节点和 GUI 节点都继承于 Canvasltem 节点
  • Canvasltem 是按树的树的深度优先遍历顺序绘制的
  • 可以通过属性设置改变最终染到屏幕上的画面顺序

Texture 贴图:附加到物体表面的贴图

Material 材质:物体的质地,透明度等

shader 着色器:使用代码来渲染图形的技术

自定义节点

  • 创建脚本,叫 my_Node,继承 Node2D 节点,并自定义节点的 icon。

    extends Node2D
    class_name MyNode, "res://icon.png"
    
    export var a = 1
    export var b: String
    
    func _ready( ):
        print(b)
    
  • 编写脚本,定义变量,编写代码。

  • 添加刚才创建的自定义节点。

节点的继承

通过继承节点来扩展功能,并且在脚本中修改功能

脚本的定义

用代码控制逻辑,每当创建一个脚本,就要将它添加到一个节点,节点是一个具有属性和方法的类

脚本的使用

大部分都继承 Node2D 节点就足够了

常用默认函数内部执行顺序:_init​, _ready​, _process

  • _init()​脚本初始化的时候调用,对象的构造器
  • _ready()​开始调用一次,可用于初始化脚本
  • _process(delta)​每帧调用,帧间隔不等,可用于更新游戏
    delta 是每帧调用的间隔

导出变量

export 关键字可以让变量在编辑器属性中编辑

export var a = 1

# 导出一个数字
export var a = 1
# 导出一个节点路径
export var b:NodePath
# 导出一个节点路径,不同的写法
export(NodePath) var c
# 导出一个文件路径
export(String,FILE) var e
# 导出一个文件路径,以txt结尾
export(String,FILE,"*.txt") var d
# 导出一个资源文件路径
export(Resource) var f
# 导出一个颜色
export(Color,RGB) var g

静态变量和静态方法

const a = 42

static func kun():
    return 42

调试工具 Debugger

性能统计 Profile

监视器 Monitor

内存管理 free

GDScript

数据类型和字面值

变量

变量是一种工具可以为你想要在代码中存储并修改****的值起一个名字。例如,角色的血量:角色受到攻击时,你希望血量减少。受到治疗时,你希望血量增加。你可以创建一个名叫 health 的变量来代表血量。此后,你每次在代码中写下 health 关键字,计算机都会为你从内存中获取对应的值。变量的原理类似于超市里的货品标签。他们是你为某些值附加的名称。你随时都可以把这个标签撕下来贴到其他新的货品,也就是新的

值上。

变量定义:var​开头

# 变量命名方法

var a = 1
var _1 = 12
# 必须以字母或者下划线开头
# 变量名的其它元素也必须是字母、下划线、数字
  • 在 GDS 中,你可以创建自定义的变量,也可以使用内置代码中的变量。
  • 变量可以进行计算与转换。
  • 即使是同一脚本文件,绑定不同的对象从而产生了同名的变量,这两个变量以及变量代表的数据也分别属于不同的对象,互相独立。因此使用变量前必须指明变量所属的对象。
    func _enter_tree():
    self._1 = self._1 + self._1
    print(self._1)
    这里的 self.​可以省略,但是当多个对象的变
  • 量互相计算获取,必须要知名对象。

四种基本变量的定义、计算与转换
整形:int
浮点型:float
字符串:String
布尔:bool

成员变量是附加在特定值类型上的变量,类似向量的 x 和 y 子变量。我们也管它们叫属性,或者向量的字段。
要访问成员变量,你必须先写出这个值的名称,然后跟上访问操作符(.)。例如,position.x。

函数

函数是您可以随时执行的代码包。要定义一个函数,您需要编写 func 关键字、函数名称、括号和冒号:​ func run():​ ​​​定义了一个名为 ​ run() ​​​的函数。然后转到下一行来编写函数体,就是该函数中所包含的指令。

  • 函数的概念

    1. 函数也包括自定义函数和内置函数
    2. 大部分函数可以对游戏内容产生实质性影响或者在
    3. 代码中起到传递数据的作用。少部分函数比较特别。
    4. 一个函数除名称外,还包括了**【输入信息、处理流程、处理结果】**等关键信息。在程序中,一切数据的处理流程都要通过调用函数实现。
    5. 函数调用一般要指明调用对象。
  • 内置虚函数的调用

    1. 虚函数是指没有实际处理流程的函数,这部分函数的内容可以由我们自行编写。而内置虚函数则是指 Godot 的制作组规定了特定名称却没有实际处理流程的函数。节点内的虚函数会在特定的条件下自动被触发。
    2. 条件 1:当节点本身或其周围节点状态发生改变时。【节点创建、节点入“树”、节点出“树”、节点死亡前、节点的子节点全部加入场景树
    3. 条件 2:当场景树的状态发生改变时,且节点自身处于“树”下。【画面刷新、物理引擎刷新、硬件设备输入】
    4. 常用虚函数:_init,ready,_process,_physic_process
  • 函数中的变量

    1. 函数中也可以定义变量,各个函数都拥有一个独立的作用域,在一个函数内部定义的变量无法在另一个函数中直接访问变量名。
    2. 在函数运行结束后变量和变量所代表的数据会自动销毁。某些情况下,变量所代表的数据不会自动销毁,(后续教程提及)但变量一定会自动销毁。

if 条件语句和 while 循环语句

起初,不需要经常使用 while 循环。甚至我们在这里展示的代码也有更有效的替代方紧。此外,还有一种更安全的循环,for 循环。然而,while 循环具有重要的中级到高级用途,因此至少需要知道它们的存在以及如何使用它们。每次我们需要循环未知次数时,我们都会使用 while 循环。例如,游戏循环运行,通常每秒生成 60 张图像,直到用户关闭游戏。这要归功于 while 循环。
while 循环还有其他很好的用途:
逐行读取和处理文件,如文本文档。
处理源源不断的数据流,例如有人用麦克风录制音频。
阅读代码并将其转换为计算机可以理解的指令。
各种中级到高级算法,例如为 AI 寻找地图周围的路径

  • 使用 if 可以在函数中设置情况分支,起到控制数据处理流程的作用。
  • elif 是 if 的补充,一个 if 可以有无数个 elif 对其进行补充。
  • 当所有的 if 与 elif 判断失败时,程序执行 else 内部内容。一个 if 后只能有一个对应的 else。
  • 一个 if 与它对应的 elif 以及 else 都有各自独立的作用域,作用域内定义的变量互相独立。

for 循环语句和数组

数组是一组值。在 GDScript 中,数组可以包含任意类型的值,创建数组的方法是将值写在方括号中,值与值之间用逗号分隔:var three_numbers = [1,2,3]。在游戏中,我们会一直用到数组:保存组队的角色、背包中的道具、解锁的法术等等。哪里都有数组的身影。数组是计算机编程中的一种基础值类型。几乎在任何编程语言中都能找到数组。

  • 数组是一组数据的集合。在程序中负责批量处理数据。

  • 数组中的元素可以包括各个类型的数据,也可以对数组内数据类型进行限定。

  • 可以通过 数组名【数字】 的形式来访问数组元素,数字 0 代表数组的第一个元素。

  • 数组可以通过调用函数来增加或去除内部数据。可以使用 size()来获取数组内元素总数。

  • 数组是引用变量

  • 引用变量:执行 变量 A=变量 B 这段代码时,变量 A 不会重新创建一个与 B 变量值相同的数据。对于 A 变量的修改可以影响到 B。

  • 使用 for 可以快速遍历数组

  • while 的使用方法

    1. while 与 if 写法一致,但没有 elif 与 else
    2. 当 while 内部模块执行完毕时,程序会再次判断 while 是否成立,若成立则会继续执行 while 内容。
  • while 的注意事项

    1. 避免死循环。要设置破坏循环条件的代码。
    2. while 也会产生一个作用域,效果同 if。

语言概括

变量

GD 是动态类型语言,一个变量可以为任何数据类型,并且随时转换。

动态类型语言 200 行代码内,动态语言写非常舒服,秒杀静态语言 500 行不太舒服,1000 行代码,文字注释的部分需要 500 行。文字注释比代码本体还要长,如果没注释,看不懂代码了为了解决动态语言的缺陷,godot 提出静态类型,简单理解成指定类型。

变量定义语法:var a = 1​,命名变量不能数字开头

指定数据类型:var a: int = 2
在 GDScript 中,赋值给变量类型的值必须具有兼容类型。如果需要强制某个值为特定类型,请使用强制转换运算符 as,例如
var a: int = 'kun' as int

null​空值

integer​是表示数学整数范围的数据类型
在 Godot,integer 的有效范围为:-9223372036854775808-9223372036854775807

常量

语法:const a = 1

常量是只读的,因此声明和赋值后不允许修改

不允许对常量进行二次赋值

常量优于字面量,在一些游戏中不会改变的值

确保常量的命名风格在视觉上与变量不同,常量首字母大写

函数

函数也可以有返回值:return

可以指定默认值:func my(a: int = 4) -> int:

函数定义语法:func my(a: int):​​

if 与 while

注释

  • 方法描述注释
    注释可用于解释所使用的函数或方法的用途我们可以使用一些约定俗成的关键字和形式,比如:

    • @param 参数参数述,参数数据类型等

    • @return 返回值描述

      ###############################
      # 比较两个整数,返回比较大的一个
      # @param a 第1个比较数,整型
      # @param b 第2个比较数,整型
      # @return 返回比较大的一个
      ###############################
      
  • 元数据注释
    这些注释位于脚本顶部,可能包括公司名称、文件名、脚本创建年份、维护者姓名、版权等

    """
                      此文件是以下的一部分:
                          GODOT 引擎
                    https://godotengine.org
    ********************************************************
          版权所有(C) 2007-2020 胡安里涅茨基,阿里尔 曼祖尔
    """
    
  • 调试注释
    注释掉 print 语句,print 语句是程序员调试代码的一种常见方式,当调试完成后,往往会将其注释掉,以便以后再次使用

    if a = 10:
        #print(x)
    
  • 代码描述注释
    通常用于让其他人理解代码行的意图,此类注释应该仅在需要时使用(而不是滥用)

基础

GDScript 是一种动态类型的脚本语言,专门为免费开源游戏引擎 Godot 制作。 GDScript 的语法类似 Python。 它的主要优点是易于使用和与引擎深度集成。 它非常适合游戏开发。

# 单行注释使用 # 号书写。
"""
  多行
  注释
  是
  使用
  文档字符串(docstring)
  书写。
"""

# 脚本文件本身默认是一个类,文件名为类名,您也可以为其定义其他名称。
class_name MyClass

# 继承
extends Node2D

# 成员变量
var x = 8 # 整型
var y = 1.2 # 浮点型
var b = true # 布尔型
var s = "Hello World!" # 字符串
var a = [1, false, "brown fox"] # 数组(Array) - 类似于 Python 的列表(list),
                                # 它可以同时保存不同类型的变量。
var d = {
  "key" : "value",
  42 : true
} # 字典包含键值对。
var p_arr = PoolStringArray(["Hi", "there", "!"]) # 池数组只能包含单一类型。
                                                  # 放入其他类型会被转换为目标类型

# 内置向量类型:
var v2 = Vector2(1, 2)
var v3 = Vector3(1, 2, 3)

# 常量
const ANSWER_TO_EVERYTHING = 42
const BREAKFAST = "Spam and eggs!"

# 枚举
enum { ZERO, ONE , TWO, THREE }
enum NamedEnum { ONE = 1, TWO, THREE }

# 导出的变量将在检查器中可见。
export(int) var age
export(float) var height
export var person_name = "Bob" # 如果设置了默认值,则不需要类型注解。

# 函数
func foo():
  pass # pass 关键字是未书写的代码的占位符

func add(first, second):
  return first + second

# 打印值
func printing():
  print("GDScript ", "简直", "棒呆了")
  prints("这", "些", "字", "被", "空", "格", "分", "割")
  printt("这", "些", "字", "被", "制", "表", "符", "分", "割")
  printraw("这句话将被打印到系统控制台。")

# 数学
func doing_math():
  var first = 8
  var second = 4
  print(first + second) # 12
  print(first - second) # 4
  print(first * second) # 32
  print(first / second) # 2
  print(first % second) # 0
  # 还有 +=, -=, *=, /=, %= 等操作符,但并没有 ++ 和 -- .
  print(pow(first, 2)) # 64
  print(sqrt(second)) # 2
  printt(PI, TAU, INF, NAN) # 内置常量

# 控制流
func control_flow():
  x = 8
  y = 2 # y 最初被设为一个浮点数,
        # 但我们可以利用语言提供的动态类型能力将它的类型变为整型!

  if x < y:
    print("x 小于 y")
  elif x > y:
    print("x 大于 y")
  else:
    print("x 等于 y")

  var a = true
  var b = false
  var c = false
  if a and b or not c: # 你也可以用 &&, || 和 !
    print("看到这句说明上面的条件判断为真!")

  for i in range(20): # GDScript 有类似 Python 的 range 函数
    print(i) # 所以这句将打印从 0 到 19 的数字

  for i in 20: # 与 Python 略有不同的是,你可以直接用一个整型数开始循环
    print(i) # 所以这行代码也将打印从 0 到 19 的数字

  for i in ["two", 3, 1.0]: # 遍历数组
    print(i)

  while x > y:
    printt(x, y)
    y += 1

  x = 2
  y = 10
  while x < y:
    x += 1
    if x == 6:
      continue # continue 语句使 x 等于 6 时,程序跳过这次循环后面的代码,不会打印 6。
    prints("x 等于:", x)
    if x == 7:
      break # 循环将在 x 等于 7 处跳出,后续所有循环不再执行,因此不会打印 8、9 和 10

  match x:
    1:
      print("match 很像其他语言中的 switch.")
    2:
      print("但是,您不需要在每个值之前写一个 case 关键字。")
    3:
      print("此外,每种情况都会默认跳出。")
      break # 错误!不要在 match 里用 break 语句!
    4:
      print("如果您需要跳过后续代码,这里也使用 continue 关键字。")
      continue
    _:
      print("下划线分支,在其他分支都不满足时,在这里书写默认的逻辑。")

  # 三元运算符 (写在一行的 if-else 语句)
  prints("x 是", "正值" if x >= 0 else "负值")

# 类型转换
func casting_examples():
  var i = 42
  var f = float(42) # 使用变量构造函数强制转换
  var b = i as bool # 或使用 as 关键字

# 重载函数
# 通常,我们只会重载以下划线开头的内置函数,
# 但实际上您可以重载几乎任何函数。

# _init 在对象初始化时被调用。
# 这是对象的构造函数。
func _init():
  # 在此处初始化对象的内部属性。
  pass

# _ready 在脚本节点及其子节点进入场景树时被调用。
func _ready():
  pass

# _process 在每一帧上都被调用。
func _process(delta):
  # 传递给此函数的 delta 参数是时间,即从上一帧到当前帧经过的秒数。
  print("Delta 时间为:", delta)

# _physics_process 在每个物理帧上都被调用。
# 这意味着 delta 应该是恒定的。
func _physics_process(delta):
  # 使用向量加法和乘法进行简单移动。
  var direction = Vector2(1, 0) # 或使用 Vector2.RIGHT
  var speed = 100.0
  self.global_position += direction * speed * delta
  # self 指向当前类的实例

# 重载函数时,您可以使用 . 运算符调用父函数
# like here:
func get_children():
  # 在这里做一些额外的事情。
  var r = .get_children() # 调用父函数的实现
  return r

# 内部类
class InnerClass:
  extends Object

  func hello():
    print("来自内部类的 Hello!")

func use_inner_class():
  var ic = InnerClass.new()
  ic.hello()
  ic.free() # 可以自行释放内存

访问场景树中其他节点

extends Node2D

var sprite # 该变量将用来保存引用。

# 您可以在 _ready 中获取对其他节点的引用。
func _ready() -> void:
  # NodePath 对于访问节点很有用。
  # 将 String 传递给其构造函数来创建 NodePath:
  var path1 = NodePath("path/to/something")
  # 或者使用 NodePath 字面量:
  var path2 = @"path/to/something"
  # NodePath 示例:
  var path3 = @"Sprite" # 相对路径,当前节点的直接子节点
  var path4 = @"Timers/Firerate" # 相对路径,子节点的子节点
  var path5 = @".." # 当前节点的父节点
  var path6 = @"../Enemy" # 当前节点的兄弟节点
  var path7 = @"/root" # 绝对路径,等价于 get_tree().get_root()
  var path8 = @"/root/Main/Player/Sprite" # Player 的 Sprite 的绝对路径
  var path9 = @"Timers/Firerate:wait_time" # 访问属性
  var path10 = @"Player:position:x" # 访问子属性

  # 最后,获取节点引用可以使用以下方法:
  sprite = get_node(@"Sprite") as Sprite # 始终转换为您期望的类型
  sprite = get_node("Sprite") as Sprite # 这里 String 被隐式转换为 NodePath
  sprite = get_node(path3) as Sprite
  sprite = get_node_or_null("Sprite") as Sprite
  sprite = $Sprite as Sprite

func _process(delta):
  # 现在我们就可以在别处使用 sprite 里保存的引用了。
  prints("Sprite 有一个全局位置 ", sprite.global_position)

# 在 _ready 执行之前,使用 onready 关键字为变量赋值。
# 这是一种常用的语法糖。
onready var tween = $Tween as Tween

# 您可以导出这个 NodePath,以便在检查器中给它赋值。
export var nodepath = @""
onready var reference = get_node(nodepath) as Node

信号(Signals)

信号系统是 Godot 对观察者编程模式的实现。例子如下:

class_name Player extends Node2D

var hp = 10

signal died() # 定义一个信号
signal hurt(hp_old, hp_new) # 信号可以带参数

func apply_damage(dmg):
  var hp_old = hp
  hp -= dmg
  emit_signal("hurt", hp_old, hp) # 发出信号并传递参数
  if hp <= 0:
    emit_signal("died")

func _ready():
  # 将信号 "died" 连接到 self 中定义的 _on_death 函数
  self.connect("died", self, "_on_death")

func _on_death():
  self.queue_free() # 死亡时销毁 Player

类型注解

GDScript 可以选择性地使用静态类型。

extends Node

var x: int # 定义带有类型的变量
var y: float = 4.2
var z := 1.0 # 使用 := 运算符根据默认值推断类型

onready var node_ref_typed := $Child as Node

export var speed := 50.0

const CONSTANT := "Typed constant."

func _ready() -> void:
  # 此函数不返回任何东西
  x = "string" # 错误!不要更改类型!
  return

func join(arg1: String, arg2: String) -> String:
  # 此函数接受两个 String 并返回一个 String。
  return arg1 + arg2

func get_child_at(index: int) -> Node:
  # 此函数接受一个 int 并返回一个 Node
  return get_children()[index]

signal example(arg: int) # 错误!信号不能接受类型参数!

节点

2D

ColorRect:在 Godot 引擎中,ColorRect 节点主要用于显示简单的矩形颜色块。
它的主要作用包括:

  1. 显示固定颜色的矩形区域 ColorRect 节点有 color 属性,可以直接设置要显示的矩形区域颜色。这提供了一个简单快捷的方式来显示色块。
  2. 作为子节点实现装饰效果可以将 ColorRect 作为控件的子节点添加进去,来为控件添加背景色、边框、装饰等图形效果。
    1. 作为图形容器使用 ColorRect 本身可以作为一个容器来组织其它控件,然后同时应用颜色和矩形区域效果。
    1. 实现简单的动画可以通过脚本改变 ColorRect 的 color、rect_size、rect_position 等属性来实现简单的动画效果。
    1. 占位和分割空间由于其固定大小的特点,ColorRect 也可以用来作为界面元素的占位符或空间分割符。
    1. 遮罩效果调整 ColorRect 的颜色和透明度可以实现遮罩层效果。
  3. 总之,ColorRect 是 Godot 中一个用途广泛的基础 UI 节点,可以用来增强界面并实现各种简单图形效果。充分利用它可以提高开发效率

Area2D:Area2D 是一个非常重要的碰撞、空间分区节点,通过使用它可以大大简化游戏中的物理交互逻辑。如碰撞检测、视野范围判定等,都可以通过 Area2D 来非常直观地实现

3D

4 操作
ZanMei 在 2023-11-20 18:11:28 更新了该帖
ZanMei 在 2023-11-20 18:11:06 更新了该帖
ZanMei 在 2023-11-20 16:54:37 更新了该帖
ZanMei 在 2023-11-20 16:54:01 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...