UnrealEngine4で2Dゲームを作ろう! 「2Dゲームを作るために必要なこと 基本編」
2Dゲームとは直接関係のない話題ばかりになっているので、いったん2Dについて一通りまとめて、「UnrealEngine4で2Dゲームを作ろう!」シリーズはいったん締めたいと思います。
UE4に関する話題の更新は、まだネタもあるので続けますのでご安心(?)を。
ドットバイドット表示設定やスプライトについて紹介する「基本編」と、2DゲームにおけるUE4の機能について紹介する「機能編」の2本立てでお送りします。
対象バージョンは、UE4.10以上(Windows)です。
ドットバイドットで表示する
ドットバイドット表示とは、ディスプレイの1ピクセルとゲームの1ピクセルのサイズをぴったり合わせた表示のことです。ドットバイドット表示にしないと、スプライトがボケたりズレたりします。スプライトのボケズレはポリゴンよりかなり目立つため、できれば避けたい所です。
UE4のドットバイドット表示は、ウィンドウ・カメラ・PixelPerUnitの設定を適切に行うことで実現できます。
ウィンドウの設定
ウインドウの設定(UE4.11以降)
UE4.11で、ウインドウサイズやフルスクリーン設定がBPノードとして公開されました。
GameUserSettingsを取得して、SetScreenResolution/SetFullscreenModeで設定し、最後にApplySettingsを読んで設定を適用します。
ウィンドウサイズ設定(UE4.10以前)
ウィンドウサイズ設定の仕方は、いくつかありますがコンソールコマンドを使う方法をお勧めします。
ExecuteConsoleCommandノードを使い「r.SetRes 幅x高さ」とコマンドを入れます。幅x高さの後にwだと「ウィンドウモード」、fだと「フルスクリーンモード」になります。
このコマンドを最初にロードされるLevelやGameModeのBeginPlayで呼ぶことで、ゲーム起動時にウィンドウサイズが設定されます。
フルスクリーンへの対応(UE4.10以前)
ドキュメントにちゃんと書かれていないのですが、UE4は2種類のフルスクリーンをサポートしています。
1つはディスプレイサイズも設定したウィンドウサイズに変更するフルスクリーン(以下単にフルスクリーン)、1つはウィンドウサイズを現在のディスプレイサイズに拡大するウィンドウフルスクリーン*1、です。
これらの切り替えは、ウィンドウサイズの設定と同じようにコンソールコマンドで切り替えを行います。
「r.FullScreenMode 0」ならフルスクリーン、「r.FullScreenMode 1」ならウィンドウフルスクリーンとなります*2。
このコマンドは「r.SetRes」コマンドの前に呼ぶ必要があります。
フルスクリーンとウィンドウフルスクリーンの主な違いは以下の通りです。
フルスクリーン | ウィンドウフルスクリーン | |
---|---|---|
アスペクト比 | ディスプレイのアスペクト比になる | カメラの設定に従う |
DPIスケーリング | ウィンドウサイズが使われる | ディスプレイサイズが使われる |
特にDPIスケーリングの違いは忘れがちなので、ちゃんとDPIスケーリングのカーブを設定しておきましょう。
ウインドウリサイズへの対応(4.14以降)
常にドットバイドットで表示するために、ウィンドウの最大化やマウスドラッグによるウィンドウサイズ変更をできないようにします。
[ProjectSettings] → [Description] → [Settings]にて、最大化・リサイズなどの有効無効が設定できるようになりました。
ウィンドウリサイズへの対応(4.13以前)
UE4.13以前で、実現するにはC++を利用する必要があります。
BPから呼び出すのことのできるC++関数を定義します。関数の中身は以下の通りです。
#include <windows.h> void UCommonBPFunctionLibrary::SetupSpriteGameWindow() { // Editor起動の時は何もしない if(GIsEditor) return; // MainWindowのウインドウハンドルを取得する HWND hWnd = NULL; TSharedPtr<SWindow> MainWindow = GEngine->GameViewport->GetWindow(); if(MainWindow.IsValid() && MainWindow->GetNativeWindow().IsValid()) { hWnd = static_cast<HWND>(MainWindow->GetNativeWindow()->GetOSWindowHandle()); } if(hWnd) { LONG Style = ::GetWindowLong(hWnd, GWL_STYLE); // ドラッグによるサイズ変更と、最大化ボタンを無効にする Style &= ~(WS_THICKFRAME|WS_MAXIMIZEBOX); ::SetWindowLong(hWnd, GWL_STYLE, Style); ::ShowWindow(hWnd, SW_SHOW); } }
ビルド設定に、Slate/SlateCoreモジュールを追加してください。
この関数をウィンドウモードに変更した際に呼び出すことで、ウィンドウリサイズを無効にすることができます。
C++の関数をBPに公開する方法は、「UE4 C++コードをブループリントで使えるようにする(関数ライブラリー編) - Let's Enjoy Unreal Engine」あたりを参照してください。
カメラの設定
カメラで重要な設定は、図の緑線を引いた4つの項目です。
項目 | 意味 | 設定値 |
---|---|---|
ProjectionMode | 射影モードを設定する | Orthographic(正射影) |
OrthWidth | 正射影の時のカメラの範囲とする幅(UEUnit単位) | 画面幅のピクセル数を入れる。図だと幅1280ピクセル |
ConstrainAscpectRatio | アスペクト比を維持するかどうか | チェックを入れる |
AspectRation | アスペクト比 | 画面幅÷画面高さ の値を入れる。直接計算式を入れて良い |
PixelPerUnitの設定
最後にPiexlPerUnit(以下PPU)の設定を行います。PPUとは、名前の通り、1ユニット単位のピクセル数を設定する項目です。
使うスプライトシステムごとに設定する必要がありますが、とりあえず、1.0を入れておくのが一番簡単です。
1.0にすれば1ユニットが1ピクセルになりますので、座標の値がピクセルと一致します。様々な計算が楽になります。
もし、1.0以外の値を使う時は、カメラのOrthWidthの値も忘れずに変更してください。OrthWidthの正確な算出式は、「画面幅(ピクセル)×PPU」です。
本記事で紹介する2つのスプライトシステムでのPPU設定場所は次の通りです。
Paper2D
[プロジェクト設定]→[エディタ]→[Paper2D Import]→[Default Piexel Per Unreal Unit]
スプライトを作る時のデフォルト値として設定しておくのが簡単です。
スプライトごと、個別に設定したい場合は、設定したいPaper2D Spriteを開いて、[Sprite]→[Pixel Per Unit]で設定できます。
SpriteStudio
[SsPlayerComponent]→[Sprite Studio Render Settings]→[UUPer Pixel]
こちらは、1ピクセルが何ユニットかという設定なので、Paper2Dとは逆値を設定する必要がありますので、両方使う場合はご注意ください。
2Dの描画(Sprite)
UE4で、2D(スプライト)描画を行うには、現状2つのシステムがあります。
UE4が公式に用意しているPaper2Dシステムと、WebTechnology社が制作ししてるSpriteStudioシステムです。
正直な所、どちらも一長一短です。共存は問題なくできますので用途に合わせて使い分けます。
Paper2D
まずは公式の方から、細かい使い方はドキュメントを見ていただくとして、ここでは概要を紹介します。
Paper2Dの機能は大きく2つあります。Paper2D Sprite と Paper2D Flipbook です。
Paper2D Sprite
名前の通りSpriteを描画する基本機能です。Paper2DのシステムはまずテクスチャからSpriteを作ってから操作を行います。
1枚のSpriteを任意の設定で描画します。
Paper2D Flipbook
任意枚数のPaper2D Spriteを指定した時間で連続再生する機能です。いわゆるパラパラアニメを実現する機能です。
長所
UE4公式なので、UE4の機能との親和性が高いです。マテリアルも任意に設定できますし、ダイナミックマテリアルインスタンスも使えます。
最近、新機能の追加が行われていませんが、これからの機能追加も期待できます。
短所
間接アニメを簡単に作ることはできません。間接アニメとは、2D絵を腕・胴・脚といった部分に分解して、個別に動かすことでアニメーションさせる手法です。Flipbookはレイヤーに対応していませんし、キーフレームには1つのSpriteしか設定できませんので、単純なパラパラアニメしか作ることができません。
その他
他にも、マップチップを使用して任意サイズのスプライトを作るTileMap機能もあります。
SpriteStudio
もう一つはSpriteStudioです。2Dアニメ制作ツールですが、パラパラアニメを作るのはもちろんのこと、特に関節アニメ制作に力が入っているツールです。
インディーライセンスならばすべて無料で使うことができます。Webサイトを見ると半年だけとありますが、半年ごとにライセンスを取り直して良いという公式の見解があります。
@A_Lakryu__m 半年ごとの更新、になります。更新のタイミングで利用条件満たしてれば、そのまま再申請できますよー。
— OPTPiX SpriteStudio (@spritestudio) October 1, 2014
SpriteStudioそのものは、2Dアニメを制作するためのエディタです。エディタで作ったデータを読み込んでUE4上で使うにはSpriteStudioプラグインを使います。
SpriteStudioプラグインは完全に無料だそうです。プラグイン開発はWebTechnology社の認可を受けて、あのhistoria社が行っていますのでプラグインの出来も問題ありません。
エディタの使い方、プラグインの使い方は、各公式サイトのドキュメントを参照してください。
長所
- 間接アニメを作ることができます。
- 元からゲーム用途を想定して作られているため、なかなかかゆい所に手が届く出来です。アニメーションエディタとしても優秀ですし、PGとしてはアニメーションにデータを埋め込んで、それをイベント(OnSsUserData)として簡単に取れるので連携がしやすいです。
Spriteシステムの使いわけ
基本的にはSpriteStudioを使うようにして、カスタムマテリアルが必要ならばPaper2Dを使うのが良いと思います。
描画順
スプライトの描画順は、カメラに対する距離を調整することで行います。
見下ろし(XY平面)ならZ値、横スクロール(XZ平面)ならY値を調整することで、描画順をコントロールします。
見下ろしの場合は、スプライトのpivotをベースライン(Spriteの一番下のライン)に設定することで違和感の少ない描画順を設定することができます。
描画順を設定するのは、Spriteだけにするのをお勧めします。Actorごと動かしてしまうとCollisionも動きますので、調整が大変になります。
SpriteActor(Pawn)
最小の機能を持ったSpriteActor(Pawn)は、空のActor(Pawn)にCollisionComponent/Sprite系Componentを追加して実装します。
図では、FlipbookとSsPlayer(SpriteStdioのComponent)を両方いれてますが、実際には必要な方だけいれてください。
Sprite系Componentは、必ずCollisionComponentの子供にします。そうしないとCollsionヒットした際のブロック処理が上手く動きません。
もしCollisionComponentを使わずに、Spriteに設定したコリジョンを使う時は、Sprite系Componentをルートに配置します。
もちろん、他に必要なComponetがあれば追加します。おそらく移動系とサウンド系は追加することになると思います。
フレームレート(FPS)の設定
UE4は可変フレームレートで実装するのが基本になります。そのため、Paper2DもSpriteStudioも可変フレームレートで動作するようになっていますので、UE4の機能を使って実装している限りは特に気にする必要はありません。2Dアニメーションは、3Dのモーションのような補間はされませんので、極端にフレームレートが落ちた場合はアニメーションのフレームが飛んでカクカクすることはあります。
とはいえ、FPSをコントロールしたいと思うこともあると思いますので、その方法を紹介します。
*1:仮想フルスクリーンとも呼ばれます
*2:ちなみに、この引数はEWindowMode列挙型に連動しています
*3:'Use Fixed Frame Rate' not working properly - UE4 AnswerHub
*4:実時間経過は無視して、1フレごとに設定された時間が経過したことに強制的にする
UnrealEngine4で2Dゲームを作ろう! その17 UMGでテクスチャアトラスを使う
※ 任意サイズに切り出すマテリアルが作れましたので、以下の記事も参照してみてください
katze.hatenablog.jp
UMGにおいて画像を使いUIを作るにはimageウィジェットを使います。
imageウィジェットは、グラフィックを表示するのにSlateBrushを利用していますので、Brushを設定することで画像を表示します。
Brushのimage設定には、テクスチャもしくはマテリアルを割り当てることができます。
テクスチャ指定がお手軽ではあるのですが、テクスチャ全体が表示されてしまいます。
テクスチャ指定で作るには、imageウィジェット1つに付き1つのテクスチャを用意すれば良いのですが、最近の描画システムはテクスチャ切り替えのコストが重いので、できれば避けたい所です。
しかし、困ったことにimageウィジェットはテクスチャアトラスに対応していないため、テクスチャから部分的に画像を切り出すのは自前でやる必要があります。
幸いBrushはマテリアルを使うことができますので、テクスチャから必要な部分を切り出すマテリアルを画像ごとに作ります。
マテリアルインスタンスも利用してシェーダー切り替えコストも抑えたい所ですが、どう汎用化するかはお任せしまして*1、ここでは簡単な例と考え方を紹介します。
テクスチャを切り出すマテリアル
一番簡単な例として、切り出す画像が等間隔で並んでいる場合を考えます。
先ほどの横2列 縦4列並んでる画像から、ひとつ切り出してみます。
マテリアルは以下の通りです。
最初のADDで画像内の分割された位置(横,縦)を指定します。次のDivideで横を2分の1、縦を4分の1にして1枚分に切り出します。
マテリアルの仕組み
imageウィジェットは、1つの四角ポリゴンを使ってBrush設定に従って描画されます。その四角ポリゴンに割り当てられるUVは、左上(0,0)~右下(1,1)となっているようです。
そこで、欲しい画像の範囲がUV(0,0)~(1,1)になるように演算をいれます。
ADDによってUVの原点をズラし、そこから分割数で割ることで1つに切り出します。
ADDする値はDivideされることを考慮にいれます。Divideしなければ、横に1画像ズラすには0.5足しますが、横は2分の1されますので、2倍して1を足すことで1画像分ズレることになります。縦も同様です。
Misc
パタパタアニメを仕込むのは、FlipBookマテリアル関数を使えばできるようです。
テクスチャアトラス対応の要望自体は上がっているみたいですので、対応を期待したい所です。
*1:描画系苦手なので他力本願! むしろ、良い感じのマテリアルが出来たらご教授願います……
UnrealEngine4で2Dゲームを作ろう! その16 CreateEventノード!
イベントディスパッチャーってやつがあります。
イベントディスパッチャーにイベントをBindするノードの仕様で、イベントを必ず同じEventGraph上に作らなければならず、イベントディスパッチャーを使い始めるとなかなかスパゲティなコードになってしまい困っていました。
関数をEvent化してbindできるようになってくれればなぁ、と思い調べた所、できるようになっていました。それがCreateEventノードです。
リリースノートを見るとUE4.9で「使いやすくしました」とあるので、前々からあったようです。
New:CreateEvent nodes are now more user friendly - they will use the current blueprint as the default context and no longer suggest that the user select an asset for context.
使い方
例えば、以下のようなイベントディスパチャーがあったとします。
integer1つと、Stringを1つ引数に取るイベントとします。
bindするイベントとまったく同じ引数を持った関数を定義する
ブループリントクラス内で関数を定義します。引数は、イベントディスパッチャーのものと同じにします。
引数の名前は、わかりやすく同じにしていますが同じである必要はありません。引数の型・数・順番があっていればOKです。
bindノードをおいて、CreateEventとつなぐ
bindノードにつなぐと、CreateEventノードの下部に「関数を選択」と出ます。
関数を選択から先ほど定義した「StateEnd」を選ぶ
これで、bind完了です。
関数リストにbindしようとした関数が出て来ない場合は、引数が間違っているということになりますので見直して見ましょう。
UnrealEngine4で2Dゲームを作ろう! その15 BehaviorTreeのDecorator/Task作ってみた
やっとザコのBTが出来た…… pic.twitter.com/Yuwt7GWw8x
— katze (@katze_7514) December 26, 2015
という感じで、BehaviorTree(以下BT)で敵AIを作ってみた時にわかったことを書きます。
BT全体
BT全体は上図の通りで、移動して攻撃したらちょっと待つを繰り返します。
Decorator/IsEnableAI
BTは、外からPauseしたりStopしたりする機能がないので、代わりに根元で実行を止めるDecoratorを付けました。
中身は、対応するControllerの状態を調べてBTを実行する状態かをチェックしています。
Decorator実装
Decoratorの実装は、「PerformConditionCheckAI」をオーバーライドし、boolをリターンします。
AIがついていない「PerformConditionCheck」もあり、どっちも使えるのですが、AI付きの方はBTを実行しているController/Pawnを引数として取れるのでAI付きの方が使いやすいと思います。
Task/SimpleMoveTo,Attack
SimpleMoveTo
NavMeshがリビルドエラーを吐き使えなかったので、MoveToを自前実装したのがSimpleMoveToです。中身は単純にPlayerPawnに近づいて一定距離になったら止まるというだけです。
このリビルドエラーは、AnswerHubにも投稿されていますが完全な解決には至ってないようです。
移動中は処理を継続し、PlayerPawnの一定距離内に来たらFinishExecuteを読んでTaskを終了します。
また、処理を途中で打ち切るための仕込みもいれてあります。BlackBoardにAbortフラグが立っているかをチェックして、立っていたらFinishAbortします。この辺りについては後述します。
Attack
攻撃アニメを再生して再生終わりを待つTaskです。アニメ再生終わりは、Controllerの方で検出してBlackBoardにフラグを書き込んでいます。Taskの中で直接再生終了をイベントをbindしても良いと思います。
ダメージのやりとりはコリジョンイベントの方で行っています。
Task実装
Taskは2つのイベントを実装します。
ExecuteはActorのBeginPlayに相当するのもので、Taskが実行開始時に呼ばれます。BeginPlayと違うのは、Taskが実行開始する度に呼ばれることです。
つまり、今回の例ですと、SimpleMoveTo→Attack→Wait と実行され、最初に戻り再びSimpleMoveToが実行される時にもまたExecuteが実行されます。
Tickは、Task内でFinish系ノードが呼ばれるまで毎フレーム呼ばれます。
Taskの実行中断
※ DecoratorのObserveAbortsという項目を使うことで途中中断ができたみたいです。Decoratorの値が変化した時に接続されているノードを中断するかを選択することができます。
今回、BT(というかTask)の実行を中断する必要があったのは、Playerの攻撃でノックバックしたり、HPが0になったら死亡エフェクトを表示したりする必要があるからでした。
先にも書きました通り、BT自体に動作を止める手段が用意されてないため、実行中のTask以外の動作をさせるには一端Taskを止める必要があります。
BTのデバッグ表示を見ると以下のようになっていたので、
SequenceのIsEnableAIも毎フレーム実行されるのかと勝手に思っていたのですが、そんなことはなく、上図の場合はSimpleMoveToのTickだけが呼ばれている状態になります。
Decoratorは実行"開始"条件判定を行うだけで、一度判定が通り先に進むとDecoratorは実行されなくなります。実行"継続"条件としては使用できません。
よって、Taskの実行を中断させるには、根元にIsEnableAIを仕込むだけは足りないということになり、Task内に中断する仕組みを導入する必要があります。
TaskのTick内で中断フラグをチェックする機構をいれておきます。
IsAbordはBlackBoardに用意したAbordフラグを見にいっています。BlackBoardはBT内からでも外からでも操作できますので、どこからでもTaskの実行を中断できます。
UE4の入力取得の仕組みとプラグインの作り方
「Unreal Engine 4 (UE4) 其の弐 Advent Calendar 2015」 3日目の記事になります。
UE4エディタ・BPなどが一切でません。C++のみのプログラマ向けの記事になります。
ここで示すソースコードは「4.10.0-release」タグのものになります。
UE4の入力取得
確認しておきますと、ここでいう入力とは、ゲームパッド・キーボード・マウスといった入力デバイスのことを指しています。
UE4でこの入力状態をチェックしイベントを発生させているのは2ヶ所あります。
まず、ゲームパッド及び独自の入力デバイスの処理が以下の場所。
LaunchEngineLoop.cpp 2414~2415行目
2414: FSlateApplication& SlateApp = FSlateApplication::Get(); 2415: SlateApp.PollGameDeviceState();
2415行目のPollGameDeviceStateメソッドが、ゲームパッドと入力プラグインなどで指しこんだ独自入力デバイスのチェックを行いイベントを発生します。全ActorのTick前に一括して処理しています。
FSlateApplicationクラスはプラットフォームを抽象化したクラスです。プラットフォームごとのApplicationクラスを内部でもっており、プラットフォームAPI処理を行っています。*1
2つ目は、キーボードやマウスなどウインドウメッセージによる入力処理です。メッセージ系はチェックとイベント発生の2段階にわかれて処理されています。
1段目、メッセージの確認が以下の場所。
LaunchEngineLoop.cpp 2391行目
2391: FPlatformMisc::PumpMessages(true);
メッセージキューにたまっているメッセージを確認して、一端、メッセージバッファに情報を格納しています。
その後、全ActorのTickが終わった後に、メッセージイベントを発生します。もちろん、すぐに処理すべきウインドウメッセージは、メッセージチェックの段階でも処理されていますが、入力系は遅延処理されます。
その遅延処理しているのが、以下の場所。
LaunchEngineLoop.cpp 2460行目
2460: FSlateApplication::Get().Tick();
FSlateApplicationのTickメソッドの中で、ProcessDeferredEventsメソッドが呼ばれてメッセージによるイベントを発生させます。
ざっくりですが、UE4の入力処理は上のようになっています。
ちなみにLaunchEngineLoopは、いわゆるメインループの実装です。メインスレッドが何をやってるのかを見たい時は、LaunchEngineLoopをのぞくと良いと思います。
独自の入力デバイスプラグインを作る
以上を踏まえて、独自の入力デバイスを使うためのプラグインの作り方を紹介します。独自といってもオリジナルハードという意味ではなく、UE4が公式対応していない程度の意味です。Oculusタッチなどが該当します。*2
プラグイン全般の作り方は公式ドキュメントなどを参考にしていただくとして、ここでは入力プラグインに特化した内容を紹介します。
*1:プラットフォームごとのApplictionクラスの名前は、F[プラットフォーム名]Appicationという名前になっています。Windowsだと、FWindowsApplicationクラスになります。