#はじめに
あけましておめでとうございます!
2021年初のUE4記事を投稿しようと思って日付が変わった瞬間に投稿しましたが、果たして初記事になれたのでしょうか...w
それはさておき、今回はプログラムからエディタ上での入力を行う方法をご紹介したいと思います。
どういうことかというと、コピペする際にCtrl+Cを押したりすると思いますが、その入力をプログラムから行います。絶妙に使い道が無さそうな内容ですが、お雑煮でも啜りながら読んでいただければと思います。
最近ちまちま作っていたグラフエディタを画像ファイルに出力するプラグインがいい感じに動くようになった!#UE4 pic.twitter.com/J5ZIUjo7Ct
— Naotsun (@Naotsun_UE) December 3, 2020
こちらのプラグインでコピペをプログラムからする必要があったため、こちらのプラグインのコードで説明していきます。
#つくりかた
まず、今回の主役であるFSlateApplication
を使用するためにSlate
モジュールとApplicationCore
モジュールを追加します。
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"UMG",
"Slate", // これ
"SlateCore",
"RenderCore",
"UnrealEd",
"GraphEditor",
"MainFrame",
"EditorStyle",
"ApplicationCore", // これ
"DesktopPlatform",
"ImageWriteQueue",
"AssetManagerEditor",
}
);
さっそく本題に入り、入力を呼び出す関数を見ていきましょう。
GraphPrinterCore.cpp
bool FGraphPrinterCore::ExportGraphToPngFile(const FString& FilePath, TSharedPtr<SGraphEditor> GraphEditor)
{
if (!GraphEditor.IsValid())
{
return false;
}
FPngTextChunkWriter Writer(FilePath);
// Make a shortcut key event for copy operation.
FKeyEvent KeyEvent;
if (!GetKeyEventFromUICommandInfo(FGenericCommands::Get().Copy, KeyEvent))
{
return false;
}
// Since the clipboard is used, the current data is temporarily saved.
FString CurrentClipboard;
FPlatformApplicationMisc::ClipboardPaste(CurrentClipboard);
// Get information about the selected node via the clipboard.
bool bWasSucceedKeyDown = FSlateApplication::Get().ProcessKeyDownEvent(KeyEvent);
FString ExportedText;
FPlatformApplicationMisc::ClipboardPaste(ExportedText);
// Restore the saved clipboard data.
FPlatformApplicationMisc::ClipboardCopy(*CurrentClipboard);
if (!bWasSucceedKeyDown || !FEdGraphUtilities::CanImportNodesFromText(GraphEditor->GetCurrentGraph(), ExportedText))
{
return false;
}
return Writer.WriteTextChunk(GraphPrinterCoreDefine::PngTextChunkKey, ExportedText);
}
入力の呼び出しにはFSlateApplication::ProcessKeyDownEvent
関数を使用します。引数ではFKeyEvent
で入力するキーなどの情報を受け渡します。
こちらの関数ではGetKeyEventFromUICommandInfo
関数でエディタのコピーのショートカットキーの情報のFKeyEvent
を作成しクリップボードを経由して現在開いているグラフエディタのテキスト情報を取得しています。
コメントにも書いてありますが、クリップボードを経由するためクリップボードの内容を保管して、作業後に元に戻しています。
続いてGetKeyEventFromUICommandInfo
関数の中身でFKeyEvent
をどうやって作成するのかを見ていきましょう。
今回のようにエディタのショートカットキーを使いたい場合など、この方法でやるのが良いかと思います。
GraphPrinterCore.cpp
bool FGraphPrinterCore::GetKeyEventFromUICommandInfo(const TSharedPtr<FUICommandInfo>& UICommandInfo, FKeyEvent& OutKeyEvent)
{
if (!UICommandInfo.IsValid())
{
return false;
}
const TSharedRef<const FInputChord>& Chord = UICommandInfo->GetFirstValidChord();
FModifierKeysState ModifierKeys(
Chord->bShift, Chord->bShift,
Chord->bCtrl, Chord->bCtrl,
Chord->bAlt, Chord->bAlt,
Chord->bCmd, Chord->bCmd,
false
);
const uint32* CharacterCodePtr;
const uint32* KeyCodePtr;
FInputKeyManager::Get().GetCodesFromKey(Chord->Key, CharacterCodePtr, KeyCodePtr);
uint32 CharacterCode = (CharacterCodePtr != nullptr ? *CharacterCodePtr : 0);
uint32 KeyCode = (KeyCodePtr != nullptr ? *KeyCodePtr : 0);
FKeyEvent KeyEvent(Chord->Key, ModifierKeys, FSlateApplication::Get().GetUserIndexForKeyboard(), false, CharacterCode, KeyCode);
OutKeyEvent = KeyEvent;
return true;
}
FKeyEvent
のコンストラクタは以下のようになっていて、それぞれの引数が以下のようになっています。
FKeyEvent
(
const FKey InKey,
const FModifierKeysState & InModifierKeys,
const uint32 InUserIndex,
const bool bInIsRepeat,
const uint32 InCharacterCode,
const uint32 InKeyCode
)
InKey
はBPでもよく見かけるFKey
型で何のキーかを指定します。
InModifierKeys
はFModifierKeysState
でモディファイアキー、つまりSHIFTキー、CTRLキー、ALTキー、CMDキーの状態を指定します。
InUserIndex
はGetPlayerController
などでよく見るPlayerIndex
と同じようなものだと思います。
bInIsRepeat
は名前の通りTrueの場合自動繰り返し入力になります。
InCharacterCode
はInKey
で指定したキーの文字コードを指定します。
InKeyCode
はInKey
で指定したキーのキーコードを指定します。
GetKeyEventFromUICommandInfo
関数ではまず、FUICommandInfo
から有効な入力情報をFInputChord
型で取得します。
FUICommandInfo::GetFirstValidChord
関数を使用すると、ショートカットキーのプライマリとセカンダリから有効な方を取得できます。
続いて、FInputChord
からそれぞれのモディファイアキーの状態を取得してFModifierKeysState
の変数を用意します。
さらに続いて、FInputKeyManager::GetCodesFromKey
関数を使ってFKey
で指定したキーの文字コードとキーコードを取得します。取得する際にuint32のポインタでの受け取りとなる点に注意してください。
これで素材は揃ったので、FKeyEvent
を作成して戻り値としています。
InUserIndex
の指定にFSlateApplication::GetUserIndexForKeyboard
関数を指定していますが、プログラムからの入力なので適当に0などにしてもよいかと思います。
#おわりに
タイトルではエディタ上の入力と書きましたが、今回使用したモジュールはすべてランタイムモジュールなのでインゲームでも使用できるかもしれません(試していないためとりあえずエディタ上と書いておきました...)。
また、今回ご紹介したFSlateApplication::ProcessKeyDownEvent
関数ではキーの押した時のイベントを取れますが、以下の関数を使用すればマウスやキーを離した時などのイベントも呼び出せそうでした。
Source\Runtime\Slate\Public\Framework\Application\SlateApplication.h
public:
/**
* Called by the native application in response to a mouse move. Routs the event to Slate Widgets.
*
* @param InMouseEvent Mouse event
* @param bIsSynthetic True when the even is synthesized by slate.
* @return Was this event handled by the Slate application?
*/
bool ProcessMouseMoveEvent( const FPointerEvent& MouseEvent, bool bIsSynthetic = false );
/**
* Called by the native application in response to a mouse button press. Routs the event to Slate Widgets.
*
* @param PlatformWindow The platform window the event originated from, used to set focus at the platform level.
* If Invalid the Mouse event will work but there will be no effect on the platform.
* @param InMouseEvent Mouse event
* @return Was this event handled by the Slate application?
*/
bool ProcessMouseButtonDownEvent(const TSharedPtr< FGenericWindow >& PlatformWindow, const FPointerEvent& InMouseEvent);
/**
* Called by the native application in response to a mouse button release. Routs the event to Slate Widgets.
*
* @param InMouseEvent Mouse event
* @return Was this event handled by the Slate application?
*/
bool ProcessMouseButtonUpEvent( const FPointerEvent& MouseEvent );
/**
* Called by the native application in response to a mouse release. Routs the event to Slate Widgets.
*
* @param InMouseEvent Mouse event
* @return Was this event handled by the Slate application?
*/
bool ProcessMouseButtonDoubleClickEvent( const TSharedPtr< FGenericWindow >& PlatformWindow, const FPointerEvent& InMouseEvent );
/**
* Called by the native application in response to a mouse wheel spin or a touch gesture. Routs the event to Slate Widgets.
*
* @param InWheelEvent Mouse wheel event details
* @param InGestureEvent Optional gesture event details
* @return Was this event handled by the Slate application?
/
bool ProcessMouseWheelOrGestureEvent( const FPointerEvent& InWheelEvent, const FPointerEvent InGestureEvent );
/**
* Called when a character is entered
*
* @param InCharacterEvent Character event
* @return Was this event handled by the Slate application?
*/
bool ProcessKeyCharEvent( const FCharacterEvent& InCharacterEvent );
/**
* Called when a key is pressed
*
* @param InKeyEvent Keyb event
* @return Was this event handled by the Slate application?
*/
bool ProcessKeyDownEvent( const FKeyEvent& InKeyEvent );
/**
* Called when a key is released
*
* @param InKeyEvent Key event
* @return Was this event handled by the Slate application?
*/
bool ProcessKeyUpEvent( const FKeyEvent& InKeyEvent );
/**
* Called when a analog input values change
*
* @param InAnalogInputEvent Analog input event
* @return Was this event handled by the Slate application?
*/
bool ProcessAnalogInputEvent(const FAnalogInputEvent& InAnalogInputEvent);
/**
* Called when a drag from an external (non-slate) source enters a window
*
* @param WindowEntered The window that was entered by the drag and drop
* @param DragDropEvent Describes the mouse state (position, pressed buttons, etc) and associated payload
* @return true if the drag enter was handled and can be processed by some widget in this window; false otherwise
*/
bool ProcessDragEnterEvent( TSharedRef WindowEntered, const FDragDropEvent& DragDropEvent );
/**
* Called when a touchpad touch is started (finger down) when polling game device state
*
* @param ControllerEvent The touch event generated
*/
void ProcessTouchStartedEvent( const TSharedPtr< FGenericWindow >& PlatformWindow, const FPointerEvent& InTouchEvent );
/**
* Called when a touchpad touch is moved (finger moved) when polling game device state
*
* @param ControllerEvent The touch event generated
*/
void ProcessTouchMovedEvent( const FPointerEvent& InTouchEvent );
/**
* Called when a touchpad touch is ended (finger lifted) when polling game device state
*
* @param ControllerEvent The touch event generated
*/
void ProcessTouchEndedEvent( const FPointerEvent& InTouchEvent );
/**
* Called when motion is detected (controller or device) when polling game device state
*
* @param MotionEvent The motion event generated
*/
void ProcessMotionDetectedEvent( const FMotionEvent& InMotionEvent );