第一部分 状态机
首先按照代码框架说明,按顺序阅读相关代码,最后来到animation_FSM.cpp。
观察到animation_FSM.cpp的update函数的开头,可见已经获取了上一帧结束时的状态信息。
而后面的swtch结构便是针对上一帧结束时的状态选择决定更新下一帧开始时的状态。
我们根据上图,用if结构进行具体的判断,优先级高的就先写更新其状态的if结构,优先级低的就后写,这样就完成了状态机的功能,代码如下。
1.bool AnimationFSM::update(const json11::Json::object& signals)
2.{
3. States last_state = m_state;
4. bool is_clip_finish = tryGetBool(signals, "clip_finish", false);
5. bool is_jumping = tryGetBool(signals, "jumping", false);
6. float speed = tryGetFloat(signals, "speed", 0);
7. bool is_moving = speed > 0.01f;
8. bool start_walk_end = false;
9.
10. switch (m_state)
11. {
12. case States::_idle:
13. /**** [0] ****/
14. if (is_jumping)
15. {
16. m_state = States::_jump_start_from_idle;
17. break;
18. }
19. if (is_moving)
20. {
21. m_state = States::_walk_start;
22. break;
23. }
24. break;
25.
26. case States::_walk_start:
27. /**** [1] ****/
28. if (is_clip_finish)
29. m_state = States::_walk_run;
30. break;
31.
32. case States::_walk_run:
33. /**** [2] ****/
34. if (is_jumping)
35. {
36. m_state = States::_jump_start_from_walk_run;
37. break;
38. }
39. if (start_walk_end && is_clip_finish)
40. {
41. m_state = States::_walk_stop;
42. break;
43. }
44. if (!is_moving)
45. m_state = States::_idle;
46. break;
47.
48. case States::_walk_stop:
49. /**** [3] ****/
50. if (!is_moving&&is_clip_finish)
51. m_state = States::_idle;
52. break;
53.
54. case States::_jump_start_from_idle:
55. /**** [4] ****/
56. if (is_clip_finish)
57. m_state = States::_jump_loop_from_idle;
58. break;
59.
60. case States::_jump_loop_from_idle:
61. /**** [5] ****/
62. if (!is_jumping)
63. m_state = States::_jump_end_from_idle;
64. break;
65.
66. case States::_jump_end_from_idle:
67. /**** [6] ****/
68. if (is_clip_finish)
69. m_state = States::_idle;
70. break;
71.
72. case States::_jump_start_from_walk_run:
73. /**** [7] ****/
74. if (is_clip_finish)
75. m_state = States::_jump_loop_from_walk_run;
76. break;
77.
78. case States::_jump_loop_from_walk_run:
79. /**** [8] ****/
80. if (!is_jumping)
81. m_state = States::_jump_end_from_walk_run;
82. break;
83.
84. case States::_jump_end_from_walk_run:
85. /**** [9] ****/
86. if(is_clip_finish)
87. m_state = States::_walk_run;
88. break;
89. default:
90. break;
91. }
92. return last_state != m_state;
93.}
第二部分 blend
1.void AnimationPose::blend(const AnimationPose& pose)
2.{
3. for (int i = 0; i < m_bone_poses.size(); i++)
4. {
5. auto& bone_trans_one = m_bone_poses[i];
6. const auto& bone_trans_two = pose.m_bone_poses[i];
7.
8. float sum_weight = m_weight.m_blend_weight[i] + pose.m_weight.m_blend_weight[i];//总权重为两个关键帧的权重之和
9. if (sum_weight != 0)
10. {
11. float cur_weight = pose.m_weight.m_blend_weight[i] / sum_weight; //当前权重为要混合的那个pose的权重与总权重之比
12. m_weight.m_blend_weight[i] = pose.m_weight.m_blend_weight[i];
13. bone_trans_one.m_position = Vector3::lerp(bone_trans_one.m_position, bone_trans_two.m_position, cur_weight);
14. bone_trans_one.m_scale = Vector3::lerp(bone_trans_one.m_scale, bone_trans_two.m_scale, cur_weight);
15. bone_trans_one.m_rotation =
16. Quaternion::sLerp(cur_weight, bone_trans_one.m_rotation, bone_trans_two.m_rotation,true);
17. }
18. }
19.}
完成之后便实现了走跑过渡动画。
注意这里旋转的插值应该要用slerp (球面线性插值),而且要以最短路径插值,否则在状态切换时会发生大角度突变。
完成该部分时遇到的小问题:
最开始我的cur_weight是等于m_weight.m_blend_weight[i] / sum_weight的,结果导致idle的时候也会播放_walk_start的动画,因为idle状态下的m_weight.m_blend_weight[i]是大于0的,所以导致cur_weight大于0,所以会向后插值,于是看到了_walk_start的效果。
后面改成了cur_weight = pose.m_weight.m_blend_weight[i] / sum_weight就好了。
第三部分 move
当碰到墙壁时,在水平面上的最终位移应该是horizontal_displacement向量在墙面上的投影所形成的向量。这样就能实现前进碰到墙壁可以 自动调整位移方向往侧边移动的效果。
1.// side pass
2. if (physics_scene->sweep(
3. m_rigidbody_shape,
4. world_transform.getMatrix(),
5. horizontal_direction,
6. horizontal_displacement.length(),
7. hits))
8. {
9. //final_position += /**** [3] ****/ -horizontal_displacement; //这里是无位移修正效果的实现方法(已废弃)
10. final_position += horizontal_displacement
11. -(hits[0].hit_normal.dotProduct(horizontal_displacement)
12. /hits[0].hit_normal.length())
13. *hits[0].hit_normal.normalisedCopy();
14. }
15. else
16. {
17. final_position += horizontal_displacement;
18. }
19.
20. return final_position;
完成代码后便实现了跳起后空中碰到墙壁可以落回地面,前进碰到墙壁可以 自动调整位移方向往侧边移动的效果。
走跑跳动画效果:
第三部分部分实现后的效果: