Godot-笔记

@@@@@@ 概述

所有游戏资源都是一个节点构成一个树形结构

场景也就是一个个类,本质是一个节点树

最终整个游戏也是一个节点树,包含多个实例化场景。

脚本动作通过信号(观察者模式)实现,和 QT 类似。

帧长度 delta 的理解:这样不同帧率,动画的播放时间都会一致。

Node 相对于面向对象的 Object,所有的东西都是继承下来的。

@@@@@@@ 画布层

对于多个元素需要变换而保持相互独立需要用到 画布层|画布层(CanvasLayer)(CanvasLayer ,在其他数字(整形)图层绘制,不会互相影响。(Viewport 默认在 0 图层绘制,当然也可以做全局画布变换)

/ob/%E3%80%901%E3%80%91%E5%B7%A5%E4%BD%9C%E5%8C%BA/%E8%AE%A1%E7%AE%97%E6%9C%BA/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91%E6%8A%80%E6%9C%AF/%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E/Godot/%E5%AD%A6%E4%B9%A0/2D/assets/Pasted%20image%2020230718153133.png
控制节点的绘制顺序并不一定要用 CanvasLayer。确保节点被正确绘制在“前面”或“后面”的标准方法是操作场景面板中节点的顺序。也许违反直觉,但在视口中,场景面板中较上面的节点会被画在较下面的节点的 _ 后面 _。

@@@@@@@ Viewport

用于显示的区域,可以理解成屏幕能看到的区域。

@@@@@@@ 画布变换

类型

  • 全局画布变换
  • 拉伸变换(坐标会变动,如:算鼠标位置需要考虑变换)

RemoteTransform(如果图层和动画等结构冲突,可以用这个来代替动画的变化而不影响图层渲染顺序)

变换顺序

/ob/%E3%80%901%E3%80%91%E5%B7%A5%E4%BD%9C%E5%8C%BA/%E8%AE%A1%E7%AE%97%E6%9C%BA/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91%E6%8A%80%E6%9C%AF/%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E/Godot/%E5%AD%A6%E4%B9%A0/2D/assets/Pasted%20image%2020230718155116.png

@@@@@@@ 2D 对象

2D 相关对象继承树:

  • CanvasItem
    • Node 2D (2D 元素)
    • Control (UI)

CanvasItem 都是 Viewport 子节点,才能显示。

_draw() 方法重载模式类似 QT 的自定义控件绘制,对应也有 update 方法刷新(本身运行一次就缓存,如果有 update 会重新绘制更新)。

_process 表示每帧的变化处理,可以把 queue_redraw() 放入(对于不同帧会有不同的绘制)。

@export 可以把属性拓展到检查器中,可以进行手动配置(如果是一个自定义控件)。

@@@@@@@ 2D 运动

八向移动

wasd 控制移动方向(归一化构建方向向量)

旋转 + 移动

左右控制旋转,前后控制前进后退

旋转 + 移动(鼠标)

通过看向鼠标控制方向,移动作为平移

点击移动

@@@@@@@ 碰撞检测

可以用向量判断各种几何属性,其中多个多边形碰撞使用分离轴定理(SAT)进行判断。

用点到直线距离来判断是否在多边形内部,多边形法向量要朝外部

@@@@@@@ tool

GD 脚本头部加 tool,可以不用运行场景在编辑器看到变化。

@@@@@@@ 精灵动画

可以使用 AnimatedSprite 控制多张图片动画或精灵表

也可以用 Sprite 纹理控制精灵表,然后再用 AnimationPlayer 添加关键帧进行播放(在 3D 中也会用到)。
advance(0) 可以用于 AnimationPlayer 中间插入动画,相当于 update

@@@@@@@ 动画

动画要和帧一起考虑,本身是一张张的帧。

@@@@@@ 动画

  • 剪纸动画
  • 多边形骨骼动画

复杂动画编排:AnimationTree (可以联系外部软件的动画进行编排)

@@@@@@ 音频

除了纯音频流的播放还有环境的音频流(会考虑到 2D,3D 环境),考虑多普勒效应等。

@@@@@@ 引擎的设计经验

可以看到完整的 OOP 设计实现,如:Node 的抽象。

@@@@@@ 轻量 API

Object:需要手动内存管理

Reference:包含引用计数

Resource:带序列化和反序列化能力

@@@@@@ Backlog

  • 国际化
  • UI
    • HUD
    • load UI
    • Splash/Prefab
  • 着色器
  • GD Native
    • 用原生语言编写
  • 渲染
    • 多分辨率
    • Viewport
  • 插件
  • 优化
  • 平台相关
  • 导出
  • 游戏配置系统
  • 资源管理系统
  • 3D

@@@@@@ 后台加载

ResourceInteractiveLoader,

@@@@@ Godot C++

目前 Godot 有三种原生方式:GDNative(3.x)、C++module、GDExtension(4.x+)

各有个的特点,可以选取使用

可以魔改引擎参考 UE 中 Actor 和 Component 的关系,可以相互调用(Actor 负责调度和调用 Component 的函数,比如 BeginPlay(),Tick() 等。)。

@@@@@ 源码编译

需要考虑到后续裁剪,把不要的模块去掉。

  • 先编译原版,弄清楚 scons
  • 跨平台的设计可以学习

编译系统

模块

Core

1
通用的核心(开发基础)代码,包括预定义类型,内存管理,容器类型,内置的 StringName 类型,数学库。io 抽象, os 抽象,游戏引擎中节点(Node) 和资源(Resource)的一些帮助类等等

os

os 各平台类的基类,singleton。
虚函数

  • set_main_loop,设置一个 OS 的主循环

实现类:

编译
  • 因为这块是直接编译为主程序,所以需要单独链接 obj,和平台相关,很多依赖没有处理,没法链接
内存管理

用内存池管理游戏资源的内存

  • new ("") m_class,用法是来自于 operator new 重载了 new 方法

Drivers

1
驱动?主要是音频,显示相关的跨平台驱动封装

Mian

1
应用生命周期处理类

处理游戏的 Main Loop 循环。
游戏循环
基础循环(外部可能有一些平台相关的,比如用户输入和显示的处理,基础循环是通用的):

1
2
3
4
5
6
7
Main::iteration
    loop:
       计算 fps
       物理引擎 update
       显示引擎 draw update
       脚本引擎 frame process
       音频引擎 update

Main::cleanup:内存数据的清理

step:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*引擎初始化
*
*由各个平台特定的main(argc,argv)调用的几个方法组成。
*为了充分理解engine init,我们应该从平台的main和
*查看它如何调用 Main 类的方法。
*
*初始化通常分 3 个步骤完成(setup2 步骤触发
*通过设置自动进行,或在平台的 main 中手动进行)。
*
*-setup(execpath, argc, argv, p_second_phase) 是所有平台的主要入口点,
*负责所有低级单例和核心类型的初始化,并解析
*命令行参数来进行相应的配置。
*如果 p_second_phase 为 true,它将链接到 setup2() (默认行为)。这是
*在某些平台(Android、iOS、UWP)上禁用,这会触发其第二步
*自己的时间。
*
*-setup2(p_main_tid_override) 注册高级服务器和单例,显示
*启动启动画面,然后注册更高级别的类型(场景、编辑器等)。
*
*-start() 是最后一步,这是命令行工具可以运行的地方,或者是主循环
*最终可以创建并将项目设置付诸实践。这也是那里
*创建编辑器节点(如果相关)。
*start() 自己对所描述的命令行参数的子集进行参数解析
*在帮助中,它有点混乱,应该以某种方式通过 setup() 解析进行全球化。
*/

Modules

1
子模块,自定义模块也可放置于此

Scene

1
场景部分代码 2d / 3d /控件等等

Servers

1
系统单列服务:包括显示,音频,物理,VR 等

platform

1
平台相关部分

Editor

1
编辑器部分代码

doc

1
系统对象文档,xml 文件,用于生成文档

test

测试各模块,可以最为测试构建的参考

  • 如何构建测试框架
  • 如何同步拓展测试框架

thirdparty

1
第三方库代码,通常被子模块引用,很多部分根据 Godot 引擎修改过

@@@@@ 引擎拆解

首先要进行 源码编译,把编译部分跑通

  • 并且简单熟悉下引擎结构
  • 巩固 3D 基础,可以理解更多高级图形学概念
  • 积累代码风格,与 Cesium-Unreal 形成对比
  • 剥离 UI 框架,当成应用程序库使用。

拓展:可以考虑不同的裁剪方式应用于不同场景

找引擎定制化的案例,看看别人怎么改造引擎的。

问题:

  • 中文字体载入?

架构

/ob/%E3%80%901%E3%80%91%E5%B7%A5%E4%BD%9C%E5%8C%BA/%E8%AE%A1%E7%AE%97%E6%9C%BA/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91%E6%8A%80%E6%9C%AF/%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E/Godot/attachments/Godot%20%E6%9E%B6%E6%9E%84%E5%9B%BE.png

重组

自己写 main 函数调用初始化流程,载入内容

@@@@@ scons

跨平台的构建工具,基于 Python, 比较现代,和面向未来。

SConstruct 开始然后到各个子模块的 SCsub 中,因为基于 python 所以语法统一,可以随便修改构建源码。

拓展:了解 Scons 源码和架构。

@@@@@ Shader

着色器程序的源代码,着色器是并行在 GPU 上运行的程序,处理显示的最终效果

  • 可以优化渲染速度,利用 GPU 并行计算
  • 实现特殊效果
  • 像素级运算
  • 需要很多图形学知识, 各种有具体意义的数学运算

可以先从积累各种所需效果开始熟悉 shader

注意点

  • 不要隐式转换
  • 全局数组要常量声明
  • 不可重载

原理

如何运作?

  • 如何处理到一个像素中

首先要从 3D 渲染的开头开始说,最早是只有贴图的概念,即屏幕渲染一张图片,通过变化图片产生动画。
后来就有了即时渲染的需求,即需要实时渲染 3D 模型到 2D 屏幕上(计算机图形学)。
因为计算重复可并行就产生了图形显卡的产品,把这部分工作流水线放到 GPU 中。

  • 3D 模型本质是一个多边形建模,呈现的效果是通过 “渲染” 这个操作得到的。
  • 在 2D 屏幕上 z 轴成为了深度的概念,也就是 ViewPort 所看到的平面。
    • 本质是把 POV 的视野投影在 ViewPort 的平面上,然后 ViewPort 在屏幕上进行输出
      • 所以超出 POV 范围的顶点会被去掉,并且顶点之间也需要进行深度测试(判断其 z Value 的顺序)
      • 因为屏幕有分辨率的说法,所以还要对图形进行光栅化上色等

拓展:Unified Shader?统一了顶点和片元着色器,GPU 中不做划分,提高并行效率(全栈)

固定管线和可编程管线

光照模型等一些图形学算法是固定的,没法自定义,只能配置参数,效果上就不会那么灵活。

技巧


范围暗示:
uniform <type> <name> : hint_range(<min>, <max> [, <step>]);
可以优化编译,按时变量属于哪些范围


@@@@@ GPU

Tesla

/ob/%E3%80%901%E3%80%91%E5%B7%A5%E4%BD%9C%E5%8C%BA/%E8%AE%A1%E7%AE%97%E6%9C%BA/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91%E6%8A%80%E6%9C%AF/%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E/Godot/attachments/Tesla%E6%9E%B6%E6%9E%84.png

说明:

  • Host Interface:外包公司的大包工头,负责收发来自甲方 CPU 的各种订单(多是枯燥无味重复单调,需要大量廉价劳动力,CPU 自己不屑于干、做不来的活)并处理 GPU 在各种订单间的上下文切换;还负责获取来自内存等待加工的原材料(顶点数据、纹理数据、各种 buffer 等),将其存放到仓库(显存)中。
  • Input Assembler:负责将甲方给的顶点数据进行简单组装(根据顶点索引与图元类型装配),并搭配上属于它们的顶点属性,才能传给 Vetex Work Distribution。
  • Vertex、Pixel、Compute Work Distribution:三个小包工头,负责将自己领域更加具体的工作分发给底下一大堆流水线工人们去做(从名字就可以看出,他们分别分发顶点、片元和计算着色器的任务)。
  • TPC(Texture Processing Clusters):这个名字比较令人困惑,里面有一个纹理单元和两个负责计算的 SM(Streaming Multiprocessor)。都是干活的生力军,上面三个小包工头的活全都是它们完成的,从这里就可以看出所谓的统一的硬件结构是指什么了。这个结构下一章会具体展开,先让我们记住这个啥都能做的“全栈工程师”。
  • Viewport/clip/setup/raster/zcull block:顶点着色器处理完,只是输出一堆裁剪坐标(还未透视除法)和一堆与其一起等待光栅化插值的属性,这个模块就是负责这些,流水线中到目前为止都未开放编程的固定功能部分。
  • ROP(Raster Operations Processor):除了格子间里流水线上的工人,身为码头工人的 ROP 也是任劳任怨的好员工。他们负责对片元着色器处理后的像素进行测试和装箱:同一个像素位置的深度/模板测试和写入、颜色混合、抗锯齿都由它完成。因为片元着色器处理一个位置的片元时,并不知道该位置其他片元的信息,所以需要有这么一位码头工人在最终将产品(像素颜色)发往仓库前,做最后的统筹合并修缮工作。
  • L2 Cache、Memory Controller 和 DRAM:从图上可以看到,每个仓库(DRAM)搭配一个码头(图中未标的 Memory Controller)、码头临时库存(L2 Cache)和一个码头工人(ROP),共有 6 组这样的搭配,每个搭配对应着显存六分之一的物理地址。格子间 TPC 里并行工作着的 SM 们发来了一条又一条的数据吞吐请求,会在码头集中处理,这些请求可能会被合并,并根据优先级从仓库获取数据以实现数据传输效率的最大化;被码头工人最终装箱完(颜色混合、硬件抗锯齿)的像素数据也会被码头发往仓库中的帧缓冲。

@@@@@ 游戏循环

/ob/%E3%80%901%E3%80%91%E5%B7%A5%E4%BD%9C%E5%8C%BA/%E8%AE%A1%E7%AE%97%E6%9C%BA/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91%E6%8A%80%E6%9C%AF/%E6%B8%B8%E6%88%8F%E5%BC%95%E6%93%8E/Godot/attachments/%E6%B8%B8%E6%88%8F%E7%9A%84%E4%B8%80%E5%B8%A7.png

考虑到计算机是尽可能快的进行计算,如果要抱持游戏时间恒定,则要计算每次经过时间片段 delta。
可选:使用 sleep 等待到一帧,节省功耗。

不稳定性:
但是因为浮点计算有精度问题,多次计算可能会丢失精度,导致根据 delta 计算的游戏逻辑数值有偏差

所以进一步分开渲染时间,和游戏计算时间:

  • 使用一个时间段变量计算游戏时间慢计算机时间多少,如果超出一帧长度,就进行一次循环,直到整个时间小于一帧时间。这样保证了 update 这个更新函数的稳定性和不同机器上的时间一致性。
  • 但是也会引出渲染出现间断的问题,即在一次渲染前后的 update 发生了明显的变化,会导致渲染出现间断跳跃的现象。
  • 但是这已经把渲染时间和游戏时间分开了。

渲染(预测)插值:

  • 只要在渲染加上间隔时间 delta = 差额时间/一帧长度,渲染就能知道游戏时间和显示时间的差距(游戏时间保持为 delta = 0)
  • 这样只要把需要渲染的游戏动作,乘上这个比值就能得到应该正确渲染的时间,比如 0.5 帧。这保证了渲染和游戏逻辑的一致性并且在不同机器上能同步。
  • 有点类似服务器游戏的逻辑同步。