UE5-ChaosDestruction
场与破碎阈值
Field
场可以造成物体破碎,也可以用于固定物体等
UE中使用AFieldSystemActor
来管理场,AFieldSystemActor
中的FieldSystemComponent
用于创建场。从蓝图的角度看,我们会创建一个继承自AFieldSystemActor
的蓝图类来自定义场,如官方示例中的FS_AnchorField_Generic
,FS_MasterField
以及FS_SleepDisable_Generic
场分为三种
Transient Field
:瞬时场。最常用的场,可以通过BeginPlay,Tick或者是Event的形式生成并生效Persistent Field
:持久场Construction Field
:构造场。需要在构造函数中创建,并需要在Geometry Collection
中注册。用途可以参照官方的FS_SleepDisable_Generic
AGeometryCollectionActor
通常为摆在场景中,代表一个可破坏的Geometry
class GEOMETRYCOLLECTIONENGINE_API AGeometryCollectionActor: public AActor
{
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
TObjectPtr<UGeometryCollectionComponent> GeometryCollectionComponent;
};
UGeometryCollectionComponent
中维护了一个UGeometryCollection
,以及各项Destruction相关的数据
class GEOMETRYCOLLECTIONENGINE_API UGeometryCollectionComponent : public UMeshComponent, public IChaosNotifyHandlerInterface
{
UPROPERTY(EditAnywhere, NoClear, BlueprintReadOnly, Category = "ChaosPhysics")
TObjectPtr<const UGeometryCollection> RestCollection;
// 等等和碰撞有关的数据...
};
UGeometryCollection
是一个UObject
,其中也维护了和UGeometryCollectionComponent
中重复的Destruction数据。在生成AGeometryCollectionActor
时,会将数据拷贝至AGeometryCollectionActor
的UGeometryCollectionComponent
中,此方法通过ActorFactory实现
UActorFactoryGeometryCollection::UActorFactoryGeometryCollection(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
DisplayName = LOCTEXT("GeometryCollectionDisplayName", "GeometryCollection");
NewActorClass = AGeometryCollectionActor::StaticClass();
}
void UActorFactoryGeometryCollection::PostSpawnActor(UObject* Asset, AActor* NewActor)
{
Super::PostSpawnActor(Asset, NewActor);
// ...
// Set configured clustering properties.
NewGeometryCollectionActor->GetGeometryCollectionComponent()->EnableClustering = GeometryCollection->EnableClustering;
NewGeometryCollectionActor->GetGeometryCollectionComponent()->ClusterGroupIndex = GeometryCollection->ClusterGroupIndex;
NewGeometryCollectionActor->GetGeometryCollectionComponent()->MaxClusterLevel = GeometryCollection->MaxClusterLevel;
NewGeometryCollectionActor->GetGeometryCollectionComponent()->SetPhysMaterialOverride(GEngine->DefaultDestructiblePhysMaterial);
// ...
}
DamageThreshold
在UGeometryCollectionComponent
中,DamageThreshold
控制了物体不同的损坏层级
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TArray<float> DamageThreshold;
array index | value |
---|---|
0 | 2000 |
1 | 1000 |
2 | 1500 |
假设物体的损坏层级和数组的大小相同,那么当"Damage"处于0 - 2000时,物体处于原状,当2000 - (2000 + 1000) 时,物体处于损坏一级,当 (2000+1000) - (2000 + 1000 + 1500) 时,处于损坏二级,以此类推
这里UE虽然取名为
DamageThreshold
,但它与AActor::TakeDamage
并无关系,它在UE结算的底层起始被称作Straintemplate<class T, int d> class TPBDRigidClusteredParticles : public TPBDRigidParticles<T, d> { const auto& Strains(int32 Idx) const { return MStrains[Idx]; } };
引擎对DamageThreshold的计算方式与上述有些出入,但上述描述更易于理解,具体可见以下代码
TSet<FPBDRigidParticleHandle*> FRigidClustering::ReleaseClusterParticlesImpl( FPBDRigidClusteredParticleHandle* ClusteredParticle, const TMap<FGeometryParticleHandle*, Chaos::FReal>* ExternalStrainMap, bool bForceRelease, bool bCreateNewClusters)
场与DamageThreshold
以瞬态场为例,设为该场添加了RadialFalloff FieldNode
,下面剖析RadialFalloff
是如何影响到场景中物体的DamageThreshold
,以造成不同等级的物体破碎效果的
// Display As "Add Transient Field" In Blueprint
void UFieldSystemComponent::ApplyPhysicsField(bool Enabled, EFieldPhysicsType Target, UFieldSystemMetaData* MetaData, UFieldNodeBase* Field)
{
BuildFieldCommand(Enabled, Target, MetaData, Field, true);
}
构建一个场关键注意两个参数
-
UFieldNodeBase
:用于评估Field作用域的节点,其中承载了各项评估数据。上图中的Field Magnitude
是用于累加于Strain上,并以此来评估DamageThreshold
的UCLASS() class FIELDSYSTEMENGINE_API UFieldNodeBase : public UActorComponent { GENERATED_BODY() public: virtual ~UFieldNodeBase() {} virtual FFieldNodeBase::EFieldType Type() const { return FFieldNodeBase::EFieldType::EField_None; } virtual bool ResultsExpector() const { return false; } // 把UFieldNodeBase Component中的数据 拷贝到对应的new出来的数据类FFieldNodeBase 用于后续评估 virtual FFieldNodeBase* NewEvaluationGraph(TArray<const UFieldNodeBase*>& Nodes) const { return nullptr; } };
UE使用
UFieldNodeBase
作为可视化组件编辑,FFieldNodeBase
作为数据载体进行后续运算,FFieldNodeBase
的继承结构大致如下图所示/** * FieldNode<T> * * Typed field nodes are used for the evaluation of specific types of data arrays. * For exampe, The FFieldNode<FVector>::Evaluate(...) will expect resutls * of type TFieldArrayView<FVector>, and an example implementation is the UniformVectorField. * */ template<class T> class FFieldNode : public FFieldNodeBase { public: virtual ~FFieldNode() {} // 评估计算 virtual void Evaluate(FFieldContext&, TFieldArrayView<T>& Results) const = 0; static EFieldType StaticType(); virtual EFieldType Type() const { return StaticType(); } }; template<> inline FFieldNodeBase::EFieldType FFieldNode<int32>::StaticType() { return EFieldType::EField_Int32; } template<> inline FFieldNodeBase::EFieldType FFieldNode<float>::StaticType() { return EFieldType::EField_Float; } template<> inline FFieldNodeBase::EFieldType FFieldNode<FVector>::StaticType() { return EFieldType::EField_FVector; }
-
EFieldPhysicsType
:在底层用作判断不同UFieldNodeBase*
类型的枚举值(compare and static_cast)
瞬态场与持久场
-
调用
UFieldSystemComponent::ApplyPhysicsField
或UFieldSystemComponent::AddPersistentField
构建场 -
调用
FFieldObjectCommands::CreateFieldCommand
创建FFieldSystemCommand
,其中包含了TUniquePtr<FFieldNodeBase>
以及其他数据,然后将该指令Dispatch到物理线程 -
物理线程中循环调用
FPerSolverFieldSystem::FieldParameterUpdateCallback
,若发现有待执行的指令,则处理void FPerSolverFieldSystem::FieldParameterUpdateCallback( Chaos::FPBDRigidsSolver* InSolver, Chaos::FPBDPositionConstraints& PositionTarget, TMap<int32, int32>& TargetedParticles) { if (InSolver && !InSolver->IsShuttingDown()) { FieldParameterUpdateInternal(InSolver, PositionTarget, TargetedParticles, TransientCommands, true); FieldParameterUpdateInternal(InSolver, PositionTarget, TargetedParticles, PersistentCommands, false); } }
-
根据指令中记录的
FFieldNodeBase::EFieldType
与EFieldPhysicsType
,对先前创建的节点的类型进行区分// FPerSolverFieldSystem::FieldParameterUpdateInternal if (FieldCommand.RootNode->Type() == FFieldNodeBase::EFieldType::EField_Int32) {} else if (FieldCommand.RootNode->Type() == FFieldNodeBase::EFieldType::EField_Float) {} else if (FieldCommand.RootNode->Type() == FFieldNodeBase::EFieldType::EField_FVector) {}
-
以
EFieldPhysicsType::Field_ExternalClusterStrain
为例if (FieldCommand.PhysicsType == EFieldPhysicsType::Field_ExternalClusterStrain) { TMap<Chaos::FGeometryParticleHandle*, Chaos::FReal> ExternalStrain; // 评估 获得要施加破碎力的采样点 static_cast<const FFieldNode<float>*>(FieldCommand.RootNode.Get())-> Evaluate(FieldContext, ResultsView); for (const FFieldContextIndex& Index : FieldContext.GetEvaluatedSamples()) { if (ResultsView[Index.Result] > 0) { ExternalStrain.Add(ParticleHandles[Index.Sample], ResultsView[Index.Result]); } } // 收集完力后 进一步计算模型的破碎情况 UpdateSolverBreakingModel(RigidSolver, ExternalStrain); }
最终计算是否产生破碎效果的代码为
// FRigidClustering::BreakingMode AllActivatedChildren.Add(ClusteredParticle, ReleaseClusterParticles(ClusteredParticle, ExternalStrainMap)); // FRigidClustering::ReleaseClusterParticles if (ChildStrain >= Child->Strain() || bForceRelease) {}
构造场
场Actor的基类是AFieldSystemActor
,以锚点场为例
- 在
AFieldSystemActor
的ConstructionScript
中初始化FieldNode并调用"Add Construction Field"
- 在
AFieldSystemActor
的OnConstruction
中将UFieldNodeBase
等信息记录在记录在ConstructionCommands
数组中
蓝图中
ConstructionScript
的执行先与代码中的OnConstruction
/** * Construction script, the place to spawn components and do other setup. * @note Name used in CreateBlueprint function */ UFUNCTION(BlueprintImplementableEvent, meta=(BlueprintInternalUseOnly = "true", DisplayName = "Construction Script")) void UserConstructionScript(); /** * Called when an instance of this class is placed (in editor) or spawned. * @param Transform The transform the actor was constructed at. */ virtual void OnConstruction(const FTransform& Transform) {}
-
UGeometryCollectionComponent
在Register中(runtime)拿出InitializationFields
中记录的数据,进行遍历并构造FFieldSystemCommand
,最后传递给物理线程处理UPROPERTY(EditAnywhere, NoClear, BlueprintReadOnly, Category = "ChaosPhysics") TArray<TObjectPtr<const AFieldSystemActor>> InitializationFields;
PhysicsState
对于可破碎的物体来讲,在Editor模式下进行切割后,需要在运行时设置好各种物理状态,以在以后的物理循环中进行模拟
主要看UGeometryCollectionComponent这个类
-
在组件执行完注册后,开始创建物理状态(UE会根据是否需要生成overlap事件等因素来判断是否需要延迟创建物理状态,这里不讨论)
void UActorComponent::ExecuteRegisterEvents(FRegisterComponentContext* Context) { // ... OnRegister(); // ... CreatePhysicsState(/*bAllowDeferral=*/true); }
-
创建物理状态
void UGeometryCollectionComponent::OnCreatePhysicsState() { UActorComponent::OnCreatePhysicsState(); // ... TManagedArray<int32> & DynamicState = DynamicCollection->DynamicState; // if this code is changed you may need to account for bStartAwake EObjectStateTypeEnum LocalObjectType = (ObjectType != EObjectStateTypeEnum::Chaos_Object_Sleeping) ? ObjectType : EObjectStateTypeEnum::Chaos_Object_Dynamic; // 如果不是Chaos_Object_UserDefined用户自定义的ObjectState if (LocalObjectType != EObjectStateTypeEnum::Chaos_Object_UserDefined) { if (RestCollection && (LocalObjectType == EObjectStateTypeEnum::Chaos_Object_Dynamic)) { TManagedArray<int32>& InitialDynamicState = RestCollection->GetGeometryCollection()->InitialDynamicState; // 设置每一个Particles的状态 for (int i = 0; i < DynamicState.Num(); i++) { DynamicState[i] = (InitialDynamicState[i] == static_cast<int32>(Chaos::EObjectStateType::Uninitialized)) ? static_cast<int32>(LocalObjectType) : InitialDynamicState[i]; } } else { for (int i = 0; i < DynamicState.Num(); i++) { DynamicState[i] = static_cast<int32>(LocalObjectType); } } } // ... // 初始化PhysicsProxy并传递到物理线程 if (BodyInstance.bSimulatePhysics) { RegisterAndInitializePhysicsProxy(); } }
-
初始化PhysicsProxy
void UGeometryCollectionComponent::RegisterAndInitializePhysicsProxy() { FSimulationParameters SimulationParameters; // 初始化SimulationParameters中各项数据... // ... // 获构造场中的命令 将在物理线程Tick前被使用 GetInitializationCommands(SimulationParameters.InitializationCommands); // 将PhysicsProxy添加至物理线程中 PhysicsProxy = new FGeometryCollectionPhysicsProxy (this, *DynamicCollection, SimulationParameters, InitialSimFilter, InitialQueryFilter); FPhysScene_Chaos* Scene = GetInnerChaosScene(); Scene->AddObject(this, PhysicsProxy); RegisterForEvents(); SetAsyncPhysicsTickEnabled(GetIsReplicated()); }
粒子数据获取
ObjectState
锚点场的本质就是将GeometryCollection中的部分粒子设置为Static或Kismet的状态,以此来进行固定的作用。当物体处于静态或者刚体状态时,就无法再对非物理模拟的刚体进行碰撞响应(如动画)。在Gameplay中有多种方式对GeometryCollection中粒子的状态进行设置
-
重写
OnCreatePhysicsState
if (FGeometryDynamicCollection* GeometryDynamicCollection = const_cast<FGeometryDynamicCollection*>(GetDynamicCollection()); bSetRootGeometryStatic && GeometryDynamicCollection) { // 将ObjectState设置为Chaos_Object_UserDefined后 就可以通过DynamicState数组来控制粒子的初始状态 TManagedArray<int32>& DynamicState = GeometryDynamicCollection->DynamicState; if (DynamicState.Num() > 0) { DynamicState[0] = static_cast<int32>(Chaos::EObjectStateType::Static); } } Super::OnCreatePhysicsState();
-
读取PhysicsProxy,直接设置
if (const FGeometryCollectionPhysicsProxy* PhysicsProxy = GeometryCollectionComponent->GetPhysicsProxy()) { const TArray<FGeometryCollectionPhysicsProxy::FClusterHandle*>& ClusterHandleArray = PhysicsProxy->GetParticles(); Chaos::FPBDRigidsSolver* Solver = PhysicsProxy->GetSolver<Chaos::FPBDRigidsSolver>(); for (FGeometryCollectionPhysicsProxy::FClusterHandle* ClusterHandle : ClusterHandleArray) { // 需要进行判空 避免物理线程上的对象还未初始化 if (ClusterHandle) { // 设置为Dynamic Solver->GetEvolution()->SetParticleObjectState(ClusterHandle, Chaos::EObjectStateType::Dynamic); } } }
ClusterHandle
匀速场中力的作用是通过是直接拿到Handle
,然后修改其速度实现的(修改发生在物理线程中)
Chaos::FPBDRigidParticleHandle* RigidHandle = ParticleHandles[Index.Sample]->CastToRigidParticle();
if (RigidHandle && RigidHandle->ObjectState() == Chaos::EObjectStateType::Dynamic)
{
RigidHandle->V() += ResultsView[Index.Result];
}
下面演示如何在AGeometryCollectionActor
本身中获取他所管理的粒子(Gameplay主线程中获取)
FString ParticlesDebugMessage = GetName() += " ParticlesInfo:\n";
// 获取当前对象所有模拟中的粒子
if (const FGeometryCollectionPhysicsProxy* PhysicsProxy = GeometryCollectionComponent->GetPhysicsProxy())
{
const TArray<FGeometryCollectionPhysicsProxy::FClusterHandle*>& ClusterHandleArray = PhysicsProxy->GetParticles();
for (FGeometryCollectionPhysicsProxy::FClusterHandle* ClusterHandle : ClusterHandleArray)
{
if (ClusterHandle == nullptr)
{
continue;
}
if (bHideDebugInfoWhenZeroVelocity && (ClusterHandle->V() == FVector::Zero() || ClusterHandle->W() == FVector::Zero()))
{
continue;
}
ParticlesDebugMessage += "Linear Velocity: " + ClusterHandle->V().ToString() + " " +
"Angular Velocity:" + ClusterHandle->W().ToString() + " ";
ParticlesDebugMessage += "CollisionImpulse: " + FString::SanitizeFloat(ClusterHandle->CollisionImpulse()) + " ";
ParticlesDebugMessage += "Stain: " + FString::SanitizeFloat(ClusterHandle->Strain()) + " ";
ParticlesDebugMessage += "Mass: " + FString::SanitizeFloat(ClusterHandle->M()) + " ";
// 将Chaos::EObjectStateType转换为带反射的EObjectStateTypeEnum
ParticlesDebugMessage += "ObjectState: " + StaticEnum<EObjectStateTypeEnum>()->GetNameByValue(static_cast<int64>(ClusterHandle->ObjectState())).ToString() + "\n";
}
}
GEngine->AddOnScreenDebugMessage(-1, 0, FColor::Red, ParticlesDebugMessage);
Proxy
下次一定
Bug汇总
-
在工厂中只对部分属性进行复制,这也意味着配置在UObject上的部分数据无法得到使用
void UActorFactoryGeometryCollection::PostSpawnActor(UObject* Asset, AActor* NewActor) { Super::PostSpawnActor(Asset, NewActor); // ... // Set configured clustering properties. NewGeometryCollectionActor->GetGeometryCollectionComponent()->EnableClustering = GeometryCollection->EnableClustering; NewGeometryCollectionActor->GetGeometryCollectionComponent()->ClusterGroupIndex = GeometryCollection->ClusterGroupIndex; NewGeometryCollectionActor->GetGeometryCollectionComponent()->MaxClusterLevel = GeometryCollection->MaxClusterLevel; NewGeometryCollectionActor->GetGeometryCollectionComponent()->SetPhysMaterialOverride(GEngine->DefaultDestructiblePhysMaterial); // 如DamageThreshold数据就没有进行拷贝 // ... }
-
UFieldSystemComponent::ApplyStayDynamicField
方法无法对Cluster Level大于等于2的物体生效 -
返回非引用的const对象
// GeometryCollectionPhysicsProxy.h const TArray<FClusterHandle*> GetParticles() const { return SolverParticleHandles; }
-
在设置粒子的DisableSleep阈值速度时,将线速度和角速度混用(但也有可能就是这么设计的)
UpdateMaterialSleepingThreshold
// FieldSystemProxyHelper.h UpdateMaterialDisableThreshold if (ResultThreshold != InstanceMaterial->DisabledLinearThreshold) { InstanceMaterial->DisabledLinearThreshold = ResultThreshold; InstanceMaterial->DisabledAngularThreshold = ResultThreshold; }
-
EFieldPhysicsType::Field_AngularVelociy
单词错别字