从此

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


综合/最新

Godot游戏开发 | 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

制作实务(直球+冗余)

综合:
  模型单位为厘米,1米为一个方格。
  以 Godot IDE 后视图(摄像头面向方向/ALT+1)为准,入眼方向为Z轴,即Vector3.ModelFront(出眼-Z则用Vector3.Forward),故模型资源也应该用这个入眼朝向;LookAt(...)尾参为true则会使模型的面部朝向目标。
  CharacterBody3D 节点方向应保持后视图,调整3D模型方向来面向摄像头。

经验:
  边走边攻击通常是用并行状态机或层次状态机,结合上下半身骨骼动画混合而成。
  跳起后大多数游戏是允许位移的,楼梯可以做成平滑坡道,就不存在上下时的碰撞问题了。
  滑步 - 如何解决?

编程:
  类型 - 分2种:Variant即继承于GodotObject的类,标注了[GlobalClass]的全局类会显示在IDE向导;C# 普通类。
  信号 - 分2种:[Signal] + delegate + Variant入参 + 继承Node;C# event + Action + 入参不限。
    [Signal] public delegate void PastEventHandler(string variantCompatible);
    或动态声明信号:AddUserSignal("Past",[new Collections.Dictionary() { { "name", "s" }, { "type", (int)Variant.Type.String } }]);
    Past += (v) => { }; // 入参只能是基本类型或继承自GodotObject。
    等效:button.Connect(Button.SignalName.Pressed, Callable.From(xMethod));
    断开回调:public override void _ExitTree(){ timer.Timeout -= _callback; }
    EmitSignal(SignalName.Past, "Hi!");

    延迟至当前帧末尾:player.CallDeferred(Player.MethodName.AddChild);
      或:new Callable(this, Player.MethodName.AddChild).CallDeferred(); 以及无需上下文的:Callable.From(() => GD.Print("x"));
    全局延至下帧开头:GetTree().Connect(SceneTree.SignalName.ProcessFrame, Callable.From(() => GD.Print("x")), (uint)ConnectFlags.OneShot);

    // 纯C#方式,不依赖Godot环境。
    public event Action<string> StateChanged;
    StateChanged += (anyObject) => { };
    StateChanged("C# events");

3D:
  入参用弧度居多,可先将度数转为角度:Mathf.DegToRad(-90)。

  全局中心点 + 离地1米:GlobalPosition = Vector3.Zero with { Y = Vector3.Zero.Y + 100.0f }; // 或 Vector3.Up * 100.0f;

  // 执行顺序影响最终姿态;XxxObjectLocal(...) 后缀为基于模型自身方向,而非全局。
  _Node3D.TranslateObjectLocal(new Vector3(0, 0, -100)); // 离地N厘米
  _Node3D.RotateX(Mathf.DegToRad(-90)); // 前后倾倒角度;左右倾倒用RotateZ;贴地转圈用RotateY。
  _Node3D.RotateObjectLocal(Vector3.Down, Mathf.DegToRad(180)); // 面向地面(Down)顺时针(正数)旋转

  动画:
    设置循环和总长度(影响全局):_Animation.LoopMode = LoopModeEnum.None; _Animation.Length = 1;

  动画树:
    流程图从 output 节点向前反查。
    AnimationNodeTransition - 类似枚举,手动切换,可后挂多个 AnimationNodeStateMachine 来自动决策。
    AnimationNodeStateMachine - 根据连线自动化切换状态,也可调用 travel("StateName") 手动切换。
    AnimationNodeOneShot - 手动播放“shot”端口动画后,将回落到“in”端口并持续播放。

  骨骼:
    3D模型内骨架导入 Godot 后会通过 Skeleton3D 描述,动画用 AnimationPlayer 描述。
    将3D模型内嵌动画导出后,其骨骼名通常与骨架一致,可直接导入 AnimationLibrary 驱动。
    查看所有骨骼名: player.GetNode("Armature/Skeleton3D").GetConcatenatedBoneNames();
    骨架重定向:选中 Skeleton3D 节点 -> 重定向(Retarget) -> 新建 BoneMap 并选中 -> Profile:SkeletonProfileHumanoid -> 鼠标选中蓝点会出现模型骨骼名,红点则属未自动映射。
      映射后模型的 Skeleton3D 节点默认变为 GeneralSkeleton,模型内的 AnimationPlayer 轨道名也会同步修改为 SkeletonProfileHumanoid 标准命名 %GeneralSkeleton:Hips。
      因为 %GeneralSkeleton 只能场景内访问,故将纯动画场景内的 AnimationPlayer 提升至模型场景子级:
        var ap = p.GetNode("attackScene/AnimationPlayer"); ap.Reparent(ap.GetParent().GetParent()); ap.Play("mixamo_com");
        或动态添加 _AnimationLibrary.AddAnimation(animName, al.GetAnimation("mixamo_com")); // 确保根节点处于默认,或设死 ap.RootNode = ap.GetParent().GetPath();

    [不优雅] 模型与动画骨骼名不一致时,也可导出纯文本的动画文件(*.tres),手动替换所有的 tracks/0/path = NodePath("Skeleton3D:mixamorig_Hips");对外发布时,最好转存为 *.anim。
	for (var i = 0; i < anim.GetTrackCount(); i++)
	{
		GD.Print(anim.TrackGetPath(i).GetConcatenatedNames()); // 取冒号前字符;完整值为 %GeneralSkeleton:Hips
		var newPath = new NodePath("../GeneralSkeleton:" + anim.TrackGetPath(i).GetConcatenatedSubNames());
		anim.TrackSetPath(i, newPath);
	}

2D:
  

内容

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

  2D节点命名:
    Game - 根节点,用Node3D,Node2D,特征不明显可用Node;通常不挂脚本。
      Stage - 主舞台,用Node3D,Node2D;脚本名为Stage.cs;命名备选arena竞技场。
        Player - 玩家角色,player.tscn,脚本名为Player.cs

  3D节点命名:
    Game - 根节点,用Node3D,Node2D,特征不明显可用Node;通常不挂脚本。
      Stage - 主舞台,用Node3D,Node2D;脚本名为Stage.cs;命名备选arena竞技场。
        Player - 玩家角色,封装至player.tscn,根类型用CharacterBody3D(需要隔离时再加个Node3D),脚本名为Player.cs,挂载至player.tscn内,在当前节点右侧会显示脚本图标;演示暂用跟人类躯体相像的圆柱体(CapsuleMesh)。
        Thing- 地面门窗等,用StaticBody3D+子节点CollisionShape3D(设置其shape属性为无限平面WorldBoundaryShape3D),由于物体多,故根节点类型用Node3D隔离下。
      Camera3D - 摄像头,2D用Camera2D。
      WorldEnvironment - 光照。

特殊:
  游戏资源对象默认全局共享,但可通过“唯一化”或duplicate()方法创建个副本,以免一处修改就影响全部引用处。
  线程异常(属性set中调)可用 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"); }) // 匿名委托

跨平台:
  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

其他

官方分层状态机 - https://github.com/godotengine/godot-demo-projects/tree/master/2d/finite_state_machine
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()