たすブログ

近況:4 告知:4 技術ブログ:2 な割合です

BPをテキストエディタに貼れるのならテキストエディタでBP書けるんじゃないのチャレンジ

この記事は「Unreal Engine (UE) Advent Calendar 2021」16日目となります。

qiita.com

 


・はじめに

さて、皆様ご存じの通り、ブループリントで書いた処理をコピーして
テキストエディタに貼り付けると、テキストに変換され、
逆にテキストエディタからUEエディタに貼るとBPに変換されます。

ということは。
テキストだけでBPある程度かけるのでは!?

ということで、テキストエディタ上に貼られたBPの中身を軽く読んで、
BPをテキスト編集する際に、最低限ここを触ればいいのではという部分を調べました。
実際に編集する機会は、置いておくとして……!


・目次

内容としては以下の流れとなります。

  • ノードの構成
  • get/set変数の取り方
  • 標準的なノードの呼び出し
  • ピン
  • ノード接続
  • カスタムイベント
  • 最後に

※記事内で全てのテキストを説明するととても多くなってしまうため
省略できる箇所(デフォルト値で補間される箇所)は省略して
最低限の部分だけで試したものとなります。

また、定義元のファイルを記載していますが全部は調べてないので
別の箇所にもある可能性もある前提でお願いします。

では行きましょう…!


・ノードの構成

まず最小構成としてノードの置き方です。
Begin Object Class=…~End Objectで挟んだものがひとつのノードになります。

Nameは省略可能ですが、ピン接続時に必要になるので入れておくと良いです。

例えば変数のGetとSet(中身なし)を置く場合は以下の形。

//getObject
Begin Object Class=/Script/BlueprintGraph.K2Node_VariableGet Name="test1"
End Object
NodePosX=0
NodePosY=0
End Object
//setObject
Begin Object Class=/Script/BlueprintGraph.K2Node_VariableSet 
NodePosX=200
NodePosY=0
End Object

f:id:tasuya:20211214154909p:plain

中身がないGetとSetが置けました

NodePosX/NodePosYで座標指定が出来ますが、
これはBPにペーストした時点でクリックした位置が原点になるので、
手書きでやる場合は「並べるときにずらすオフセット」と思えばよさそうです。

ちなみに座標を揃えて同じノードを複数貼ろうとすると、同一と判断されてひとつしか貼られません。
良く出来てる。


・get/set変数の取り方

上記get/set命令は「どの変数が対象」かは含まれていないので、
どれを参照するかを追加する必要があります。

変数宣言はBP上でする必要があるので、
あらかじめ宣言しておいてそれをVariableReferenceのMemberNameに入れると
その変数が参照されます。

 

例えばBP上にfloat floatTestの変数を用意した上で下記をペーストすると

floatTestのgetが配置されます。

Begin Object Class=/Script/BlueprintGraph.K2Node_VariableGet 
   VariableReference=(MemberName="floatTest",bSelfContext=True)
End Object

 

f:id:tasuya:20211214160259p:plain

宣言されている変数名と一致していれば取れます

bSelfContextはFalseにすると左側に親を指定できるようになりますが
その場合はピン設定もちゃんと書かないと取得できないので今回は飛ばします。


・標準的なノードの呼び出し

さて、突然でてきたGetとSetですが、これら基本的な命令は
\Engine\Source\Editor\BlueprintGraph\Classes\
K2Node_~~.hという形でヘッダー名がそのまま命令の名前になっているものがあります。
これらの名前をClass=/Script/BlueprintGraph.~~の部分に入れてペーストしてやると概ね呼び出すことができます。
LoadAssetとかSpawnActorとかStructMemberGetとか。
BranchはK2Node_IfThenElseでした。

f:id:tasuya:20211214162410p:plain

UE4のIfはブランチですと説明したりしますがIfでした

PrintStringやfloat+float等の関数・イベントは
Class=/Script/BlueprintGraph.K2Node_CallFunction 
で呼び出します。
FunctionReferenceのMemberParentにヘッダー、MemberNameに関数名を入れます。

Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction
   FunctionReference=(MemberParent=Class'"/Script/Engine.KismetSystemLibrary"',MemberName="PrintString")
   EnabledState=DevelopmentOnly
End Object

f:id:tasuya:20211214163010p:plain

EnabledState=DevelopmentOnlyは「開発のみ」のオンオフなので
省略すると「開発のみ」が外れたPrintStringも置けます。

関数が定義されているヘッダーは
Engine\Source\Runtime\Engine\Classes\Kismet\
にありますので、ここから探すと良さそうです。

PrintStringはKismetSystemLibrary.hに、
floatの足し算(Add_FloatFloat)はKismetMathLibrary.hにありました。


・ピン

引数や実行ピンは命令の宣言側で定義されているので、
呼び出し側で省略してもデフォルト値が入る場合が多いため
ここまで省略してきましたが、
必要な場合はピンごとにCustomProperties Pin()で設定します。

ピン一つに対しての設定項目が多いため
全て書くとそれだけでひと記事が必要となってしまいますので
最低限必要な部分として以下あたりを押さえておくとよさそうです。

  • PinId:接続で使用する自分のピンのID 接続先になる場合に使用
  • Direction:入力ピンか出力ピンかの判別 "EGPD_Output"なら出力(右側)ピン
  • PinName:ピンの名前
  • PinType.PinCategory:ピンの種類 "exec"なら実行ピン
  • LinkedTo:このピンから接続する先のノード名(BeginObjectで指定したName)とつなぎたいPinId
  • DefaultValue:引数等のデフォルト値

DirectionのEGPD_Output/EGPD_Inputなど、ノード関連のenumは
\Engine\Source\Runtime\Engine\Classes\EdGraph\EdGraphNode.h
にて定義されているようです。

set floatTestを例にすると

Begin Object Class=/Script/BlueprintGraph.K2Node_VariableSet
   VariableReference=(MemberName="floatTest",bSelfContext=True)
   CustomProperties Pin (PinId=A0000000000000000000000000000000,Direction="EGPD_Output",PinType.PinCategory="exec")
   CustomProperties Pin (PinId=B0000000000000000000000000000000,PinName="floatTest",PinType.PinCategory="float",DefaultValue="10.0")
End Object

の形で

  • A0000000000000000000000000000000:右側出力ピン側の実行ピンID
  • B0000000000000000000000000000000:左側入力ピンのfloat ピンのIDとデフォルト値を10に設定

となります。


・ノード接続

ノードの接続は、

  • 接続元の出力ピン(EGPD_Output)のLinkedToに接続先のPinId
  • 接続先の入力ピンのLinkedToに接続元のPinId

の形で、双方に接続するピンを指定する形で接続されます。

PinIdは32桁固定のようですので、桁数だけは揃えるようにしてください。

//接続元のPrintString
Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="print1"
   NodePosX=0
   NodePosY=0
   FunctionReference=(MemberParent=Class'"/Script/Engine.KismetSystemLibrary"',MemberName="PrintString")
   CustomProperties Pin (PinId=B0000000000000000000000000000000,PinName="then",Direction="EGPD_Output",PinType.PinCategory="exec",LinkedTo=(print2 A0000000000000000000000000000000))
End Object
//接続先のPrintString
Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="print2"
   NodePosX=200
   NodePosY=0
   FunctionReference=(MemberParent=Class'"/Script/Engine.KismetSystemLibrary"',MemberName="PrintString")
   CustomProperties Pin (PinId=A0000000000000000000000000000000,PinName="execute",PinType.PinCategory="exec",LinkedTo=(print1 B0000000000000000000000000000000))
End Object

f:id:tasuya:20211215001642p:plain

実行ピンを相互指定した状態

・カスタムイベント

最後にカスタムイベントの作り方です。
ClassをK2Node_CustomEventにしてCustomFunctionNameにイベント名をつけると作成できます。
ピンは出力ピンでdelegate(右上の赤四角)とexec(実行ピン)は最低限入れておくようにしてください。
delegateを省略するとエディタが落ちました。

引数が必要な場合はCustomPropertiesにUserDefinedPinとしてピンを追加してやると設定できます。
引数のタイプはPinType.PinCategoryではなくPinType=(PinCategory="")になります。

Begin Object Class=/Script/BlueprintGraph.K2Node_CustomEvent Name="TestEvent"
   CustomFunctionName="TestCustomEvent"
   CustomProperties Pin (Direction="EGPD_Output",PinType.PinCategory="delegate")
   CustomProperties Pin (Direction="EGPD_Output",PinType.PinCategory="exec")
   CustomProperties UserDefinedPin (PinName="floatParam",PinType=(PinCategory="float"),DesiredPinDirection=EGPD_Output)
End Object

f:id:tasuya:20211215005206p:plain

ここまでやれば基本的な部分は作れるかなあ、というところだと思います。


・最後に

あとはID生成のソース周りもちょっと調べてたんですが、
それはまたの機会に書ければ。

調べ始めたときは「何をやっているんだろう…」と思わなかったか、
と言えば、思った!と強く言い切れますが
実際のところ調べていくうちに「ホンット良く出来てんなあ」と。
省略がすごい出来たので調べるのも簡単でしたし。

BPをテキストで編集する機会自体はあまりないかとは思いますが、
自作スクリプトをノードベースで作る際等の参考としては
この辺りを追いかけるのも良いのかなと。

良く出来てますホント。

【UE4】インラインアセンブラを使えるかどうかについて

この記事はUnrealEngine 4(UE4)AdventCalender 2020の17日目の投稿記事です。 

qiita.com

 

箸休め記事のつもりで書いています。

 題材は他に「BPはテキストエディタを介してコピペできるのだから

頑張ってソースからBPにするコンバータ作れないかチャレンジ」と

悩みましたが「C++にしたほうが早い」と思ったのでこっちにしました。

 

 さて、UE4で開発をする際に

「昔のゲームロジックやソースの一部分だけでも有効活用したい」

ということがあるかと思いますが

何かの拍子でそれがアセンブラだったらどうしよう、といった場合を想定して

インラインアセンブラを動かす調査をしてみました。

 

結論

先に書いておきます。

  • C++ソースにインラインアセンブラ直埋めは出来ない(出来るならすみません調査漏れです)
  • どうしてもやりたいなら外部ライブラリに入れる
  • 別の方法で解決したほうが多分いい

となります。

ここから先は上記結論を出すためにいろいろした内容の共有ですので、

UE4インラインアセンブラを触らなければならなくなりそうなときは

「避けた方がいいっすよ」という材料としていただければ。

 

前提

実際問題、アセンブラのソースを移植する必要が出てきた場合は

書き方の違いや特にMC68000系だと逆、ということがありますし

今時のコンパイラを超える速度を手書きは現実味がなく、むしろ遅くなる可能性と

描画周りはそのままはまず無理なので作り直すかと思います。

 

ですので、ロジック部分だけの移植になりますがそれでも

  • エミュレーションさせる(ちゃんと作ればちゃんとそのまま動くはず)
  • ソースを読み取って書き直す(オーバーヘッドをつぶしながら進められるので処理速度が確保できる)
  • ソースをコンバートする(ベースを有効活用したまま拡張性がある)

あたりが第一候補で、

インラインアセンブラでなんとかする、は選択肢としては

あまり出ないと思うのですが、それはそれ、というお話です。

 

UE4C++ソースにインラインアセンブラを入れられるのか

まず大前提としてUE4C++ソースにインラインアセンブラ

埋め込んで走ってくれるのか、という部分ですが、うまく動いてくれませんでした。

(もし動く方法あれば教えていただけると嬉しいです)

 

以下、調査結果。

まず、VisualStudioでx64でビルドしようとした場合、

インラインアセンブラは使えません。

なので、__asmを使用することは諦めてx64用のアセンブラ対応にします。

 

ちなみにWin32にしてビルドするとどうなるのかなあとしてみた結果、

ソース無視されてビルド通りました。

f:id:tasuya:20201216021145p:plain

どうみてもダメなaがいるのにビルドが通るの図

 

 ということで、ごにょごにょ頑張るよりはもうこの時点で

素直にx64用のインラインアセンブラ対応に切り替えます。

対応方法は以下を参考にすると良さそうです。

docs.microsoft.com

 

 ソリューションエクスプローラーでプロジェクトを選択して、
メニュー>プロジェクト>ビルドのカスタマイズを開いて、masmをオンに。

f:id:tasuya:20201216191904p:plain

ちなみにここでチェックを入れてもGenerateVisualStudioProjectFileをすると

プロジェクトファイル作り直されるのでリセットされます。

 

次に新しい項目の追加からC++ファイルを選択して

拡張子をtest.asmのように拡張子をasm指定して作成。 

f:id:tasuya:20201216021857p:plain

masmをオンにしてから作成すると自動で項目の種類が変更されます

で、あとは.asmをがりがり書いていきましょう。

今回は通すことが目的なのでシンプルに引き算です。 

.code
public asmtest
asmtest proc
sub ecx,edx
mov eax,ecx
ret
asmtest endp
end

 

呼び出し側は、適当にUObjectとかUActorでクラスを作成して

extern "C" int asmtest(int a, int b);

を追加、asmtest(3,2,);とかをどこかで呼び出してやると1が返るはずという想定です。

 

ということで、この状態でインラインアセンブラが準備できたのでビルドします。

すると、関数未解決エラーが出ます。

 asmファイルに適当にaとかを入れてビルドしてもそこで引っかからずに

同じ結果になるので

asmファイルをそもそも解決してくれないようです。

 

asmファイルを組み込むようにごにょごにょ出来ないか

解決方法を探したんですがすぐには見つけきれなかったので、

可能性は残りますが、これは一旦直接呼べないという形で次へ行きます。

 

外部ライブラリで呼ぶ

ビルドが通らないなら通ったものを渡せばいい、ということで

次に外部ライブラリでインラインアセンブラを埋め込んで

それを呼び出すとどうなるか、を試してみました。

 

外部ライブラリの作成方法はまめおさんの昨年のアドカレ記事が詳しいです。
papersloth.hatenablog.com

 

この手順で外部ライブラリを作成します。

今回は、静的ライブラリで試しましたので

  1. 静的ライブラリの手順でプロジェクトを準備
  2. メニュー>プロジェクト>ビルドのカスタマイズから、masmをオン
  3. 新しい項目の追加からC++ファイルを選択してAdCalAsmTest.asmを作成し、内容を上記asmtest命令に
  4. ヘッダーとソース準備
// header.h
#pragma once
#include <sdkddkver.h>
#define WIN32_LERN_AND_MEAN
namespace AdCalLib
{
 class AsmLib
 {
  public:
  AsmLib();
  virtual ~AsmLib();
  int CallAsmTest(int a, int b);
 };
}
//Source.cpp
#include "Header.h"
extern "C"  int asmtest(int a, int b);\\Asmコード
using namespace AdCalLib;

AsmLib::AsmLib(){}
AsmLib::~AsmLib(){}

int AsmLib::CallAsmTest(int a, int b)
{
 return asmtest(a , b);
}

で、ビルドするとインラインアセンブラの入ったLibが作成できます。

 

そして出来たlibと作成したhファイルをUE4プロジェクトに入れてC++

呼び出せるようにします。

今回は適当にActorを作ってBeginPlayに入れました。

#include "AsmTestActor.h"
#if PLATFORM_WINDOWS #include "Header.h" #include <memory> #endif AAsmTestActor::AAsmTestActor() {  PrimaryActorTick.bCanEverTick = true; } void AAsmTestActor::BeginPlay() {  Super::BeginPlay(); #if PLATFORM_WINDOWS  int a = 3, b = 2;  const auto asmLib(std::make_shared<AdCalLib::AsmLib>());  UE_LOG(LogTemp, Log, TEXT("AsmTest : %d - %d = %d"), a,b,asmLib->CallAsmTest(a,b)); #endif }

asmtestの呼び出しはライブラリ側で行っているので

普通に外部ライブラリ命令を呼び出す形でOKです。

 

で、これをビルドして、BPの親にしてレベルに配置して呼び出してやると、

 

f:id:tasuya:20201216192423p:plain

うごいた


ということで、

asmファイルを入れた外部ライブラリでは、ひとまず動く

という形で今回は決着としたいです。

 

まとめ

速度面や調整面を考えず実際呼び出せるかどうか、

という点だけをみれば出来るのは出来ます

今回ベンチマーク的なものとかは取れていないですが、

どうしても処理が細かく大量に呼ばれる作りにはなってしまうので、

どうしても使う必要がある時だけにとどめるのがよさそうです。

 

直接UE4のC++への埋め込みは何か見落としてそうな気がすごいするので

引き続き調査は続けます。あと「直接BPにコンバートチャレンジ」も。

 

それでは皆様良い年末年始になりますよう。 

自己紹介(開発実績等)と、このブログについて

こんにちは。見ていただきありがとうございます。

はじめに自己紹介をさせてください。

私はプログラミングオフィスたすや、という屋号で

名古屋を拠点として、ゲーム開発・VR開発・遊技機開発を主に行なっているフリーランスプログラマです。

このブログは、UE4などでいろいろ試したり遊んだりした時に共有したいと思ったところの展開と、

何かしら告知できる活動があった場合に告知させていただければ、という形でやっています。

よろしくお願いいたします。

お問い合わせ等はお気軽にどうぞ。

 

——————
・プログラミングオフィスたすや 業務内容

ゲーム・VR・遊技機のプログラム開発を主にしています。
その他、ゲームやVR開発の全体的なご相談や
ゲームエンジンの導入コンサル・VRコンサルなどでも
メッセージ等でお気軽にご相談ください。

UE4やUnity等のゲームエンジン使用のほか
C/C++openGL等での非ゲームエンジン環境での開発や
アセンブラを使用した業務も可能です。

活動拠点が名古屋となりますので、勤務が必要な場合は
遠方は申し訳ありませんがしばらくは難しい状態とさせてください。
近郊であれば週1〜2程度まで、となります。

——————
・経歴
2002年より遊技機メーカー・ゲーム会社のプログラマとして活動後、
2012年2月にフリーランスのゲームエンジニアとして独立。

——————
・開発実績(一部)
--------------------------------------------------
講座:
・Udemy アンリアルエンジンで学ぶゲーム系UI・HUDデザイン - UnrealEngine4 UMG/Widget入門‐
https://www.udemy.com/uihud-unrealengine4-umgwidget/…

--------------------------------------------------
Nintendo Switchタイトル:

・L.F.O. -Lost Future Omega-
 株式会社メビウス
 (担当箇所;プログラム移植)
http://d-mebius.com/lfo/

・刑事J.B.ハロルドの事件簿 マーダー・クラブ(殺人倶楽部)
 株式会社メビウス
 (担当箇所;プログラム移植)
http://d-mebius.com/jb_murder/

--------------------------------------------------
Windows用タイトル

エルミナージュオリジナル
 株式会社メビウス
 (担当箇所;プログラムサポート)
http://d-mebius.com/elmi_original/

--------------------------------------------------
VRタイトル:

・VRRV
 株式会社DCG Entertainment
 (担当箇所;プログラム全般)
http://steamcommunity.com/sharedfiles/filedetails/…

--------------------------------------------------
iOSタイトル:
・『あの、素晴らしい  をもう一度/再装版』for iOS
・『空の浮動産/再装版』for iOS
・『ロストカラーズ/再装版』for iOS
 合資会社自転車創業
 (担当箇所;プログラム移植)

http://www.anos.jp/

--------------------------------------------------
その他
VRコンテンツ開発・コンサルティング業務
Windows・コンソールゲーム機・スマートフォン用ゲーム プログラム全般
スマートフォン・PC用AR/ネットワーク対応アプリ ネットワーク対応
・遊技機関連

Nintendo Switch版 L.F.O. -Lost Future Omega- 発売中です

少し前ですが、私が移植担当をしました
Nintendo Switch版 L.F.O. -Lost Future Omega-
が発売中です。
d-mebius.com

 

内容はシューティングゲームですが
攻撃がBGMのリズムと揃っているので、SEやエフェクト等がゲームにシンクロする形で
音と光を楽しみながら遊ぶゲームになっています。

これはもともと友人がUE4で個人制作で作られていたゲームなんですが
面白かったので、展開をお手伝いさせていただいた形です。

 

ありがたいことに、アンリアルエンジンのブログにもインタビューしていただきました。

www.unrealengine.com

 

 

ご興味ありましたら……!

 

UdemyでUE4 UMG入門講座を公開しました。

 

Udemyにて

 

アンリアルエンジンで学ぶゲーム系UI・HUDデザイン - UnrealEngine4 UMG/Widget入門‐

www.udemy.com



という講座を公開しました。

 

f:id:tasuya:20180624124420p:plain

UE4でUIやHUDを作成するためのUMG入門講座となります。

・UMGのエディタの使用方法
Widgetブループリントの使用方法・表示方法
・ボタンやスライダー等のUIで使用する頻度の高いパーツの使用方法・データ取得方法
・アニメーション作成方法
・レイアウト調整方法
・応用編としてダイアログ状の表示作成
・応用編として会話シーンの画面パーツ作成

を行っていく内容となります。

ご興味ありましたらよろしくお願いいたします。
お役に立てると幸いです……!

 

補足ではないですが、このブログでUMG関係も色々できればいいなーと。

ProjectileMovementのホーミング使用時の注意点

ProjectileMovement、つかってますか?

簡単に弾が出せますし、

ホーミングもターゲットをちょちょいとつけるだけでやってくれて

非常に便利だなあと思ってたんですが

そのホーミングにちょっと罠がありました。

 

ホーミング

例えば敵の弾をプレーヤーに向けてホーミングしたい場合、

弾にProjectileMovementをつけてホーミングを有効にします。

 

で、その後BPでターゲットにプレーヤーをつけます。

f:id:tasuya:20180320155317p:plain

こんな感じ。

 

で、プレイすると良い感じにホーミングしてくれるんですが、

この形でホーミングをつけた敵の弾をDestroyすると、

内部的にホーミングが残ってしまうようです。

 

何がおきるか

残った場合、何がおきるかというと、

・プレーヤーが移動すると処理落ち

・プレーヤーを止めてしばらく待つと速度が戻る

という中々嫌な感じの影響が出ます。

Destroyしているので画面上に敵の弾はありませんが

プレーヤーが移動するとそこに向けてホーミングされるので処理落ち、

しばらく止まっていると直線に戻るので速度が戻る、ということだと思います。

 

原因

なーんでー?と思いながら調べてもわからず、ふと休憩した時に判明しました。

f:id:tasuya:20180320155318p:plain

HomingTargetComponentは弱参照じゃないと駄目みたいです。

カーソルのせた時のコメントに助けられました……

 

対処としては、

WeakObjectを渡してしまえば解決するので

WeakObjectが取れるLineTrace系を利用すればよさそうです。

f:id:tasuya:20180320155323p:plain

(すみません画像ミスありです。前後なのでForwardVectorに対して*10と*-10です)

対象の前後を取得してLineTraceForObjects等で確実に当てる形が

楽でいいかなと思いますが対象とLineTraceの間に何かが挟まってしまった場合

上手くいかないので、ObjectTypeを絞る等が必要です。

 

ちなみに普通の参照のまま対応できないかなーと

Destroy直前にTargetComponentをかえたり、ホーミングをオフにしたりと、

そもそもそれで対応できるなら多分Destroyでも解放されるよな、と

思いながら試した結果、特に変化はありませんでした。

素直にWeakObjectを入れてください。

 

 

問題になりにくい場合

参照が問題になるので、対象(この場合はプレーヤー)がDestroyされると

おそらく解放されます。

 

なので、

・レベル切り替えやプレーヤーのDestroyが適度にされる

・ホーミングの生成数がそもそもあまり多くない

といった場合は、問題が表面化まではいかないのかなと思います。

内部的には、たまってますが。

 

・「自分の攻撃」がホーミングで、あたった敵がDestroyされる

・ホーミングをDestroyしない

等は問題は発生しないと思いますので、

弾をプールする、等でも大丈夫かなと。 

 

WeakObject

プログラマUE4を使っている方にはこれ結構説明が難しいと思うんですが

誤解を招く覚悟で言うと

いつメモリを解放するかの話で

・Weakは使わなくなったらメモリ解放されやすい

・してほしくない場合も多々あるのでわかれてる

くらいで今日のところはお願いします。

 

WeakObjectがBP上で使われている箇所があるとわかった以上、

別の箇所でも処理がたまっていそうな挙動があった場合は

これを疑ってみてもいいのかなと。