top of page
- Detail -
RADNODZのUnreal Engine 5用プラグインを使用してRIGDOCKSのAPIを使用し、Reaperを操作するアプリを作成します。
本プロジェクトではReaperへの接続、RIGDOCKSのAPIの実行までを行います。
- Images -
- Link -
- Youtube -
- Sources -
RadnodzDemoWidget.h
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "RadnodzDemoWidget.generated.h"
class UButton;
class UEditableTextBox;
class UTextBlock;
class URadnodzClient;
/**
* REAPER に接続し、選択中のメディアアイテムのうち 1 つ目の名前を変更するデモ画面。
*/
UCLASS()
class REAPERCONTROLLER_API URadnodzDemoWidget : public UUserWidget
{
GENERATED_BODY()
protected:
virtual void NativeConstruct() override;
virtual void NativeDestruct() override;
// ---- UMG のパーツと結びつける ----
// ここの変数名は、Widget Blueprint 側のパーツ名と完全に一致させること
UPROPERTY(meta = (BindWidget))
TObjectPtr<UEditableTextBox> IpTextBox;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UEditableTextBox> PortTextBox;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UEditableTextBox> NameTextBox;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UButton> RenameButton;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UTextBlock> StatusText;
private:
UFUNCTION()
void OnRenameClicked();
/** 未接続なら Connect する。成功したら true。失敗理由は StatusText に出す。 */
bool EnsureConnected();
void SetStatus(const FString& Message);
UPROPERTY()
TObjectPtr<URadnodzClient> Client;
};
RadnodzDemoWidget.cpp
#include "RadnodzDemoWidget.h"
#include "RadnodzClient.h" // URadnodzClient / FRigdocksError
#include "RigdocksError.h" // AZ_ErrorCode
#include "RigdocksBronze_MediaItem.h" // URigdocksBronze_MediaItem
#include "Components/Button.h"
#include "Components/EditableTextBox.h"
#include "Components/TextBlock.h"
namespace
{
// 選択中のメディアアイテムのうち 1 つ目
constexpr int FirstSelectedItemID = 0;
}
void URadnodzDemoWidget::NativeConstruct()
{
Super::NativeConstruct();
// ボタンが押されたら OnRenameClicked を呼ぶよう登録する
if (RenameButton)
{
RenameButton->OnClicked.AddDynamic(this, &URadnodzDemoWidget::OnRenameClicked);
}
}
void URadnodzDemoWidget::NativeDestruct()
{
if (Client && Client->IsConnected())
{
Client->Disconnect();
}
Super::NativeDestruct();
}
void URadnodzDemoWidget::SetStatus(const FString& Message)
{
if (StatusText)
{
StatusText->SetText(FText::FromString(Message));
}
UE_LOG(LogTemp, Log, TEXT("[RADNODZ] %s"), *Message);
}
bool URadnodzDemoWidget::EnsureConnected()
{
// クライアントは初回だけ生成し、以降は使い回す
if (!Client)
{
Client = URadnodzClient::CreateRadnodzClient();
}
if (Client->IsConnected())
{
return true;
}
if (!IpTextBox || !PortTextBox)
{
SetStatus(TEXT("IP / ポートの入力欄が見つかりません"));
return false;
}
const FString Ip = IpTextBox->GetText().ToString().TrimStartAndEnd();
const FString PortString = PortTextBox->GetText().ToString().TrimStartAndEnd();
if (Ip.IsEmpty())
{
SetStatus(TEXT("IP を入力してください"));
return false;
}
if (!PortString.IsNumeric())
{
SetStatus(TEXT("ポートは数字で入力してください"));
return false;
}
const int Port = FCString::Atoi(*PortString);
Client->Connect(Ip, Port);
// 成否は戻り値ではなく GetError() で判定する
const FRigdocksError Error = Client->GetError();
if (Error.ErrorCode != static_cast<int>(AZ_ErrorCode::Success) || !Client->IsConnected())
{
SetStatus(FString::Printf(TEXT("接続失敗 (%d): %s"), Error.ErrorCode, *Error.Message));
return false;
}
return true;
}
void URadnodzDemoWidget::OnRenameClicked()
{
// ---- 1. クライアントを用意する(初回だけ生成し、以降は使い回す)----
if (!Client)
{
Client = URadnodzClient::CreateRadnodzClient();
}
// ---- 2. 接続する ----
const FString Ip = IpTextBox->GetText().ToString();
const FString Port = PortTextBox->GetText().ToString();
if (!Client->Connect(Ip, FCString::Atoi(*Port)))
{
const FRigdocksError Error = Client->GetError();
SetStatus(FString::Printf(TEXT("接続失敗 (%d): %s"), Error.ErrorCode, *Error.Message));
return;
}
// ---- 3. 名前を設定する ----
const FString NewName = NameTextBox->GetText().ToString();
URigdocksBronze_MediaItem::AZ_SetSelectedMediaName(Client, 0, 0, NewName);
// ---- 4. 成否を確認する ----
const FRigdocksError Error = Client->GetError();
if (Error.ErrorCode != 0)
{
SetStatus(FString::Printf(TEXT("リネーム失敗 (%d): %s"), Error.ErrorCode, *Error.Message));
return;
}
SetStatus(FString::Printf(TEXT("リネーム成功: %s"), *NewName));
}RadnodzDemoFromAI.h
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "RadnodzDemoFromAI.generated.h"
class UButton;
class UEditableTextBox;
class UTextBlock;
class UVerticalBox;
class URadnodzClient;
/**
* REAPER に接続し、選択中のメディアアイテムのうち 1 つ目の名前を変更する画面。
*
* ウィジェットの配置も含めてすべて C++ で組み立てるので、
* Widget Blueprint を作らなくてもこのクラスだけで動く。
*
* UUserWidget* Screen = CreateWidget<URadnodzDemoFromAI>(GetWorld(), URadnodzDemoFromAI::StaticClass());
* Screen->AddToViewport();
*/
UCLASS()
class REAPERCONTROLLER_API URadnodzDemoFromAI : public UUserWidget
{
GENERATED_BODY()
protected:
/** ここで画面の中身(ウィジェットツリー)を組み立てる。 */
virtual TSharedRef<SWidget> RebuildWidget() override;
virtual void NativeConstruct() override;
virtual void NativeDestruct() override;
private:
// ---- 画面を組み立てる ----
/** ウィジェットツリーを一度だけ作る。 */
void BuildLayout();
/** 「ラベル + 入力欄」の 1 行を作って Parent に足し、作った入力欄を返す。 */
UEditableTextBox* AddLabeledTextBox(
UVerticalBox* Parent,
const FName& WidgetName,
const FString& Label,
const FString& Hint,
const FString& InitialText);
// ---- 画面のパーツ(BuildLayout で生成する)----
/** 接続先 IP (例: 127.0.0.1) */
UPROPERTY()
TObjectPtr<UEditableTextBox> IpTextBox;
/** 接続先ポート */
UPROPERTY()
TObjectPtr<UEditableTextBox> PortTextBox;
/** 付け直す名前 */
UPROPERTY()
TObjectPtr<UEditableTextBox> NameTextBox;
/** 実行ボタン */
UPROPERTY()
TObjectPtr<UButton> RunButton;
/** 進捗・エラーの表示先 */
UPROPERTY()
TObjectPtr<UTextBlock> StatusText;
// ---- 処理 ----
UFUNCTION()
void OnRunClicked();
/**
* 入力欄の IP / ポートで接続を用意する。成功したら true。
* 失敗した理由は StatusText に出す。
*/
bool EnsureConnected();
/** StatusText に出しつつ、同じ内容をログにも残す。 */
void SetStatus(const FString& Message);
/** 接続クライアント。初回に生成して以降は使い回す。 */
UPROPERTY()
TObjectPtr<URadnodzClient> Client;
/** 今つながっている接続先。入力が変わったら張り直すために覚えておく。 */
FString ConnectedIp;
int ConnectedPort = 0;
};
RadnodzDemoFromAI.h
#include "RadnodzDemoFromAI.h"
#include "RadnodzClient.h" // URadnodzClient / FRigdocksError
#include "RigdocksError.h" // AZ_ErrorCode
#include "RigdocksBronze_MediaItem.h" // URigdocksBronze_MediaItem
#include "Blueprint/WidgetTree.h"
#include "Components/Border.h"
#include "Components/BorderSlot.h"
#include "Components/Button.h"
#include "Components/ButtonSlot.h"
#include "Components/EditableTextBox.h"
#include "Components/HorizontalBox.h"
#include "Components/HorizontalBoxSlot.h"
#include "Components/SizeBox.h"
#include "Components/SizeBoxSlot.h"
#include "Components/TextBlock.h"
#include "Components/VerticalBox.h"
#include "Components/VerticalBoxSlot.h"
#include "Styling/CoreStyle.h"
namespace
{
// 選択中のメディアアイテムのうち 1 つ目
constexpr int FirstSelectedItemID = 0;
// ---- 見た目の数値をここにまとめておく ----
constexpr float PanelWidth = 560.f; // 画面全体の横幅
constexpr float LabelWidth = 130.f; // ラベル列の横幅
constexpr float RowSpacing = 6.f; // 行と行のすき間
FSlateFontInfo TitleFont() { return FCoreStyle::GetDefaultFontStyle("Bold", 18); }
FSlateFontInfo BodyFont() { return FCoreStyle::GetDefaultFontStyle("Regular", 13); }
/**
* 入力欄の文字を黒にする。
* SEditableTextBox は「通常 / 入力中 / 読み取り専用」で参照する色が別々なので、全部そろえる。
*/
void ApplyBlackText(UEditableTextBox* TextBox)
{
const FSlateColor Black(FLinearColor::Black);
FEditableTextBoxStyle Style = TextBox->GetWidgetStyle();
Style.TextStyle.ColorAndOpacity = Black; // 文字そのものの色
Style.ForegroundColor = Black; // 未フォーカス時
Style.FocusedForegroundColor = Black; // 入力中
Style.ReadOnlyForegroundColor = Black; // 読み取り専用時
TextBox->SetWidgetStyle(Style);
}
/** RADNODZ の呼び出し結果を、そのまま画面に出せる文にする。 */
FString FormatError(const FRigdocksError& Error)
{
if (Error.Details.IsEmpty())
{
return FString::Printf(TEXT("(%d) %s"), Error.ErrorCode, *Error.Message);
}
return FString::Printf(TEXT("(%d) %s / %s"), Error.ErrorCode, *Error.Message, *Error.Details);
}
bool IsSuccess(const FRigdocksError& Error)
{
return Error.ErrorCode == static_cast<int>(AZ_ErrorCode::Success);
}
}
// ============================================================
// 画面の組み立て
// ============================================================
TSharedRef<SWidget> URadnodzDemoFromAI::RebuildWidget()
{
// RebuildWidget は作り直しのたびに呼ばれうるので、中身が無いときだけ組み立てる
if (WidgetTree && !WidgetTree->RootWidget)
{
BuildLayout();
}
return Super::RebuildWidget();
}
void URadnodzDemoFromAI::BuildLayout()
{
// ---- 一番外側: 画面いっぱいの背景 ----
UBorder* Background = WidgetTree->ConstructWidget<UBorder>(UBorder::StaticClass(), TEXT("Background"));
Background->SetBrushColor(FLinearColor(0.02f, 0.02f, 0.03f, 0.85f));
Background->SetPadding(FMargin(24.f));
WidgetTree->RootWidget = Background;
// ---- 中身の横幅を決めて中央に置く ----
USizeBox* Panel = WidgetTree->ConstructWidget<USizeBox>(USizeBox::StaticClass(), TEXT("Panel"));
Panel->SetWidthOverride(PanelWidth);
Background->AddChild(Panel);
if (UBorderSlot* PanelSlot = Cast<UBorderSlot>(Panel->Slot))
{
PanelSlot->SetHorizontalAlignment(HAlign_Center);
PanelSlot->SetVerticalAlignment(VAlign_Center);
}
// ---- 縦に積んでいく本体 ----
UVerticalBox* Root = WidgetTree->ConstructWidget<UVerticalBox>(UVerticalBox::StaticClass(), TEXT("RootBox"));
Panel->AddChild(Root);
// 見出し
UTextBlock* Title = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("Title"));
Title->SetText(FText::FromString(TEXT("REAPER: 選択中の 1 つ目のメディアアイテムをリネーム")));
Title->SetFont(TitleFont());
if (UVerticalBoxSlot* TitleSlot = Root->AddChildToVerticalBox(Title))
{
TitleSlot->SetPadding(FMargin(0.f, 0.f, 0.f, 16.f));
}
// 入力欄 3 つ
IpTextBox = AddLabeledTextBox(Root, TEXT("IpTextBox"), TEXT("IP"), TEXT("127.0.0.1"), TEXT("127.0.0.1"));
PortTextBox = AddLabeledTextBox(Root, TEXT("PortTextBox"), TEXT("ポート"), TEXT("例: 5000"), FString());
NameTextBox = AddLabeledTextBox(Root, TEXT("NameTextBox"), TEXT("新しい名前"), TEXT("付け直す名前"), FString());
// 実行ボタン
RunButton = WidgetTree->ConstructWidget<UButton>(UButton::StaticClass(), TEXT("RunButton"));
UTextBlock* RunLabel = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("RunLabel"));
RunLabel->SetText(FText::FromString(TEXT("実行")));
RunLabel->SetFont(BodyFont());
RunLabel->SetColorAndOpacity(FSlateColor(FLinearColor::Black));
RunButton->AddChild(RunLabel);
if (UButtonSlot* RunLabelSlot = Cast<UButtonSlot>(RunLabel->Slot))
{
RunLabelSlot->SetPadding(FMargin(24.f, 8.f));
RunLabelSlot->SetHorizontalAlignment(HAlign_Center);
RunLabelSlot->SetVerticalAlignment(VAlign_Center);
}
if (UVerticalBoxSlot* ButtonSlot = Root->AddChildToVerticalBox(RunButton))
{
ButtonSlot->SetPadding(FMargin(0.f, 16.f, 0.f, 12.f));
ButtonSlot->SetHorizontalAlignment(HAlign_Right);
}
// ここで押されたときの処理をつないでおく(BuildLayout は一度しか通らない)
RunButton->OnClicked.AddDynamic(this, &URadnodzDemoFromAI::OnRunClicked);
// 状態・エラーの表示先
StatusText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass(), TEXT("StatusText"));
StatusText->SetFont(BodyFont());
StatusText->SetAutoWrapText(true);
if (UVerticalBoxSlot* StatusSlot = Root->AddChildToVerticalBox(StatusText))
{
StatusSlot->SetHorizontalAlignment(HAlign_Fill);
}
}
UEditableTextBox* URadnodzDemoFromAI::AddLabeledTextBox(
UVerticalBox* Parent,
const FName& WidgetName,
const FString& Label,
const FString& Hint,
const FString& InitialText)
{
UHorizontalBox* Row = WidgetTree->ConstructWidget<UHorizontalBox>(UHorizontalBox::StaticClass());
// 左: ラベル。幅を固定して入力欄の左端をそろえる
UTextBlock* LabelText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass());
LabelText->SetText(FText::FromString(Label));
LabelText->SetFont(BodyFont());
USizeBox* LabelArea = WidgetTree->ConstructWidget<USizeBox>(USizeBox::StaticClass());
LabelArea->SetWidthOverride(LabelWidth);
LabelArea->AddChild(LabelText);
if (USizeBoxSlot* LabelTextSlot = Cast<USizeBoxSlot>(LabelText->Slot))
{
LabelTextSlot->SetVerticalAlignment(VAlign_Center);
}
if (UHorizontalBoxSlot* LabelSlot = Row->AddChildToHorizontalBox(LabelArea))
{
LabelSlot->SetVerticalAlignment(VAlign_Center);
}
// 右: 入力欄。残り幅いっぱいに広げる
UEditableTextBox* TextBox = WidgetTree->ConstructWidget<UEditableTextBox>(UEditableTextBox::StaticClass(), WidgetName);
ApplyBlackText(TextBox);
TextBox->SetHintText(FText::FromString(Hint));
if (!InitialText.IsEmpty())
{
TextBox->SetText(FText::FromString(InitialText));
}
if (UHorizontalBoxSlot* TextBoxSlot = Row->AddChildToHorizontalBox(TextBox))
{
TextBoxSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill));
TextBoxSlot->SetVerticalAlignment(VAlign_Center);
}
if (UVerticalBoxSlot* RowSlot = Parent->AddChildToVerticalBox(Row))
{
RowSlot->SetPadding(FMargin(0.f, RowSpacing));
RowSlot->SetHorizontalAlignment(HAlign_Fill);
}
return TextBox;
}
// ============================================================
// ライフサイクル
// ============================================================
void URadnodzDemoFromAI::NativeConstruct()
{
Super::NativeConstruct();
SetStatus(TEXT("IP / ポート / 新しい名前を入力して実行してください"));
}
void URadnodzDemoFromAI::NativeDestruct()
{
// 画面を閉じるときは必ず切っておく
if (Client && Client->IsConnected())
{
Client->Disconnect();
}
Super::NativeDestruct();
}
// ============================================================
// REAPER とのやりとり
// ============================================================
void URadnodzDemoFromAI::SetStatus(const FString& Message)
{
if (StatusText)
{
StatusText->SetText(FText::FromString(Message));
}
UE_LOG(LogTemp, Log, TEXT("[RadnodzDemoFromAI] %s"), *Message);
}
bool URadnodzDemoFromAI::EnsureConnected()
{
const FString Ip = IpTextBox->GetText().ToString().TrimStartAndEnd();
const FString PortString = PortTextBox->GetText().ToString().TrimStartAndEnd();
if (Ip.IsEmpty())
{
SetStatus(TEXT("IP を入力してください"));
return false;
}
if (!PortString.IsNumeric())
{
SetStatus(TEXT("ポートは数字で入力してください"));
return false;
}
const int Port = FCString::Atoi(*PortString);
// クライアントは初回だけ生成し、以降は使い回す
if (!Client)
{
Client = URadnodzClient::CreateRadnodzClient();
}
// 同じ相手につながったままなら、そのまま使う
if (Client->IsConnected())
{
if (ConnectedIp == Ip && ConnectedPort == Port)
{
return true;
}
// 入力が変わったので張り直す
Client->Disconnect();
}
SetStatus(FString::Printf(TEXT("接続中... %s:%d"), *Ip, Port));
Client->Connect(Ip, Port);
// 成否は戻り値ではなく GetError() で判定する
const FRigdocksError Error = Client->GetError();
if (!IsSuccess(Error) || !Client->IsConnected())
{
ConnectedIp.Reset();
ConnectedPort = 0;
SetStatus(FString::Printf(TEXT("接続失敗 %s"), *FormatError(Error)));
return false;
}
ConnectedIp = Ip;
ConnectedPort = Port;
return true;
}
void URadnodzDemoFromAI::OnRunClicked()
{
// ---- 1. 新しい名前を先に確かめる(接続前に弾けるものは弾く)----
const FString NewName = NameTextBox->GetText().ToString().TrimStartAndEnd();
if (NewName.IsEmpty())
{
SetStatus(TEXT("新しい名前を入力してください"));
return;
}
// ---- 2. 接続する ----
if (!EnsureConnected())
{
return;
}
// ---- 3. 選択中の 1 つ目のメディアアイテムをリネームする ----
// proj に nullptr を渡すと、プラグイン側が今開いているプロジェクトを使う
URigdocksBronze_MediaItem::AZ_SetSelectedMediaName(Client, nullptr, FirstSelectedItemID, NewName);
// ---- 4. 成否を確認する ----
const FRigdocksError Error = Client->GetError();
if (!IsSuccess(Error))
{
SetStatus(FString::Printf(TEXT("リネーム失敗 %s"), *FormatError(Error)));
return;
}
SetStatus(FString::Printf(TEXT("リネーム成功: %s"), *NewName));
}
bottom of page











