zer0から始めるプログラミング生活

UE4,unityなどいろいろメモ

【UnrealC++】⑩レベルブループリント【UE4】

C++側でレベルブループリントをいじる方法をメモ。

①クラスの作成

LevelScriptActorを親クラスとしてクラスを作成。
いじりたいレベル(マップ)を作成していない場合は作成してください。

②レベルブループリントを編集

いじりたいレベルのレベルブループリントを開きます。
f:id:bigden:20180227080244p:plain

「Class Settings→Class options→Parent Calss」を①で作成したものに変更してあげることでC++側でレベルブループリントをいじることが出来ます。
f:id:bigden:20180227080511p:plain

【Unreal C++】⑨モジュール 【UE4】

C++でコードを書く際、UE4の公式ドキュメントで調べていると思いますが、UnrealC++ではHeaderファイルをincludeするだけではだめみたいです。


以下によるとUE4C++コードは、「モジュール」と呼ばれる単位で、「UnrealBuildTool」を通してビルドされるそうです。なので必要に応じて追加してあげる必要があります。
[UE4] モジュールについて|株式会社ヒストリア

モジュールに関してUE4.15以降で仕様変更があったので以下を参照してください。
ちなみに自分の環境はver4.18.3です。
miyahuji111.hatenablog.com

追加方法

①[ProjectName].Build.cs

([ProjectFolder]/source/[ProjectName]/)にある[ProjectName].Build.csを開くと、以下のようになっていると思います。

using UnrealBuildTool;

public class hogeProject : ModuleRules
{
	public hogeProject(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
	
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });

		PrivateDependencyModuleNames.AddRange(new string[] {  });
	}
}

②Moduleの追加

公式ドキュメントのAPIのリファレンスを見るとModuleの項目があり、
f:id:bigden:20180227074532p:plain

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay", "UMG","UMGEditor" });

のnew string[] 以下の部分になければ "" で囲んで追加することでその関数を使用することが出来ます。

【Unreal C++】 ⑧Level Streaming 【UE4】

C++でLevel Streaming 使うときにちょっと躓いたのでメモ。

BPでのLevel Streamingは以下を参考に。
unrealengine.hatenablog.com

詳しくは以下に書いていますがロード後にすぐ表示しているので、今回はロード後すぐに表示しないパターンを書いておきます。
docs.unrealengine.com

ロード

ロード後すぐに表示しないパターンにするため
第3引数・・・読み込み後に表示を行うかどうか
第4引数・・・同期読み込みを行うかどうか
を両者ともfalseにしました。
docs.unrealengine.com
[UE4] レベルストリーミングについて|株式会社ヒストリア

マップ(サブレベル)表示

BPと大体同じで
第1引数・・・表示非表示をいじりたいマップ
第2引数・・・表示非表示のフラグ

UFunctionLibrary::beVisibleMap(MapName, true);

アンロード

関数については以下を参考に。
docs.unrealengine.com

マップ(サブレベル)非表示

UFunctionLibrary::beVisibleMap(MapName, false);

Tips

マップの遷移で分岐があったので2つのマップを同時にロードする必要があり、FLatentActionInfo型の変数を2つ用意するだけでいいのかなと思ったのですが、1つだけしかロードされませんでした。
調べてみると、UUIDで識別しているみたいで2つの変数に別々のUUIDを与えてあげることで2つロードすることができました。
ちなみにUUIDのデフォルトの値は-1みたいです。

【Unreal C++】⑦イベントディスパッチャー【UE4】

今回はイベントディスパッチャーについてです。
BPでの実装は以下を参考に。
unrealengine.hatenablog.com

動的マルチキャストデリケートでBP側のイベントディスパッチャーと同等の処理が出来るみたいです。

呼び出し側

DECLARE_DYNAMIC_MULTICAST_DELEGATEマクロを用いてクラスを作成します。
命名規則からクラス名には「F」の接頭辞が必要です。
また宣言の際にプロパティをBlueprintAssignableにする必要があります。

Call.h

#include "CoreMinimal.h"
#include "Call.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FhogeDispather);

UCLASS()
class HogeProject_API ACall : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
       ACall();
protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	//Event Dispather(Multicast Delegate)
	UPROPERTY(BlueprintAssignable)
        FhogeDispather hogeDispather;
}


先ほど作成したクラスの変数のBroadcastメソッドを呼んであげます。

Call.cpp

#include "Call.h"

ACall::ACall()
{
}

ACall::HogeFunc()
{
hogeDispather.Broadcast();
}

受信側

Funcは呼び出したい処理です。
UFUNCTION()がついてないとクラッシュしました。

Receive.h

#include "CoreMinimal.h"
#include "Call.h"
#include "Engine.h" //GEngine
#include "Receive.generated.h"

UCLASS()
class HogeProject_API AReceive : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
       AReceive();
protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	//Event Dispather(Multicast Delegate)
        UFUNCTION()
        void Func();
        
}

BeginPlayでAddDynamicを用いて作成したクラスの変数と呼びたい処理をバインディングします。

Receive.cpp

#include "Receive.h"


// Sets default values
AReceive::AReceive()
{
}

// Called when the game starts or when spawned
void AReceive::BeginPlay()
{
	Super::BeginPlay();
	
	//Event Dispather
	for (TActorIterator<ACall>ActItr(GEngine->GameViewport->GetWorld()); ActItr; ++ActItr)
	{
		ACall* Call = *ActItr;
		Call->hogeDispather.AddDynamic(this, &AReceive::Func);
	}
}

void AReceive::Func()
{
//呼び出したい処理
}

以上で呼び出し(call)側のHogefunc関数が呼ばれるタイミングで受信(Receive)側のFunc関数が同時に呼ばれます。

【Unreal C++】⑥Interface【UE4】

今回はC++とブループリントでインターフェースを呼ぶ機会があったので備忘録として。
他にもいい方法があると思うのであくまで参考程度でお願いします。
エンジンはVer4.18.3を使用しています。
ちなみにブループリントインターフェースなどインターフェースについては以下のリンクを参照してください。
docs.unrealengine.com
unrealengine.hatenablog.com


①クラスの作成&宣言

おそらくBP側で作成したインターフェースはC++側に呼ぶことが出来ないのでC++側でインターフェースを作成します。C++でインターフェースを継承したクラスを作成します。
関数指定子をBlueprintImplementableEventでもいけるという情報もありましたが、参考したサイトの多くがBlueprintNativeEventを使用していたのでこちらを使用します。
unreallife.hatenablog.com


hogeInterface.h

#include "CoreMinimal.h"
#include "hogeInterface.generated.h"

UINTERFACE(Blueprintable)
class hogeProject_API UhogeInterface : public UInterface
{
	GENERATED_UINTERFACE_BODY()
};


class hogeProject_API IhogeInterface
{
	GENERATED_IINTERFACE_BODY()

public:
	UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
		void hogeFunc();
}

hogeInterface.cpp

#include "hogeInterface.h"


UhogeInterface::UhogeInterface(const FObjectInitializer& ObjectInitializer)
	:Super(ObjectInitializer)
{
}

void IhogeInterface::hogeFunc_Implementation()
{
}

ブループリントでの実装

イベント側

「Event hogeFunc」を呼んであげます。

イベントを呼び出すためには「Class Settings→interface→Implemented Interface」に先ほど作成したhogeInterfaceを追加してコンパイルする必要があります。

呼び出し側

「hogeFunc(Message)」を呼んであげます。
Targetノードにはイテレータ、Get All Actors with Interface、Get All Widgets with Interfaceなどを用いて検索したイベント側のクラスを繋いであげます。
自分はレベルストリーミング使用しているのでGet All Widgets of Classの第2引数のTop Level Onlyはfalseにしました。

f:id:bigden:20180214202143p:plain

C++での実装

イベント側(hogeActor)

hogeInterface を継承したクラスを作成し、関数をOverrideしてあげます。

hogeActor.h

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "hogeInterface.h”
#include "hogeActor.generated.h"

UCLASS()
class hogeProject_API AApple : public AActor, public IhogeInterface
{
	GENERATED_BODY()
public:
	//Interface 
	virtual void hogeFunc_Implementation() override;
};

hogeActor.cpp

void hogeActor::hogeFunc_Implementation()
{
//処理
}

呼び出し側

検索して配列に格納してRanged-forを用いて配列に対して処理をしてあげます。

TArray<AhogeActor*>hogeActAry;
UWorld* World=GEngine->GameViewport->GetWorld();
UGameplayStatics::GetAllActorsWithInterface(World,UhogeInterface::StaticClass(),hogeActAry);
for(AhogeActor* hogeAct : hogeActAry)
{
IhogeInterface::Execute_hogeFunc(hogeAct);
}


Get All Actors of Class、Get All Widgets of Classなどは遅い処理なのでTickなどで呼ばないでください。
docs.unrealengine.com

Markdown記法

GitBucketに挙げたリポジトリWikiを書く機会があったのでMarkdown記法をメモ。はてなブログでもMarkdownで書けるみたいです。

Markdown

とりあえず、GitBucketのwikiで使えるのだけ。ほかは↓を参考にしてください。

qiita.com

見出し

# 見出し大
## 見出し中
### 見出し小

記号の後ろに半角の空白が必須。

強調

hoge

**hoge**
__hoge__ (_(アンダースコア)2つ)

リスト

* hoge
+ hoge
- hoge

上下に空白行必須。
記号の後ろに半角の空白が必須。

ハイライト(コード挿入時など)

`hoge`

打消し線

hoge

~~hoge~~

リンク

zer0から始めるプログラミング生活

[リンクテキスト](URL)

画像埋め込み

f:id:bigden:20171226213050p:plain

![テキスト](画像のURL)

左揃え 中央 右揃え

hoge

hoge

hoge

|左揃え|中央|右揃え|
|:---|:---:|---:|
|hoge|hoge|hoge|

【Unreal C++】⑤Timer(Delay)【UE4】

BPのDelayに相当する処理をするにはTimerを使うようです。
他に方法があればコメントで教えて頂けると幸いです。

Timerの開始

Timerを開始するにはFTimerManagerのSetTimer 関数を用います。

World->GetTimerManager().SetTimer(_TimerHandle,Callback,Rate,bLoop,FirstDelay);


引数 説明
_TimerHandle FTimerHandle Timerを管理する変数
Callback TFunction 呼びたい関数
Rate float 関数を何秒ごとに呼ぶか
bLoop bool ループするかどうか
FirstDelay float 最初に何秒待つか


例(カウントタイマー)

hogeActor.h

UCLASS()
class HOGEPROJECT_API AhogeActor : public AActor
{
	GENERATED_BODY()
	
public:
	// Sets default values for this actor's properties
	AhogeActor();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	UFUNCTION()
	void Timer();

public:
	// Called every frame
	virtual void Tick(float DeltaTime) override;

protected:
	UPROPERTY()
	UWorld* World;

public:
         UPROPERTY()
	int PlayTime;
};


hogeActor.cpp

void AhogeActor::BeginPlay()
{
World=GEngine->GameViewport->GetWorld();
FTimerHandle _TimerHandle;	
World->GetTimerManager().SetTimer(_TimerHandle, this, &AhogeActor::Timer,1.0f, true);
}

void AhogeActor::Timer()
{
PlayTime+=1;
}


Timerの停止

Timerを停止するにはFTimerManagerのClearTimer 関数を用います。
また、もう一度同じTimerHadleでSetTimerを呼ぶと既存のものが破棄され、新しいものに置き換えられます。
ClearしたTimerHandleは新しいTimerを管理するために再利用することが出来ます。

World->GetTimerManager().ClearTimer(_TimerHandle);
引数 説明
_TimerHandle FTimerHandle Timerを管理する変数


Timerの一時停止

Timerによる関数呼び出しを一時的に実行しないようにするにはFTimerManagerのPauseTimer 関数を用います。

World->GetTimerManager().PauseTimer(_TimerHandle);


Timerの再開

一時停止したTimerを再開させるにはFTimerManagerのUnPauseTimer 関数を用います。

World->GetTimerManager().UnPauseTimer(SampleTimerHandle);  

Timerが一時停止状態の確認

Timerが一時停止しているかどうかを確認するためにはFTimerManagerのIsTimerPaused 関数を用います。bool値がreturnされます。

 if( World->GetTimerManager().IsTimerPaused(_TimerHandle) == true )  
{ 
}

反対にTimerがアクティブで一時停止していないことを確認するためにはFTimerManagerのIsTimerActive 関数を用います。bool値がreturnされます。

 if( World->GetTimerManager().IsTimerActive(_TimerHandle) == true )  
{ 
}

追記

(2018.01.10)

↓のドキュメントではループありのタイマーをClearTimer関数でタイマーのクリアを行っています。
これは実質、タイマーの停止も担っています。
ここで1つ疑問に思ったのが、ループなしのタイマーでは自動的に停止するのでタイマーの停止を目的としたClearTimer関数の使用は必要無くなりますが、クリアを目的としたClearTimerが必要なのかということです。
docs.unrealengine.com

APIにはIsTimerActive関数やTimerExists関数がありますので、こちらを用いてループなしのタイマーが自動停止した後にまだタイマーが存在しているかを調べてみたいと思います。
docs.unrealengine.com

調査

いろいろ調べ方はあると思いますが、簡単な方法で実装します。
bLoopをfalseとしたSetTimer関数をBeginPlayで呼びます。
IsTimerActive関数やTimerExists関数をTickで呼びます。


TimerAct.h

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Engine.h"
#include "TimerAct.generated.h"

UCLASS()
class HOGEPROJECT_API ATimerAct : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ATimerAct();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

protected:
	UWorld* World;
	FTimerHandle _TimerHandle;

protected:
	UFUNCTION()
	void Process();
};

TimerAct.cpp

void ATimerAct::BeginPlay()
{
World=GEngine->GameViewport->GetWorld();
FTimerHandle _TimerHandle;	
World->GetTimerManager().SetTimer(_TimerHandle, this, &ATimerAct::HogeProcess,1.0f, false); //bLoop=false
}

void ATimerAct::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (World->GetTimerManager().IsTimerActive(_TimerHandle))
	{
		GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Green, "Active");
	}
	if (World->GetTimerManager().TimerExists(_TimerHandle))
	{
		GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, "Exist");
	}
}

void ATimerAct::HogeProcess()
{
}
結果

プレイ開始からHogeProcessが呼ばれるまでタイマーが存在し、その後は自動的にクリアされ、存在しなくなっているようです。


※何か間違えがあればコメントよろしくお願い致します。