游戏产品制作、标准化命名、项目模板、独游实战、素材导入、IDE导出
编辑器
开发调试:
运行时选中“远程”选项卡下的节点,会在运行视图里显示一个1米的黄色正方体区域。
输出代码位置堆栈:
GD.Print($"[FieldName] 状态变更: {value} | 完整堆栈: {System.Environment.StackTrace}");
var trace = new StackTrace(1).GetFrames().Take(3).Select(f => $"{f.GetMethod().DeclaringType.Name}.{f.GetMethod().Name}");
GD.Print($"[FieldName] {value} 调用链: {string.Join(" < ", trace)}");
OS.HasFeature("editor"); // 判断是否通过 Godot IDE 编辑器启动的游戏。
注意:
Node2D 直接包裹 Control 会导致锚点失效,故应加一层 CanvasLayer 隔离下。
Node3D,Node2D、Control 比 Node 多出一些必要属性,更适合做父容器,后者仅用于无界面的节点(状态机状态节点等)。
着色器:
Shader Parameters 属性暴漏通过 uniform sampler2D noise_texture_2d; 其中改为 sampler3D 编辑器才支持设置 NoiseTexture3D。
游戏标准化
综合
collision_layer - Player设1或4、敌人NPC设2、中性NPC设3。
节点命名
目录命名:
目录名按相关性(非文件后缀)规划 - _docs/README.md、src/sc/Idle.cs、src/scenes/player/Player.tscn、src/scenes/player/Player.cs;公共资源放 res/*.svg。
模型动画至少分下男女 - res://res/3d/animations/humanoid/[male或female]/walk.fbx
2D节点命名:
All - 根节点,用Node3D,Node2D,特征不明显可用Node;通常不挂脚本。
Stage - 主舞台,用Node3D,Node2D;脚本名为Stage.cs;命名备选arena竞技场。
Player - 玩家角色,player.tscn,脚本名为Player.cs
3D(Three-dimensional)节点命名:
说明 - 场景根节点均加 Owner(场景唯一节点路径前最好加个场景Owner:"GameOwner/%Ground") 或 Scope/Domain、Branch、Reuse 后缀。
Entrance.tscn
EntranceOwner - 放个“Play”按钮,GetTree().ChangeSceneToFile("res://Game.tscn");背景图用能调整铺放(stretch_mode属性)的 TextureRect,而非简陋的 Sprite2D。
Game.tscn
GameOwner(or All) - 根节点,用Node3D,Node2D,特征不明显可用Node;通常不挂脚本。
Stage - 主舞台,用Node3D,Node2D;脚本名为Stage.cs;命名备选arena竞技场。
Character
PlayerOwner - 玩家角色,封装至player.tscn,根类型用CharacterBody3D,锁定入眼的全局方位,只负责 MoveAndSlide() 平移,旋转交给子对象PlayerModel,脚本名为Player.cs,挂载至player.tscn内,在当前节点右侧会显示脚本图标;演示暂用跟人类躯体相像的圆柱体(CapsuleMesh)。
Mesh - 或名为Body或Substance;3D模型,内含骨骼动画等,只负责旋转,平移交给Player,Godot tps-demo 就是这种做法。 非可视化节点(CharacterBody3D等)无边界框,但可视化节点(MeshInstance3D等)则有,取其高度:((VisualInstance3D)node.GetNode("Body/GeneralSkeleton/MeshInstance3D")).GetAabb().Size.Y;
RayCast3D - 需Z轴放至Body肚皮之外,否则会被自身碰撞体遮挡。
SpringArm3D/Camera3D - 摇臂避免摄像头嵌入建筑物而看不到Player。
CharacterInvisibleOwner - 状态机等不可见组件;比如耐力值 - Attributes/StaminaManager。
RemoteTransform3D - 【可选】指向 Player 外部的 Camera3D,瞬移场景用;或 RootMotion + at.GetRootMotionRotation() 修改骨骼朝向。
NPCs - 非玩家角色。
HostileOwner - 敌对NPC:放个 AnimationTree 节点,并重用同性别Player等场景的动画树,npc.at.TreeRoot = player.at.TreeRoot; at.anim_player = ../Mesh/AnimationPlayer。
Top - 头顶血条。
PureNPCOwner - 中性NPC,或 Neutral、FairNPC。
PartnerOwner - 友好NPC,或叫 Amiable、Friendly。
NPC - 首个非玩家角色;根据 res://NPC.tscn 动态创建。
Thing - 地面门窗等,用StaticBody3D+子节点CollisionShape3D(设置其shape属性为无限平面WorldBoundaryShape3D),由于物体多,故根节点类型用Node3D隔离下;
Ground - StaticBody3D 可视化 StandardMaterial3D albedo_texture GradientTexture2D fill Square;或将 icon.svg 拖入其 MeshInstance3D Mesh PlaneMesh material 属性中;
地面用土色 new Color(0.5f, 0.4f, 0.2f);Door - 门窗,用AnimatableBody3D。
Grass - 可用 QuadMesh(或修改 PlaneMesh.orientation) 制作。
System
Camera3D - 摄像头,2D用Camera2D。
WorldEnvironment - 光照。
DirectionalLight3D - 可通过 light_energy 调整明暗度。
HUD - Heads-up display,用CanvasLayer(通过Layer数字决定是否为最外层/跟节点出现顺序无关)或Node节点;Node2D会导致Control锚点失效。
VJ/LeftVJ - 虚拟摇杆
Surface/SurfaceOwner - 最外层全屏窗口,用CanvasLayer;或用 Outermost;暂停和恢复游戏:GetTree().Paused = true; // 豁免 GetTree().Root.GetNode("All/Surface").ProcessMode = ProcessModeEnum.Always;
AnimationTree 上下半身分离(未上ActionContex前):
tree_root - AnimationNodeBlendTree output ->
OneShot in -> Motion idle/walk -> AnimationNodeAnimation
OneShot shot -> AnimationNodeAnimation attack
OneShot 编辑筛选器 -> Spine 及其 子节点全部勾上。
通过 partial 拆分业务逻辑:
public partial class Humanoid : CharacterBody3D { }
Humanoid.cs - _Ready()
HumanoidAction.cs - _PhysicsProcess(double delta) + ActionContex; Player 需调 base._PhysicsProcess(delta);
HumanoidOther.cs - InitCharacters()
游戏实战
综合
玩家通常命名为 Player,敌人命名为 Hostile,其上还应有个基类 CharacterBody。
WASD 或方向键控制平移,鼠标或右摇杆控制旋转。
Player 和 Hostile 基础行为均应封装到并发状态机里,Player 通过键鼠和手柄切换状态,Hostile 则通过行为树切换。
Node 不添加 tree.AddChild(node) 至节点树,将丧失引擎对 _Ready() 各种函数的触发,就是普通的 OOP 对象而已。
编辑器部分“生成物”在模式切换时可能要重新生成:烘焙光照贴图 (Lightmap)、粒子系统 (GPUParticles)等。
调试:
开发阶段划线识别 - EditorNode3DGizmo.add_lines(...)、add_unscaled_billboard(...)
优化:
网格体属性 visibility_range_begin 可用于 - 当玩家(摄像头)走近时从低模变高模。
协作:
动画 - *.fbx.import 针对动画进行了配置项 _subresources={"animations": {"动画名": {"save_to_file/enabled": true,"save_to_file/fallback_path": "res://x2.tres","save_to_file/keep_custom_tracks": true,...
重新导入 *.fbx 时,若 keep_custom_tracks 为 true,是指保留“新建继承”后的场景文件,通过编辑器新增的动画轨道;fallback_path 指从内嵌模式改为独立资源文件。
动作:
因3D游戏战斗时击空,故通常要提供目标锁定功能:锁敌后,左右跑环绕敌人,后退则依然面向敌方;软锁也叫自动吸附或辅助瞄准,用于非Boss不重要的对敌。
IDE编辑器
导出
导出模板 - Godot_v4.6.1-stable_mono_export_templates.tpz 后缀名是为了双击后让 Godot.exe 感知到并自动安装,实质就是个标准 zip 压缩包。
Android 导出最简单模板: export_presets.cfg
; Android 导出必须写上 name、 platform、package/unique_name,其他均为可选。
; 虽报错但不影响导出 - ERROR: Couldn't find the given section "preset.0" and key "runnable等大多数key", and no default was given.
[preset.0]
name="Android"
platform="Android"
; 可选 - 但写上后可省略命令行传入。
export_path=".godot/mono/x.apk"
runnable=false
[preset.0.options]
package/unique_name="com.example.$genname"
; 是否启用 Android AOT,默认false。
;dotnet/android_use_linux_bionic=true
; 调试符号过大,故不生成,默认true。
dotnet/include_debug_symbols=false
; Windows 导出必须写上 name、 platform,其他均为可选。
[preset.0]
name="Windows Desktop"
platform="Windows Desktop"
; 可选 - 但写上后可省略命令行传入。
export_path=".godot/mono/x.exe"
runnable=false
[preset.0.options]
; 调试控制台很少用,故不生成,默认1。
debug/export_console_wrapper=0
EOF方式生成导出文件($符号必须转义,github actions yaml会自动清除左侧空格,故应将左侧Tab符改为空格):
cat > export_presets.cfg <<- EOF
[preset.0]
name="Android"
platform="Android"
export_path=".godot/mono/x.apk"
[preset.0.options]
package/unique_name="com.example.\$genname"
dotnet/android_use_linux_bionic=true
dotnet/include_debug_symbols=false
EOF
[可读性差]换行符方式: echo $'[preset.0] \n name="Android" \n platform="Android" \n [preset.0.options] \n package/unique_name="com.example.$genname"' > newgame/export_presets.cfg
项目模板
项目:至少存在一个 project.godot 文件,IDE 感知改动会格式化,后文配置覆盖前文,并清除注释;运行时改动则会存入优先级更高的 user://override.cfg。
config_version=5
[application]
config/name="g1"
; 建项目时确定,首选能力最大的"Forward Plus",专用环境才考虑"Mobile",切换渲染器依然会保持初值;版本号用来升降迁移。
config/features=PackedStringArray("4.6", "C#", "Forward Plus")
run/main_scene="res://control.tscn"
[dotnet]
# *.csproj 要跟本程序集同名,才能被 IDE 感知。
project/assembly_name="g1"
; 以下项应按情况谨慎设置 - rendering_method 跟计算能力相关,rendering_device 跟显卡驱动相关
[rendering]
; 只用于 Windows 导出。
rendering_device/driver.windows="d3d12"
;rendering_device/driver.android="vulkan"
;
Android GPU 只支持无专利问题的 ASTC。
; PC 能用 ASTC 但慢,手机端直接不识别 EC 系列,故在导出 apk 时变为 true。
; 修改后需删除 .godot/imported/ 并重启编辑器方生效。
;textures/vram_compression/import_etc2_astc=true
; 即 IDE 右上角选中的渲染器(专用环境才做变动/android导出时会自动降级至mobile):
;renderer/rendering_method="forward_plus"
;renderer/rendering_method.mobile="mobile"
; 以下项均可选,但推荐显式指定:
[animation]
compatibility/default_parent_skeleton_in_mesh_instance_3d=true
[physics]
3d/physics_engine="Jolt Physics"
说明:
config_version=5 即 Godot 4.x;config_version=4 则为 3.x。
project/assembly_name="g1" 决定了 g1.csproj 和 g1.sln 文件名及C#程序集名。
run/main_scene="uid://qkbsbukvmysy" 也支持uid前缀。
C#方案生成(g1.csproj、不常改动的g1.sln): 项目 -> 工具 -> C# -> Create C# solution
<Project Sdk="Godot.NET.Sdk/4.5.0">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
</PropertyGroup>
</Project>
C#脚本默认模板:.NET 6 默认 C# 版本为 10; v4.4 = net8.0 + C#12。
using Godot;
using System;
// 节点类定义必须声明为partial;
// 空类也可 - public partial class MyNode : Control { }
public partial class MyNode : Control
{ // 区别? new MyNode() Vs. Script.New();
public MyNode() { GD.Print("GD.Load<CSharpScript>("res://NewScript.cs").New();"); } // C#构造函数名即类名
public MyNode(int x) { GD.Print("GD.Load<GDScript>("res://new_script.gd").New(123);"); } // GDS构造函数名为_init(p)
// this.AddChild(node) 执行后触发 _Ready()。
public override void _Ready()
{
GD.Print("_Ready()");
if (GetTree().GetFrame() > 0) // p.AddChild(node) 时触发:
{ await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame); }
else // 等待所属场景载入完毕,Owner默认为主场景的根节点;run/main_scene 时触发:
{ await ToSignal(Owner ?? GetParent(), Node.SignalName.Ready); }
OS.Alert("Ready.");
}
public override void _Process(double delta)
{
}
}