LoginSignup
1
2

More than 1 year has passed since last update.

【UE4】RoboRecallを参考にNavLinkProxyを使いやすくする

Last updated at Posted at 2017-03-29

UE4.25以降では不要なテクニックになりました

UE4.25から、「コンポーネントの始点/終点位置とLinkRelativeStart/Endを同期する機能」が追加されたようです。よってこの記事で紹介するテクニックは不要となりました。

それでも何らかの理由でこの記事で紹介するテクニックを利用したい場合は、コメントの方にUE4.26.2で動作させるために必要な修正を書いていますので、そちらを参照してください。

はじめに

以前書いたNav Link Proxyの記事ではNav Link Proxyが持つSmart Linkを設定しAIがそこに到達することでイベントを発生させるという手順を踏んでジャンプをさせました。

NavLinkProxyが持つ厄介な点にSmartLinkはSimpleLinkと同期して位置が設定されない、というものがあります。
1.png
NavLinkProxyの到達イベントを使わないのであれば不要な設定なのですが、使う場合はレベル上に配置する作業が以下のようになります。

NavLinkを配置 -> Simple Linkを調整 -> SimpleLinkのLeft, Rightの座標をコピー -> SmartLinkのStart, Endに貼り付け

かなり面倒です。5~10個程なら難なく配置出来るかもしれませんが、これが倍以上増え、更には仕様変更により再調整しなければならない。という事が起きたときにはちょっとした喧嘩が起きるレベルだと思います。

今回はこういった面倒くさい作業を軽減出来るようにNavLinkProxyをカスタムしようと思います。

※ここで紹介していくテクニックはRoboRecallを参考にしたものです

NavLinkProxyを継承する

SmartLinkはブループリント上ではアクセス出来ないためC++を使います。
「新規追加 -> 新規C++クラス」
ダイアログが出たら
「全てのクラスの表示 -> NavLinkで検索 -> NavLinkProxyをクリック -> 次へをクリック -> 名前を決めてクラスを作成」
2.png
3.png
4.png
※名前を決める所でエラーが出ていますが自分は既に同名のクラスを作成しているためです。初めて追加される方はエラーは出ずクラスを作成ボタンも押せます。

NavJumpLinkProxy.h

NavJumpLinkProxy.h 
-------------------------------------
#pragma once

#include "AI/Navigation/NavLinkProxy.h"
#include "NavJumpLinkProxy.generated.h"

/**
 *
 */
UCLASS()
class MYAITESTPROJECT_API ANavJumpLinkProxy : public ANavLinkProxy
{
    GENERATED_BODY()

public:
    ANavJumpLinkProxy(const FObjectInitializer& ObjectInitializer);

#if WITH_EDITOR
    virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
    virtual void PostEditMove(bool bFinished) override;
    virtual void PostRegisterAllComponents() override;

    virtual void SyncLinkDataToComponents();
    virtual void OnChildTransformUpdated(USceneComponent* UpdatedComponent, EUpdateTransformFlags UpdateTransformFlags, ETeleportType TeleportType);
    bool bRegisteredCallbacks;
#endif


protected:

#if WITH_EDITORONLY_DATA
    // SmartLinkのStart位置を分かりやすくするためのボックス
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
        UBoxComponent* StartEditorComp;
    // こちらはEnd位置をわかりやすくするためのボックス
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
        UBoxComponent* EndEditorComp;
#endif

};

overrideが付いている関数はUE4エディタでの動作を定義します。
overrideが付いていない関数はこのクラス、またはクラスを継承した別のクラスで定義する独自の処理です。

NavJumpLinkProxy.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "MyAITestProject.h"
#include "NavJumpLinkProxy.h"

#include "AI/Navigation/NavLinkCustomComponent.h" // <- Add


ANavJumpLinkProxy::ANavJumpLinkProxy(const FObjectInitializer& ObjectInitializer)
    :Super(ObjectInitializer)
{
    // Simple Linkの配列を空にする
    PointLinks.Empty();

    // Smart Linkを有効にする
    bSmartLinkIsRelevant = true;

    // 以下の処理はレベル編集中にわかりやすくするための処理で
    // 実際のAIに対する振る舞いとは関係はない
#if WITH_EDITORONLY_DATA
    StartEditorComp = CreateDefaultSubobject<UBoxComponent>(TEXT("StartBox0"));
    StartEditorComp->InitBoxExtent(FVector(20.f, 20.f, 10.f));
    StartEditorComp->SetupAttachment(RootComponent);
    StartEditorComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    StartEditorComp->bGenerateOverlapEvents = false;

    EndEditorComp = CreateDefaultSubobject<UBoxComponent>(TEXT("EndBox0"));
    EndEditorComp->InitBoxExtent(FVector(20.f, 20.f, 10.f));
    EndEditorComp->SetupAttachment(RootComponent);
    EndEditorComp->RelativeLocation = FVector(0, 0, 500.f);
    EndEditorComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    EndEditorComp->bGenerateOverlapEvents = false;
#endif
}

コンストラクタではアクタの初期設定やコンポーネントの生成と設定を行っています。
SimpleLinkはもはや必要ないので配列を空にします。
bSmartLinkIsRelevantにTRUEを設定して、SmartLinkを有効にします。
UBoxComponentは位置をわかりやすくするための目印ですので、コリジョンに関する設定はOFFにします。

/* 
*   エディタ上でプロパティを変更した際に呼ばれるイベント
*/
void ANavJumpLinkProxy::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
#if WITH_EDITORONLY_DATA
    // Update the editor boxes. This assumes we aren't changing the location of the start/end components in this edit action.
    UNavLinkCustomComponent* const LinkComp = GetSmartLinkComp();
    if (LinkComp)
    {
        StartEditorComp->SetWorldLocation(LinkComp->GetStartPoint());
        EndEditorComp->SetWorldLocation(LinkComp->GetEndPoint());
    }
#endif // WITH_EDITORONLY_DATA
    UE_LOG(LogTemp, Log, TEXT("Call PostEditChangeProperty"));
    SyncLinkDataToComponents();
    Super::PostEditChangeProperty(PropertyChangedEvent);
}


/*
*   移動、回転、拡縮したりレベル上に配置した時等に毎回呼ばれる
*/
void ANavJumpLinkProxy::PostEditMove(bool bFinished)
{
    UE_LOG(LogTemp, Log, TEXT("Call PostEditMove"));
    SyncLinkDataToComponents();
    Super::PostEditMove(bFinished);
}


/*
*   このアクタの全てのコンポーネントが登録された後に呼ばれるイベント
*/
void ANavJumpLinkProxy::PostRegisterAllComponents()
{
    Super::PostRegisterAllComponents();

#if WITH_EDITORONLY_DATA
    if (!bRegisteredCallbacks && !IsTemplate())
    {
        // 子コンポーネントのトランスフォームを更新
        bRegisteredCallbacks = true;
        StartEditorComp->TransformUpdated.AddUObject(this, &ANavJumpLinkProxy::OnChildTransformUpdated);
        EndEditorComp->TransformUpdated.AddUObject(this, &ANavJumpLinkProxy::OnChildTransformUpdated);
    }
#endif // WITH_EDITORONLY_DATA
}

/*
*   2つのボックスコンポーネントとSmart LinkのStart, Endのトランスフォームを同期する
*/
void ANavJumpLinkProxy::SyncLinkDataToComponents()
{
#if WITH_EDITORONLY_DATA
    // StartEditorComp、EndEditorCompの相対位置を取得
    FVector const NewLinkRelativeStart = GetActorTransform().InverseTransformPosition(StartEditorComp->GetComponentLocation());
    FVector const NewLinkRelativeEnd = GetActorTransform().InverseTransformPosition(EndEditorComp->GetComponentLocation());

    FVector LeftPt, RightPt;
    ENavLinkDirection::Type Dir;
    // Smart LinkのStart位置、End位置、向きを取得
    GetSmartLinkComp()->GetLinkData(LeftPt, RightPt, Dir);
    // Smart LinkのStart、Endの位置と向きを設定
    GetSmartLinkComp()->SetLinkData(NewLinkRelativeStart, NewLinkRelativeEnd, Dir);
#endif // WITH_EDITORONLY_DATA
}

void ANavJumpLinkProxy::OnChildTransformUpdated(USceneComponent* UpdatedComponent, EUpdateTransformFlags UpdateTransformFlags, ETeleportType TeleportType)
{
    SyncLinkDataToComponents();
}

PostEditChangeProperty関数はコメントにも書かれていますがプロパティを変更した際に呼ばれるイベントです。
アクタをクリックした時に詳細パネルにアクタのプロパティが表示されます。
5.png
移動、回転、拡縮を直接打ち込む。チェックボックスにチェックを入れたりした際に呼ばれます。

PostEditMove関数はアクタをクリックした時にマニピュレータが表示されますが、これを用いてアクタを移動、回転、拡縮した際に呼ばれるイベントです。
※赤、青、緑の矢印です
6.png

詳細パネルを弄った時に呼ばれるのはPostEditChangeProperty
マニピュレータを弄ったときはPostEditMoveが呼ばれると覚えればOKでしょう。

SyncLinkDataToComponents関数はStartEditorCompとEndEditorCompの相対位置を取得し、SmartLinkのStartとEndに設定しています。
SyncLinkDataToComponetsをプロパティを弄った時やマニピュレータを弄った時に毎回呼び出す事で常にBoxComponentとSmartLinkが同期しています。

動作確認をしてみる

プログラムが書けたらコンパイルを忘れずに実行します。
エラーが出ず無事にコンパイル出来たら、このクラスを基にブループリントを作成します。
7.png
名前は「BP_NavJumpLink」とします。
イベントグラフに以下の処理を追加します。
8.png
※C_Jumperはこちらで作成しています。
後はBP_NavJumpLinkをコンパイルして、レベル上に配置します。

StartEditorCompとEndEditorCompをそれぞれ移動させると、SmartLinkのLink Relative StartとLink Relative Endの位置が同じになっているはずです。
9.png

おわりに

C++でNavLinkProxyを継承し、SmartLinkを自動的に設定出来るようにしました。
これによりNavLinkProxyの配置作業が結構楽になったかと思います。
SmartLinkに対してブループリントでアクセス出来るならC++を書かずとも良いのですが、現状はこういった方法を使うしかないのかなと思います。

以上です。お疲れ様でした。

1
2
8

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2