UE4オンラインラーニング
ゲームプレイのコンポーネントを分解する
を受講した際のおぼえ書きです。
https://learn.unrealengine.com/course/3762527
ActorComponentの作り方
・コンテンツブラウザで
C++フォルダで右クリック>新規C++クラスを選択>ActorComponentを選択
することで作成することができます。
・クラス名を入力して、クラスを作成
・エディタから、C++のヘッダとソース作成に成功しました
・ソースを記述し、ビルド実行
ヘッダーファイル
#pragma once #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "FloatingComponent.generated.h" UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) class COMPONENTSOFGAMEPLAY_API UFloatingComponent : public UActorComponent { GENERATED_BODY() public: UFloatingComponent(); protected: virtual void BeginPlay() override; public: virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Floating") FVector FloatRange = FVector(0,0,0); UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Floating") float SpeedMultiplier = 1.0f; private : UPROPERTY() AActor* Parent = nullptr; FVector StartLocation; };
ソースファイル
#include "FloatingComponent.h" UFloatingComponent::UFloatingComponent() { PrimaryComponentTick.bCanEverTick = true; } void UFloatingComponent::BeginPlay() { Super::BeginPlay(); Parent = GetOwner(); if (Parent) { StartLocation = Parent->GetActorLocation(); Parent->GetRootComponent()->SetMobility(EComponentMobility::Movable); } } void UFloatingComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); const float Time = GetWorld()->GetRealTimeSeconds(); const float Sine = FMath::Sin(Time * SpeedMultiplier); if (Parent) { Parent->SetActorLocation(StartLocation + (FloatRange * Sine)); } }
・制作したFloatingComponentをアクタに追加し、
プロパティを入力します。
・無事、上下運動をする
FloatingComponentを持つアクタを配置することに成功しました。
youtu.be
SceneComponentの作り方
SceneComponentは、ActorComponentと違いTransformを含みます。
位置情報を含みます。
・コンテンツブラウザで
C++フォルダで右クリック>新規C++クラスを選択>SceneComponentを選択
することで作成することができます。
・クラス名を入力して、クラスを作成
・エディタから、C++のヘッダとソース作成に成功しました
・ソースを記述し、ビルド実行
ヘッダーファイル
#pragma once #include "CoreMinimal.h" #include "Components/SceneComponent.h" #include "HoverComponent.generated.h" UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) class COMPONENTSOFGAMEPLAY_API UHoverComponent : public USceneComponent { GENERATED_BODY() public: UHoverComponent(); protected: virtual void BeginPlay() override; public: virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; private: UPROPERTY() UPrimitiveComponent* ParentRoot; UPROPERTY(EditAnywhere, Category="Hover Variables") float MaxHoverForce = 100.0f; UPROPERTY(EditAnywhere, Category="Hover Variables") float HoverForceDistance = 100.0f; UPROPERTY(EditAnywhere, Category="Debug") bool bDrawDebug = false; };
ソースファイル
#include "HoverComponent.h" #include "DrawDebugHelpers.h" UHoverComponent::UHoverComponent() { PrimaryComponentTick.bCanEverTick = true; } void UHoverComponent::BeginPlay() { Super::BeginPlay(); ParentRoot = GetOwner()->FindComponentByClass<UPrimitiveComponent>(); if(ParentRoot) { ParentRoot->SetMobility(EComponentMobility::Movable); ParentRoot->SetSimulatePhysics(true); ParentRoot->SetLinearDamping(2.0f); ParentRoot->SetAngularDamping(2.0f); MaxHoverForce = MaxHoverForce * ParentRoot->GetMass(); } } void UHoverComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); FHitResult Hit; GetWorld()->LineTraceSingleByChannel(Hit, GetComponentLocation(), GetComponentLocation() - FVector(0,0,HoverForceDistance), ECollisionChannel::ECC_Visibility); if(Hit.bBlockingHit) { const FVector StartLocation = GetComponentLocation(); const float Distance = (StartLocation - Hit.Location).Size(); const float Ratio = FMath::Clamp(Distance / HoverForceDistance, 0.0f, 1.0f); const FVector Force = (1.0f - Ratio) * MaxHoverForce * Hit.ImpactNormal; ParentRoot->AddForce(Force, NAME_None, false); if(bDrawDebug) { DrawDebugLine(GetWorld(), StartLocation, Hit.Location, FColor::White, false, 0.0f); DrawDebugPoint(GetWorld(), Hit.Location, 10.0f, FColor::Black, false, 0.0f); } } }
・制作したHoverComponentをアクタに追加し、
プロパティを入力します。
・無事、上下運動をする
HoverComponentを持つアクタを配置することに成功しました。
youtu.be
・C++でのコンポーネントを追加する
ヘッダーファイルでは、以下のように記述しプロパティを作成
UPROPERTY(EditDefaultsOnly, Category="Components") UStaticMeshComponent* Mesh = nullptr;
ソースファイルでは、以下のように記述しコンポーネントを作成
Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
さらに、作成したコンポーネントでルートコンポーネントを置き換えることが可能です。
RootComponent = Mesh;
・ビルド実行
無事ADemoActorを継承した、BP_DemoActorは
最初からコンポーネントが追加された状態になることが確認できます。
※ルートコンポーネントがMeshになっているのも注目点です
・メッシュのアセットの指定
BP_DemoActorのMeshコンポーネントのStaticMeshをエディタで指定します。
・無事、コンポーネントはC++で定義し、
スタティックメッシュはBPで指定したアクタの作成に成功します。
スタティックメッシュの作成方法
・アクタを配置パネルから、キューブをレベルに配置
右クリック>スタティックメッシュへ変換をクリック
・スタティックメッシュ名を入力することで、
アセットを作成することができます。
・コリジョンの指定
作成したスタティックメッシュを開き
メニューバーから
コリジョン>ボックス単純化コリジョンを追加
を選択することでコリジョンを追加することを忘れずに
オーバーラップイベント関数の登録方法
HomingComponent.hを確認すれば理解できます。
・C++でのコールバック関数を登録する
ヘッダーファイルでは、以下のように自作の関数を記述
UFUNCTION(Category = "Homing") void OnOverlapEnter(class UPrimitiveComponent* ThisComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); UFUNCTION(Category= "Homing") void OnEndOverlap(class UPrimitiveComponent* ThisComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
ソースファイルでは、以下のように記述しコールバック関数を登録
MagnetSphere->OnComponentBeginOverlap.AddDynamic(this, &UHomingComponent::OnOverlapEnter); MagnetSphere->OnComponentEndOverlap.AddDynamic(this, &UHomingComponent::OnEndOverlap);
ダメージ時イベント関数の登録方法
・C++でのコールバック関数を登録する
ヘッダーファイルでは、以下のように自作の関数を記述
UFUNCTION() void TakeDamage(AActor* DamagedActor, float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser);
ソースファイルでは、以下のように記述しコールバック関数を登録
Owner->OnTakeAnyDamage.AddDynamic(this, &UHealthComponent::TakeDamage);
・インターフェイスの呼び出し
TakeDamageでは、アクターのインターフェイスを呼び出すようにしています。
インターフェイスを実装していれば、その処理を行い
インターフェイスを実装していなければ、スルーされるテクニックです。
IHealthInterface* Interface = Cast<IHealthInterface>(Owner);
if(Interface)
{
Interface->Execute_HealthDepleted(Owner);
}
アクタ破棄時イベント関数の登録方法
ヘッダーファイルでは、以下のように自作の関数を記述
UFUNCTION()
void Destruct(AActor* DestroyedActor);
ソースファイルでは、以下のように記述しコールバック関数を登録
Owner->OnDestroyed.AddDynamic(this, &UDestructionComponent::Destruct);
破壊時のコールバック関数では、
アセットが指定されていた時のみ、爆破エフェクトを再生するようにしています。
if(ParticleSystem)
{
UNiagaraComponent* Effect =
UNiagaraFunctionLibrary::SpawnSystemAtLocation(
GetWorld(), ParticleSystem, Owner->GetActorLocation());
}
爆破の指定がある場合のみ
周辺のアクタに対してダメージをあたえるようにしてあります
const bool Hit = GetWorld()->SweepMultiByChannel( HitResults, Start, End, FQuat::Identity, ECC_Camera, ColShape); for(FHitResult const HitResult : HitResults) { FPointDamageEvent PointDamage; HitResult.GetActor()->TakeDamage( 100.0f, PointDamage, GetWorld()->GetFirstPlayerController(), GetOwner()); }
そして、エディタで爆破の有無、アセットの指定などを個別に行うようにしてあります
実際にできたものはこちら
youtu.be
その他のテクニック
コンポーネントのアクタへのアタッチ方法はこちら
MagnetSphere->SetupAttachment(this);
プロパティの変更によって呼び出される関数はこちら
void UHomingComponent::PostEditChangeProperty(
FPropertyChangedEvent& PropertyChangedEvent)
※注意点
UE4.26で検証しているのですが、コース通りに作成してもうまく動かず、
以下の2点を修正することでビルドが通るようになりました。
DevelopmentEditor Win64 でビルド
includeパスが通っていない場合がある
エンジニアであれば、必ず受講しておく必要があるすばらしいコースです。
コンポーネントを利用した、再利用可能なクラス設計ができるようになります。
クラス継承による拡張ではなく、
コンポーネントによる拡張ができるようになります。
コールバック関数のC++での登録方法なども理解できます。