⚰️

【UE5】InstancedPropertyを用いてBoxCollisionとTriggerVolume両方に対応できるシステムを作る

2024/10/28に公開

概要

最近InstancedPropertyというのを知り、こう言ったTriggerVolumeにオーバーラップが発生した際に自由にイベントを実行できるようなシステムを作ってました。
https://x.com/daria_nicht/status/1850088611133394971

この自作TriggerVolumeのシステムを自作ActorのBPで追加しようとしたのですが、自分が作ったTriggerVolumeがコンポーネントの中になく使えませんでした。

調べた所、BoxCollisionとTriggerVolumeの違いをUE4 トリガーボックス(Box Trigger)とトリガーボリューム(Trigger Volume)の違いを見て最近知りました。
そのため、自作のTriggerVolumeが使えないのは仕方ないですが、BoxCollisionのOverlapに同じシステムを流用出来るようにしたほうが、今後仕様変更が来ても両方同時に対応出来ると考えました。
色々調べて試した所、無事作成することができたので、今回はそのことについて記事を書こうと思います

ただ、これが最良の形かと言われると微妙で、もっといい形に作れそうな気もしてるので、あくまで参考程度に記事を読んでいただければ幸いです。

InstancedPropertyについてはエディタのUXを向上させるUnreal C++との付き合い方を主に参考にさせていただいてますので、こちらの記事を読んでからの方が理解が早いかも知れません。

今回はBeginOverlap時に自作イベントを実行できるように手順と説明を書こうと思います。

環境

UE5.4.4

手順

手順は以下になります

  1. オーバーラップイベントで呼び出す関数を持ったインターフェースを作成
  2. オーバーラップイベント時にインターフェースの関数を実行するロジッククラスを作成
  3. ロジッククラスを呼び出す自作TriggerVolume、自作BoxCollisionを作成

オーバーラップイベントで呼び出す関数を持ったインターフェースを作成

以下のようなオーバーラップ時にイベントを実行してもらうインターフェースを用意します。

ExampleGameEventExecuter
#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "ExampleGameEventExecuter.generated.h"

UINTERFACE(Blueprintable)
class UExampleGameEventExecuter : public UInterface
{
    GENERATED_BODY()
};

class IExampleGameEventExecuter
{
    GENERATED_BODY()
public:
    UFUNCTION(Blueprintable)
    virtual void Execute(AActor* TargetActor);
};

オーバーラップイベント時にインターフェースの関数を実行するロジッククラスを作成

次に、インターフェースを内包してロジックを実行するクラスを作ります。

#pragma once

#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "ExampleGameEventOverlap.generated.h"

UCLASS(Blueprintable,EditInlineNew,CollapseCategories)
class UExampleGameEventOverlap : public UObject
{
    GENERATED_BODY()
public:
    //実行するイベント。BP上で設定できるようにInstancedにしてる。MustImplementsで特定のインターフェースを実装してるUObjectのみにしてる
    UPROPERTY(EditAnywhere,Instanced,meta=(MustImplement="GameEventExecuter"))
    TArray<TObjectPtr<UObject>> OverlapEvents;
    
    void Execute(AActor* TargetActor,AActor* OwnerActor);
};
ExampleGameEventExecuter.cpp
#include "GameEventOverlap.h"
#include "ExampleGameEventExecuter.h"

void UExampleGameEventOverlap::Execute(AActor* TargetActor, AActor* OwnerActor)
{		
    //ロジックの内容は重要ではないので簡易にしてます。共通処理や発動条件、イベントの実行などのロジックは各々で追加していただければ幸いです。
    for (auto& OverlapEvent : OverlapEvents)
    {
        IExampleGameEventExecuter* Executer = Cast<IGameEventExecuter>(OverlapEvent);
        if(Executer)
        {
            Executer->Execute(OwnerActor);
        }
    }
}

ロジッククラスを呼び出す自作TriggerVolume、自作BoxCollisionを作成

次に自作TriggerVolume、自作BoxComponentを作成します

TriggerVolume

ExampleTriggerVolume.h

#pragma once

#include "CoreMinimal.h"
#include "Engine/TriggerVolume.h"
#include "GameEventOverlap.h"
#include "ExampleGameEventTriggerVolume.generated.h"

UCLASS(Blueprintable)
class AExampleGameEventTriggerVolume : public ATriggerVolume
{
    GENERATED_BODY()

public:
    AExampleGameEventTriggerVolume();
public:
    virtual void NotifyActorBeginOverlap(AActor* OtherActor) override;
public:
    UPROPERTY(Instanced,EditAnywhere,meta=(AllowedClasses="GameEventOverlap"))
    TObjectPtr<UExampleGameEventOverlap> BeginOverlapEventExecuter; 
};
ExampleTriggerVolume.cpp
#include "GameEventTriggerVolume.h"

#include "GameEventExecuter.h"
#include "GameEventOverlap.h"

AExampleGameEventTriggerVolume::AExampleGameEventTriggerVolume()
{
}

void AExampleGameEventTriggerVolume::NotifyActorBeginOverlap(AActor* OtherActor)
{
    BeginOverlapEventExecuter->Execute(OtherActor,this);
}

BoxCollision

ExampleBoxCollision.h

#pragma once

#include "CoreMinimal.h"
#include "Components/BoxComponent.h"
#include "GameEventOverlap.h"
#include "GameEventTriggerBoxCollision.generated.h"


UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class UExampleGameEventTriggerBoxCollision : public UBoxComponent
{
    GENERATED_BODY()

public:
    UGameEventTriggerBoxCollision();

protected:
    virtual void BeginPlay() override;
    
    // UFUNCTIONはAddDynamicに必要でこれがないとエラーになる
    UFUNCTION()
    void OnOverlapBegin(UPrimitiveComponent* OverlappedComponent,AActor* OtherActor,UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult &SweepResult);
public:
    UPROPERTY(EditAnywhere,Instanced)
    TObjectPtr<UExampleGameEventOverlap> BeginOverlapEventExecuter; 
};


ExampleBoxCollision.cpp
#include "Collisions/GameEventTriggerBoxCollision.h"

UExampleGameEventTriggerBoxCollision::UGameEventTriggerBoxCollision()
{
}


void UExampleGameEventTriggerBoxCollision::BeginPlay()
{
    Super::BeginPlay();
    OnComponentBeginOverlap.AddDynamic(this,&UGameEventTriggerBoxCollision::UGameEventTriggerBoxCollision::OnOverlapBegin);
}

void UExampleGameEventTriggerBoxCollision::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
    UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    BeginOverlapEventExecuter->Execute(OtherActor,this->GetOwner());
}

見ていただくと、TriggerVolumeNotifyActorBeginOverlapBoxCollisionOnComponentBeginOverlapデリゲート、
と呼び出し元は別々ですが、呼び出し先はUExampleGameEventOverlap::Execute関数になったことで同じロジックを使い回すことができました。

結果

こういった感じにTriggerVolumeとBoxCollisionに同じようにイベントが登録出来るようになり、コリジョンにヒットした時に同じようなオーバーラップイベントの発生の仕方が確認ができれば成功です。
https://x.com/daria_nicht/status/1850726252635890067

ここまで読んでいただきありがとうございました。
少しでも参考になりましたら幸いです。

参考

【Unreal C++】②Overlapイベント【UE4】

Discussion