ИНТЕГРАЦИЯHOST-ИНТЕРФЕЙСЫ
Host-интерфейсы
Адаптер автомобиля отвечает на эти вопросы вместо вас. Используйте интерфейсы, когда у вас нет адаптера или когда в вашей игре есть собственный host-объект. На странице Свой автомобиль описан адаптер и полный host-объект колёс, а сводная таблица есть на странице Основные понятия. На этой странице по очереди разобран каждый интерфейс: о чём его спрашивают, что он отвечает по умолчанию и как его реализовать.
Кто отвечает и в каком порядке
Заголовок раздела «Кто отвечает и в каком порядке»Компонент запрашивает ответы у владельца через host-объект, который находит в таком порядке:
- Явно заданный host-объект, установленный через
SetCrushformHostдо BeginPlay. Только C++. - Объект, зарегистрированный для владельца в
UCFHostRegistry, подсистеме мира. ЕёRegister,UnregisterиFindHostдоступны только в C++. - Сам владелец, если его класс реализует интерфейс.
- Host-объекта нет. Тогда компонент отвечает как тело без host-объекта.
Этот порядок описан одним абзацем на странице Свой автомобиль.
Адаптер автомобиля регистрирует себя как host-объект для пяти интерфейсов, которые реализует: ICFDeformHost, ICFWheelHost, ICFBreakerHost, ICFTireHost и ICFEngineHost. Автомобилю с адаптером для них не нужен собственный host-объект. ICFTireViewer адаптер не реализует. Этот интерфейс принадлежит пешке, в которой сидит игрок.
Host-объект, который не реализует интерфейс, отклоняется с ошибкой. Она описана в разделе «Решение проблем» в конце этой страницы. Второй host-объект для того же эктора и интерфейса заменяет первый, и при этом выводится предупреждение.
Регистрация 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_ использовать вместо этого.
ICFDeformHost
Заголовок раздела «ICFDeformHost»Решётка UCFDeformComponent запрашивает этот интерфейс, как и UCFNetImpactFeedbackComponent кузова автомобиля. Реализуйте его на владельце.
| Событие | Отвечает | По умолчанию |
|---|---|---|
IsCrushformImpactSourceHere | ECFHostAnswer: симулирует ли этот инстанс тело и видит ли он его контакты | Авторити |
GatherCrushformIgnoredActors | Выходной массив экторов, которые никогда не деформируют владельца, и bool, который заменяет прикреплённых к владельцу экторов | False, ничего не собрано |
ShouldCrushformIgnore | Bool для одного эктора; спрашивается раньше списка выше, например для человека, наткнувшегося на автомобиль | False, ни один эктор не игнорируется |
IsCrushformReportPlausible | ECFHostAnswer для отчёта клиента об ударе. Сервер спрашивает его после собственных проверок | Правдоподобен |
ShouldCrushformDeform | ECFHostAnswer для одного меша. Теги профиля уже пропустили меш | Деформирует |
GatherCrushformWheelMeshes | Выходной массив мешей колёс. Они получают грязь и никогда не деформируются | Ничего |
GetCrushformGlass | UCFGlassUnitsComponent владельца или null | Null |
Blueprint. Добавьте интерфейс в Class Settings > Implemented Interfaces > Add. В My Blueprint его функции перечислены в Interfaces > Crushform под своими именами из C++ без пробелов, например 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;}ICFWheelHost
Заголовок раздела «ICFWheelHost»Грязевой слой кузова, UCFRollingAudioComponent и UCFRollingFXComponent запрашивают этот интерфейс. Blueprint-host-объект опрашивается раз в кадр, и все вызывающие в этом кадре получают один и тот же ответ. Событие заполняет один FCFWheelContact для каждого колеса, стоит оно на земле или нет:
WheelIndexколеса должен оставаться неизменным от кадра к кадру.- Колесо в воздухе оставляет
bGroundedравным false. ЕгоPointиPhysicalMaterialне читаются. - Host-объект, который сообщает только колёса на земле, работает правильно, но тогда ни одно колесо никогда не будет замечено отрывающимся от земли.
Без host-объекта список пуст, и кузов никогда не загрязняется.
Blueprint. Добавьте интерфейс в Class Settings > Implemented Interfaces > Add, затем реализуйте GatherCrushformWheelContacts в My Blueprint. У этого события выходной массив, поэтому это функция. Граф ниже реализует её, выводит сообщение при вызове и оставляет массив пустым. Заполните массив в своём графе.
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.}ICFBreakerHost
Заголовок раздела «ICFBreakerHost»UCFPropContactComponent запрашивает этот интерфейс. Он решает, что ломается и куда уходит отчёт. Интерфейс полезен для прицепа, контакты которого ломают объекты через его тягач. У каждого ответа есть значение по умолчанию, которое описывает тело без host-объекта:
| Событие | Отвечает | По умолчанию |
|---|---|---|
IsBreakerSimulatedHere | ECFHostAnswer: симулирует ли этот инстанс тело | Авторити |
GetBreakerBody | Тело, контакты которого ломают элементы | Null, то есть корневой примитив владельца |
GetBreakReportRoute | UCFPropContactComponent, по соединению которого идут отчёты о поломке | Null, то есть собственный компонент владельца |
GetBreakerDeform | UCFDeformComponent, в который сломанный элемент оставляет вмятину | Null: сломанный элемент никогда не оставляет вмятин на теле |
Blueprint. Добавьте интерфейс и реализуйте нужные события. Граф ниже реализует IsBreakerSimulatedHere. Он выводит сообщение при вызове события и оставляет ответ Default.
C++. Переопределение для прицепа, который отчитывается через свой тягач:
#include "CFBreakerHost.h"
UCFPropContactComponent* ATrailer::GetBreakReportRoute_Implementation() const{ return TractorContact;}ICFTireHost
Заголовок раздела «ICFTireHost»UCFTireComponent запрашивает у него колёса автомобиля и записывает ему обратно смещения и выталкивания шин. Host-объект не хранит состояния шин. Компонент запоминает, что изменил, и возвращает все смещения, когда удаляется. Всего четыре события:
| Событие | Отвечает | По умолчанию |
|---|---|---|
GatherCrushformTireWheels | Массив FCFTireWheelDesc: меш колеса, его PhysicsRadius, HostIndex, VisualOffset и bAcceptsPush | Ничего, шины остаются круглыми |
SetCrushformTireWheelOffsets | Получает смещения, которые изменил компонент, одним вызовом: каждый OffsetCm и каждый PushCm | Ничего |
GatherCrushformTireLoadRatios | Сила контакта, делённая на номинальную нагрузку, по HostIndex. Пустой массив означает, что модели силы нет | Пусто |
GatherCrushformTireRig | Экторы, связанные сцепкой с этим, включая его самого | Только этот эктор |
Blueprint. Добавьте интерфейс и реализуйте события gather как функции. Граф ниже реализует 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
Заголовок раздела «ICFTireViewer»Пешка реализует ICFTireViewer, чтобы сообщить бюджету шин, в каком автомобиле едет игрок. Так бюджет узнаёт собственный автомобиль зрителя. Событие возвращает автомобиль или null, пока пешка находится вне автомобиля.
Blueprint. Добавьте интерфейс в пешку. Граф ниже реализует GetCrushformTireViewedHost в пешке. Он выводит сообщение при вызове и возвращает null.
C++.
#include "CFTireViewer.h"
AActor* AMyPawn::GetCrushformTireViewedHost_Implementation() const{ return Vehicle.Get();}ICFEngineHost
Заголовок раздела «ICFEngineHost»UCFEngineAudioComponent запрашивает этот интерфейс раз в кадр, чтобы получить состояние двигателя. Ответ является FCFEngineState:
| Поле | Смысл |
|---|---|
RunState | Off, Cranking или Running |
RPM | Обороты двигателя. Звук воспринимает отрицательные обороты как их модуль |
IdleRPM | Обороты, ниже которых работающий двигатель не звучит |
MaxRPM | Максимальные обороты, которым следует звук. Ноль означает отсутствие предела |
Load | Положение газа, от 0 до 1 |
По умолчанию двигатель выключен. Адаптер автомобиля тоже реализует этот интерфейс.
Blueprint. Добавьте интерфейс и реализуйте 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++
Заголовок раздела «Интерфейсы, доступные только в C++»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.
