UE5から始める C++ & BP 14 【C++版】Flow Control(Loop)
ついに次が基本文法最後のタスクになります。
平日の朝にしか書けなくなってしまったので、進捗が一気に落ちてしまいました。
いよいよ朝活と休日にしか書けない。
朝活のいいところは時間制限があることです。
少ない時間しかないからかなり集中して書けます。
どうせ、今まで何もしてこなかったから0だったのが、1でも増えているからかなりの進捗をたたき出している。
そう考えると焦ることもないか
日々勉強!
コンスタントに勉強していればいいことあるさ。
Blueprintの内容をC++で再現します。
【C++】Flow Control(Loop)
C++でBlueprintを再現すること
Blueprintの3種類のLoopノードをC++で再現します。
For Loop
For Each Loop
While Loop
[For Loop]と[For Earch Loop]にはBreak付きのノードが用意されています。
3つのLoopで同じ結果になる処理を紹介します。
For Loop
For Each Loop
While Loop
編集するActorクラスを作成する
プロジェクトを閉じていたら、プロジェクトを開き、「Chapter_2_FlowControl_Loop」を開きます。
[Tools]メニューから[New C++ Class]を開きます。
親クラスに[Actor]を選択します。
ClassTypeとClass名を設定します。
Solution Explorerから今回編集する2つのファイルを開きます。
CPPFlowControlLoop.h
CPPFlowControlLoop.cpp
開いたファイルを学習する初期状態に修正します。
CPPFlowControlLoop.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CPPCalcType.h"
#include "CPPFlowControlLoop.generated.h"
UCLASS()
class CPP_BP_API ACPPFlowControlLoop : public AActor
{
GENERATED_BODY()
public:
ACPPFlowControlLoop();
// Event Dispatcher[OnPrintHello]
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FPrintHelloDelegate);
UPROPERTY(BlueprintAssignable, Category = "CPP_BP")
FPrintHelloDelegate OnPrintHello;
// Custom Event[PrintHello]
UFUNCTION()
void PrintHello();
int32 Sum(int32 A, int32 B);
// Action Mappingsに設定したActionを処理する関数
void PressedActionPrintCalcResult();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
private:
TArray<FString> Messages = { TEXT("C++ Hello World!"), TEXT("你好 世界!"), TEXT("Bonjour le monde!"), TEXT("Hallo Welt!"), TEXT("こんにちは世界!") };
//TArray<FString> Messages = { "C++ Hello World!", "你好 世界!", "Bonjour le monde!", "Hallo Welt!", "こんにちは世界!" };
// 計算結果を出力する関数
void PrintCalcResult(const ECPPCalcType Type, const int32 A, const int32 B, const float PrintDuration);
// PrintString関数のDurationに設定する変数
const float Duration = 10.0f;
// PrintString関数のTextColorに設定する変数
const FLinearColor TextColor = FColor(255, 255, 255);
// 計算用の変数
int32 CalcVarA = 7;
int32 CalcVarB = 3;
// Flow Control用の変数
bool IsPrintHello = false;
int32 TypeIndex = 0;
TArray<ECPPCalcType> CalcTypes = { ECPPCalcType::Add, ECPPCalcType::Subtract, ECPPCalcType::Multiply, ECPPCalcType::Divide };
// Input設定
void SetupInput();
// Input Eventを処理する関数
void PressedH();
};
CPPFlowControlLoop.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "CPPFlowControlLoop.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Kismet/GameplayStatics.h"
ACPPFlowControlLoop::ACPPFlowControlLoop()
{
// Event Dispathcer[OnPrintHello]にCustom Event[PrintHello]をバインドする
OnPrintHello.AddDynamic(this, &ACPPFlowControlLoop::PrintHello);
}
int32 ACPPFlowControlLoop::Sum(int32 A, int32 B)
{
return A + B;
}
// Called when the game starts or when spawned
void ACPPFlowControlLoop::BeginPlay()
{
Super::BeginPlay();
SetupInput();
if (IsPrintHello)
{
// Hello World!を出力する処理
PrintHello();
}
else
{
// 計算結果を出力する処理
PressedActionPrintCalcResult();
}
}
void ACPPFlowControlLoop::PrintCalcResult(const ECPPCalcType Type, const int32 A, const int32 B, const float PrintDuration)
{
switch (Type)
{
case ECPPCalcType::Add:
{
// Add(足し算)の処理
// 値渡し
int32 ResultAdd = Sum(A, B);
FString StrResultAdd = FString::Printf(TEXT("%d"), ResultAdd);
UKismetSystemLibrary::PrintString(this, StrResultAdd, true, true, FColor::Red, PrintDuration);
break;
}
case ECPPCalcType::Subtract:
{
// Subtract(引き算)の処理
int32 ResultSubtract = A - B;
FString StrResultSubtract = FString::Printf(TEXT("%d"), ResultSubtract);
UKismetSystemLibrary::PrintString(this, StrResultSubtract, true, true, FColor::Yellow, PrintDuration);
break;
}
case ECPPCalcType::Multiply:
{
// Multiply(掛け算)の処理
int32 ResultMultiply = A * B;
FString StrResultMultiply = FString::Printf(TEXT("%d"), ResultMultiply);
UKismetSystemLibrary::PrintString(this, StrResultMultiply, true, true, FColor::Green, PrintDuration);
break;
}
case ECPPCalcType::Divide:
{
// Divide(割り算)の処理(int > float)
float ResultDivide = (float)A / (float)B;
FString StrResultDivide = FString::Printf(TEXT("%f"), ResultDivide);
UKismetSystemLibrary::PrintString(this, StrResultDivide, true, true, FColor::Blue, PrintDuration);
}
}
}
void ACPPFlowControlLoop::SetupInput()
{
// 入力を有効にする
EnableInput(UGameplayStatics::GetPlayerController(GetWorld(), 0));
// HキーのPressedとReleasedをバインドする
InputComponent->BindKey(EKeys::H, IE_Pressed, this, &ACPPFlowControlLoop::PressedH);
// ActionMappingsに設定したActionをバインドする
InputComponent->BindAction("ActionPrintCalcResult", IE_Pressed, this, &ACPPFlowControlLoop::PressedActionPrintCalcResult);
}
void ACPPFlowControlLoop::PressedH()
{
// Event Dispathcer[OnPrintHello]をコールする
OnPrintHello.Broadcast();
}
void ACPPFlowControlLoop::PressedActionPrintCalcResult()
{
// 計算結果を出力する処理
PrintCalcResult(CalcTypes[TypeIndex], CalcVarA, CalcVarB, Duration);
TypeIndex++;
TypeIndex = TypeIndex % CalcTypes.Num();
}
void ACPPFlowControlLoop::PrintHello()
{
int32 randomIndex = FMath::RandRange(0, Messages.Num() - 1);
// 1行で書くなら
// Messages[FMath::RandRange(0, Messages.Num() - 1)]
// Hello World!を出力する処理
UKismetSystemLibrary::PrintString(this, Messages[randomIndex], true, true, TextColor, Duration);
}
For LoopノードでPrintStringを繰り返し呼び出す
For LoopノードでPrintStringを繰り返し呼び出した処理をC++で再現します。
C++でFor Loopノードを再現すると以下のようになります。
indexでは文字が長いので、i,j,kといった1文字を使用します。
for (int i = 0; i <= 4; i++)
{
// 繰り返し処理
}
// 完了後の処理
今回はBlueprintのノードを再現するので、分かりやすいようにBlueprintのノードの名称を使用します。
int32 FirstIndex = 0;
int32 LastIndex = 4;
// for loop
for (int32 index = FirstIndex; index <= LastIndex; index++)
{
// Loop Body
}
// Completed
Blueprintの名称と同じにすることで、C++の書き方との対応が分かりやすくなったのではないでしょうか。
「CPPFlowControlLoop.cpp」の[PrintHello]を[For Loop]ノードを再現する記述に編集します。
CPPFlowControlLoop.cpp PrintHello()
void ACPPFlowControlLoop::PrintHello()
{
int32 FirstIndex = 0;
int32 LastIndex = 4;
// for loop
for (int32 index = FirstIndex; index <= LastIndex; index++)
{
// indexの値を出力する
UKismetSystemLibrary::PrintString(this, FString::Printf(TEXT("%d"), index), true, true, TextColor, Duration);
}
// CompletedをPrintStringで出力する
UKismetSystemLibrary::PrintString(this, TEXT("Completed"), true, true, FColor::Cyan, Duration);
}
Ctrl + Sでファイルを保存し、Compileを行います。
「CPPFlowControlLoop」をViewportにDrag&Dropします。
PrintStringの出力結果が分かりづらくなるので、「BP_FlowControl_Loop」を削除します。
Level Editorの[Play]ボタンをクリックします。
Blueprintと同様に配列のIndexNoがすべて出力された後に、Completedの文字が出力されました。
For Loopノードで配列をすべて出力する
[For Loop]ノードの[Last Index]ピンに配列[Messages]のLastIndexを接続して、配列をすべて出力する処理をC++で再現します。
C++ではfor文の条件を柔軟に書き換えられます。
配列のLastIndexか配列数でfor文を続けるか条件を書きます。
// 配列のLastIndexで比較する場合
int32 LastIndex = Messages.Num() - 1;
// 配列のLastIndex以下なら(BlueprintのFor Loopノードと同じ書き方)
for (int32 index = FirstIndex; index <= LastIndex; index++)
// 配列数で比較する場合
int32 LastIndex = Messages.Num();
// 配列数より小さかったらLoopを続ける
for (int32 index = FirstIndex; index < LastIndex; index++)
// 配列数に一致していなかったらLoopを続ける
for (int32 index = FirstIndex; index != LastIndex; index++)
今回はBluprintノードを再現するので、「配列のLastIndex以下か」の条件で再現します。
ACPPFlowControlLoop.cpp PrintHello()
void ACPPFlowControlLoop::PrintHello()
{
int32 FirstIndex = 0;
int32 LastIndex = Messages.Num() - 1;
// for loop
for (int32 index = FirstIndex; index <= LastIndex; index++)
{
// Messagesの値を出力する
UKismetSystemLibrary::PrintString(this, Messages[index], true, true, TextColor, Duration);
}
// CompletedをPrintStringで出力する
UKismetSystemLibrary::PrintString(this, TEXT("Completed"), true, true, FColor::Cyan, Duration);
}
Ctrl + Sでファイルを保存し、Compileを行います。
Level Editorの[Play]ボタンをクリックします。
Blueprintと同様に配列を最初から最後まで出力できました。
For Loop with BreakノードでLoopを途中で抜ける
配列[Messages]の文字列に「Boujour」が含まれていた時に[Break]実行ピンでLoopを途中で抜けた処理をC++で再現します。
C++ではfor文内の「break」が呼び出されるとfor文を途中で抜けます。
for (int i = 0; i <= 4; i++)
{
// 繰り返し処理
if(条件)
{
//条件がTrue
break;
}
}
// 完了後の処理
文字列が含まれているか判定する「Contains」ノードはC++で以下のように書きます。
FString str = TEXT("文字列が含まれているか");
str.Contains(TEXT("文字列"); // 含まれている(true)、含まれていない(false)
Blueprintの処理を再現すると以下のような記述になります。
int32 FirstIndex = 0;
int32 LastIndex = 4;
// for loop
for (int32 index = FirstIndex; index <= LastIndex; index++)
{
// Loop Body
if(Messages[index].Contains(TEXT("文字列")))
{
// 文字列が含まれる場合
break;
}
}
// Completed
[PrintHello]に再現した処理を反映します。
CPPFlowControlLoop.cpp PrintHello()
void ACPPFlowControlLoop::PrintHello()
{
int32 FirstIndex = 0;
int32 LastIndex = Messages.Num() - 1;
// for loop
for (int32 index = FirstIndex; index <= LastIndex; index++)
{
// 文字列に"Bonjour"が含まれているか
if (Messages[index].Contains(TEXT("Bonjour")))
{
// TrueならLoopを抜ける
break;
}
// Messagesの値を出力する
UKismetSystemLibrary::PrintString(this, Messages[index], true, true, TextColor, Duration);
}
// CompletedをPrintStringで出力する
UKismetSystemLibrary::PrintString(this, TEXT("Completed"), true, true, FColor::Cyan, Duration);
}
Ctrl + Sでファイルを保存し、Compileを行います。
Level Editorの[Play]ボタンをクリックします。
[Bonjour]が含まれていない、[Break]が実行される前の文字列のみ表示されました。
continue文(C++のみ)
C++では「break」の他に「continue」を使用できます。
「continue」が呼ばれると、処理の途中で次のindexのLoopが始まります。
for (int i = 0; i <= 4; i++)
{
// 繰り返し処理
if(条件)
{
//条件がTrue
continue;
}
}
// 完了後の処理
「break」を「continue」に変更します。
CPPFlowControlLoop.cpp PrintHello()
void ACPPFlowControlLoop::PrintHello()
{
int32 FirstIndex = 0;
int32 LastIndex = Messages.Num() - 1;
// for loop
for (int32 index = FirstIndex; index <= LastIndex; index++)
{
// 文字列に"Bonjour"が含まれているか
if (Messages[index].Contains(TEXT("Bonjour")))
{
// TrueならLoopを続ける
continue;
}
// Messagesの値を出力する
UKismetSystemLibrary::PrintString(this, Messages[index], true, true, TextColor, Duration);
}
// CompletedをPrintStringで出力する
UKismetSystemLibrary::PrintString(this, TEXT("Completed"), true, true, FColor::Cyan, Duration);
}
Ctrl + Sでファイルを保存し、Compileを行います。
Level Editorの[Play]ボタンをクリックします。
[Bonjour]が含まれていた文字以外は出力されました。
Foreach Loop with Breakノードで配列をすべて出力する
[For Loop with Break]ノードから[For Each Loop with Break]ノードに変更した処理をC++で再現します。
C++で[For Each Loop]ノードを再現した書き方は以下になります。
TArray<FString> Strs;
for (FString Str : Strs)
{
// Index[0]~Index[LastIndex]までLoopする
}
// 完了後の処理
[For Each Loop]ノードを再現した処理と、「break」を追加して、[For Each Loop with Break]ノードを再現します。
CPPFlowControlLoop.cpp PrintHello()
void ACPPFlowControlLoop::PrintHello()
{
// C++版のFor Each Loop with Break
for (FString Message : Messages)
{
// 文字列に"Bonjour"が含まれているか
if (Message.Contains(TEXT("Bonjour")))
{
// TrueならLoopを続ける
break;
}
// Messagesの値を出力する
UKismetSystemLibrary::PrintString(this, Message, true, true, TextColor, Duration);
}
// CompletedをPrintStringで出力する
UKismetSystemLibrary::PrintString(this, TEXT("Completed"), true, true, FColor::Cyan, Duration);
}
Ctrl + Sでファイルを保存し、Compileを行います。
Level Editorの[Play]ボタンをクリックします。
[For Each Loop with Break]ノードの処理と同様の結果となりました。
配列を0~LastIndexまで処理するのであれば、「For Each Loop」の書き方が最適です。
While loopノード
最後に[While Loop]ノードの処理を再現します。
[While Loop]ノードはC++では以下のように書きます。
whileの条件が[false]になった時にLoopを抜けます。
[While Loop]は無限ループになりやすいので、ループを抜ける処理を必ず用意します。
while (whileの条件)
{
// 繰り返し処理
if(条件)
{
//条件がTrue
whileの条件 = false;
}
else
{
処理
}
}
// 完了後の処理
[While Loop]ノードの処理を再現するように編集します。
PPFlowControlLoop.cpp PrintHello()
void ACPPFlowControlLoop::PrintHello()
{
bool NotBonjour = true;
int32 HelloIndex = 0;
while (NotBonjour)
{
// 文字列に"Bonjour"が含まれているか
if (Messages[HelloIndex].Contains(TEXT("Bonjour")))
{
// While Loopの条件をfalseに設定する
NotBonjour = false;
}
else
{
// Messagesの値を出力する
UKismetSystemLibrary::PrintString(this, Messages[HelloIndex], true, true, TextColor, Duration);
}
// HelloIndexをインクリメント
HelloIndex++;
}
// CompletedをPrintStringで出力する
UKismetSystemLibrary::PrintString(this, TEXT("Completed"), true, true, FColor::Cyan, Duration);
}
Ctrl + Sでファイルを保存し、Compileを行います。
Level Editorの[Play]ボタンをクリックします。
[While Loop]ノードと同様の処理になりました。
すべて保存
C++側の説明は以上になります。
プロジェクトをすべて保存しましょう。
Visual StudioのSolutionもすべて保存しましょう。
まとめ
この文法執筆はサグラダ・ファミリア作るくらいまだまだ時間がかかる活動です。
少しずつでも積みあがって立派な建物になるように頑張ります!
引き続き応援よろしくお願いします。
参照URL
UE5から始めるC++&Blueprint 進捗とロードマップ
Zennで進捗報告を行い、GitHubでロードマップを公開中です。
よかったら覗いてみてください。