集成宿主接口
宿主接口
车辆适配器会替你为车辆回答这些问题。当你没有适配器,或者你的游戏自带宿主时,请使用这些接口。自定义车辆介绍了适配器和一个完整的车轮宿主,核心概念中有汇总表。本页依次介绍每个接口:它会被问什么、默认回答什么,以及如何实现。
由谁回答,按什么顺序
Section titled “由谁回答,按什么顺序”组件按以下顺序查找宿主,并通过宿主向其拥有者提问:
- 显式宿主,在 BeginPlay 之前用
SetCrushformHost设置。仅限 C++。 - 在世界子系统
UCFHostRegistry中为拥有者注册的对象。其Register、Unregister和FindHost仅限 C++。 - 拥有者本身,前提是其类实现了该接口。
- 没有宿主。此时组件以无宿主的车身身份作答。
自定义车辆用一段话给出了相同的顺序。
车辆适配器会把自己注册为它所实现的五个接口的宿主:ICFDeformHost、ICFWheelHost、ICFBreakerHost、ICFTireHost 和 ICFEngineHost。带有适配器的车辆无需为这些接口另设宿主。适配器不实现 ICFTireViewer,它属于玩家所乘坐的 Pawn。
未实现该接口的宿主会被拒绝并报错,详见本页末尾的故障排除一节。同一 Actor 和接口的第二个宿主会替换第一个,并给出警告。
在注册表中注册宿主
Section titled “在注册表中注册宿主”在注册表中为某个 Actor 注册宿主后,该 Actor 的每个组件都能找到它,无需调用 SetCrushformHost。注册表是世界子系统,因此通过世界获取它。在 Actor 的 BeginPlay 中,将 Actor 自身注册为其车轮宿主,并在 EndPlay 中移除注册:
#include "CFHostRegistry.h"#include "CFWheelHost.h"#include "Engine/World.h"
void AMyVehicle::BeginPlay(){ Super::BeginPlay();
if (UCFHostRegistry* Registry = GetWorld()->GetSubsystem<UCFHostRegistry>()) { // This actor implements ICFWheelHost. Registry->Register(*this, *UCFWheelHost::StaticClass(), *this); }}
void AMyVehicle::EndPlay(const EEndPlayReason::Type EndPlayReason){ if (UCFHostRegistry* Registry = GetWorld()->GetSubsystem<UCFHostRegistry>()) { Registry->Unregister(*this, *UCFWheelHost::StaticClass(), *this); }
Super::EndPlay(EndPlayReason);}Unregister仅在条目仍持有该宿主时才将其移除。- 重复注册同一宿主不会产生任何改变,也不会输出日志。
FindHost在没有注册表时也能工作。此时,若 Actor 实现了该接口,它会以 Actor 本身作答。
每个答案都是 ECFHostAnswer:
| 值 | 含义 |
|---|---|
Default | 按组件在无宿主时的方式决定。 |
Yes | 是。 |
No | 否。 |
在蓝图中未修改的事件,返回的答案与无宿主时相同。返回 ECFHostAnswer 的事件返回 Default。返回列表的事件不收集任何内容。GatherCrushformIgnoredActors 返回 false,这意味着拥有者所附着的 Actor 仍被忽略。
不要通过接口指针按名称调用接口事件。自定义车辆中展示了应改用的 Execute_ 调用。
ICFDeformHost
Section titled “ICFDeformHost”UCFDeformComponent 的晶格会向它提问,车身的 UCFNetImpactFeedbackComponent 也是如此。请在拥有者上实现它。
| 事件 | 回答内容 | 默认值 |
|---|---|---|
IsCrushformImpactSourceHere | ECFHostAnswer:本机是否模拟该车身并能看到其接触 | 权威端 |
GatherCrushformIgnoredActors | 输出数组:永不使拥有者变形的 Actor;以及一个布尔值,用于替换拥有者所附着的 Actor | 假,且不收集任何内容 |
ShouldCrushformIgnore | 针对单个 Actor 的布尔值,比上面的列表先被询问:例如行人走进汽车 | 假,即不忽略任何 Actor |
IsCrushformReportPlausible | 客户端撞击报告的 ECFHostAnswer。服务器在完成自身检查后询问它 | 合理 |
ShouldCrushformDeform | 针对单个网格的 ECFHostAnswer。配置文件的标签已先行放行该网格 | 会变形 |
GatherCrushformWheelMeshes | 输出车轮网格数组。它们接收污垢,且从不变形 | 无 |
GetCrushformGlass | 拥有者的 UCFGlassUnitsComponent,或 null | 空(null) |
蓝图。 在 Class Settings > Implemented Interfaces > Add 中添加接口。在 My Blueprint 中,其函数列在 Interfaces > Crushform 下,名称与 C++ 名称相同且不含空格,例如 IsCrushformImpactSourceHere。下方的图表实现了该函数。函数运行时它会打印信息,并将答案保持为 Default。
C++。 在 Actor 上实现该接口,并为你要回答的每个事件重写其 _Implementation。
#include "CFDeformHost.h"
UCLASS()class AMyVehicle : public AActor, public ICFDeformHost{ GENERATED_BODY()
public: virtual ECFHostAnswer IsCrushformImpactSourceHere_Implementation() const override; virtual bool ShouldCrushformIgnore_Implementation(const AActor* Actor) const override; virtual ECFHostAnswer ShouldCrushformDeform_Implementation(const UMeshComponent* Mesh) const override; virtual UCFGlassUnitsComponent* GetCrushformGlass_Implementation() const override;};ECFHostAnswer AMyVehicle::IsCrushformImpactSourceHere_Implementation() const{ // Only the machine that simulates the body judges its contacts. return HasAuthority() ? ECFHostAnswer::Yes : ECFHostAnswer::No;}ICFWheelHost
Section titled “ICFWheelHost”车身的污垢层、UCFRollingAudioComponent 和 UCFRollingFXComponent 会向它提问。蓝图宿主每帧被询问一次,同一帧内的所有调用者共享其答案。该事件为每个车轮填充一个 FCFWheelContact,无论车轮是否着地:
- 同一车轮的
WheelIndex必须在各帧之间保持不变。 - 悬空的车轮保持
bGrounded为假。其Point和PhysicalMaterial不会被读取。 - 只报告着地车轮的宿主也是正确的,但这样就永远看不到车轮离开地面。
没有宿主时列表为空,车身永远不会变脏。
蓝图。 在 Class Settings > Implemented Interfaces > Add 中添加接口,然后在 My Blueprint 中实现 GatherCrushformWheelContacts。它带有输出数组,因此是一个函数。下方的图表实现了它,运行时打印信息,并保持数组为空。请在你自己的图表中填充该数组。
C++。 如同上面的 Deform 代码块那样在类中声明重写,并给出定义。自定义车辆构建了一个完整的车轮宿主,其中包含从每个轮毂发出的射线追踪。
#include "CFWheelHost.h"
// In the class: virtual void GatherCrushformWheelContacts_Implementation(TArray<FCFWheelContact>& OutContacts) const override;
void AMyVehicle::GatherCrushformWheelContacts_Implementation(TArray<FCFWheelContact>& OutContacts) const{ // One entry per wheel, WheelIndex stable from frame to frame.}ICFBreakerHost
Section titled “ICFBreakerHost”UCFPropContactComponent 会向它提问。它决定什么会被破坏,以及报告送往何处。它对拖车很有用,拖车的接触通过其牵引车来破坏道具。每个答案都有一个默认值,描述的是没有宿主的车身:
| 事件 | 回答内容 | 默认值 |
|---|---|---|
IsBreakerSimulatedHere | ECFHostAnswer:本机是否模拟该车身 | 权威端 |
GetBreakerBody | 其接触会破坏单元的刚体 | 空(null),即拥有者的根基元 |
GetBreakReportRoute | 其连接负责传递破坏报告的 UCFPropContactComponent | 空(null),即拥有者自身的组件 |
GetBreakerDeform | 被破坏的单元使其凹陷的 UCFDeformComponent | 空(null):被破坏的单元永远不会使车身凹陷 |
蓝图。 添加接口,并实现你需要的事件。下方的图表实现了 IsBreakerSimulatedHere。事件运行时它会打印信息,并将答案保持为 Default。
C++。 通过牵引车进行报告的拖车的重写:
#include "CFBreakerHost.h"
UCFPropContactComponent* ATrailer::GetBreakReportRoute_Implementation() const{ return TractorContact;}ICFTireHost
Section titled “ICFTireHost”UCFTireComponent 向它询问车辆的车轮,并将轮胎偏移量和推挤量写回宿主。宿主不保存任何轮胎状态。组件会记住它改动过的内容,并在自身移除时将每个偏移量还原。共有四个事件:
| 事件 | 回答内容 | 默认值 |
|---|---|---|
GatherCrushformTireWheels | FCFTireWheelDesc 数组:车轮的网格、其 PhysicsRadius、HostIndex、VisualOffset 和 bAcceptsPush | 无,即保持轮胎为圆形 |
SetCrushformTireWheelOffsets | 一次性接收组件改动过的偏移量:每个 OffsetCm 和每个 PushCm | 无 |
GatherCrushformTireLoadRatios | 按 HostIndex 给出的接触力与标称载荷之比。空数组表示没有力模型 | 空 |
GatherCrushformTireRig | 与此 Actor 耦合的 Actor,包括其自身 | 仅此 Actor |
蓝图。 添加接口,并将收集事件实现为函数。下方的图表实现了 GatherCrushformTireWheels,运行时打印信息,并保持数组为空。
C++。 车辆上的两个收集函数:
#include "CFTireHost.h"
void AMyVehicle::GatherCrushformTireWheels_Implementation(TArray<FCFTireWheelDesc>& OutWheels) const{ // One entry for each wheel that has a mesh, in a stable order. HostIndex is the index you give the component back.}
void AMyVehicle::GatherCrushformTireLoadRatios_Implementation(TArray<float>& OutRatios) const{ // Contact force over nominal load, by HostIndex. Leave the array empty when you have no force model.}推挤。 设置了 bAcceptsPush 的车轮,在轮辋否则会嵌入障碍物时,会按其 PushCm 移动,使轮胎凹陷绕过障碍物,而不是让轮辋陷入。只有当 SetCrushformTireWheelOffsets 在同一次调用中把 PushCm 应用到车轮网格上(采用车主坐标系),并且网格保持该推挤直到下一次调用时,才设置 bAcceptsPush。如果宿主忽略推挤,推挤就会一路增大到配置的 MaxPush。如果宿主让推挤过期,因为其他东西移动了网格,或使其父级相对于拥有者转动,凹陷就会偏离其应有的位置。轮辋半径取自轮胎材质的 RimRadius 参数,或取自配置的 RimRadiusOverride;两者都没有的车轮从不被推挤。
适配器。 UCFVehicleAdapterComponent 自行应用推挤,并在没有蓝图覆盖 SetCrushformTireWheelOffsets 之前,为其车轮报告 bAcceptsPush。覆盖了它的蓝图自行摆放车轮,不会获得推挤。在 C++ 中覆盖 SetCrushformTireWheelOffsets_Implementation 的子类,承担与任何宿主相同的职责:调用基类(它会应用 PushCm),或自行应用推挤,或覆盖 BuildTireWheels 并把 bAcceptsPush 报告为 false。基类在推挤到达时将其一次性转换到车轮父级的空间中。如果车轮的父级相对于车主转动(例如转向节),或游戏改写了车轮的相对位置,子类便不再携带轮胎会减去的那份推挤,于是应把 bAcceptsPush 报告为 false。
ICFTireViewer
Section titled “ICFTireViewer”Pawn 实现 ICFTireViewer,用于告诉轮胎预算玩家所乘坐的车辆,从而让预算知道观察者自己的车辆。该事件返回车辆;Pawn 步行时返回空(null)。
蓝图。 将接口添加到 Pawn。下方的图表在 Pawn 上实现了 GetCrushformTireViewedHost,运行时打印信息,并返回 null。
C++。
#include "CFTireViewer.h"
AActor* AMyPawn::GetCrushformTireViewedHost_Implementation() const{ return Vehicle.Get();}ICFEngineHost
Section titled “ICFEngineHost”UCFEngineAudioComponent 每帧向它询问发动机状态。其答案为 FCFEngineState:
| 字段 | 含义 |
|---|---|
RunState | Off、Cranking 或 Running |
RPM | 发动机转速。声音将负的转速视为其数值大小 |
IdleRPM | 运行中的发动机,其声音永远不会低于该转速 |
MaxRPM | 声音所跟随的最高转速。零表示没有上限 |
Load | 油门开度,0 到 1 |
默认值是熄火的发动机。车辆适配器同样实现了该接口。
蓝图。 添加接口,并将 GetCrushformEngineState 实现为返回该结构体的函数。下方的图表实现了它,运行时打印信息,并返回默认的发动机状态,即熄火的发动机。
C++。
#include "CFEngineHost.h"
FCFEngineState AMyVehicle::GetCrushformEngineState_Implementation() const{ FCFEngineState State; State.RunState = bEngineRunning ? ECFEngineRunState::Running : ECFEngineRunState::Off; State.RPM = EngineRPM; State.IdleRPM = IdleRPM; State.MaxRPM = MaxRPM; State.Load = Throttle; return State;}仅限 C++ 的接口
Section titled “仅限 C++ 的接口”ICFBreakableHost 和 ICFBreakableUnitPart 使用 CannotImplementInterfaceInBlueprint 声明。请仅在 C++ 中实现它们。它们属于可破坏的碎片,两者的成员都列在 API 参考中。
检查是否正常工作
Section titled “检查是否正常工作”- 在非 Shipping 构建中,运行
Crushform.Debug 1,然后用Crushform.DebugImpact撞击车身。受损节点会出现在车身凹陷处,这说明形变宿主已作出回答。 - 使用 Play In Editor 运行,并在 Output Log 中查看被拒绝宿主的错误与被替换宿主的警告。日志干净,说明每个宿主都已被接受。
- 实现车轮宿主并设置好路面后,驾车穿过一段泥浆。污垢会附着在车轮和车身上。
宿主被拒绝,且日志中给出了接口名
Section titled “宿主被拒绝,且日志中给出了接口名”原因。 该对象未实现此接口,因此注册表拒绝了它。日志内容如下:
Host %s does not implement %s, so it cannot answer for %s解决。 在蓝图中,于 Class Settings > Implemented Interfaces > Add 中添加接口。在 C++ 中,将接口加入类的基类列表,并重写相应事件。
车轮不沾污垢
Section titled “车轮不沾污垢”原因。 要么车轮宿主没有收集到任何车轮,要么项目没有路面资产。没有路面资产时,任何车身都不会变脏。未实现收集事件的宿主会返回空列表,这也就是默认值。
解决。 实现 GatherCrushformWheelContacts,为每个车轮添加一个 FCFWheelContact。在 Project Settings > Plugins > Crushform 中设置 Surfaces。
在编辑器中调用接口事件失败
Section titled “在编辑器中调用接口事件失败”原因。 该事件通过名称在接口指针上被调用,而其生成的函数体是一个 check。
解决。 如自定义车辆所示,通过 Execute_ 调用,或在蓝图中使用 Message 节点。
