工作流
建模:
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;