从此
上网
📄文章 #️⃣专题 🌐上网 📺 🛒 📱

游戏设计、制作、创作

综合

发行商 - 出渠道推广游戏,拿走游戏纯利润30%分成。

3D = Three-dimensional
命名:
  Main - 主节点,用Node。
    Camera3D - 摄像头,2D用Camera2D。
    WorldEnvironment - 光照。
    Game - 3D层,用Node3D。
      Ground - 地面,用StaticBody3D。
      Player - 玩家角色,用CharacterBody3D。脚本名为Player.cs
      Door - 门窗,用AnimatableBody3D。
    HUD - Heads-up display,用Node2D。
    Game2D - 或GameTwo,2D层,用Node2D。

模型朝向约定 地图、地形等都约定+X为东、-X为西。
Godot IDE前视图的入眼方向为Z轴,即Vector3.ModelFront(出眼-Z则用Vector3.Forward),故模型资源也应该用这个入眼朝向;LookAt(...)尾参为true则会使模型的面部朝向目标。
旋转顺序 - Godot组合时默认为Euler YXZ,分解时则倒序。
重要组件 - 
  组件化最外层节点:独有MoveAndSlide()的CharacterBody3D和都有MoveAndCollide()的StaticBody3D、RigidBody3D
  Godot 4.3+ 允许CollisionShape3D等碰撞体处于间接(孙)节点 - https://github.com/godotengine/godot/pull/77937

默认重力数值 - float gravity = ProjectSettings.GetSetting("physics/3d/default_gravity").AsSingle();
SpringArm3D(弹簧臂) - 
  作用:摄像头通常跟随在角色身后一段距离,当关门之后它将无法穿透门板(具有碰撞体)拍到角色,故应将摄像头包裹在弹簧臂内,其会自动缩距(发射线检测碰撞体),直至拍到角色。
  用法:SpringArm3D节点的中心点即发射起点,浅蓝色的射线未碰撞时则将所有子节点(摄像头等/方位由SpringArm3D管理)临时弹出至射线终点,一旦遇到碰撞体,就将子节点全部回缩至射线最近的碰撞点。
    排除角色自身碰撞体 - springArm3D.AddExcludedObject(this.GetRid());
    注意 - 加了SpringArm3D后,编辑器和运行时的摄像头位置可能会不一致,以后者为准。

视角:
  第一人称 - 摄像头在眼睛位置,摄像头组件直接放角色节点内即可;第三人称 - 摄像头在身后或头顶,且跟随自身旋转,摄像头要放入SpringArm3D内来实现;俯视角 - 在空中跟随而已,并不会随着角色旋转。

第三人称视角 - 
  左右移动鼠标,角色同步移动,我们只能看到角色的背面
  左右移动鼠标,角色静止不同,我们可以看到角色的全方向
  注意 - 第三人称视角实现时,似乎都是旋转CharacterBody3D内的Mesh体,是否标准做法? https://www.bilibili.com/video/BV1Eu4y1i7is/  https://www.bilibili.com/video/BV1Ny4y1q71z/

强记(冗余)

  Variant即动态类型,永不为null,若实值判null要用: n.Get("blend_shapes/browDownLeft").VariantType != Variant.Type.Nil
  ResourceLoader.Load(...)不识别绝对路径(C:/x.txt这种),路径前缀必须为 res://、user:// 或 uid:// ,未填写则自动补 res:// ,或用外部文件专用类 Image.LoadFromFile(@"D:\x\x.jpg")。


最佳实践

  CharacterBody3D转向时应整体操作,不能只转换其节点内部的Mesh体,若需要跟随(摄像头等)可将子节点放外部,并结合RemoteTransform3D使用。


工作流

建模:
  Godot直接支持.glb(首选)、.gltf,若已安装blender并填写编辑器设置filesystem/import/blender/blender3_path=C:/Program Files/Blender Foundation/Blender 4.0/,则会自动将.blend转为.gltf。
  Blender出模型和设置碰撞区域(网格名加后缀-convcol)、导航网格NavigationMesh。(基于网格名后缀自动原样创建 用法视频)
    非网格的Blender Empty Objects(仅*.dae?)带上-colonly后缀,也会识别为各种XxxShape3D。
    [大地模型]Blender - File->New->General->将Cube改名为Cube-convcol,并修改Scale的X/Y为5,Z改为0.1,若灯光过亮和勾上闭眼。
  或直接从Blender导出Godot识别的*.escn文件格式 - https://github.com/godotengine/godot-blender-exporter

建模和编程:
  模型导入 - Godot支持“场景”和“AnimationLibrary”两种,导入AnimationLibrary后文件后缀不会变,但只会保留动画,忽略其他资源。
    场景用法:直接拖拽至3D视图中 或者 右键“实例化子场景”,当前不建议用“新建继承”,资源文件全权由设计人员修改;若做了继承,部分资源(动画等)就能被其他节点勾选上了。
    AnimationLibrary用法:AnimationPlayer->动画->管理动画->加载库;若为glb场景节点还应将其自身勾选至AnimationPlayer.root_node。
  升级网格表面弹框 - “仅升级”指每次打开项目都会生成至内存中,不写入磁盘;“重启并升级”指写盘式升级,无法回退或降低版本。

语法:
  等待信号完成:
    var file = await ToSignal(fd, FileDialog.SignalName.FileSelected);
    GD.Print(file[0]); // ToSignal(...)返回值即该信号的入参数组。

编程:
  Godot快捷键:
    2D视图 - Ctrl+F1
    3D视图 - Ctrl+F2
    3D视图默认视角 - Ctrl+Shift+W 重开场景
    3D视图前视图 - 数字键盘1 或 添加快捷键值 Alt+1

  性能 - GD.Load载入后资源即驻留在当前Node对象,等当前节点被Free()后,该资源也就自动销毁。

  常用 - 
    GetTree().Paused = true; quit(); 除canvasItem.SetVisible(false)外优先用x.Hide()
  场景 - 
    GetTree().ReloadCurrentScene(); var ps = new PackedScene(); ps.Pack(node); // 持久化标志node.SetOwner(p);
    GetTree().ChangeSceneToPacked(GD.Load("res://x.tscn")); // 或 ChangeSceneToFile("res://x.tscn");
  获取分组节点 - GetTree().GetNodesInGroup("name"); 或 GetTree().GetFirstNodeInGroup("name");
  根视口Viewport(即含Autoload节点的Window) - GetTree().Root;
  根节点(永在单例节点之后/using System.Linq;) - GetTree().Root.GetChildren().Last();
  按名遍历子节点(性能没“场景唯一节点”GetNode("%Name")高/window.FindChild永为null) -
    var firstByName = (Node3D)GetTree().Root.GetChildren().Last().FindChild("name");
  跨层级分组 - node.AddToGroup("name"); 或能序列化的 node.AddToGroup("name", true);
  节点自身存取元数据 - node.SetMeta("x", new Variant()); node.GetMeta("x");
  CanvasItem面板支持鼠标调整文字颜色,或代码调整(btn.Modulate会影响子节点颜色值):btn.SelfModulate = Colors.Red;
  捕获鼠标 - Input.MouseMode = Input.MouseModeEnum.Captured;
  代码映射输入键值 - Action同名则覆盖
    var key = new InputEventKey(); key.Keycode = Key.W;
    InputMap.AddAction("new_key"); InputMap.ActionAddEvent("new_key", key);

  攻击判定 - 
    近战:
      CollisionShape3D - 动画第0帧至伸出武器帧设为disabled,之后设disabled=off,收武器至动画结束重新设为disabled。
      射线检测 - 攻击时发射一连串射线进行碰撞检测。

  摔死:
	public override void _Process(double delta)
	{
	  if (Velocity.Y < -20) { GD.Print("摔死"); GetTree().ReloadCurrentScene(); }
	}

核心:
  3D物体由朝向(Basis)+原点(Origin/中心点)来定位(属性名为Transform),默认值为Transform3D.Identity;原点默认值为(0,0,0),远离父节点的原点时,其会变为实际距离值;朝向默认值为Basis.Identity,数值范围只有0、1、-1三个。
  获取模型宽高和深度 - AABB即axis aligned bounding box (轴对齐-边界盒) 
  原点y值朝上正数,向下负数;当前朝向乘上即将移往的方向并归一化,就能得到该帧的移动偏移量,加负号则翻转方向。
  移动碰撞模拟性检查 - CharacterBody3D.test_move(...)
  四方向移动,最简未优化写法 - _PhysicsProcess(double delta):
    var v2 = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down"); // 获取输入量,但无物体定位信息。
    Velocity = (Transform.Basis * new Vector3(v2.X, 0, v2.Y)).Normalized() * Speed; // 乘当前物体的空间位置Basis得出基于此定位的移动量,未处理旋转和跳落。
    MoveAndSlide(); // 加负号则移动方向互换:... - new Vector3(v2.X, 0, v2.Y) ...
    说明 - 官方移动模板的Godot.Mathf.MoveToward(...)作用仅仅是让物体平滑回落至Vector3.Zero偏移量。
  绕轴旋转:局部变换;全局转换
    node3D.RotationDegrees可设置绝对角度,node3D.RotateY(angle)则为相对角度。
    360°度数(deg/RotationDegrees属性)=6.28...弧度(rad/Rotation属性/PI * 2/Mathf.Tau)
    //RotateY(-0.5f); // 绕Y轴(上下轴=左右)旋转;若不想用负号,可采用下行直观的视角旋转
    RotateObjectLocal(Vector3.Down, 0.5f); // 绕Y轴=左转Up和右转Down(即RotateY负数值)
  朝向目标:
      LookAt(targetNode.Transform.Origin + direction); // LookingAt比较平滑:
      //Transform = Transform.InterpolateWith(Transform.LookingAt(targetNode.Transform.Origin + direction), (float)delta * Speed);
  FPS自由视角:
    弧度方式 - 
        第一人称时该脚本放玩家节点,第三人称(Third-person POV)时放SpringArm3D节点(用上SpringArm3D的Y轴)
        第三人称跟随SpringArm3D方向移动:var dir = (Transform.Basis * new Vector3(inputDir.X, 0, inputDir.Y).Rotated(Vector3.Up, sa.Rotation.Y)).Normalized();

	public override void _Input(InputEvent @event)
	{
		if (@event is InputEventMouseMotion mouseMotion)
		{
			// Rotation和RotationDegrees属性赋值均支持数值超出后循环映射,或 Mathf.Wrap(x, 0, 9);
			// 场景文件用弧度存储的Rotation值,因精度因素可能每次转换至RotationDegrees的度数均有出入,故统一用弧度才能精确比对。
			var v2 = new Vector2(Rotation.X, Rotation.Y); // 转动前的旋转值
			v2 += new Vector2(mouseMotion.Relative.Y, -mouseMotion.Relative.X) * 0.004f;
			var rad = Mathf.DegToRad(50); var v2x = Mathf.Clamp(v2.X, -rad, rad); // 允许的斜身弧度
			//Rotation = new Vector3(v2x, v2.Y, Rotation.Z);

			// 取代Rotation - 必须先陀螺式(左右)、再车轮式(上下)的旋转顺序
                        var t3d = Transform; t3d.Basis = Basis.Identity; Transform = t3d;
			RotateObjectLocal(Vector3.Up, v2.Y);
			RotateObjectLocal(Vector3.Right, v2x);
		}
	}
    变量方式 - https://docs.godotengine.org/zh-cn/4.x/tutorials/3d/using_transforms.html#setting-information
      _rotationY = Mathf.Clamp(_rotationY, -200 * 0.005f, 200 * 0.005f);// 允许的斜身弧度
      RotateObjectLocal(Vector3.Up, -_rotationX); // 应为负数;非鼠标移动方式改旋转值不会同步该字段!


    播放音频:
	var asw = new AudioStreamWav();
	asw.Format = AudioStreamWav.FormatEnum.Format16Bits;
	asw.MixRate = 48000; // 必须跟原wav一致,否则影响速度。
	asw.Data = bytes; // FileAccess.GetBuffer(...);
	audioStreamPlayer.Stream = asw; // 非运行时wav文件可换用:GD.Load(trackPath);
	audioStreamPlayer.Play();

	var vsp = (VideoStreamPlayer)GetNode("VideoStreamPlayer");
	var vst = new VideoStreamTheora(); vst.File = "x.ogv"; // 仅支持ogv格式
	vsp.Stream = vst; vsp.Play(); // vsp.Expand为true则限定视频尺寸。

    3D中显示2D内容:
	var mesh = (MeshInstance3D)GetNode("MeshInstance3D");
	var sv = (SubViewport)GetNode("SubViewport"); // 子节点放2D内容
	var sm = new StandardMaterial3D();
	sm!.AlbedoTexture = sv.GetTexture();
	mesh.MaterialOverride = sm;

素材、模型、示例

示例:
  Godot官方 - https://github.com/godotengine/godot-demo-projects
  学习机构示例 - https://github.com/gdquest-demos

资源:
  双击资源导入时或重导时均可以在IDE中指定资源的根类型,便于附加具体类型(CharacterBody3D等)的脚本。
地板、场所:
  海滩(*.glb) - https://sketchfab.com/3d-models/beach-lowpoly-0425667a812247cabedaa60594bd11b1
  两座小山 - https://sketchfab.com/3d-models/elephants-foot-tonalea-arizona-312feac08b344045ba6460864271f03e
  卡通城堡 - https://sketchfab.com/3d-models/low-poly-castle-in-ruins-b4beaf78a6784847b4e63da79351a86b
  山路台阶 - https://sketchfab.com/3d-models/fort-tryon-park-staircase-6d19979112b541aea02f9b68a66fbf7d
  小家带院(*.glb、*.blend) - https://github.com/godotengine/godot-demo-projects/tree/master/3d/physical_light_camera_units
  山河高楼(*.glb) - https://github.com/godotengine/godot-demo-projects/tree/master/3d/truck_town
  人物动画(*.glb) - https://github.com/godotengine/godot-demo-projects/tree/master/3d/platformer/player

单机

整体:
  玩家操作游戏人物打小怪、打大Boss后进入下一关。
  《Palworld》买断式游戏可允许玩家自建服务器,分担游戏厂商网络服务压力。

常规做法:
  玩家出生后应处于idle动画姿态,移动时应面向前方,并切换至walk动画。
  发射物首选射线,或者用RigidBody3D构建,通过ApplyForce施力射出。

联机