目录
引言
本次作业为作业3的内容,我们需要在Piccolo项目上拉取分支games104/homework03-animation-physics,可以通过git进行拉取,输入命令 git clone -b games104/homework03-animation-physics + 项目地址 如下:
一、实现走跑跳状态机
在Piccolo小引擎代码中找到Piccolo/engine/source/runtime/function/animation/animation_FSM.cpp,找到update函数补充代码,实现机器人走跑跳状态机
根据状态机示意图,补全代码即可,下来我会简单梳理一下状态机逻辑
switch结构首先会根据上一帧结束时的状态决定下一帧开始时的状态
状态机最开始的时候指向_idle,接下来有两个数字1和2表示动作的优先级,当is_jumping为真时,会进入jump_start_from_idle状态,当is_moving为真时,会进入到walk_start状态,剩余状态同理,根据当前状态以及bool变量的值来决定!
补全代码如下:
bool AnimationFSM::update(const json11::Json::object& signals)
{
States last_state = m_state;
bool is_clip_finish = tryGetBool(signals, "clip_finish", false);
bool is_jumping = tryGetBool(signals, "jumping", false);
float speed = tryGetFloat(signals, "speed", 0);
bool is_moving = speed > 0.01f;
bool start_walk_end = false;
//switch结构根据上一帧结束时的状态选择 决定下一帧开始时的状态
switch (m_state)
{
case States::_idle:
/**** [0] ****/
if(is_jumping){
m_state = States::_jump_start_from_idle;
break;
}
if(is_moving){
m_state = States::_walk_start;
break;
}
break;
case States::_walk_start:
/**** [1] ****/
if(is_clip_finish){
m_state = States::_walk_run;
break;
}
break;
case States::_walk_run:
/**** [2] ****/
if(is_jumping){
m_state = States::_jump_start_from_walk_run;
break;
}
if(start_walk_end && is_clip_finish){
m_state = States::_walk_stop;
break;
}
if(!is_moving){
m_state = States::_idle;
}
break;
case States::_walk_stop:
/**** [3] ****/
if(!is_moving && is_clip_finish){
m_state = States::_idle;
}
break;
case States::_jump_start_from_idle:
/**** [4] ****/
if(is_clip_finish){
m_state = States::_jump_loop_from_idle;
}
break;
case States::_jump_loop_from_idle:
/**** [5] ****/
if(!is_jumping){
m_state = States::_jump_end_from_idle;
}
break;
case States::_jump_end_from_idle:
/**** [6] ****/
if(is_clip_finish){
m_state = States::_idle;
}
break;
case States::_jump_start_from_walk_run:
/**** [7] ****/
if(is_clip_finish){
m_state = States::_jump_loop_from_walk_run;
}
break;
case States::_jump_loop_from_walk_run:
/**** [8] ****/
if(!is_jumping){
m_state = States::_jump_end_from_walk_run;
}
break;
case States::_jump_end_from_walk_run:
/**** [9] ****/
if(is_clip_finish){
m_state = States::_walk_run;
}
break;
default:
break;
}
//判断 last_state 是否等于 m_state
//表示状态发生了改变 还是 状态未改变
return last_state != m_state;
}
二、走跑跳过渡动画
只有状态的转换可不行,走跑跳之间骨骼的过渡也是动画的一部分,这样才能看起来很自然!
所以还需要补全blend函数,在Piccolo小引擎中找到Piccolo/engine/source/runtime/function/animation/pose.cpp,对走跑跳进行一个过渡,代码都有详细的注释
//混合函数 接受一个AnimationPose类型的参数 pose
void AnimationPose::blend(const AnimationPose& pose)
{
//遍历数组m_bone_poses中所有数组
for (int i = 0; i < m_bone_poses.size(); i++)
{
//表示当前动画姿势的骨骼变换
auto& bone_trans_one = m_bone_poses[i];
//表示传入的动画姿势pose的骨骼变换
const auto& bone_trans_two = pose.m_bone_poses[i];
//计算两个关键帧的权重之和,用于后续的归一化操作
float sum_weight = m_weight.m_blend_weight[i] + pose.m_weight.m_blend_weight[i];
if (sum_weight != 0)
{
//计算当前姿势的权重 要混合的姿势pose的权重与总权重之比
float cur_weight = m_weight.m_blend_weight[i] / sum_weight;
//将要混合姿势pose的权重赋值给当前姿势的权重,用于后续的权重比较
m_weight.m_blend_weight[i] = pose.m_weight.m_blend_weight[i];
//使用线性插值函数lerp对当前姿势的位置进行混合
bone_trans_one.m_position = Vector3::lerp(bone_trans_one.m_position, bone_trans_two.m_position,1 - cur_weight);
//使用线性插值函数lerp对当前姿势的缩放进行混合
bone_trans_one.m_scale = Vector3::lerp(bone_trans_one.m_scale, bone_trans_two.m_scale, 1 - cur_weight);
//使用球面线性插值函数sLerp对当前姿势的旋转进行混合
bone_trans_one.m_rotation = Quaternion::sLerp(1 - cur_weight, bone_trans_one.m_rotation, bone_trans_two.m_rotation, true);
}
}
}
三、结果展示
走跑跳