游戏设计、关卡交互、动画美工、3D建模/2D Tilemap、节点网格、骨骼骨架、WASD视角、AnimationTree/AnimationPlayer
综合/最新
Godot游戏开发 | 知名游戏作品 | 游戏开发文章
游戏动画师 - 游戏开发中重要程度较大的职位。
新:
Skeleton3D子节点SkeletonModifier3D可干预AnimationMixer制动骨骼时的执行效果。
SpringBoneSimulator3D 可用于制作头发。
综合:
资源通过 Resource ID (RID) 存取,对象通过 Instance Id 存取。
统一约定设置:
模型姿势:
Rest Pose:Godot动画混合推荐双臂平伸的T-Pose;A-Pose 则更符合人类日常姿势,通常取45°值。
有条件的话,建议创建一个1帧的 RESET 动画,用于 AnimationTree 混合或Root Motion的骨骼参考值。
髋关节(hip joint)即大腿根与骨盆旋转处,髋部的通俗叫法为胯部;
从运动角度看,胯(Hips)连接了3部分:脊柱(Spine)及以上、左腿(LeftUpperLeg)及以下、右腿(RightUpperLeg)及以下。
先建立上接脊柱(Spine)的骶骨,然后建立使裆部产生宽度的左右髋关节,在中心连接一起组成三叉形态的胯,也就是骨骼的Root Bone;因胯部跟随上身情况较多,故只将双腿视为下半身。
BoneAttachment3D 附着至骨头的中心点位置是该骨头的起点端;也可不放入 Skeleton3D 子节点(external_skeleton)。
Player/NPC:
WASD 移动+鼠标转向控制的是 Player 根节点,而非 Mesh 及骨骼节点。各种视角 - https://www.bilibili.com/video/BV1B341157Xb/
NPC 无需 Camera 和 Input.GetVector(...),直接由导航组件控制(Y轴自然贴地):var v = GlobalPosition.DirectionTo(na.GetNextPathPosition()) * Speed / 2; velocity = new Vector3(v.X, velocity.Y, v.Z);
血条最简单做法是直接用ProgressBar,跟随3D版NPC头部的话,用支持“布告板 (Billboard)”能力的Sprite3D,想向其加入2D控件可指定其texture 为 SubViewport 容器,来混合提供2D/3D内容。
动作设计(前摇/闪避等) - https://gwb.tencent.com/community/detail/125828
动画资源循环播放 -
通过“高级导入设置”导入的动画,默认均已自动设为了 Animation.LoopModeEnum.None;
若想改变默认循环模式,可点击行为树内“动画”节点,修改其属性;或者代码方式修改:
animationNodeAnimation.UseCustomTimeline = true; ana.StretchTimeScale = false; // 即不循环 ana.LoopMode = Animation.LoopModeEnum.None;
循环播放行为(Animation.LoopModeEnum.Linear):站、走、跑; 不循环播放行为(默认行为):攻击、跳
节点/组件/网格
节点:
2D控件一览图 - https://docs.godotengine.org/zh-cn/4.x/tutorials/ui/control_node_gallery.html
子节点遍历:
public void x(Node pNode)
{
var nodes = pNode.GetChildren();
foreach (var node in nodes)
{
GD.Print(node.Name);
if (node.GetChildCount() > 0){ x(node); }
}
}
支持包裹碰撞体(CollisionObject3D)的节点 - Area3D(区域重叠+进出信号), PhysicsBody3D(CharacterBody3D, PhysicalBone3D(骨骼专用/布娃娃Ragdoll), RigidBody3D, StaticBody3D);碰撞体必须是其直接子节点;BoneAttachment3D只能附着,提供不了碰撞。
获取非直接子节点:_Node.FindChildren("@RemoteTransform3D@*", null, true, false);
锁定左右移动:AxisLockLinearX = true; // AxisLockAngularY = true; // 或锁定旋转。
将父节点空间变动同步至受控节点:_RemoteTransform3D.RemotePath = controlledNode.GetPath();
角色头顶血条/红字:直接使用带有 Billboard 属性的 Label3D(纯文字)、Sprite3D(图像+SubViewport)。
全屏:_Control.SetAnchorsPreset(Control.LayoutPreset.FullRect);
选项卡标题:GetNode("TabContainer").SetTabTitle(0, "x");
网格:
网格(Mesh)由表面(Surface)组成,表面数组(Array[Dictionary])是由必须的顶点(ARRAY_VERTEX/PackedVector3Array等效C#的Vector3)和可选的法线(ARRAY_NORMAL/实时计算损耗大,故应专门指定/用于光照着色)、UV等子数组组成。
Godot IDE 可视化编辑器支持 CSG,即Constructive Solid Geometry(构造实体几何),但如果需要复杂的建模能力,请使用Blender等。
CSG用法:添加 CSGPolygon3D 至编辑器,并用鼠标调整锚点,支持导出 glTF;Inspector -> Transform -> Rotation Y 轴填 -90 则可沿平面增减调整。
如果想调整 Blender 制作出来的网格,则需要用 CSGMesh3D 导入。
CSGCombiner3D 套住后,会从上至下的应用操作。
ArrayMesh 类创建网格示例:
var rId = RenderingServer.InstanceCreate();
RenderingServer.InstanceSetScenario(rId, GetWorld3D().Scenario);
var v3s = new Vector3[] { new Vector3(-1, 1, 0), new Vector3(1, 1, 0), new Vector3(0, -1, 0) };
var arrays = new Godot.Collections.Array();
arrays.Resize((int)Mesh.ArrayType.Max);
arrays[(int)Mesh.ArrayType.Vertex] = v3s;
var mesh = new ArrayMesh();
mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, arrays);
//or mesh = GD.Load("res://MyMesh.obj");
RenderingServer.InstanceSetBase(rId, mesh.GetRid());
var v3 = new Vector3(0.5f, 0.1f, 0.5f); // or Vector3.Zero
var t3d = new Transform3D(Basis.Identity, v3);
RenderingServer.InstanceSetTransform(rId, t3d);
代码创建网格工具类 - SurfaceTool
骨骼骨架
骨架重定向:选中 Skeleton3D 节点 -> 重定向(Retarget) -> 新建 BoneMap 并选中 -> Profile:SkeletonProfileHumanoid -> 鼠标选中蓝点会出现模型骨骼名,红点则属未自动映射。
映射后模型的 Skeleton3D 节点默认变为 GeneralSkeleton,模型内的 AnimationPlayer 轨道名也会同步修改为 SkeletonProfileHumanoid 标准命名 %GeneralSkeleton:Hips。
因为 %GeneralSkeleton 只能场景内访问,故将纯动画场景内的 AnimationPlayer 提升至模型场景子级:
var ap = p.GetNode<AnimationPlayer>("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);
}
骨骼控制权/编辑筛选器:
_AnimationNode.FilterEnabled = true; // 勾上后 in 端口就会让出部分控制权了;多根就多次执行 SetFilterPath(...)。
_AnimationNode.SetFilterPath(new NodePath("%GeneralSkeleton:LeftUpperArm"), true); // false去除,无骨骼名则忽略。
骨骼其他:
作用 - 使物体具有肢体活动的能力。
var sk=GetNode<Skeleton3D>("player/Skeleton/Skeleton3D");
//var c = sk.GetBoneCount(); GD.Print(c);
// 3D模型节点“在编辑器中打开”->“新建继承”(即存为场景)-> 选中Skeleton3D骨架节点右键“创建物理骨架”(即含碰撞体的PhysicalBone3D)
// 射线碰到骨骼体后判断命中:raycast.collider.bone_name == "爆头骨骼名"
sk.PhysicalBonesStartSimulation(); // PhysicalBone3D.bone_name指定3D模型资源内定义的骨骼部位名。
骨骼映射/导入重定向(Retargeting) - https://docs.godotengine.org/zh-cn/4.x/tutorials/assets_pipeline/retargeting_3d_skeletons.html
导入时选中Skeleton3D节点,点击“骨骼映射->BoneMap->Profile:SkeletonProfileHumanoid”,窗口Filtered Tracks所示为映射后的标准骨头部位名。
骨骼名 - Upper Arm即上臂,Forearm或Lower Arm为前臂;Upper Arm即大腿,Lower Leg为小腿
视角
视图约定:
// 转换即远近+方位
this.GlobalTransform = new Transform3D(GlobalTransform.Origin, GlobalTransform.Basis);
// 不升降,只修改平移和绕柱。
this.Transform = new Transform3D(t3d.Basis, new Vector3(t3d.Origin.X, Transform.Origin.Y, t3d.Origin.Z));
// 全局距离 - 相对于绝对原点的远近;即3D视图中心
GD.Print(this.GlobalTransform.Origin);
// 全局方向 - 相对于绝对正面的方位
GD.Print(this.GlobalTransform.Basis);
// 绝对原点 - IDE 3D默认视图 - 后、右、顶
GD.Print(Vector3.Zero);
// 绝对朝向 - 物体朝向观众或摄像头的那一面,即IDE的3D后视图
GD.Print(new Basis(1, 0, 0, 0, 1, 0, 0, 0, 1));
// 相对于父节点距离 - 0或实际米数;即物体中心
GD.Print(this.Transform.Origin);
// 相对于父节点方向 - 索引值只能0或1
GD.Print(this.Basis); // 等同this.Transform.Basis
计算:
首选四元数:new Quaternion(x: float, y: float, z: float, w: float); // 入参必须归一化
旋转Player:p.Quaternion = new Quaternion(Vector3.Up, Mathf.DegToRad(180));
取反方向 - 加负号即可 -myVector3 with { Y = p.GlobalPosition.Y } // with 可以简化字段修改;以Vector3.Zero方向为基准。
视角:
射击游戏通常采用FPS;魂类游戏战斗时则是手动锁定某个敌人的方式;也存在自动锁定或吸附较近敌人的方式(《原神》、《枕刀歌:白刃行》)。
《刺客信条》等 - 在移动时身体前方跟随鼠标,但静止时鼠标进行自由观察,而身体不跟;《法环》与之不同点是,鼠标静止1秒后会回正至身体正前方的朝向。
挥拳时能否转向,取决于游戏类型,魂系不能,而较宽容刷怪的ARGP则可以。
Basis 类型能够同时存储旋转和缩放,而四元数 Quaternion 只能存储旋转,后者的作用是可以更直观的绕球体轴旋转,解决了Vector3.rotated(...)对次序敏感的问题 - https://quaternions.online/
绕轴旋转 - 首选四元数 Quaternion,次选方案是在轴上放个Node3D做旋转原点,然后把 SpringArm3D/Camera3D 套进去转;仅用一次的可调 Transform.Rotated(player.GlobalPosition, Mathf.Pi / 2.0f)。
看向某位置:LookAt(centerPoint, Vector3.Up);
其他备选方案(可能有性能问题):
var centerPoint = player.GlobalPosition;
// 1. 平移到原点 (相对于被绕节点的位置)
var gt = GlobalTransform.Translated(-centerPoint);
// 2. 鼠标绕柱旋转角度
gt = gt.Rotated(Vector3.Down, Mathf.Pi / 2.0f);
// 3. 平移回原位
gt = gt.Translated(centerPoint);
// 4. 应用变换
GlobalTransform = gt;
第一人称视角(FPV)最大特点是摄像头处于眼睛位置,站立或走动时身体会跟随摄像头;
第三人称视角(TPV)则摄像头和眼睛各行其事,站立或走动时身体会跟随摄像头,审看皮肤时不跟随;摄像头下移摇臂抬高,交战视野会更好;NPC对话时,才会侧身一下不致遮挡,跟同身高的敌人对打时,摄像头稍稍拉高;
俯视角(Top-Down View)则摄像头固定,行走方向也固定;其他 - 站立或走动时按照身体朝向移动。
移动 - 非战斗状态为“自由移动”,左右移动时攻击方向为斜向前方45°,W+A同时按则22.5°;第一人称视角的攻击则一直朝向鼠标方向;
遇到敌人时,可手动切换至“锁定目标移动”,被锁定者会标记个白色亮点,此时你只能面对锁定敌人后退,左右移动时攻击目标依然朝向前方(与行进方向约90°),软锁(原神)则优先考虑玩家目标,锁定者次之。
WASD移动参考视频(法环) - https://www.bilibili.com/video/BV1XU4y1Z7D9/
参考:
Unity自制锁定敌人镜头与自由镜头丝滑切换 - https://www.bilibili.com/opus/828582114360295429
其他
骨骼蒙皮
_PhysicsBody3D.GetGravity() 已取代 ProjectSettings.GetSetting("physics/3d/default_gravity").AsSingle();
动画树 Active = false 后骨骼乱掉解决:
p.GetNode("AP").Play("idle"); // 骨骼归位后再启用动画树。
at.Active = true;