游戏产品制作、标准化命名、项目模板、独游实战
综合/最新
Godot游戏开发 | 知名游戏作品 | 游戏开发文章
编辑器
开发调试:
运行时选中“远程”选项卡下的节点,会在运行视图里显示一个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)}");
注意:
Node2D 直接包裹 Control 会导致锚点失效,故应加一层 CanvasLayer 隔离下。
Node3D,Node2D、Control 比 Node 多出一些必要属性,更适合做父容器,后者仅用于无界面的节点(状态机状态节点等)。
着色器:
Shader Parameters 属性暴漏通过 uniform sampler2D noise_texture_2d; 其中改为 sampler3D 编辑器才支持设置 NoiseTexture3D。
游戏标准化
节点命名
2D节点命名: All - 根节点,用Node3D,Node2D,特征不明显可用Node;通常不挂脚本。 Stage - 主舞台,用Node3D,Node2D;脚本名为Stage.cs;命名备选arena竞技场。 Player - 玩家角色,player.tscn,脚本名为Player.cs 3D(Three-dimensional)节点命名: 说明 - 场景根节点均加 Owner 或 Scope/Domain、Branch、Reuse 后缀。 Entrance.tscn EntranceOwner - 放个“Play”按钮,GetTree().ChangeSceneToFile("res://Game.tscn"); 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。 Top - 头顶血条。 PureNPCOwner - 中性NPC,或 Neutral、FairNPC。 PartnerOwner - 友好NPC,或叫 Amiable、Friendly。 NPC - 首个非玩家角色;根据 res://NPC.tscn 动态创建。 Thing - 地面门窗等,用StaticBody3D+子节点CollisionShape3D(设置其shape属性为无限平面WorldBoundaryShape3D),由于物体多,故根节点类型用Node3D隔离下; Ground - 地面用土色 new Color(0.5f, 0.4f, 0.2f),用StaticBody3D;Door - 门窗,用AnimatableBody3D。 Grass - 可用 QuadMesh(或修改 PlaneMesh.orientation) 制作。 Basic Camera3D - 摄像头,2D用Camera2D。 WorldEnvironment - 光照。 DirectionalLight3D - 可通过 light_energy 调整明暗度。 HUD - Heads-up display,用CanvasLayer(通过Layer数字决定是否为最外层/跟节点出现顺序无关)或Node节点;Node2D会导致Control锚点失效。 Surface/SurfaceOwner - 最外层全屏窗口,用CanvasLayer;或用 Outermost;暂停和恢复游戏:GetTree().Paused = true; // 豁免 GetTree().Root.GetNode("All/Surface").ProcessMode = ProcessModeEnum.Always;
游戏实战
综合
玩家通常命名为 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不重要的对敌。
项目模板
项目:至少存在一个 project.godot 文件。config_version=5 [application] config/name="g1" config/features=PackedStringArray("4.3") run/main_scene="res://control.tscn" [dotnet] project/assembly_name="g1"说明: config_version=5 即 Godot 4.x;config_version=4 则为 3.x。 project/assembly_name="g1" 决定了 g1.csproj 和 g1.sln 文件名及C#程序集名。 config/features 不写则自动生成当前IDE版本,且渲染模式回落至 "Forward Plus"。 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) { } }