游戏动画(Animation)、状态机、骨骼蒙皮
游戏动画、状态机、骨骼蒙皮
综合
游戏动画师 - 游戏开发中重要程度较大的职位。 新: Skeleton3D子节点SkeletonModifier3D可干预AnimationMixer制动骨骼时的执行效果。 SpringBoneSimulator3D 可用于制作头发。 统一约定设置: 动画资源循环播放 - 通过“高级导入设置”导入的动画,默认均已自动设为了 Animation.LoopModeEnum.None; 若想改变默认循环模式,可点击行为树内“动画”节点,修改其属性;或者代码方式修改: animationNodeAnimation.UseCustomTimeline = true; ana.StretchTimeScale = false; // 即不循环 ana.LoopMode = Animation.LoopModeEnum.None; 循环播放行为(Animation.LoopModeEnum.Linear):站、走、跑; 不循环播放行为(默认行为):攻击、跳
动画
动画: 动画树(AnimationTree)支持更高级的多动画混合、过渡处理等,AnimationPlayer则只支持一个简单的两动画混合过渡功能set_blend_time 轻量级补间动画节点: var tw = GetTree().CreateTween(); // 比AnimationPlayer轻量灵活。 tw.TweenProperty(GetNode("Sprite"), "modulate", Colors.Red, 1.0f); // 参数为节点对象、属性名、最终值、每次变化值。 tw.TweenProperty(GetNode("Sprite"), "scale", Vector2.Zero, 1.0f).SetTrans(Tween.TransitionType.Bounce); tw.TweenProperty(GetNode("Sprite"), "position:x", 200.0f, 1.0f); tw.TweenProperty(GetNode("Sprite"), "position", Vector2.Right * 300.0f, 1.0f).AsRelative().FromCurrent().SetTrans(Tween.TransitionType.Expo); tw.TweenCallback(Callable.From(GetNode("Sprite").QueueFree)); // 结束时释放。 动画节点: 动画面板 - ALT+N AnimationPlayer是通过AnimationLibrary列表(libraries)来存取动画的。 AnimationPlayer全局(或默认)动画 - ap.GetAnimationLibrary(""); // 非全局则将入参改为具体动画库名字。 动画时间刻度上选取标记点并“Add Marker Key”后,可到AnimationNodeAnimation的“Set Custom Timeline from Marker”中设置开始和结束Marker的动画范围。 添加AnimationPlayer,切至底部Animation面板,点“动画”-> 新建:动画名 -> 添加轨道:属性轨道 -> 选节点属性position等 ->调整属性后右键“插入关键帧”,两个关键帧之间属性要有些变化。 // 重复、循环播放 var ap = GetNode("player/AnimationPlayer"); //GD.Print(String.Join(",", ap.GetAnimationList())); ap.GetAnimation("idle").LoopMode = LoopModeEnum.Linear; ap.Play("idle"); // LoopMode会影响Play行为。 播放规律:动画没播完,再次play同一个动画不会从头播放,如果play不同的动画则会中断当前动画,马上进行切换。 动画时长:animationPlay.GetAnimation("aName").Length; // 返回值为秒数 存储动画:ResourceSaver.Save(ap.GetAnimation("name"), "d:/" + DateTime.Now.ToString("yyyy-MM-dd_hh-mm-ss") + ".anim"); // 明文用*.tres Blender自定义BlendShape用于动画轨道:Animation Editor -> Add Track -> Blend Shape Track; 属性Blend Shapes值后🔑图标用来添加至动画轨道。 var node = GetNode("rain_rig/RIG-rain/Skeleton3D/GEO-rain_head"); node.Set("blend_shapes/mouthFunnel", 0.5); GD.Print(node.Get("blend_shapes/mouthFunnel")); 或 代码构造Blend Shapes关键帧动画: var anim = new Animation(); anim.AddTrack(Animation.TrackType.BlendShape); anim.Length = 3; anim.TrackSetPath(0, new NodePath("RIG-rain/Skeleton3D/GEO-rain_head:mouthFunnel")); // 无需“blend_shapes/”前缀 anim.BlendShapeTrackInsertKey(0, 1, 0.1f); anim.BlendShapeTrackInsertKey(0, 2, 0.5f); anim.BlendShapeTrackInsertKey(0, 3, 0.9f); var ap = GetNode ("AnimationPlayer"); ap.GetAnimationLibrary("").AddAnimation("test", anim); ap.Play("test"); 最常见姿态有3个:idle、walk和run; 何时用状态机? _PhysicsProcess: if (direction != Vector3.Zero) { if (ap.CurrentAnimation != "1H_Melee_Attack_Chop") { ap.Play("Walking_A"); } } if (!ap.IsPlaying()) { ap.Play("Idle"); } 动画树(AnimationTree)支持更高级的多动画混合、过渡处理等: 用法 - 通过IDE可视化工具编排动画播放顺序及过渡关系。 AnimationTree.tree_root设为AnimationNodeBlendTree,即 IDE->AnimationTree->“根”视图,anim_player选中AnimationPlay节点供tree_root可视化编排; 若在未继承的模型场景内,可点“⋮ -> 编辑”直接输入“../AnimationPlayer”(全路径超过260字符可临时用:“../../Player/AnimationPlayer”),依然能被IDE感知到其动画列表; 可视化“动画树”窗口->“根”->添加节点->“添加动画”,即用于连线最基本单位的AnimationNodeAnimation。 如果动画在模型文件内,可通过AnimationLibrary方式导入后再用。 其他 - AnimationNodeTransition是简单版AnimationNodeStateMachine,可用于切换Flag状态:animationTree.Set("parameters/Transition/transition_request", "state_2"); // 空字符串为清除状态。 触发 - GetNode ("AnimationTree").Get("parameters/playback").As ().Travel("idle"); // 将当前状态按照编排顺序切换为入参状态。 或 根据变量字段而变化 https://www.bilibili.com/video/BV1Ye411R7Bo/ 动画混合 - AnimationNodeBlendTree之"Edit Filters"只混合选取的骨骼部位(与导入时骨骼映射无关),作用是去In(未勾)存Blend(勾中)方向动画。 // 从 12 秒处开始播放子动画。 animationTree.Set("parameters/TimeSeek/seek_request", 12.0); 当Blend2执行at.Set("parameters/Blend2/blend_amount", 0.5)时前方的AnimationNodeOneShot会自动播一次in端口的动画; AnimationNodeBlendTree调用: var at=GetNode ("AnimationTree"); at.Set("active", false);at.Set("active", true); at.Set("parameters/Blend2/blend_amount", 0.5); at.AnimationFinished += (StringName x) => { GD.Print("AnimationFinished"); }; 注意 - AnimationTree的active为true时,其anim_player指定的AnimationPlayer.Play(...)等调用会失效,且animation_finished等信号转移到了AnimationTree,可复制一份AnimationPlayer来解决: var ap2 = new AnimationPlayer(); foreach (var name in apInTree.GetAnimationLibraryList()) { ap2.AddAnimationLibrary(name, apInTree.GetAnimationLibrary(name)); } AddChild(ap2); ap2.Play("Walking_A"); // 指定new出来的AnimationPlayer,AnimationTree可视化中的AnimationNodeAnimation允许直输动画名。 GetNode ("AnimationTree").AnimPlayer = ap2.GetPath(); 动态创建AnimationTree: var at = new AnimationTree(); at.Name = "AnimationTree"; at.AnimPlayer = ap2.GetPath(); var bt = new AnimationNodeBlendTree(); at.TreeRoot = bt; AddChild(at); var ana = new AnimationNodeAnimation(); ana.Animation = "Walking_A"; bt.AddNode("Walk", ana); var anaAttack = new AnimationNodeAnimation(); anaAttack.Animation = "1H_Melee_Attack_Chop"; bt.AddNode("Attack", anaAttack); var b2 = new AnimationNodeBlend2(); bt.AddNode("Blend2", b2); // ConnectNode属于链式,可后接AnimationNodeOneShot等多步处理 bt.ConnectNode("Blend2", 0, "Walk"); bt.ConnectNode("Blend2", 1, "Attack"); // output已内置,无需AddNode bt.ConnectNode("output", 0, "Blend2"); //at.Active = false; // 遵守原动画循环设定,支持“首个循环播放”混合“第二个播一次” at.Set("parameters/Blend2/blend_amount", 0.5); 骨骼: 作用 - 使物体具有肢体活动的能力。 var sk=GetNode ("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为小腿 有限状态机(Finite State Machine): N种状态下处于单一状态 - 比如老写法currentState = enumState.IDLE;switch (currentState) { case enumState.IDLE: ... } FSM写法(适合于状态多于3种的情况) - 将enumState的每个状态分散到单个(基于StateBase)子类中单独控制。 Godot+C#状态机实例(相同状态时应改为跳过) - https://github.com/spaceyjase/sr-6 https://www.bilibili.com/video/BV1z34y1A7rg/ 3D移动人物FSM - https://www.bilibili.com/video/BV1de411i7Lh/ C#非移动人物版 - https://www.youtube.com/watch?v=Kcg1SEgDqyk 多层次状态机原理 - https://zhuanlan.zhihu.com/p/662567305 并发状态机 - 即两个状态机分别负责腿部动作(站立,下蹲,奔跑)和手部动作(持枪,空手,瞄准) 把攻击动画的控制骨骼限制在上身,下身依然执行的是老动画walk。 并发状态机参考示例 - https://info.congci.com/main/infomations/articles/8df94b5f-c8cd-11ee-904e-592f6ee49b9d