需求

怪物有细长舌头,垂下。带舌头的怪物要把3p视角下的玩家拉上去,过程中舌头要表现的向绳子物理。玩家在拉的过程中,下半身开启物理配合拉的节奏。

舌头

物理资产

舌头物理表现向绳子,需要每根骨骼设置物理约束。物理形状要大一些,避免快速往上拉时,绳子摆动太快,乱飞。
可以写个工具,自动创建物理Shape和Constraint。

const FPhysAssetCreateParams& NewBodyData = GetDefault<UPhysicsAssetGenerationSettings>()->CreateParams;  
const int32 NewBodySetupIndex = FPhysicsAssetUtils::CreateNewBody(SharedData->PhysicsAsset, BoneName, NewBodyData);

BodySetup->Modify();
// 创建Shape,设置BodySetup
...

// 给上下两个骨骼的BodySetup创建约束
// Create new constraint with a name not related to a bone, so it wont get auto managed in code that creates new bodies  
const int32 NewConstraintIndex = FPhysicsAssetUtils::CreateNewConstraint(SharedData->PhysicsAsset, *ConstraintName);  
UPhysicsConstraintTemplate* ConstraintSetup = SharedData->PhysicsAsset->ConstraintSetup[NewConstraintIndex];  
  
ConstraintSetup->Modify(false);  
  
UBodySetup* ChildBodySetup = SharedData->PhysicsAsset->SkeletalBodySetups[NewBodySetupIndex];  
UBodySetup* ParentBodySetup = SharedData->PhysicsAsset->SkeletalBodySetups[NewBodySetupIndex - 1];  
  
// Place joint at origin of child  
ConstraintSetup->DefaultInstance.ConstraintBone1 = ChildBodySetup->BoneName;  
ConstraintSetup->DefaultInstance.ConstraintBone2 = ParentBodySetup->BoneName;  
  
const FTransform ParentTransform = ConstraintSetup->DefaultInstance.CalculateDefaultParentTransform(SharedData->PhysicsAsset);  
const FTransform ChildTransform = ConstraintSetup->DefaultInstance.CalculateDefaultChildTransform();  
ConstraintSetup->DefaultInstance.SetRefPosition(EConstraintFrame::Frame1, (ChildTransform.GetLocation() - ParentTransform.GetLocation()) * 0.5f);  
ConstraintSetup->DefaultInstance.SetRefPosition(EConstraintFrame::Frame2, (ParentTransform.GetLocation() - ChildTransform.GetLocation()) * 0.5f);  
  
ConstraintSetup->DefaultInstance.ProfileInstance.LinearLimit.XMotion = LCM_Locked;  
ConstraintSetup->DefaultInstance.ProfileInstance.LinearLimit.YMotion = LCM_Locked;  
ConstraintSetup->DefaultInstance.ProfileInstance.LinearLimit.ZMotion = LCM_Locked;  
ConstraintSetup->DefaultInstance.ProfileInstance.ConeLimit.Swing1Motion = ACM_Limited;  
ConstraintSetup->DefaultInstance.ProfileInstance.ConeLimit.Swing2Motion = ACM_Limited;  
ConstraintSetup->DefaultInstance.ProfileInstance.TwistLimit.TwistMotion = ACM_Limited;  
  
ConstraintSetup->DefaultInstance.ProfileInstance.ConeLimit.Swing1LimitDegrees = 5.0f;  
ConstraintSetup->DefaultInstance.ProfileInstance.ConeLimit.Swing2LimitDegrees = 30.0f;  
ConstraintSetup->DefaultInstance.ProfileInstance.TwistLimit.TwistLimitDegrees = 15.0f;  
ConstraintSetup->DefaultInstance.SetDisableCollision(true);  
  
ConstraintSetup->DefaultInstance.UpdateLinearLimit();  
ConstraintSetup->DefaultInstance.UpdateAngularLimit();  
  
ConstraintSetup->SetDefaultProfile(ConstraintSetup->DefaultInstance);

物理设置

舌头根部以下开启物理,设置物理Blend权重(避免绳子乱飞)

玩家

跟舌头同步吊上去

玩家是挂在舌头上吊上去的,上半身不开启物理,仅仅下半身开启物理。
让舌头固定在玩家模型的固定位置,但玩家要跟着舌头动:
目前固定在spine_01位置。每帧获取舌头挂点位置,将spine_01和挂点世界坐标重合,反向调整玩家Actor的位置。每帧设置玩家Actor的位置。

注:

直接将玩家Actor或Mesh Attach到舌头上还不够,因为Attach后,自身的RelativeTransform不会改变,当舌头挂点有旋转时,就会出现舌头挂点不在玩家身上的情况。所以上面要保持舌头始终在玩家身上。

相当于已知spine_01的world space的位置,计算玩家Actor的world space的transform。
spine_01 在 world space空间的transform,设为SpineWS。
spine_01 在MeshComponent的transform可以获取,设为SpineCS。
MeshComponent相对Actor的transform可以获取,设为MeshRelativeAS。
而Acotr的ComponentToWorld是我们要求的,设为ActorWS。

则。这里使用列向量右乘。SpineWS里的Location设置成舌头的位置,Rotation,Scale保持不变。
则

注:

设置Actor位置的时候,不要Reset或者Teleport 物理,表现会不正确。所以要使用RootComponent->SetWorldTransform(ActorWS, false,nullptr, ETeleportType::None);的方式。

关闭位置同步

3p视角里的位置不能受服务器同步影响。
需要将3p视角里的角色SetReplicatingMovement(false),这样网络位置同步过来也不会理会。
但主控端依然会上传位置给服务端,两者保持同步,3p下自由处理。

遗留问题

舌头的根部不开启物理,舌头拉上去是靠平移这根骨骼控制的。会出现拉上去时,如果舌头晃动,舌头会穿怪物的嘴巴模型。
尝试过在舌头的子开启物理骨骼上升到指定高度后就关闭物理,发现会对子骨骼有瞬变的影响,导致玩家跟着瞬变。也许需要关闭过程要缓一下