[UnrealEngine4] 2DSpriteやFlipbookを高速に描画する為のアイデア [UE4]

# 本記事の内容

UE4を使った2Dゲーム開発をしようとしたときに、恐らく最初に躓く個所は **2D描画の遅さ** です。

私自身も苦労をし速くするためにどうすればよいか調べても周りも同様に困っている人が多く、解決に向けた1歩目まで多くの時間を費やした為、
同じ苦労をする人を減らすためにも記事にしました。

本記事ではUE4の2D描画をどうすれば

* 速く
* 簡単に組み込めるか

という点に関する1つのアイデアとなっています。

※本記事はUE4及びC++の基礎知識が必要となる箇所があります。

# 目次

1. UE4の2D描画に関して
2. 動作環境
3. 作成したアプリ概要
4. 動作結果
5. どうすれば速くなるのか？

まずは順を追ってみていきます。

# 【背景】

## UE4の2D描画に関して

UE4では基本的に3Dの空間上に平面ポリゴンを用いて1枚絵(2D画像)を描画するのですが、その主なコンポーネントとしては、

* Sprite ... UE4のビューポート上に配置可能な2D画像  
* Flipbook ... UE4のビューポート上に配置可能なスプライトの連番からなるアニメーション

の二種類となっています。

早速この二種類を使って簡単なゲームを開発していこうとこの時は意気込んでいました。

## 開発環境

* UE4.20
* UE4のプロジェクト設定
   * 固定60FPS
   * カメラはOrthographic(横幅640/Aspect Ratio 1.3333)
   * TargetPlatformはWindows
* CPU Corei7 2600
* GPU NVIDIA GeForce GTX 570
* Memory DDR3-1333MHz 24GB
* Windows10 64bit

2011年代で最もよかったパーツばかりです！
Pentium3の頃ですら **DirectDraw** を使って多くのスプライトを描画しているゲームが大量にあったので、スペックとしては十分ですね！(**とこの時は思っていました。**)

次にUE4上で動作させるアプリの仕様です。

## UE4で作成したアプリ概要

簡単に説明すると大量のエフェクトが発生する綺麗な画面をまずは作ろうと意気込んでおりました。
仕様としては

* Flipbookコンポーネントを持ったアクター(DisplayActor)を作成する。
  * Actor内ではFlipbookのアニメーションループ設定をOFFとする。
  * Flipbookのアニメーションが終了したタイミングで自身のActorをDestroyする。(約0.5秒でアニメーション終了)
  * Flipbookは32x32のSpriteの16枚連番アニメーションとする。
* 毎フレーム(1/60フレーム)に8枚DisplayActorを画面上にSpawnする。

つまり 最大瞬間描画数としては1フレームに約240個のFlipbookを画面上に表示させようとしました。
ごちゃごちゃした2Dゲーム(弾幕シューティングなど)であれば一般的な描画数ですね。

では実際に実行してみました。

## 動作結果

見事に処理落ちしました。

![ue4-1.png](https://qiita-image-store.s3.amazonaws.com/0/383232/136b07f0-e060-cb24-e2df-a3eabea898ef.png)

**たかだか毎フレーム240枚程度のFlipbookの描画に42FPSです。**
より詳細に情報を取得する為、どこで問題が起こっているか `stat unitGraph` で表示しました。

![ue4-2.png](https://qiita-image-store.s3.amazonaws.com/0/383232/51aa4f5f-abcb-39cb-2160-26c7e5a247b2.png)

少し見難いですが、GPUの箇所で多くの時間がとられており、
1フレーム22msかかってしまっています。(60FPS出すためには1フレーム16msに減らす必要があります。)

恐らくここが問題だという事で原因の調査に踏み出しました。

## 原因

憶測となってしまい申し訳ないのですが、
恐らく板ポリゴンに貼った2Dの絵1枚1枚に対してシェーダー(マテリアル)が適用されているように思います。

SpriteやFlipbookは正式には UPaperSpriteComponent、UPaperFlipbookComponentというコンポーネントクラスを使用しており、いずれもUMeshComponentを継承しています。

このクラスの名前から推測すると、1Flipbookの描画につき、
1. 3D空間上のどの位置に描画を行うかという4頂点の計算。(頂点シェーダー)
2. マテリアルを適応しDirectionalLightの計算などを行う。(ピクセルシェーダー)

が行われている為、非常に重くなっているのではないかという事です。

またカメラActorで描画出来る範囲外にFlipbookをSpawnしても処理落ちはしませんでした。これは

1. カメラの範囲外は視錐台カリングで描画されていない(頂点シェーダー・ピクセルシェーダーが動いていない。)
* Spawnの処理がたとえ重かったとしても処理落ちするまでのクリティカルな重さで無い。

という事を示しているのではないかと思います。

# ここからが本題、どうすれば速くなるのか？

ではどうすれば速く2D描画(FlipbookやSprite)が出来るようになるのか？という点を踏まえてサンプルソースコードを作成してみました。

[ソースコードはこちら](https://github.com/Takmg/UE4-Fast2DDrawActor) です。

## このサンプルコードは何を行っているか？

* Fast2DDrawActorと呼ばれるActorを画面上に一つ置く。
  * 本Actorはスプライト(バックバッファの役割)を子に1つ持つ。
  * 本Actorの子にAttachされたActorにSpriteとFlipbookが存在する場合、それを非表示にする。
  * 本ActorのTick処理(PostUpdateのタイミング)にAttachされたActorに存在するSpriteとFlipbookを本ActorのSprite(バックバッファの役割)に描画する。

* 2DCameraActor
  * Orthographic用のカメラ設定が組み込まれたカメラ。
  * 本ActorはFast2DDrawActorのスプライト(バックバッファの役割)を描画している。

* DisplayCharacter
  * Flipbookを表示する。
  * アニメーションが終了すると自身のActorを削除する。

という流れです。

少しわかりにくいかもしれませんが、
Assets/Blueprints内のブループリントクラスとレベルブループリントから処理を追ってもらえれば恐らくすぐに処理の流れが把握できるはずです。

# 読んでいただきありがとうございました。

本記事はあくまでUE4上で2D描画を高速に行うにはどうすればよいか。
という問題に対する私見とサンプルコードになります。
もし何かしら間違いや誤りなどありましたらご指摘お願いします。
またコメントなどあればいただけると嬉しいです。

# 本サンプルコードで使った画像

* 本資料の画像ですが [素材うｐろだ](https://www41.atwiki.jp/stgbuilder/pages/14.html) から使用させていただきました。
誠にありがとうございます。
何か問題などありましたらご連絡いただければ幸いです。