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

游戏动画、状态机、骨骼蒙皮

综合

游戏动画师 - 游戏开发中重要程度较大的职位。

动画

动画:
  轻量级补间动画节点:
    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)); // 结束时释放。

  动画节点:添加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行为。

  动画树(AnimationTree)支持更高级的多动画混合、过渡处理等,AnimationPlayer则只支持一个简单的两动画混合过渡功能set_blend_time

  动画 - 
    播放规律:动画没播完,再次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内部或外部节点字段表达式)。
        AnimationTree.tree_root设为AnimationNodeStateMachine,anim_player选中同场景的AnimationPlay;
        如果动画在模型文件内,可通过AnimationLibrary方式导入后再用。
        可视化“动画树”窗口应点右键直接“添加动画”,而不是“添加StateMachine”,且不要连线。
      其他 - 
        AnimationNodeTransition是简单版AnimationNodeStateMachine,可用于切换Flag状态:animationTree.Set("parameters/Transition/transition_request", "state_2"); // 空字符串为清除状态。
        AnimationTree设为循环播放但不影响原动画 - animationNodeAnimation.LoopMode = Animation.LoopModeEnum.Linear;
      触发 - 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