跳转到内容
CRUSHFORMV1.1.0
在 Fab 上获取

集成宿主接口

宿主接口

车辆适配器会替你为车辆回答这些问题。当你没有适配器,或者你的游戏自带宿主时,请使用这些接口。自定义车辆介绍了适配器和一个完整的车轮宿主,核心概念中有汇总表。本页依次介绍每个接口:它会被问什么、默认回答什么,以及如何实现。

组件按以下顺序查找宿主,并通过宿主向其拥有者提问:

  1. 显式宿主,在 BeginPlay 之前用 SetCrushformHost 设置。仅限 C++。
  2. 在世界子系统 UCFHostRegistry 中为拥有者注册的对象。其 Register、Unregister 和 FindHost 仅限 C++。
  3. 拥有者本身,前提是其类实现了该接口。
  4. 没有宿主。此时组件以无宿主的车身身份作答。

自定义车辆用一段话给出了相同的顺序。

车辆适配器会把自己注册为它所实现的五个接口的宿主:ICFDeformHost、ICFWheelHost、ICFBreakerHost、ICFTireHost 和 ICFEngineHost。带有适配器的车辆无需为这些接口另设宿主。适配器不实现 ICFTireViewer,它属于玩家所乘坐的 Pawn。

未实现该接口的宿主会被拒绝并报错,详见本页末尾的故障排除一节。同一 Actor 和接口的第二个宿主会替换第一个,并给出警告。

在注册表中为某个 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_ 调用。

UCFDeformComponent 的晶格会向它提问,车身的 UCFNetImpactFeedbackComponent 也是如此。请在拥有者上实现它。

事件回答内容默认值
IsCrushformImpactSourceHereECFHostAnswer:本机是否模拟该车身并能看到其接触权威端
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。

Class Settings 中的 Implemented Interfaces 列表,标题行有 Add 按钮,下方列出 CFDeform Host 和 CFWheel Host。
两个接口添加之后的 Class Settings。
该图表实现了 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;
}

车身的污垢层、UCFRollingAudioComponent 和 UCFRollingFXComponent 会向它提问。蓝图宿主每帧被询问一次,同一帧内的所有调用者共享其答案。该事件为每个车轮填充一个 FCFWheelContact,无论车轮是否着地:

  • 同一车轮的 WheelIndex 必须在各帧之间保持不变。
  • 悬空的车轮保持 bGrounded 为假。其 Point 和 PhysicalMaterial 不会被读取。
  • 只报告着地车轮的宿主也是正确的,但这样就永远看不到车轮离开地面。

没有宿主时列表为空,车身永远不会变脏。

蓝图。 在 Class Settings > Implemented Interfaces > Add 中添加接口,然后在 My Blueprint 中实现 GatherCrushformWheelContacts。它带有输出数组,因此是一个函数。下方的图表实现了它,运行时打印信息,并保持数组为空。请在你自己的图表中填充该数组。

该图表实现了 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.
}

UCFPropContactComponent 会向它提问。它决定什么会被破坏,以及报告送往何处。它对拖车很有用,拖车的接触通过其牵引车来破坏道具。每个答案都有一个默认值,描述的是没有宿主的车身:

事件回答内容默认值
IsBreakerSimulatedHereECFHostAnswer:本机是否模拟该车身权威端
GetBreakerBody其接触会破坏单元的刚体空(null),即拥有者的根基元
GetBreakReportRoute其连接负责传递破坏报告的 UCFPropContactComponent空(null),即拥有者自身的组件
GetBreakerDeform被破坏的单元使其凹陷的 UCFDeformComponent空(null):被破坏的单元永远不会使车身凹陷

蓝图。 添加接口,并实现你需要的事件。下方的图表实现了 IsBreakerSimulatedHere。事件运行时它会打印信息,并将答案保持为 Default。

该图表实现了 IsBreakerSimulatedHere,并将答案保持为 Default。

C++。 通过牵引车进行报告的拖车的重写:

#include "CFBreakerHost.h"
UCFPropContactComponent* ATrailer::GetBreakReportRoute_Implementation() const
{
return TractorContact;
}

UCFTireComponent 向它询问车辆的车轮,并将轮胎偏移量和推挤量写回宿主。宿主不保存任何轮胎状态。组件会记住它改动过的内容,并在自身移除时将每个偏移量还原。共有四个事件:

事件回答内容默认值
GatherCrushformTireWheelsFCFTireWheelDesc 数组:车轮的网格、其 PhysicsRadius、HostIndex、VisualOffset 和 bAcceptsPush无,即保持轮胎为圆形
SetCrushformTireWheelOffsets一次性接收组件改动过的偏移量:每个 OffsetCm 和每个 PushCm无
GatherCrushformTireLoadRatios按 HostIndex 给出的接触力与标称载荷之比。空数组表示没有力模型空
GatherCrushformTireRig与此 Actor 耦合的 Actor,包括其自身仅此 Actor

蓝图。 添加接口,并将收集事件实现为函数。下方的图表实现了 GatherCrushformTireWheels,运行时打印信息,并保持数组为空。

该图表实现了 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。

Pawn 实现 ICFTireViewer,用于告诉轮胎预算玩家所乘坐的车辆,从而让预算知道观察者自己的车辆。该事件返回车辆;Pawn 步行时返回空(null)。

蓝图。 将接口添加到 Pawn。下方的图表在 Pawn 上实现了 GetCrushformTireViewedHost,运行时打印信息,并返回 null。

该图表在 Pawn 上实现了 GetCrushformTireViewedHost,并返回 null。

C++。

#include "CFTireViewer.h"
AActor* AMyPawn::GetCrushformTireViewedHost_Implementation() const
{
return Vehicle.Get();
}

UCFEngineAudioComponent 每帧向它询问发动机状态。其答案为 FCFEngineState:

字段含义
RunStateOff、Cranking 或 Running
RPM发动机转速。声音将负的转速视为其数值大小
IdleRPM运行中的发动机,其声音永远不会低于该转速
MaxRPM声音所跟随的最高转速。零表示没有上限
Load油门开度,0 到 1

默认值是熄火的发动机。车辆适配器同样实现了该接口。

蓝图。 添加接口,并将 GetCrushformEngineState 实现为返回该结构体的函数。下方的图表实现了它,运行时打印信息,并返回默认的发动机状态,即熄火的发动机。

该图表实现了 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;
}

ICFBreakableHost 和 ICFBreakableUnitPart 使用 CannotImplementInterfaceInBlueprint 声明。请仅在 C++ 中实现它们。它们属于可破坏的碎片,两者的成员都列在 API 参考中。

  • 在非 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++ 中,将接口加入类的基类列表,并重写相应事件。

原因。 要么车轮宿主没有收集到任何车轮,要么项目没有路面资产。没有路面资产时,任何车身都不会变脏。未实现收集事件的宿主会返回空列表,这也就是默认值。

解决。 实现 GatherCrushformWheelContacts,为每个车轮添加一个 FCFWheelContact。在 Project Settings > Plugins > Crushform 中设置 Surfaces。

原因。 该事件通过名称在接口指针上被调用,而其生成的函数体是一个 check。

解决。 如自定义车辆所示,通过 Execute_ 调用,或在蓝图中使用 Message 节点。