从此

Godot游戏引擎、游戏编程开发、开源免费3D手游(Blender作品Dogwalk)


综合/最新

游戏编程 | 动画设计 | 状态机 | Godot C#/Mono | Godot官方文档墙内文档 | Godot源码 | Godot Shaders 着色器 | 产品发布检查清单 | 游戏开发专题 | 虚幻

新: Gemini 已支持上传图片在AI改动时,保持人脸一致性,利好游戏美工。 Blender 用 Godot 制作的游戏 - https://studio.blender.org/projects/dogwalk/ Godot v4.6 - 支持C#导出Web游戏 v4.5 - Android导出允许>=Java17;无需rcedit工具修改icon了; Blender importer -> Scene Graph ✔ Geometry Nodes Instances FoldableContainer 类似HTML <details> 标签; BoneConstraint3D OS.get_version_alias() 输出 Android 和 SDK 版本。 新增更全面复制的 duplicate_deep() 方法。 支持 Android 15 的 16KB 内存页面(page size)。 运行时自定义日志Logger用法 v4.4 - AndroidRuntime + JavaClassWrapper 总: Android导出:降级 JDK 版本 export/android/java_sdk_path 至 max=v23/zip 解决 - Unsupported class file major version 69. 理论知识: 向量(Vector),具有方向的数值(点根据方向连成的线)。 四元数(Quaternion) - 简单理解就是虚拟个球体,然后旋转并计算最终形态,W值为绕柱归一化,可以解决万向节死锁及操作次序问题。 投影(Project)可以简单理解为 - 拍扁到切平面,去除3D物体的升降值;Godot提供的投影方法 Vector3.Project、Vector3.Slide(等效其他引擎的Vector3.ProjectOnPlane)。 至少3个像素点(3D为3个“顶点”)才能确定切线(切平面),通过两侧点位的中心差分(非均值)来决定切线斜率,确定后就能找到垂直于其 90°的法线(normal line);通俗来说就是“找平”。

制作实务(直球+冗余)

综合:
  3D视图中,鼠标中键默认是旋转,同时按住Shift则为平移。
  模型单位为厘米,1米为一个方格,约定面向 +Z 轴(Blender一致吗?),中心原点在脚底,物品则在质心,实例化后应让脚底高于地面厚度(避免碰撞挤掉):node.Position = node.Position with { Y = 1.0f };
  以 Godot IDE 前视图(快捷键数字1)为准,跟摄像头一样均面向 -Z 轴,并退后1米距离。
  即Vector3.ModelFront(出眼-Z则用Vector3.Forward),故模型资源也应该用这个入眼朝向;LookAt(...)尾参为true则会使模型的面部朝向目标。
  CharacterBody3D 节点方向应保持后视图,调整3D模型方向来面向摄像头。
  RPG游戏,关卡地图根据原点向外扩展,且地图节点包含Character,角色再包含玩家和NPC节点。

  人型动画的控制,首选开启“Root Motion”模式,完全交由程序灵活控制,越框动画归位的偏移值可用 animation_tree.get_root_motion_scale() 获取,并实时计入变动。简单游戏可直接上原地动画,无需启用根运动模式。
  模型姿势首选T-Pose:动画混合时,左胳膊可沿左上抬至头顶,为了堵住向下穿过身体抬起的穿模,Godot 采用了基于Bone Rest角度的最短路径策略,并强行限制上限为180°,而双臂下垂的A-Pose,举起180°是到不了头顶的。
  摄像头视角、WASD移动

经验:
  单玩家直接通过 Unique Name 访问 %Player;因 root 下挂了 *.tscn 场景,故必须将其写为前缀 GetTree().Root.GetNode("MyTSCN/%Player"); // 同场景相对路径则可直接%起步。
  “新建继承场景”创建的 *.tscn,会显示父场景节点(黄字),但无法删除,只能修改属性。
  animation_tree.root_node 决定骨骼内“唯一名称”的查找范围。
  边走边攻击通常是用并行状态机或层次状态机,结合上下半身骨骼动画混合而成。
  跳起后大多数游戏是允许位移的,楼梯可以做成平滑坡道,就不存在上下时的碰撞问题了。
  滑步 - 如何解决?

编程:
  执行顺序 - _ready() -> _physics_process(delta)
  定位 - 
    取场景全局根节点 GetTree().Root.GetNode(this.GetPath().GetName(1)); 
    或排除“自动加载”(Autoload)节点:(Engine.GetMainLoop() as SceneTree).Root.GetChildren().Where((node) => node.SceneFilePath != String.Empty).FirstOrDefault();
  类型 - 分2种:① Variant;② C# 普通类。
    Variant即继承于Godot.GodotObject的类及String、int等常用类,内部用object? Obj属性存放。
    隐式赋值:Variant x = 2; x = "根据赋值来决定类型"; 
    显式取值: x.As(); // or x.AsString()、(String)x、x.AsInt32(); 不能 x as String; 延后转 x.As() is Node n
GDS 不标注 Variant 也能动态变动类型: extends Node #class_name MyNode # 写上 class_name 即全局类 var x:Variant = 1; # Constructor 👇 GD.Load("res://new_script.gd").New(); func _init() -> void: x = "String" + str(x) print(x) return
标注了[GlobalClass]的全局类会显示在IDE向导。 GodotObject: 存取 GodotObject.InstanceFromId(_GodotObject.GetInstanceId()); // 用于 C# 脚本装配时,对象释放的再获取 _Node.SetScript(_Script); 判null加强版 GodotObject.IsInstanceValid(this); QueueFree()后无需等待生效也能判定 _GodotObject.IsQueuedForDeletion(); 信号 - 分2种:[Signal] + delegate + Variant入参 + 继承Node;C# event + Action + 入参不限。 信号语法 输入: 首帧触发用 Input.IsActionJustPressed("ui_accept"),每帧触发用 IsActionPressed。 3D: Transform3D 的 Origin 定义远近、Basis 定义方位(含缩放);Quaternion 则不含缩放。 入参用弧度居多,可先将度数转为角度:Mathf.DegToRad(-90)。 全局中心点 + 离地1米:GlobalPosition = Vector3.Zero with { Y = Vector3.Zero.Y + 100.0f }; // 或 Vector3.Up * 100.0f; 让Player持续向某方向走:Vector3 direction = Vector3.Forward; 数字越大鼠标转速越快: new Vector2(-_InputEventMouseMotion.Relative.Y, -_InputEventMouseMotion.Relative.X) * 0.003f; // 执行顺序影响最终姿态;XxxObjectLocal(...) 后缀为基于模型自身方向,而非全局。 _Node3D.TranslateObjectLocal(new Vector3(0, 0, -100)); // 离地N厘米 _Node3D.RotateX(Mathf.DegToRad(-90)); // 前后倾倒角度;左右倾倒用RotateZ;贴地转圈用RotateY。 _Node3D.RotateObjectLocal(Vector3.Down, Mathf.DegToRad(180)); // 面向地面(Down)顺时针(正数)旋转 动画: 设置循环和总长度(影响AnimationTree?):_Animation.LoopMode = LoopModeEnum.None; _Animation.Length = 1; 动画树: 流程图从 output 节点向前反查;active=false 全局停止,而 AnimationNodeEndState 则只结束子状态机,并返回父状态机。 AnimationNodeTransition - 类似枚举,手动切换,可后挂多个 AnimationNodeStateMachine 来自动决策。 AnimationNodeStateMachine - 根据连线自动化切换状态,也可调用 travel("StateName") 手动切换。 AnimationNodeOneShot - 手动播放“shot”端口动画后,将回落到“in”端口并持续播放。 骨骼: 3D模型内骨架导入 Godot 后会通过 Skeleton3D 描述,动画用 AnimationPlayer 描述。 将3D模型内嵌动画导出后,其骨骼名通常与骨架一致,可直接导入 AnimationLibrary 驱动。 查看所有骨骼名: player.GetNode("Armature/Skeleton3D").GetConcatenatedBoneNames(); 高级技术:骨架重定向 Node2D/Control: 死记 - Control 的 size 属性是通过落地的 offset_* 存储的,思维模式应转换为锚点加偏移;同时还会被包裹它的 Container 所接管,但不会小于所设值(等效 custom_minimum_size 了),通过代码手动设置后,会在下一帧被覆盖,填充模式则通过 SizeFlags 决定。

内容

约定:
  通用按钮:新游戏(New Game/Play/Start)、重开(Restart)、继续玩(Continue)、暂停(Pause)、恢复(Resume)、退出(Quit/Exit)、设置(Options/Settings)。
    Save/Load(存档/加载)、呼出菜单(Esc/Escape、通常会暂停游戏)

  节点命名模板

特殊:
  游戏资源对象默认全局共享,但可通过“唯一化”或duplicate()方法创建个副本,以免一处修改就影响全部引用处。
  线程异常(属性set中调)可用 _Callable.CallDeferred() 或 _Node3D.CallDeferredThreadGroup("die"); 解决。
  change_scene_to_packed 载入大场景时会闪出默认灰色背景,色调改一致可临时缓解 RenderingServer.set_default_clear_color(color);或彻底解决:
    截图老场景盖住节点树,等新场景异步载入后再撤掉;截图 get_tree().get_root().get_texture().get_image()。

死记:
  节点区别 - RigidBody3D PhysicsMaterial Bounce = 1 则为完全反弹,而 CharacterBody3D 则要手动处理碰撞。
  如果 override 了自定义节点的 _Ready(),则必须先调一下 base._Ready(),重写内置节点则不用。
  AnimationTree的active为true时,其anim_player指定的AnimationPlayer.Play(...)的调用会失效。
  有时模型动画并非原地动画(In-place Animation),为了避免动画跳起越框问题,故可采用固定根骨骼的根运动(AnimationTree root_motion_track/UE5叫EnableRootMotion)方式,同步动画与碰撞体的位移,等同固定了动画中心点,也可以通过动画树 GetRootMotionRotation() 修正方向等。
  Player 整体节点由 MoveAndSlide() 控制,不能为了迁就其模型子节点(Node3D.Rotation等)而改动:
    var n = ((Node3D)player.GetNode("模型节点/不含碰撞体"));
    // 根骨骼指定后,方向可通过 direction.X、direction.Z 加 ± 调整。
    var y = Mathf.LerpAngle(n.Rotation.Y, Mathf.Atan2(direction.X, direction.Z), delta * Speed);
    n.Rotation = new Vector3(n.Rotation.X, (float)y, n.Rotation.Z); // 根据玩家输入方向来控制模型朝向(东西南北)。

  2D项目不需要Camera2D摄像头节点,3D项目则需要Camera3D+环境节点;2.5D俯视角游戏通常采用45%角度。
  TouchScreenButton - 因触摸设备横行,故用其换掉不支持多点触控的Button节点;
    默认无尺寸不显示,必须拖拽个图片(icon.svg)或新建个GradientTexture2D至texture_normal;
    在PC上 GetNode("TouchScreenButton").Pressed += () => GD.Print("Hi!") 启用“项目设置->输入设备->指点->用鼠标模拟触摸”后才会触发。

协作:
  控制方向 - Node3D.Rotation 是 Vector3 类型,X 轴管横向左右⇄,Y 轴管竖向高低⇅,Z 轴管前后远近⤢。
  CharacterBody3D WASD 控制:
    var velocity = Velocity; // 直接修改结构体,就无法进行原始值参与的计算了;初始值为(0, 0)。
    // 施加重力: velocity += GetGravity() * (float)delta; // 或直接用 new Vector3(0, -9.8, 0);
    Velocity = velocity; // 该结构体只是暂存当前帧变动偏移值,还得通过 MoveAndSlide() 进行移动。
    MoveAndSlide(); // 同时,还会将下一帧移动值再次存入 Velocity,以实现平滑等效果。

架构:
  状态机是事件“触发”机制,行为树是循环“轮询”机制。
  游戏主角通常根据用户输入和有限状态机来控制,套招可上分层状态机,NPC则用行为树来实现AI自动化。

  状态机:有限状态机(FSM/状态均平级/以模板 fsm-blend 项目为准)、分层有限状态机(HFSM/支持状态嵌套/死亡状态可中断其他任意状态)。
    AnimationTree 节点 AnimationNodeStateMachine + state_started 信号或 a.TrackInsertKey(a.AddTrack(TrackType.Method),...),只能勉强用到无需_Process回调的NPC角色FSM。

  行为树

编程语言:
  C# 获取类名与Java的this.getClass()不同,使用 GD.Print(this.GetType()); 或 GD.Print(MethodBase.GetCurrentMethod().DeclaringType.Name);
  GodotObject的GetClass()获取的是Godot内置类型(Node等),并非当前对象类型。
  C# Lambda - var 变量可直接赋值无参 Lambda 表达式;若有参必须写上入参类型,想省略则要声明为 Action<string> 或 Func<int, int, int>。
    Callable.From(Pressed) // 直接用方法名 public void Pressed() { GD.Print("pressed"); }
    Callable.From((int index) => GD.Print(index)) // lambda表达式
    Callable.From(delegate { GD.Print("pressed"); }) // 匿名委托
  延迟调用:
    var t = new Timer(); t.WaitTime = 3; t.OneShot = true; t.Autostart = true; // or t.Start();
    t.Connect(Timer.SignalName.Timeout, Callable.From(() => { OS.Alert("timeout."); }));
    p.AddChild(t); // GetTree().CreateTimer(3).Timeout += ()=>{ }; 无需添加但要 await/async。

跨平台:
  JavaClassWrapper.wrap("android.os.Build$VERSION").SDK_INT
  // 功能标签列表 - OS.has_feature("pc"); // 常用的有:mobile、web、windows、linux、macos、ios、debug
  if (OS.HasFeature("android")) { Engine.GetSingleton("AndroidRuntime").Call("getActivity").AsGodotObject(); }
  Engine.GetSingleton("AndroidRuntime").Call("getActivity").AsGodotObject().Call("getPackageManager").AsGodotObject()
    .Call("hasSystemFeature", "com.google.android.play.feature.HPE_EXPERIENCE").AsBool();

  原生对话框:DisplayServer.dialog_show(...) 比 ConfirmationDialog 交互体验更舒服。
    DisplayServer.DialogShow("t", "d", new String[] { "b1", "b2" }, Callable.From((int buttonIndex) => GD.Print(buttonIndex)));

  Android嵌入Godot库特性:
    org.godotengine.godot.utils.DialogUtils.showSnackbar(...)、showDialog(...)
    org.godotengine.godot.utils.Crypt.md5(String str)
    org.godotengine.godot.utils.GodotNetUtils.getCACertificates()
    org.godotengine.godot.utils.ProcessPhoenix - 作为Android Launcher应用时避免重启崩溃

图形卡API:
操作系统 Vulkan 其他API
Windows D3D12
Linux
Android
macOS/iOS/iPadOS ✔(通过MoltenVK模拟至Metal) Metal 3(A13/iOS 16/Godot 4.4+)
Godot IDE 编辑器: C# SDK 下载 - Windows 选 “.NET SDK x64”;macOS 选 “.NET SDK Arm64 (Apple Silicon)”,向导会自动决定安装路径,dotnet --info。 Godot编辑器 - 鼠标双击 macOS 的 Godot_v4.4-stable_mono_macos.universal.zip 会自动解压至当前目录 Godot_mono.app,故最好先将 *.zip 放到最终目录再解压。 导入: 综述 - 通常用Blender建模,导出glTF导进Godot。 建模 - 3D物体轮廓,不带外皮呈现。 材质 - 3D物体外皮的视觉效果,材质(material/光照交互)包含贴图(map/定位贴图),贴图包含纹理(texture/单纯位图);材质通常使用 基于物理的渲染(Physically Based Rendering , PBR)技术。 导出: 手机平台导出证书首选环境变量 或 .godot\export_credentials.cfg 各OS平台导出发布情况: 综述 - 导出模板用的是同一个,Godot IDE则每个平台1个。 Windows - Godot 最新版即可,渲染模式均可。 Android - aarch 架构用 Godot 最新版 + rendering_method="mobile";x86_64用 Godot 最新版 + rendering_method="gl_compatibility",若想用mobile模式只能回退至 Godot 4.4dev3 导出。 iOS - Godot 最新版即可,且必须在macOS中导出,Windows的导出按钮将永远处于置灰状态;“App Store 团队 ID”和“捆绑包标识符”为必填,故Xcode需要登录Apple Account。 测试设备 - iPhone11 iOS最新版;WeTest iOS云真机每分钟1元,起买60分钟,且要有能添加“UDID”至Devices列表的Apple开发者账号。 默认min_ios_version=14.0、driver.ios=metal,但可手动改为库最低支持的12.0 + 勾上 fallback_to_opengl3,导出后删除xcode项目中的MoltenVK库,【不确定】似乎还得 构建个不含Metal/Vulkan的导出模板。 导出模板和编辑器“One-click deploy”按钮可用任何渲染模式部署到真机,而一键部署到iOS模拟器则只能用gl_compatibility模式,即:iOS simulator does not support the Metal/Vulkan rendering driver。 iOS Simulator跑Metal和Vulkan报:Your GPU doesn't support image cube arrays...

3D

综合:

实例:
  3D免费人物模型+完善动画姿势(需登录/免费用) https://www.mixamo.com

2D

综合:
  等轴视图(远近大小一样)是完全在2D设计视图下按瓷砖等比例排列出来,而近大远小的斜视角游戏则是在3D视图的_Process中,以摄像头为中心,遍历所有2D纸片物体,使其rotation朝向摄像头方向,视角旋转也要持续朝着。
  等轴视图(TileSet.TileShape = Isometric/斜45度视图)是在 2D 游戏中制造 3D 效果的显示方法,有时我们也称它为伪 3D 或 2.5D。
  Tilemap(瓦片地图/节点名为TileMapLayer)是指由小方块(Tile瓦片)拖拽组成的二维地图(TileSet)。
  Tile指定为场景(或Atlas纹理) - https://docs.godotengine.org/zh-cn/4.5/tutorials/2d/using_tilemaps.html#id7
  Tile指定自定义数据:
    get_cell_tile_data(cell).set_custom_data(layer, value)
    notify_runtime_tile_data_update()

实例:
  2D等轴视图(需改为TileMapLayer) - https://github.com/godotengine/godot-demo-projects/tree/master/2d/isometric

其他

C# GDExtension 插件 -
  https://github.com/Delsin-Yu/CSharp-Wrapper-Generator-for-GDExtension
  https://godotengine.org/asset-library/asset/3832

游戏模型资源 - https://www.cnblogs.com/Mr147/p/19074904

IState 类未定义在同名类文件 IState.cs 则报? - TryReloadRegisteredScriptWithClass ... An item with the same key has already been added. Key: GameFSM.IState

Godot 4.x起非数字区快捷键也可用了:前视图=Num 1等。

反射常量名 - get_script().get_script_constant_map().keys()