UEブループリントのシーケンス(Sequence)ノードとソースコード

シーケンスノードの機能

Unreal EngineのブループリントにおけるExecutionSequenceノードは、実行フローの制御において非常に重要な役割を果たします。このノードは主に2種類のステートメントを生成します:KCST_PushStateとKCST_UnconditionGotoです。

KCST_PushStateステートメントはEmitPushExecState関数によって処理され、まずEX_PushExecutionFlow命令を出力し、次に実行シーケンスノードの次のブランチのバイトコードオフセットを書き込みます。これにより、この実行フローが終了した際にEX_PopExecutionFlow命令が実行され、保存されたオフセットが取り出されて続くバイトコードが実行される仕組みになっています。

ExecutionSequenceノードの使い方

UEブループリントにおけるExecutionSequenceノードは、実行シーケンス内の異なるブランチを制御するために使用されます。以下にExecutionSequenceノードの基本的な使い方を示します:

  1. ExecutionSequenceノードの作成:UEブループリントでは、右クリックして選択リストを開き、「Sequence」を選択することでExecutionSequenceノードを作成できます。
  2. 他のノードとの接続:実行するノードをExecutionSequenceノードに接続します。必要に応じて複数のノードを追加し、実行順序に従ってExecutionSequenceノードに接続します。
  3. 実行条件の設定:ExecutionSequenceノードは条件に基づいて異なるブランチを実行できます。Conditionノードやその他の条件判断ノードを追加して実行条件を設定できます。
  4. 実行フローの制御:ExecutionSequenceノードは接続されたノードの順序に従って実行します。条件判断ノードに遭遇した場合、条件の結果に基づいて異なるブランチを選択して実行します。
  5. KCST_PushStateとKCST_UnconditionGotoの使用:ExecutionSequenceノードは主にKCST_PushStateとKCST_UnconditionGotoの2つのStatementを生成します。これらのStatementは実行フローのジャンプと状態管理に使用されます。

シーケンスの使用シナリオ

Unreal Engine(UE)のブループリントにおけるシーケンスノード(ExecutionSequenceノードなど)の使用シナリオは非常に多岐にわたります。これらのノードは主にゲームロジックのフローを制御し、各イベントやアクションが予定された順序で実行されることを保証するために使用されます。以下に一般的なUEブループリントシーケンスノードの使用シナリオをいくつか示します:

  1. 初期化プロセス:ゲームオブジェクトの初期化プロセスでは、シーケンスノードを使用して初期化プロセスを整理および管理できます。例えば、キャラクター作成時に、まずキャラクターモデルをロードし、次にキャラクターの初期状態を設定し、最後にキャラクターにスキルや装備を追加するといった流れを制御できます。
  2. タスクとイベントのトリガー:ゲームタスクやイベントシステムでは、シーケンスノードを使用してタスクやイベントの実行フローを定義できます。例えば、タスク実行プロセス中に、シーケンスノードを使用してタスク目標の完了順序を制御したり、タスク完了後に特定の報酬やイベントをトリガーしたりできます。
  3. 状態管理:シーケンスノードはゲームオブジェクトの状態管理を実装するために使用できます。異なる状態遷移ロジックを作成することで、ゲームの様々な要件を満たすために異なる状態間を切り替えることができます。例えば、キャラクターコントロールシステムでは、シーケンスノードを使用してキャラクターの異なる戦闘状態や移動状態などを管理できます。
  4. アニメーションとサウンドの同期:ゲームアニメーションとサウンド処理において、シーケンスノードはアニメーションとサウンドの再生順序と同期を制御するために使用できます。精心设计的シーケンスノードを通じて、アニメーション、サウンドとゲームロジックを完璧に融合させ、ゲーム体験を向上させることができます。
  5. リソースのロードと解放:ゲームリソース管理において、シーケンスノードはリソースのロード、解放、更新プロセスを制御するために使用できます。リソースのロードと解放プロセスを合理的に整理することで、ゲームのパフォーマンスと応答速度を向上させることができます。

実装原理

  • 入力出力ピンの作成
void UK2Node_ExecutionSequence::AllocateDefaultPins()
{
	CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
	CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, GetPinNameGivenIndex(0));
	CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, GetPinNameGivenIndex(1));
}
  • FKCHandler_ExecutionSequence.RegisterNetsを呼び出して関数ピンを登録
  • Compileを呼び出してStatementを作成
for (int32 i = OutputPins.Num() - 1; i > 0; i--)
{
	FBlueprintCompiledStatement& PushExecutionState = Context.AppendStatementForNode(Node);
	PushExecutionState.Type = KCST_PushState;
	Context.GotoFixupRequestMap.Add(&PushExecutionState, OutputPins[i]);
}
// 最初のピンに即座にジャンプ
UEdGraphNode* NextNode = OutputPins[0]->LinkedTo[0]->GetOwningNode();
FBlueprintCompiledStatement& NextExecutionState = Context.AppendStatementForNode(Node);
NextExecutionState.Type = KCST_UnconditionalGoto;
Context.GotoFixupRequestMap.Add(&NextExecutionState, OutputPins[0]);

関連ソースコード

ソースコードファイル: K2Node_ExecutionSequence.h K2Node_ExecutionSequence.cpp 関連クラス: FKCHandler_ExecutionSequence K2Node_ExecutionSequence

class FSequenceHandler : public FNodeProcessingHandler
{
public:
	FSequenceHandler(FKismetCompilerContext& InCompilerContext)
		: FNodeProcessingHandler(InCompilerContext)
	{
	}

	virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override
	{
		// このブロックに対して入力ピンが接続され、有効であることを確認
		FEdGraphPinType ExpectedPinType;
		ExpectedPinType.PinCategory = UEdGraphSchema_K2::PC_Exec;

		UEdGraphPin* ExecTriggeringPin = Context.FindRequiredPinByName(Node, UEdGraphSchema_K2::PN_Execute, EGPD_Input);
		if ((ExecTriggeringPin == nullptr) || !Context.ValidatePinType(ExecTriggeringPin, ExpectedPinType))
		{
			CompilerContext.MessageLog.Error(*LOCTEXT("NoValidExecutionPinForExecSeq_Error", "@@ must have a valid execution pin @@").ToString(), Node, ExecTriggeringPin);
			return;
		}
		else if (ExecTriggeringPin->LinkedTo.Num() == 0)
		{
			CompilerContext.MessageLog.Warning(*LOCTEXT("NodeNeverExecuted_Warning", "@@ will never be executed").ToString(), Node);
			return;
		}

		// 有効で接続されている出力ピンを見つけ、処理リストに追加
		TArray<UEdGraphPin*> OutputPins;
		for (UEdGraphPin* CurrentPin : Node->Pins)
		{
			if ((CurrentPin->Direction == EGPD_Output) && (CurrentPin->LinkedTo.Num() > 0) && (CurrentPin->PinName.ToString().StartsWith(UEdGraphSchema_K2::PN_Then.ToString())))
			{
				OutputPins.Add(CurrentPin);
			}
		}

		//@TODO: ピンをピン名に追加された番号でソート!

		// ピンを処理(有効なエントリがある場合)
		if (OutputPins.Num() > 0)
		{
			if (Context.IsDebuggingOrInstrumentationRequired() && (OutputPins.Num() > 1))
			{
				const FString NodeComment = Node->NodeComment.IsEmpty() ? Node->GetName() : Node->NodeComment;

				// シーケンスXがA、B、Cに進むと仮定すると、以下を出力:
				//   X: push X1
				//      goto A
				//  X1: デバッグサイト
				//      push X2
				//      goto B
				//  X2: デバッグサイト
				//      goto C

				// 次のパスで修正が必要なプッシュステートメント(例:X1の場所がわかる前にX1をプッシュ)
				FBlueprintCompiledStatement* LastPushStatement = NULL;

				for (int32 i = 0; i < OutputPins.Num(); ++i)
				{
					// 最初のステップでない場合は、デバッグサイトを出力し、前のジャンプを修正
					const bool bNotFirstIndex = i > 0;
					if (bNotFirstIndex)
					{
						// デバッグサイトを出力
						FBlueprintCompiledStatement& DebugSiteAndJumpTarget = Context.AppendStatementForNode(Node);
						DebugSiteAndJumpTarget.Type = Context.GetBreakpointType();
						DebugSiteAndJumpTarget.Comment = NodeComment;
						DebugSiteAndJumpTarget.bIsJumpTarget = true;

						// 前のプッシュジャンプターゲットを修正
						check(LastPushStatement);
						LastPushStatement->TargetLabel = &DebugSiteAndJumpTarget;
					}

					// 次のステップに進むためのプッシュを出力(最後のステップでない場合、またはインストゥルメントビルドの場合)
					const bool bNotLastIndex = ((i + 1) < OutputPins.Num());
					if (bNotLastIndex)
					{
						FBlueprintCompiledStatement& PushExecutionState = Context.AppendStatementForNode(Node);
						PushExecutionState.Type = KCST_PushState;
						LastPushStatement = &PushExecutionState;
					}

					// 実際の状態へのジャンプを出力
					FBlueprintCompiledStatement& GotoSequenceLinkedState = Context.AppendStatementForNode(Node);
					GotoSequenceLinkedState.Type = KCST_UnconditionalGoto;
					Context.GotoFixupRequestMap.Add(&GotoSequenceLinkedState, OutputPins[i]);
				}

				check(LastPushStatement);
			}
			else
			{
				// 残りのブランチを実行するためのプッシュを直接出力
				for (int32 i = OutputPins.Num() - 1; i > 0; i--)
				{
					FBlueprintCompiledStatement& PushExecutionState = Context.AppendStatementForNode(Node);
					PushExecutionState.Type = KCST_PushState;
					Context.GotoFixupRequestMap.Add(&PushExecutionState, OutputPins[i]);
				}

				// 最初のピンに即座にジャンプ
				UEdGraphNode* NextNode = OutputPins[0]->LinkedTo[0]->GetOwningNode();
				FBlueprintCompiledStatement& NextExecutionState = Context.AppendStatementForNode(Node);
				NextExecutionState.Type = KCST_UnconditionalGoto;
				Context.GotoFixupRequestMap.Add(&NextExecutionState, OutputPins[0]);
			}
		}
		else
		{
			FBlueprintCompiledStatement& NextExecutionState = Context.AppendStatementForNode(Node);
			NextExecutionState.Type = KCST_EndOfThread;
		}
	}
};

UK2Node_ExecutionSequence::UK2Node_ExecutionSequence(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
}

void UK2Node_ExecutionSequence::AllocateDefaultPins()
{
	CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);

	// 2つのデフォルトピンを追加
	CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, GetPinNameGivenIndex(0));
	CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, GetPinNameGivenIndex(1));

	Super::AllocateDefaultPins();
}

FText UK2Node_ExecutionSequence::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	return NSLOCTEXT("K2Node", "Sequence", "シーケンス");
}

FSlateIcon UK2Node_ExecutionSequence::GetIconAndTint(FLinearColor& OutColor) const
{
	static FSlateIcon Icon("EditorStyle", "GraphEditor.Sequence_16x");
	return Icon;
}

FLinearColor UK2Node_ExecutionSequence::GetNodeTitleColor() const
{
	return FLinearColor::White;
}

FText UK2Node_ExecutionSequence::GetTooltipText() const
{
	return NSLOCTEXT("K2Node", "ExecutePinInOrder_Tooltip", "一連のピンを順番に実行します");
}

FName UK2Node_ExecutionSequence::GetUniquePinName()
{
	FName NewPinName;
	int32 i = 0;
	while (true)
	{
		NewPinName = GetPinNameGivenIndex(i++);
		if (!FindPin(NewPinName))
		{
			break;
		}
	}

	return NewPinName;
}

void UK2Node_ExecutionSequence::AddInputPin()
{
	Modify();
	CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, GetUniquePinName());
}

void UK2Node_ExecutionSequence::InsertPinIntoExecutionNode(UEdGraphPin* PinToInsertBefore, EPinInsertPosition Position)
{
	Modify();

	int32 DesiredPinIndex = Pins.Find(PinToInsertBefore);
	if (DesiredPinIndex != INDEX_NONE)
	{
		if (Position == EPinInsertPosition::After)
		{
			DesiredPinIndex = DesiredPinIndex + 1;
		}

		FCreatePinParams Params;
		Params.Index = DesiredPinIndex;
		CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, GetUniquePinName(), Params);

		// ピンリストの名前を更新:
		int32 ThenIndex = 0;
		for (int32 Idx = 0; Idx < Pins.Num(); ++Idx)
		{
			UEdGraphPin* PotentialPin = Pins[Idx];
			if (UEdGraphSchema_K2::IsExecPin(*PotentialPin) && (PotentialPin->Direction == EGPD_Output))
			{
				PotentialPin->PinName = GetPinNameGivenIndex(ThenIndex);
				++ThenIndex;
			}
		}
	}
}

void UK2Node_ExecutionSequence::RemovePinFromExecutionNode(UEdGraphPin* TargetPin) 
{
	UK2Node_ExecutionSequence* OwningSeq = Cast<UK2Node_ExecutionSequence>( TargetPin->GetOwningNode() );
	if (OwningSeq)
	{
		OwningSeq->Pins.Remove(TargetPin);
		TargetPin->MarkPendingKill();

		// ピンを再番号付けして番号をコンパクトにする
		int32 ThenIndex = 0;
		for (int32 i = 0; i < OwningSeq->Pins.Num(); ++i)
		{
			UEdGraphPin* PotentialPin = OwningSeq->Pins[i];
			if (UEdGraphSchema_K2::IsExecPin(*PotentialPin) && (PotentialPin->Direction == EGPD_Output))
			{
				PotentialPin->PinName = GetPinNameGivenIndex(ThenIndex);
				++ThenIndex;
			}
		}
	}
}

bool UK2Node_ExecutionSequence::CanRemoveExecutionPin() const
{
	int32 NumOutPins = 0;

	for (int32 i = 0; i < Pins.Num(); ++i)
	{
		UEdGraphPin* PotentialPin = Pins[i];
		if (UEdGraphSchema_K2::IsExecPin(*PotentialPin) && (PotentialPin->Direction == EGPD_Output))
		{
			NumOutPins++;
		}
	}

	return (NumOutPins > 2);
}

FName UK2Node_ExecutionSequence::GetPinNameGivenIndex(int32 Index) const
{
	return *FString::Printf(TEXT("%s_%d"), *UEdGraphSchema_K2::PN_Then.ToString(), Index);
}

void UK2Node_ExecutionSequence::ReallocatePinsDuringReconstruction(TArray<UEdGraphPin*>& OldPins)
{
	Super::AllocateDefaultPins();

	// 実行入力ピンを作成
	CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);

	// 各古い実行出力ピンに対して新しいピンを作成し、両方の側で名前を一致させる
	int32 ExecOutPinCount = 0;
	for (int32 i = 0; i < OldPins.Num(); ++i)
	{
		UEdGraphPin* TestPin = OldPins[i];
		if (UEdGraphSchema_K2::IsExecPin(*TestPin) && (TestPin->Direction == EGPD_Output))
		{
			const FName NewPinName(GetPinNameGivenIndex(ExecOutPinCount));
			ExecOutPinCount++;

			// 古いピンと新しいピンの名前が一致することを確認
			TestPin->PinName = NewPinName;

			// 一致する新しい出力ピンを作成
			CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, NewPinName);
		}
	}
}

UEdGraphPin* UK2Node_ExecutionSequence::GetThenPinGivenIndex(const int32 Index) 
{
	return FindPin(GetPinNameGivenIndex(Index));
}

FNodeProcessingHandler* UK2Node_ExecutionSequence::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const
{
	return new FSequenceHandler(CompilerContext);
}

タグ: Unreal Engine ブループリント シーケンスノード 実行フロー ソースコード

5月19日 02:00 投稿