Перейти к содержимому
CRUSHFORMV1.1.0
ПОЛУЧИТЬ НА FAB

ИНТЕГРАЦИЯHOST-ИНТЕРФЕЙСЫ

Host-интерфейсы

Адаптер автомобиля отвечает на эти вопросы вместо вас. Используйте интерфейсы, когда у вас нет адаптера или когда в вашей игре есть собственный host-объект. На странице Свой автомобиль описан адаптер и полный host-объект колёс, а сводная таблица есть на странице Основные понятия. На этой странице по очереди разобран каждый интерфейс: о чём его спрашивают, что он отвечает по умолчанию и как его реализовать.

Компонент запрашивает ответы у владельца через host-объект, который находит в таком порядке:

  1. Явно заданный host-объект, установленный через SetCrushformHost до BeginPlay. Только C++.
  2. Объект, зарегистрированный для владельца в UCFHostRegistry, подсистеме мира. Её Register, Unregister и FindHost доступны только в C++.
  3. Сам владелец, если его класс реализует интерфейс.
  4. Host-объекта нет. Тогда компонент отвечает как тело без host-объекта.

Этот порядок описан одним абзацем на странице Свой автомобиль.

Адаптер автомобиля регистрирует себя как host-объект для пяти интерфейсов, которые реализует: ICFDeformHost, ICFWheelHost, ICFBreakerHost, ICFTireHost и ICFEngineHost. Автомобилю с адаптером для них не нужен собственный host-объект. ICFTireViewer адаптер не реализует. Этот интерфейс принадлежит пешке, в которой сидит игрок.

Host-объект, который не реализует интерфейс, отклоняется с ошибкой. Она описана в разделе «Решение проблем» в конце этой страницы. Второй host-объект для того же эктора и интерфейса заменяет первый, и при этом выводится предупреждение.

Регистрация host-объекта для эктора в реестре позволяет каждому компоненту этого эктора найти его без вызова SetCrushformHost. Реестр является подсистемой мира, поэтому обращайтесь к нему через мир. В BeginPlay эктора зарегистрируйте сам эктор как его host-объект колёс, а регистрацию удаляйте в 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 удаляет запись, только пока в ней всё ещё стоит этот host-объект.
  • Повторная регистрация того же host-объекта ничего не меняет и не пишет в лог.
  • FindHost работает и без реестра. Тогда он возвращает сам эктор, если тот реализует интерфейс.

Каждый ответ является ECFHostAnswer:

ЗначениеСмысл
DefaultРешить так же, как компонент решает без host-объекта.
YesДа.
NoНет.

Событие Blueprint, которое вы оставили без изменений, возвращает тот же ответ, что и при отсутствии host-объекта. Событие с ECFHostAnswer возвращает Default. Событие, которое возвращает список, ничего не собирает. GatherCrushformIgnoredActors возвращает false, и прикреплённые к владельцу экторы остаются игнорируемыми.

Не вызывайте событие интерфейса по имени через указатель на интерфейс. На странице Свой автомобиль показано, какой вызов Execute_ использовать вместо этого.

Решётка UCFDeformComponent запрашивает этот интерфейс, как и UCFNetImpactFeedbackComponent кузова автомобиля. Реализуйте его на владельце.

СобытиеОтвечаетПо умолчанию
IsCrushformImpactSourceHereECFHostAnswer: симулирует ли этот инстанс тело и видит ли он его контактыАвторити
GatherCrushformIgnoredActorsВыходной массив экторов, которые никогда не деформируют владельца, и bool, который заменяет прикреплённых к владельцу экторовFalse, ничего не собрано
ShouldCrushformIgnoreBool для одного эктора; спрашивается раньше списка выше, например для человека, наткнувшегося на автомобильFalse, ни один эктор не игнорируется
IsCrushformReportPlausibleECFHostAnswer для отчёта клиента об ударе. Сервер спрашивает его после собственных проверокПравдоподобен
ShouldCrushformDeformECFHostAnswer для одного меша. Теги профиля уже пропустили мешДеформирует
GatherCrushformWheelMeshesВыходной массив мешей колёс. Они получают грязь и никогда не деформируютсяНичего
GetCrushformGlassUCFGlassUnitsComponent владельца или nullNull

Blueprint. Добавьте интерфейс в Class Settings > Implemented Interfaces > Add. В My Blueprint его функции перечислены в Interfaces > Crushform под своими именами из C++ без пробелов, например IsCrushformImpactSourceHere. Граф ниже реализует эту функцию. Он выводит сообщение при её вызове и оставляет ответ Default.

Список Implemented Interfaces в Class Settings: кнопка Add в строке заголовка, под ней CFDeform Host и CFWheel Host.
Class Settings после добавления двух интерфейсов.
Граф реализует IsCrushformImpactSourceHere и оставляет ответ Default.

C++. Реализуйте интерфейс на экторе и переопределите _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 запрашивают этот интерфейс. Blueprint-host-объект опрашивается раз в кадр, и все вызывающие в этом кадре получают один и тот же ответ. Событие заполняет один FCFWheelContact для каждого колеса, стоит оно на земле или нет:

  • WheelIndex колеса должен оставаться неизменным от кадра к кадру.
  • Колесо в воздухе оставляет bGrounded равным false. Его Point и PhysicalMaterial не читаются.
  • Host-объект, который сообщает только колёса на земле, работает правильно, но тогда ни одно колесо никогда не будет замечено отрывающимся от земли.

Без host-объекта список пуст, и кузов никогда не загрязняется.

Blueprint. Добавьте интерфейс в Class Settings > Implemented Interfaces > Add, затем реализуйте GatherCrushformWheelContacts в My Blueprint. У этого события выходной массив, поэтому это функция. Граф ниже реализует её, выводит сообщение при вызове и оставляет массив пустым. Заполните массив в своём графе.

Граф реализует GatherCrushformWheelContacts и оставляет массив пустым.

C++. Объявите переопределение в классе, как в блоке с деформацией выше, и определите его. На странице Свой автомобиль собран полноценный host-объект колёс с трассировкой от каждой ступицы.

#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 запрашивает этот интерфейс. Он решает, что ломается и куда уходит отчёт. Интерфейс полезен для прицепа, контакты которого ломают объекты через его тягач. У каждого ответа есть значение по умолчанию, которое описывает тело без host-объекта:

СобытиеОтвечаетПо умолчанию
IsBreakerSimulatedHereECFHostAnswer: симулирует ли этот инстанс телоАвторити
GetBreakerBodyТело, контакты которого ломают элементыNull, то есть корневой примитив владельца
GetBreakReportRouteUCFPropContactComponent, по соединению которого идут отчёты о поломкеNull, то есть собственный компонент владельца
GetBreakerDeformUCFDeformComponent, в который сломанный элемент оставляет вмятинуNull: сломанный элемент никогда не оставляет вмятин на теле

Blueprint. Добавьте интерфейс и реализуйте нужные события. Граф ниже реализует IsBreakerSimulatedHere. Он выводит сообщение при вызове события и оставляет ответ Default.

Граф реализует IsBreakerSimulatedHere и оставляет ответ Default.

C++. Переопределение для прицепа, который отчитывается через свой тягач:

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

UCFTireComponent запрашивает у него колёса автомобиля и записывает ему обратно смещения и выталкивания шин. Host-объект не хранит состояния шин. Компонент запоминает, что изменил, и возвращает все смещения, когда удаляется. Всего четыре события:

СобытиеОтвечаетПо умолчанию
GatherCrushformTireWheelsМассив FCFTireWheelDesc: меш колеса, его PhysicsRadius, HostIndex, VisualOffset и bAcceptsPushНичего, шины остаются круглыми
SetCrushformTireWheelOffsetsПолучает смещения, которые изменил компонент, одним вызовом: каждый OffsetCm и каждый PushCmНичего
GatherCrushformTireLoadRatiosСила контакта, делённая на номинальную нагрузку, по HostIndex. Пустой массив означает, что модели силы нетПусто
GatherCrushformTireRigЭкторы, связанные сцепкой с этим, включая его самогоТолько этот эктор

Blueprint. Добавьте интерфейс и реализуйте события gather как функции. Граф ниже реализует GatherCrushformTireWheels. Он выводит сообщение при вызове и оставляет массив пустым.

Граф реализует GatherCrushformTireWheels и оставляет массив пустым.

C++. Два события gather у автомобиля:

#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, когда обод иначе вошёл бы в препятствие: тогда шина вмятиной огибает препятствие, а не обод. Ставьте bAcceptsPush только тогда, когда SetCrushformTireWheelOffsets применяет PushCm к мешу колеса в том же вызове, в системе координат владельца, и меш сохраняет этот сдвиг до следующего вызова. Хост, который игнорирует выталкивание, доводит его до MaxPush профиля. Хост, у которого оно устаревает, потому что что-то другое двигает меш или поворачивает его родителя относительно владельца, сдвигает вмятину с её места. Радиус обода берётся из параметра RimRadius материала шины или из RimRadiusOverride профиля; колесо без того и другого не выталкивается никогда.

Адаптер. UCFVehicleAdapterComponent применяет выталкивание сам и сообщает bAcceptsPush для своих колёс, пока Blueprint не переопределит SetCrushformTireWheelOffsets. Blueprint, который его переопределяет, сам расставляет колёса и выталкивания не получает. Подкласс на C++, который переопределяет SetCrushformTireWheelOffsets_Implementation, несёт ту же обязанность, что и любой хост: вызвать базовый класс, который применяет PushCm, либо применить выталкивание самому, либо переопределить BuildTireWheels и сообщать bAcceptsPush как false. Базовый класс переводит выталкивание в пространство родителя колеса один раз, когда оно приходит. Подкласс, у которого родитель колеса поворачивается относительно владельца, например поворотный кулак, или у которого игра переписывает относительное положение колеса, больше не несёт выталкивание, которое вычитает шина, и сообщает bAcceptsPush как false.

Пешка реализует ICFTireViewer, чтобы сообщить бюджету шин, в каком автомобиле едет игрок. Так бюджет узнаёт собственный автомобиль зрителя. Событие возвращает автомобиль или null, пока пешка находится вне автомобиля.

Blueprint. Добавьте интерфейс в пешку. Граф ниже реализует GetCrushformTireViewedHost в пешке. Он выводит сообщение при вызове и возвращает null.

Граф реализует 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

По умолчанию двигатель выключен. Адаптер автомобиля тоже реализует этот интерфейс.

Blueprint. Добавьте интерфейс и реализуйте 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. Повреждённые узлы появятся там, где тело получило вмятину. Это значит, что deform host-объект ответил.
  • Запустите Play In Editor и посмотрите в Output Log ошибку об отклонённом host-объекте и предупреждение о заменённом. Чистый лог означает, что каждый host-объект принят.
  • С реализованным host-объектом колёс и настроенными поверхностями проедьте по полосе грязи. Грязь скапливается на колёсах и на кузове.

Host-объект отклонён, а в логе назван интерфейс

Заголовок раздела «Host-объект отклонён, а в логе назван интерфейс»

Причина. Объект не реализует интерфейс, поэтому реестр его отклоняет. В логе написано:

Host %s does not implement %s, so it cannot answer for %s

Решение. В Blueprint добавьте интерфейс в Class Settings > Implemented Interfaces > Add. В C++ добавьте интерфейс в список базовых классов класса и переопределите события.

Причина. Либо host-объект колёс не собирает ни одного колеса, либо в проекте нет ассета поверхностей. Без ассета поверхностей ни одно тело не загрязняется. Host-объект, у которого не реализовано событие gather, возвращает пустой список, то есть значение по умолчанию.

Решение. Реализуйте GatherCrushformWheelContacts так, чтобы он добавлял один FCFWheelContact для каждого колеса. Задайте Surfaces в Project Settings > Plugins > Crushform.

Вызов события интерфейса падает в редакторе

Заголовок раздела «Вызов события интерфейса падает в редакторе»

Причина. Событие было вызвано по имени через указатель на интерфейс. Сгенерированное тело такого события — check.

Решение. Вызывайте событие через Execute_, как показано на странице Свой автомобиль, или используйте узел сообщения в Blueprint.