【UE5】NativePaintを使ってFPSのグラフを作成する方法
プログラマーの尾関です。
今回は UserWidgetが持つ NativePaint を使って FPSの推移のグラフを作成する方法を紹介したいと思います。

- 動作確認バージョン:UE 5.5.4
目次
NativePaintで線を引く方法
FPSのグラフ描画を行う前に、まずは NativePaintを使って線を引く方法を紹介します。
UserWidgetを継承したクラスの作成
まずはC++で、UserWidgetを継承したクラスを作成します。

Build.csの編集
次にBuild.csの依存クラスに SlateとSlateCore、UMGを追加します。
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class TestCanvas : ModuleRules
{
public TestCanvas(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] {
"Core"
, "CoreUObject"
, "Engine"
, "InputCore"
, "EnhancedInput"
, "Slate"
, "SlateCore"
, "UMG",
});
}
}ヘッダファイルの記述
ヘッダファイルに NativePaint関数を const overrideで定義します。
#pragma once
#include "Blueprint/UserWidget.h"
#include "CPP_TestCanvasDraw.generated.h"
UCLASS()
class TESTCANVAS_API UCPP_TestCanvasDraw : public UUserWidget
{
GENERATED_BODY()
protected:
virtual int32 NativePaint(
const FPaintArgs& Args,
const FGeometry& AllottedGeometry,
const FSlateRect& MyCullingRect,
FSlateWindowElementList& OutDrawElements,
int32 LayerId,
const FWidgetStyle& InWidgetStyle,
bool bParentEnabled
) const override;
};cppファイル
cppコードには、以下のように記述しました。赤い線を引くだけのコードです。
#include "CPP_TestCanvasDraw.h"
#include "Rendering/DrawElements.h"
int32 UCPP_TestCanvasDraw::NativePaint(
const FPaintArgs& Args,
const FGeometry& AllottedGeometry,
const FSlateRect& MyCullingRect,
FSlateWindowElementList& OutDrawElements,
int32 LayerId,
const FWidgetStyle& InWidgetStyle,
bool bParentEnabled
) const
{
// サンプル描画コード
// (0, 0)から(150, 75)に線を引く.
TArray<FVector2D> Points = { FVector2D(0, 0), FVector2D(150, 75) };
FSlateDrawElement::MakeLines(
OutDrawElements,
LayerId + 1,
AllottedGeometry.ToPaintGeometry(),
Points, ESlateDrawEffect::None, FLinearColor::Red, true, 2.f
);
return Super::NativePaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId + 1, InWidgetStyle, bParentEnabled);
}UserWidgetの作成
Unrealエディタに戻って、Widgetブループリントを作成します。

このとき継承元に先ほど作成したクラスを指定します。

Widgetブループリントのアセット名は「WBP_CanvasDraw」としました。

アセットを開いて確認すると以下のように赤い線が引かれています。

NativePaintはこのように Widgetに直接描画することができる関数です。
FPSグラフ表示の自作方法
線の引き方がわかったので、後はFPS計測をしてグラフ表示するだけです。
ヘッダファイルの記述
ヘッダファイルは以下のように記述しました。FPS計測用の変数とグラフ化するためのキューを用意しています。
#pragma once
#include "Blueprint/UserWidget.h"
#include "CPP_TestCanvasDraw.generated.h"
UCLASS(meta = (EnableNativeTick))
class TESTCANVAS_API UCPP_TestCanvasDraw : public UUserWidget
{
GENERATED_BODY()
public:
// コンストラクタ
UCPP_TestCanvasDraw(const FObjectInitializer& ObjectInitializer);
// FPSを取得.
UFUNCTION(BlueprintPure)
float GetFPS() const { return m_FPS; }
protected:
// FPS計測用にTickを回す.
virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
// Canvas描画処理.
virtual int32 NativePaint(
const FPaintArgs& Args,
const FGeometry& AllottedGeometry,
const FSlateRect& MyCullingRect,
FSlateWindowElementList& OutDrawElements,
int32 LayerId,
const FWidgetStyle& InWidgetStyle,
bool bParentEnabled
) const override;
private:
// FPSの計測用.
float m_LastTime = 0.0f;
float m_FPS = 0.0f;
TArray<float> m_FPSArray;
TArray<float> m_FPSQueue; // FPSをキューに保存するための変数.
};cppファイル
#include "CPP_TestCanvasDraw.h"
#include "Rendering/DrawElements.h"
// 定数定義.
const int MAX_FPS_HISTORY = 32; // FPS履歴の最大数
const float GRAPH_HEIGHT = 320.f; // グラフの高さ
const float GRAPH_DX = 16.f; // グラフのX軸の間隔
const float MAX_FPS = 240.f; // 最大FPS値
// コンストラクタ
UCPP_TestCanvasDraw::UCPP_TestCanvasDraw(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
// FPS計測用に要素を256確保.
m_FPSArray.SetNum(256);
m_FPSQueue.SetNum(MAX_FPS_HISTORY);
}
void UCPP_TestCanvasDraw::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
Super::NativeTick(MyGeometry, InDeltaTime);
// FPSを取得.
float DeltaSeconds = FApp::GetDeltaTime();
float CurrentFPS = 1.0f / DeltaSeconds;
m_FPSArray.Add(CurrentFPS);
m_LastTime += InDeltaTime;
if(m_LastTime >= 1.0f) {
// 1秒経過したらFPSを計算.
m_FPS = 0.0f;
for(float FPS : m_FPSArray) {
m_FPS += FPS;
}
m_FPS /= m_FPSArray.Num();
// FPSの履歴をキューに保存.
if(m_FPSQueue.Num() >= MAX_FPS_HISTORY) {
m_FPSQueue.RemoveAt(0); // キューの先頭を削除
}
m_FPSQueue.Add(m_FPS);
// FPS配列をクリア.
m_FPSArray.Empty();
m_LastTime = 0.0f;
}
}
int32 UCPP_TestCanvasDraw::NativePaint(
const FPaintArgs& Args,
const FGeometry& AllottedGeometry,
const FSlateRect& MyCullingRect,
FSlateWindowElementList& OutDrawElements,
int32 LayerId,
const FWidgetStyle& InWidgetStyle,
bool bParentEnabled
) const
{
auto pos = FVector2D(0, GRAPH_HEIGHT);
for(int i = 1; i < m_FPSQueue.Num(); i++) {
// FPSの履歴をグラフとして描画.
float fps1 = m_FPSQueue[i - 1];
float fps2 = m_FPSQueue[i - 0];
FVector2D StartPos = FVector2D(pos.X, GRAPH_HEIGHT - (fps1 / MAX_FPS) * GRAPH_HEIGHT);
FVector2D EndPos = FVector2D(pos.X + GRAPH_DX, GRAPH_HEIGHT - (fps2 / MAX_FPS) * GRAPH_HEIGHT);
FSlateDrawElement::MakeLines(
OutDrawElements,
LayerId,
AllottedGeometry.ToPaintGeometry(),
{StartPos, EndPos}, ESlateDrawEffect::None, FLinearColor::Green, true, 1.f
);
pos = EndPos; // 次の位置へ移動
}
return Super::NativePaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId + 1, InWidgetStyle, bParentEnabled);
}1秒ごとにFPSの履歴をキューに登録しています。
Widgetにテキストブロックを追加
Unrealエディタに戻って、現在のFPSを確認しやすいように、WBP_CanvasDrawを開いて、TextBlockを配置します。

Widget名を “TextFPS” にして Is Variable にチェックを入れて変数化します。

Event Tickで FPSの文字を更新するようにしました。

UserWidgetを表示
レベルブループリントを開いて Begin Playイベントで WBP_CanvasDrawを生成してViewportにアタッチ。

負荷計測用エフェクト
何もないレベルだと120FPSでほぼ固定されてしまうので、負荷計測用にNiagaraのAttributeReaderTrailsを検索してプロジェクトにコピー。

アセット名は NS_Test にしておきました。
使っているPCの性能に合わせて、Spawn Countを「10000」などの極端に大きな値にしてみます。

これをレベルに直接配置すると、負荷計測用にヤバげなエフェクトができました。

動作確認
実行して動作確認。
FPSのグラフが表示されるようになりました。

おしまい
以上、FPSグラフを自作する方法でした。
負荷計測用などに役立てられれば幸いです。

