今回はフェードイン・アウトを実装したHUDをプラグインとして作成してみた話です。
UE4にはMatineeを用いたフェードイン・アウト機能がありますが(こちらの記事参照)、これは黒フェードのみです。
ゲームを作る上では黒以外のフェードも必要になることがあります。
The Loomでは強い光を表現するために白フェードを使っていますし、ホラーゲームなどであれば血を表現した赤フェードも使われるでしょう。
これらを実装する方法はいくつかありますが、今回はSlateを用いてみました。
さて、UE4はソースコードが公開されているのでエンジン側のコードを修正することでこのような問題に対応することも可能です。
しかしながら、現状ではあまりお勧めできません。
UE4の更新速度は非常に速く、サブスクリプションを始めてから8か月程度で大規模なバージョンアップを5回も行っています。
もしもエンジン側のコードを修正してしまうとこれらのバージョンアップの恩恵を受けられないか、恩恵を受けようと思うと大規模なマージ作業が発生します。
UE4のような大きなゲームエンジンをマージしようとするとかなりのコストがかかります。
しかもマージに成功したとしても潜在的な不具合が残る可能性を無視できません。
既にアップデートが一段落しているのであればバージョンアップを無視するのもありですが、現状では無視できる更新頻度ではないです。
しかしUE4ではプラグインによって機能を追加することが可能です。
機能追加はあくまでも限定的ではありますが、それでもかなり広い範囲で追加できます。
エディタに対する機能追加では自分たちのタイトルを開発するうえで特殊なUIが必要でも対応できますし、ActorやObjectを追加すればランタイムで使える機能も追加できます。
今回追加しているのはHUDアクターである"AHUD"を継承した"AFaderHUD"です。
フェードイン・アウトは色とα値を指定することができ、また、他のUIの奥と手前の2カ所でフェードをかけられるようにしています。
ただし制限もあります。
HUDアクターはOpenLevelによってレベルが遷移すると一旦削除されます。
FaderHUDは削除されるとフェードに使用しているSlateが削除されます。
これはつまり、レベル遷移によってフェードアウトがキャンセルされるということです。
フェードアウトして画面が真っ暗になっている状態でOpenLevelによるレベル遷移が行われると、遷移先のレベルではフェードアウトがキャンセルされて画面が表示されてしまいます。
回避するには遷移先のレベルのBeginPlayイベントでフェードイン処理を書く必要があります。
もう1つの制限は、フェードレイヤーは2レイヤーありますが、これらはSlateのZOrderが0と255で設定しています。
奥側(ZOrder 0)にはかからないが手前側(ZOrder 255)にはかかるようなUIはZOrderを1~254で指定してください。
また、ReceiveDrawHUDイベントでテキストやテクスチャを描画すると、これはZOrder 0のSlateよりも奥に描画されます。
どうやらこれらの描画はSlateではなくCanvasで行われているようで、Slate描画よりも前に行われてしまいます。
ですので、どのレイヤーのフェードを利用してもフェードの影響を受けると考えてください。
ではプラグインを追加していきましょう。
プラグインの追加方法はヒストリアさんのブログが詳しいのでそちらを参照してください。
[UE4] プラグインによるエディタ拡張(1) 空のプラグインを追加する
こちらの記事では元になるプラグインをUE4ソースコードの"BlankPlugin"としていますが、今回はアクターを追加するので同じフォルダにある"UObjectPlugin"をコピーしましょう。
なお、プラグインの名前は"FaderHUDPlugin"としています。
続きからで実際のコードを見ていきましょう。
基本的な修正項目はヒストリアさんのブログを参照してもらうのがいいのですが、公開するプラグインを作成する場合は.upluginファイルにもう少し修正を加えた方がいいでしょう。
このファイルはテキストファイルで、作成者やプラグインの解説なんかを書き込んでおくことが可能です。
これらはUE4エディタのプラグインウィンドウに表示されるものもありますので、正しく設定しておく方がよいです。
今回私が設定したものは以下のものです。
CreatedBy | 作成者の名前です。
個人名、もしくは企業名を入れましょう。 |
CreatedByURL | 作成者のURLです。
当ブログを指定しています。 |
EngineVersion | UE4の対応バージョンです。
ここで指定したバージョン以下では動作を保証しない、という感じでしょう。 |
Description | プラグインについての説明文です。
日本語使えるのか不明ですが、販売するつもりなら英語の方がいいかと。 |
Category | プラグインウィンドウでまとめられるカテゴリ名です。
作成者名、プロジェクト名、機能による分類などを用いるのがいいでしょう。 |
EnabledByDefault | デフォルトで有効になるかどうかです。
trueにしておけばプラグインをONにする手間を省けます。 |
このようにして作成したプラグインはプラグインウィンドウでいかのように表示されます。
URLをクリックすれば作成者のサイトにも飛べます。
次にこのプラグインを実装するうえで必要なモジュールを設定する必要があります。
Slateを使用する場合は"Slate", "SlateCore"の2つのモジュールが必要になります。
また、SlateのWidgetを画面に表示するにはGEngineが持っているGameViewportにWidgetを設定する必要があります。
そのため、"Engine"モジュールも必要になります。
使用するモジュールは"~.Build.cs"に記入します。
今回は"FaderHUDPlugin.Build.cs"に以下のように追加しています。
// 前略
PublicDependencyModuleNames.AddRange( new string[] { "Core", "CoreUObject", "Engine", // <-追加 "Slate", // <-追加 "SlateCore", // <-追加 } // 後略 |
今回はエディタ側に何らかの追加は行わないので、"IFaderHUDPlugin.h", "FaderHUDPlugin.cpp", "FaderHUDPluginPrivatePCH.h" の3つはファイル名の変更と、クラス等の名前の変更だけでOKです。
この点についてもヒストリアさんのブログが役に立つと思います。
今回のプラグインで重要になるのはAHUDクラスを継承したAFaderHUDを作成することです。
UObjectPluginをコピーすれば、ソース内部のClassesフォルダに"MyPluginObject.h"が、Privateフォルダに"MyPluginObject.cpp"が存在するはずです。
このファイル名を変更した段階でVisualStudio用のプロジェクトを作成しましょう。
このプロジェクトに各種ファイルが追加されるはずなので、いよいよ作成です。
まず、"MyPluginObject.h"を改名したファイル"FaderHUD.h"は以下のように記述します。
#pragma once #include "GameFramework/HUD.h" #include "FaderHUD.generated.h" UENUM(BlueprintTYpe) namespace EFaderPlace { enum Type { FP_BACK UMETA(DisplayName="Back"), FP_FRONT UMETA(DisplayName="Front"), FP_MAX UMETA(Hidden) }; } UCLASS() class AFaderHUD : public AHUD { GENERATED_UCLASS_BODY() public: UFUNCTION(BlueprintCallable, Category="Plugin") void SetFadeColor(const FLinearColor& fadeColor, EFaderPlace::Type place); UFUNCTION(BlueprintCallable, Category="Plugin") void SetFadeAlpha(float alpha, EFaderPlace::Type place); private: void BeginPlay(); void Destroyed(); private: TSharedPtr<class SColorBlock> Fader[EFaderPlace::FP_MAX]; FLinearColor FadeColor[EFaderPlace::FP_MAX]; } |
AFaderHUDを実装する前にフェードレイヤーを指定するEnum、EFaderPlaceを作成しています。
Backが画面奥側、Frontが画面手前側です。
これらはカラーとα値を設定する際にどちらのレイヤーのパラメータを変更したいのか指定する際に利用します。
次にAFaderHUDクラスです。こちらは当然AHUDクラスを継承します。
なお、UE4でクラスを作成する場合、Actorなら頭にAをつけます。また、.hと.cppはそのAを除いた名前を用います。
これはUE4のビルドツールが設定しているルールらしいので気をつけましょう。BPに公開しないクラスなら必要ないかもですが。
このクラスにはBPに公開するメソッドとしてSetFadeColor()とSetFadeAlpha()が存在します。
それぞれ指定のレイヤーにフェードのカラーとα値を設定することができる命令です。
非公開メソッドとしてはBeginPlay()とDestroyed()があり、前者はゲーム開始時に発行され、後者はオブジェクトが破棄される段階で発行されます。
メンバ変数はそれぞれのレイヤーのWidgetとフェードカラー(アルファ付き)です。
フェード用のWidgetとしてはColorBlockというWidgetを用いています。
カラーを持っているだけの簡易なWidgetですね。
FaderHUD.cppの方は以下のようになっています。
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. #include "FaderHUDPluginPrivatePCH.h" #include "FaderHUD.h" #include "Slate.h" #include "Engine.h" AFaderHUD::AFaderHUD( const FPostConstructInitializeProperties& PCIP ) : Super( PCIP ) { FadeColor[EFaderPlace::FP_BACK] = FadeColor[EFaderPlace::FP_FRONT] = FLinearColor(0.0f, 0.0f, 0.0f, 0.0f); } void AFaderHUD::BeginPlay() { SAssignNew(Fader[EFaderPlace::FP_BACK], SColorBlock) .Color(FadeColor[EFaderPlace::FP_BACK]) .IgnoreAlpha(false); SAssignNew(Fader[EFaderPlace::FP_FRONT], SColorBlock) .Color(FadeColor[EFaderPlace::FP_FRONT]) .IgnoreAlpha(false); //////////////////////////////////////////////////////////////////////////////////////////////////// /////Pass our viewport a weak ptr to our widget if (GEngine->IsValidLowLevel()) { GEngine->GameViewport->AddViewportWidgetContent( SNew(SWeakWidget).PossiblyNullContent(Fader[EFaderPlace::FP_BACK].ToSharedRef()), 0 ); GEngine->GameViewport->AddViewportWidgetContent( SNew(SWeakWidget).PossiblyNullContent(Fader[EFaderPlace::FP_FRONT].ToSharedRef()), 255 ); } if (Fader[EFaderPlace::FP_BACK].IsValid()) { Fader[EFaderPlace::FP_BACK]->SetVisibility(EVisibility::Visible); } if (Fader[EFaderPlace::FP_FRONT].IsValid()) { Fader[EFaderPlace::FP_FRONT]->SetVisibility(EVisibility::Visible); } } void AFaderHUD::Destroyed() { if (GEngine->IsValidLowLevel()) { GEngine->GameViewport->RemoveViewportWidgetContent( Fader[EFaderPlace::FP_BACK].ToSharedRef() ); GEngine->GameViewport->RemoveViewportWidgetContent( Fader[EFaderPlace::FP_FRONT].ToSharedRef() ); } } void AFaderHUD::SetFadeColor(const FLinearColor& fadeColor, EFaderPlace::Type place) { if *1 { return; } FadeColor[place].R = fadeColor.R; FadeColor[place].G = fadeColor.G; FadeColor[place].B = fadeColor.B; SColorBlock::FArguments arg; arg.Color(FadeColor[place]).IgnoreAlpha(false); Fader[place]->Construct(arg); } void AFaderHUD::SetFadeAlpha(float alpha, EFaderPlace::Type place) { if *2 { return; } FadeColor[place].A = alpha; SColorBlock::FArguments arg; arg.Color(FadeColor[place]).IgnoreAlpha(false); Fader[place]->Construct(arg); } |
それほど長いコードではないです。
まず、コンストラクタでフェードカラーとα値をすべて0.0でクリアしています。
つまり、フェードカラーが黒、フェードなしということです。
BeginPlay()メソッドではWidgetの作成とGameViewportへの登録を行っています。
SAssignNew()はSlateのWidgetを生成する際に利用するnewで、TSharedPtrに作成したWidgetを登録し、そのWidgetのFArgumentsを公開します。
FArgumentsの値はこの段階でのみ直接設定が可能で、生成後のWidgetでは直接いじることができなかったりします。
これはちょっと面倒かな、と思う部分ですね。何か理由があるのかは不明ですが。
Widgetを作成したらGEngine->GameViewportのAddViewportWidgetContent()メソッドを呼び出してWidgetを登録します。
登録の際にZOrderを設定しますので、Backを0に、Frontを255に指定しています。
その後はそれぞれのWidgetをVisible状態にしてBeginPlay()は終了です。
Destroyed()メソッドではAddしていたWidgetをRemoveViewportWidgetContent()で解除するだけです。
SetFadeColor()メソッドとSetFadeAlpha()メソッドがカラーとα値の設定命令です。
一旦クラスが持っているFadeColor変数に値を入れていますが、これはConstruct()命令を発行しなければならないからです。
カラーとα値はFArguments::Colorに1つでまとめられていて、現在のカラーをSColorBlockから取得することができないのでこの形になっています。
こいつを実際に使うとこんな感じになります。
フェードはBack、すなわち奥側に赤でかけられています。
Hello World!の文字はReceiveDrawHUDイベントで描画されているため、フェードより奥になりますが、手前のボタンはUMGですのでSlateを使って描画されています。
ZOrderは1にしていますので、フェードより手前に描かれます。
フェードの場所をFrontにすればこのWidgetもフェードアウトの対象となります。
フェードイン・アウトは動いてないと効果を実感しにくいと思いますので、興味がある方はプラグインを導入して試してみてください。
以下のファイルはプラグインのソースコードと.dllなどのプラグイン導入に必要なものを入れています。
Macだと動作しないと思いますが、Windowsなら各プロジェクトのPluginsフォルダに置けば使えるようになるんじゃないかと思います。
https://dl.dropboxusercontent.com/u/39588440/FaderHUDPlugin.zip
今後も何かちょっとしたものが必要になったらプラグインを書こうかとは思っていますが、作ったら適当に公開しようとは思います。
売るようなものを作るつもりもないので、ソースコード含めて全公開の予定。