
UE5でC++を使用する方法 – 完全ガイド
UE5(Unreal Engine 5)でのゲーム開発において、ブループリントは強力なツールですが、大規模なプロジェクトやパフォーマンス重視の開発では、C++の使用が不可欠です。本記事では、UE5でC++を効果的に使用するための方法を、初心者から上級者まで対応できるよう、詳細に解説します。
第1章:UE5 C++開発の基礎知識
1-1. ブループリント vs C++ の選択
UE5での開発では、ブループリント(ビジュアルプログラミング)とC++の使い分けが重要です。
| 項目 | ブループリント | C++ |
|---|---|---|
| 習得難度 | 易しい | 難しい |
| 開発速度 | 速い(プロトタイピング向け) | やや遅い(本格開発向け) |
| 実行速度 | 遅い(約70-80%) | 高速(100%) |
| メモリ効率 | 低い | 高い |
| 複雑なロジック | 困難 | 容易 |
| デバッグ性 | 視覚的 | 強力 |
| チーム開発 | 競合しやすい | Git管理が容易 |
| 再利用性 | 限定的 | 優秀 |
| パフォーマンス最適化 | 不可 | 可能 |
1-2. C++を採用すべき場合
【C++採用の判断基準】
✓ C++を使うべき場合:
├─ 大規模プロジェクト(50MB以上)
├─ ハイパフォーマンスが必須
├─ 複雑なゲームロジック実装
├─ マルチプレイネットワーク処理
├─ カスタムエンジン機能が必要
├─ チーム開発(複数プログラマー)
├─ カスタムプラグイン開発
└─ 低レベルシステム実装
✗ C++が不要な場合:
├─ シンプルなゲーム
├─ プロトタイピング段階
├─ デザイナー主体の開発
├─ 短期間でリリースが必要
└─ パフォーマンス要件が低い
1-3. UE5 C++の特徴
UE5は以下の特徴を持つC++開発環境です:
【UE5 C++の特徴】
高度な抽象化:
├─ UPROPERTY マクロで自動的にプロパティ管理
├─ UFUNCTION マクロで自動的にメソッド露出
├─ UCLASS マクロでクラスを Unreal System に登録
└─ UFunctionality が自動的にメモリ管理
リフレクションシステム:
├─ 実行時にクラス情報にアクセス可能
├─ データテーブルからの自動読み込み
├─ キャストの安全性確保
└─ プロパティの動的変更
ホットリロード(実験的):
├─ エディタプレイ中のコード変更を反映(一部)
├─ 開発効率の向上
└─ ただし信頼性は完全ではない
強力なデバッグ機能:
├─ ビジュアルデバッガー統合
├─ ログシステム(多段階フィルタリング)
├─ 画面上デバッグ表示
└─ メモリプロファイラー
第2章:開発環境のセットアップ
2-1. 必要なツールのインストール
ステップ1:Visual Studio のセットアップ
UE5はVisual Studio 2022(推奨) または Visual Studio 2019 での開発をサポートしています。
【Visual Studio 2022 インストール手順】
1. https://visualstudio.microsoft.com/ にアクセス
2. 「Community」版をダウンロード(無料)
3. インストーラーを実行
4. 「Workloads」タブで以下を選択:
✓ Desktop development with C++
✓ Game development with C++(推奨)
✓ .NET desktop development
5. 「Individual components」タブで追加選択:
✓ Windows 10/11 SDK
✓ C++ profiling tools
✓ Cmake tools for Windows
6. インストール実行(30~60分)
【推奨スペック】
CPU:Intel i5 以上(4コア以上)
RAM:16GB 以上(32GB 推奨)
SSD:100GB 以上の空き容量
GPU:任意(コンパイルは CPU を主に使用)
ステップ2:UE5 プロジェクトのセットアップ
【UE5 C++ プロジェクト作成】
1. Unreal Engine Launcher を開く
2. 「Unreal Engine 5.x」を選択
3. 「Create Project」をクリック
4. プロジェクトテンプレート選択:
├─ Blank(推奨:最小限の構成)
├─ First Person(FPS 基盤)
├─ Third Person(TPS 基盤)
└─ Top Down(俯瞰視点向け)
5. Project Settings:
├─ Project Type:C++(重要!)
├─ Target Hardware:Desktop / Console
├─ Quality Preset:Scalable 3D or Max Quality
└─ Ray Tracing:有効化するか選択
6. 「Create」をクリック
7. Visual Studio プロジェクトが自動生成される
【ファイル構成】
YourProject/
├─ Binaries/ ← コンパイル済みバイナリ
├─ Intermediate/ ← 一時ファイル(.gitignore対象)
├─ Source/ ← C++ ソースコード(重要)
│ ├─ YourProject/
│ │ ├─ YourProject.h
│ │ ├─ YourProject.cpp
│ │ ├─ Character/
│ │ ├─ GameMode/
│ │ ├─ Pawns/
│ │ └─ UI/
│ └─ YourProjectEditor/
├─ Content/ ← アセット(ブループリント、メッシュ等)
├─ Plugins/ ← カスタムプラグイン
├─ YourProject.uproject
└─ YourProject.sln ← Visual Studio ソリューション
2-2. Visual Studio の設定
【Visual Studio と UE5 の統合設定】
1. Edit → Editor Preferences(UE エディタ)
2. Search:「Source Code Editor」
3. Source Code Editor:Visual Studio 2022 を選択
4. Open Source Code Editor at Startup:チェック
5. UE エディタを再起動
【コンパイル設定】
Edit → Project Settings → Search "Compiler"
├─ C++ Standard:C++17(推奨)
├─ With Editor:チェック(エディタでのテスト用)
└─ Hot Reload:チェック(実験的機能)
2-3. プロジェクト構成のベストプラクティス
【推奨フォルダ構成】
Source/YourProject/
├─ Core/
│ ├─ YourProjectGameMode.h/cpp ← ゲームの基本ロジック
│ ├─ YourProjectCharacter.h/cpp ← プレイヤーキャラクター
│ └─ YourProjectPlayerController.h/cpp
│
├─ Character/
│ ├─ CharacterBase.h/cpp ← キャラクター基底クラス
│ ├─ PlayerCharacter.h/cpp
│ ├─ EnemyCharacter.h/cpp
│ ├─ NPCCharacter.h/cpp
│ └─ Animation/ ← アニメーション関連
│ └─ CharacterAnimInstance.h/cpp
│
├─ Gameplay/
│ ├─ Combat/ ← 戦闘システム
│ │ ├─ DamageSystem.h/cpp
│ │ ├─ WeaponBase.h/cpp
│ │ └─ AbilitySystem.h/cpp
│ ├─ Inventory/ ← インベントリシステム
│ │ ├─ InventoryComponent.h/cpp
│ │ └─ ItemBase.h/cpp
│ └─ Quest/ ← クエストシステム
│ ├─ QuestManager.h/cpp
│ └─ QuestBase.h/cpp
│
├─ AI/
│ ├─ AIController.h/cpp
│ ├─ BehaviorTree/ ← AIロジック
│ └─ Services/
│
├─ UI/
│ ├─ HUD/
│ │ └─ GameHUD.h/cpp
│ ├─ Widget/ ← UI ウィジェット C++ 実装
│ │ ├─ MainMenuWidget.h/cpp
│ │ └─ HUDWidget.h/cpp
│ └─ Canvas/
│
├─ Systems/
│ ├─ SaveGame/ ← セーブ・ロードシステム
│ │ ├─ SaveGameManager.h/cpp
│ │ └─ SaveGameData.h/cpp
│ ├─ Audio/ ← 音声管理
│ │ └─ AudioManager.h/cpp
│ └─ Manager/ ← 汎用マネージャー
│ ├─ GameManager.h/cpp
│ └─ LevelManager.h/cpp
│
├─ Utility/
│ ├─ Math/ ← 数学ユーティリティ
│ │ └─ MathLibrary.h/cpp
│ ├─ Helper/ ← ヘルパー関数
│ │ └─ GameplayHelpers.h/cpp
│ └─ Debug/ ← デバッグ用
│ └─ DebugTools.h/cpp
│
└─ Data/
├─ DataTables/ ← データドリブン設定
├─ Enums/ ← 列挙型定義
│ └─ GameEnums.h
└─ Structures/ ← 構造体定義
└─ GameStructures.h
第3章:基本的な C++ クラス実装
3-1. Actor クラスの実装
Actorはゲーム世界に存在する基本的なオブジェクトです。すべてのゲームオブジェクトはActorから派生します。
cpp
// MyActor.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"
UCLASS()
class YOURPROJECT_API AMyActor : public AActor
{
GENERATED_BODY()
public:
// コンストラクタ
AMyActor();
// エディタで編集可能なプロパティ
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Appearance")
float ActorScale;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Appearance")
FLinearColor ActorColor;
// ブループリント上のみ読み取り可能
UPROPERTY(BlueprintReadOnly, Category = "State")
bool bIsActive;
// ブループリント上で実行可能な関数
UFUNCTION(BlueprintCallable, Category = "Interaction")
void Interact();
// ブループリント・C++ 両方でオーバーライド可能
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Events")
void OnStateChanged(bool bNewState);
protected:
// ゲーム開始時に1度だけ呼ばれる
virtual void BeginPlay() override;
// フレームごとに呼ばれる
virtual void Tick(float DeltaTime) override;
private:
// 内部用プロパティ(エディタで非表示)
UPROPERTY()
class UMaterial* BaseMaterial;
UPROPERTY()
float InternalTimer;
};
cpp
// MyActor.cpp
#include "MyActor.h"
#include "Engine/World.h"
#include "TimerManager.h"
AMyActor::AMyActor()
{
// アクターの基本設定
PrimaryActorTick.bCanEverTick = true; // Tick() が呼ばれるか
PrimaryActorTick.TickInterval = 0.f; // 毎フレーム呼ぶ
ActorScale = 1.0f;
ActorColor = FLinearColor::White;
bIsActive = true;
InternalTimer = 0.0f;
// リプリケーション設定(マルチプレイ対応時)
bReplicates = false;
}
void AMyActor::BeginPlay()
{
Super::BeginPlay();
// ゲーム開始時の初期化
UE_LOG(LogTemp, Warning, TEXT("MyActor spawned at location: %s"),
*GetActorLocation().ToString());
}
void AMyActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// 毎フレーム更新処理
if (bIsActive)
{
InternalTimer += DeltaTime;
// 2秒ごとに何か実行
if (InternalTimer >= 2.0f)
{
InternalTimer = 0.0f;
OnStateChanged(!bIsActive);
}
}
}
void AMyActor::Interact()
{
if (!bIsActive)
return;
UE_LOG(LogTemp, Warning, TEXT("MyActor interacted!"));
// インタラクション処理
bIsActive = false;
// 5秒後に再度アクティブ化
GetWorld()->GetTimerManager().SetTimer(
TimerHandle_Reactivate,
[this]()
{
bIsActive = true;
},
5.0f,
false // ループしない
);
}
void AMyActor::OnStateChanged_Implementation(bool bNewState)
{
// ブループリント側がオーバーライド可能
UE_LOG(LogTemp, Warning, TEXT("State changed to: %s"),
bNewState ? TEXT("Active") : TEXT("Inactive"));
}
3-2. Character クラスの実装
Character はプレイヤーやNPCなど、移動・アニメーションが必要なActorです。
cpp
// MyCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"
#include "MyCharacter.generated.h"
class USpringArmComponent;
class UCameraComponent;
class UEnhancedInputComponent;
class UEnhancedInputLocalPlayerSubsystem;
class UInputMappingContext;
class UInputAction;
UCLASS()
class YOURPROJECT_API AMyCharacter : public ACharacter
{
GENERATED_BODY()
public:
AMyCharacter();
protected:
// カメラコンポーネント
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
USpringArmComponent* CameraBoom;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
UCameraComponent* FollowCamera;
// Enhanced Input System
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputMappingContext* DefaultMappingContext;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputAction* MoveAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputAction* LookAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
class UInputAction* JumpAction;
// キャラクター基本パラメータ
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
float MaxWalkSpeed;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
float SprintMultiplier;
UPROPERTY(BlueprintReadOnly, Category = "State")
bool bIsSprinting;
virtual void BeginPlay() override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
// Input callbacks
void Move(const FInputActionValue& Value);
void Look(const FInputActionValue& Value);
void StartSprinting();
void StopSprinting();
public:
virtual void Tick(float DeltaTime) override;
};
cpp
// MyCharacter.cpp
#include "MyCharacter.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "InputActionValue.h"
AMyCharacter::AMyCharacter()
{
// Tickを有効化
PrimaryActorTick.bCanEverTick = true;
// キャラクターの基本設定
GetCharacterMovement()->bOrientRotationToMovement = true;
GetCharacterMovement()->MaxWalkSpeed = 600.0f;
GetCharacterMovement()->MaxAcceleration = 1000.0f;
// ルートコンポーネントを Capsule に設定
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
// カメラBoomを作成(カメラをキャラクターから離す)
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 400.0f;
CameraBoom->bUsePawnControlRotation = true;
// カメラを作成
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
FollowCamera->bUsePawnControlRotation = false;
MaxWalkSpeed = 600.0f;
SprintMultiplier = 1.5f;
bIsSprinting = false;
}
void AMyCharacter::BeginPlay()
{
Super::BeginPlay();
// Enhanced Input System のセットアップ
if (APlayerController* PlayerController = Cast<APlayerController>(Controller))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem =
PlayerController->GetLocalPlayer()->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
{
Subsystem->AddMappingContext(DefaultMappingContext, 0);
}
}
}
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
if (UEnhancedInputComponent* EnhancedInputComponent =
CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
// Move Action
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMyCharacter::Move);
// Look Action
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AMyCharacter::Look);
// Jump Action
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump);
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
}
}
void AMyCharacter::Move(const FInputActionValue& Value)
{
const FVector2D MovementVector = Value.Get<FVector2D>();
if (Controller != nullptr)
{
// 前後移動
AddMovementInput(GetActorForwardVector(), MovementVector.Y);
// 左右移動
AddMovementInput(GetActorRightVector(), MovementVector.X);
}
}
void AMyCharacter::Look(const FInputActionValue& Value)
{
const FVector2D LookAxisVector = Value.Get<FVector2D>();
if (Controller != nullptr)
{
AddControllerYawInput(LookAxisVector.X);
AddControllerPitchInput(LookAxisVector.Y);
}
}
void AMyCharacter::StartSprinting()
{
if (bIsSprinting)
return;
bIsSprinting = true;
GetCharacterMovement()->MaxWalkSpeed = MaxWalkSpeed * SprintMultiplier;
}
void AMyCharacter::StopSprinting()
{
bIsSprinting = false;
GetCharacterMovement()->MaxWalkSpeed = MaxWalkSpeed;
}
void AMyCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
3-3. GameMode と PlayerController の実装
GameModeはゲームの基本ルール、PlayerControllerはプレイヤー入力を管理します。
cpp
// MyGameMode.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "MyGameMode.generated.h"
UENUM(BlueprintType)
enum class EGameState : uint8
{
MainMenu = 0 UMETA(DisplayName = "Main Menu"),
Playing = 1 UMETA(DisplayName = "Playing"),
Paused = 2 UMETA(DisplayName = "Paused"),
GameOver = 3 UMETA(DisplayName = "Game Over"),
LevelComplete = 4 UMETA(DisplayName = "Level Complete")
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGameStateChanged, EGameState, NewState);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnScoreChanged, int32, NewScore, int32, DeltaScore);
UCLASS()
class YOURPROJECT_API AMyGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
AMyGameMode();
// ゲーム状態管理
UPROPERTY(BlueprintReadOnly, Category = "Game State")
EGameState CurrentGameState;
UPROPERTY(BlueprintReadOnly, Category = "Score")
int32 CurrentScore;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Game Settings")
int32 WaveCount;
// デリゲート(イベント)
UPROPERTY(BlueprintAssignable, Category = "Events")
FOnGameStateChanged OnGameStateChanged;
UPROPERTY(BlueprintAssignable, Category = "Events")
FOnScoreChanged OnScoreChanged;
// ゲーム制御関数
UFUNCTION(BlueprintCallable, Category = "Game Control")
void StartGame();
UFUNCTION(BlueprintCallable, Category = "Game Control")
void PauseGame();
UFUNCTION(BlueprintCallable, Category = "Game Control")
void ResumeGame();
UFUNCTION(BlueprintCallable, Category = "Game Control")
void EndGame();
UFUNCTION(BlueprintCallable, Category = "Score")
void AddScore(int32 Points);
UFUNCTION(BlueprintCallable, Category = "Score")
void ResetScore();
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override;
private:
void SetGameState(EGameState NewState);
float GameTimer;
float MaxGameTime;
};
cpp
// MyGameMode.cpp
#include "MyGameMode.h"
#include "MyCharacter.h"
#include "MyPlayerController.h"
#include "Kismet/GameplayStatics.h"
AMyGameMode::AMyGameMode()
{
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.TickInterval = 0.1f;
// デフォルトクラス設定
DefaultPawnClass = AMyCharacter::StaticClass();
PlayerControllerClass = AMyPlayerController::StaticClass();
CurrentGameState = EGameState::MainMenu;
CurrentScore = 0;
WaveCount = 0;
GameTimer = 0.0f;
MaxGameTime = 300.0f; // 5分間のゲーム
}
void AMyGameMode::BeginPlay()
{
Super::BeginPlay();
SetGameState(EGameState::Playing);
}
void AMyGameMode::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (CurrentGameState == EGameState::Playing)
{
GameTimer += DeltaTime;
// タイムアップでゲーム終了
if (GameTimer >= MaxGameTime)
{
EndGame();
}
}
}
void AMyGameMode::StartGame()
{
SetGameState(EGameState::Playing);
GameTimer = 0.0f;
ResetScore();
}
void AMyGameMode::PauseGame()
{
SetGameState(EGameState::Paused);
UGameplayStatics::SetGamePaused(GetWorld(), true);
}
void AMyGameMode::ResumeGame()
{
SetGameState(EGameState::Playing);
UGameplayStatics::SetGamePaused(GetWorld(), false);
}
void AMyGameMode::EndGame()
{
SetGameState(EGameState::GameOver);
UGameplayStatics::SetGamePaused(GetWorld(), true);
}
void AMyGameMode::AddScore(int32 Points)
{
int32 PreviousScore = CurrentScore;
CurrentScore += Points;
// デリゲート呼び出し(ブループリント側に通知)
OnScoreChanged.Broadcast(CurrentScore, Points);
UE_LOG(LogTemp, Warning, TEXT("Score: %d (+%d)"), CurrentScore, Points);
}
void AMyGameMode::ResetScore()
{
CurrentScore = 0;
}
void AMyGameMode::SetGameState(EGameState NewState)
{
if (CurrentGameState == NewState)
return;
CurrentGameState = NewState;
OnGameStateChanged.Broadcast(NewState);
UE_LOG(LogTemp, Warning, TEXT("Game State Changed to: %d"), (int32)NewState);
}
第4章:コンポーネント設計
4-1. カスタムコンポーネント実装
コンポーネントはActorの機能を再利用可能なユニットに分割します。
cpp
// HealthComponent.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "HealthComponent.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHealthChanged, float, NewHealth, float, MaxHealth);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeath);
UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class YOURPROJECT_API UHealthComponent : public UActorComponent
{
GENERATED_BODY()
public:
UHealthComponent();
// プロパティ
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Health")
float MaxHealth;
UPROPERTY(BlueprintReadOnly, Category = "Health")
float CurrentHealth;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Health")
bool bDestroyActorOnDeath;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Health")
float HealthRegenRate;
// デリゲート
UPROPERTY(BlueprintAssignable, Category = "Events")
FOnHealthChanged OnHealthChanged;
UPROPERTY(BlueprintAssignable, Category = "Events")
FOnDeath OnDeath;
// 関数
UFUNCTION(BlueprintCallable, Category = "Health")
void TakeDamage(float DamageAmount);
UFUNCTION(BlueprintCallable, Category = "Health")
void Heal(float HealAmount);
UFUNCTION(BlueprintCallable, Category = "Health")
void ResetHealth();
UFUNCTION(BlueprintCallable, Category = "Health")
bool IsAlive() const { return CurrentHealth > 0.0f; }
UFUNCTION(BlueprintCallable, Category = "Health")
float GetHealthPercent() const { return MaxHealth > 0.0f ? CurrentHealth / MaxHealth : 0.0f; }
virtual void BeginPlay() override;
virtual void TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction) override;
private:
void OnOwnerTakeDamage(AActor* DamagedActor, float Damage,
const class UDamageType* DamageType,
class AController* InstigatedBy,
AActor* DamageCauser);
};
cpp
// HealthComponent.cpp
#include "HealthComponent.h"
#include "GameFramework/Actor.h"
UHealthComponent::UHealthComponent()
{
PrimaryComponentTick.bCanEverTick = true;
PrimaryComponentTick.TickInterval = 0.1f;
MaxHealth = 100.0f;
CurrentHealth = MaxHealth;
bDestroyActorOnDeath = false;
HealthRegenRate = 0.0f; // 自動回復なし
}
void UHealthComponent::BeginPlay()
{
Super::BeginPlay();
// オーナーのダメージイベントにバインド
if (AActor* OwnerActor = GetOwner())
{
OwnerActor->OnTakeAnyDamage.AddDynamic(this, &UHealthComponent::OnOwnerTakeDamage);
}
CurrentHealth = MaxHealth;
}
void UHealthComponent::TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// 自動回復
if (HealthRegenRate > 0.0f && CurrentHealth < MaxHealth && IsAlive())
{
CurrentHealth = FMath::Min(CurrentHealth + HealthRegenRate * DeltaTime, MaxHealth);
}
}
void UHealthComponent::TakeDamage(float DamageAmount)
{
if (DamageAmount <= 0.0f || !IsAlive())
return;
CurrentHealth -= DamageAmount;
CurrentHealth = FMath::Max(CurrentHealth, 0.0f);
OnHealthChanged.Broadcast(CurrentHealth, MaxHealth);
if (!IsAlive())
{
OnDeath.Broadcast();
if (bDestroyActorOnDeath)
{
GetOwner()->Destroy();
}
}
UE_LOG(LogTemp, Warning, TEXT("TakeDamage: %.1f, Remaining Health: %.1f"),
DamageAmount, CurrentHealth);
}
void UHealthComponent::Heal(float HealAmount)
{
if (HealAmount <= 0.0f || !IsAlive())
return;
CurrentHealth = FMath::Min(CurrentHealth + HealAmount, MaxHealth);
OnHealthChanged.Broadcast(CurrentHealth, MaxHealth);
UE_LOG(LogTemp, Warning, TEXT("Heal: %.1f, Current Health: %.1f"),
HealAmount, CurrentHealth);
}
void UHealthComponent::ResetHealth()
{
CurrentHealth = MaxHealth;
OnHealthChanged.Broadcast(CurrentHealth, MaxHealth);
}
void UHealthComponent::OnOwnerTakeDamage(AActor* DamagedActor, float Damage,
const UDamageType* DamageType,
AController* InstigatedBy,
AActor* DamageCauser)
{
TakeDamage(Damage);
}
第5章:ブループリント・C++ 統合
5-1. C++ からブループリントを拡張する
C++で基底クラスを作成し、ブループリントで詳細をカスタマイズする強力なパターンです。
cpp
// CharacterBase.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "CharacterBase.generated.h"
// ブループリントで実装可能なイベント
UCLASS()
class YOURPROJECT_API ACharacterBase : public ACharacter
{
GENERATED_BODY()
public:
ACharacterBase();
// ブループリント実装可能な関数
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Character")
void OnCharacterSpawned();
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Character")
void OnCharacterDied(AActor* Killer);
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Character")
float GetMaxHealth() const;
// C++ で実装する関数
UFUNCTION(BlueprintCallable, Category = "Character")
void SetCharacterColor(FLinearColor NewColor);
UFUNCTION(BlueprintCallable, Category = "Character")
float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent,
class AController* EventInstigator, class AActor* DamageCauser) override;
protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats")
float Health;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats")
float MaxHealthValue;
virtual void BeginPlay() override;
private:
UPROPERTY()
class UMaterialInstanceDynamic* DynamicMaterial;
};
cpp
// CharacterBase.cpp
#include "CharacterBase.h"
ACharacterBase::ACharacterBase()
{
PrimaryActorTick.bCanEverTick = false;
Health = 100.0f;
MaxHealthValue = 100.0f;
}
void ACharacterBase::BeginPlay()
{
Super::BeginPlay();
OnCharacterSpawned();
}
void ACharacterBase::OnCharacterSpawned_Implementation()
{
// デフォルト実装(ブループリントでオーバーライド可能)
UE_LOG(LogTemp, Warning, TEXT("Character Spawned"));
}
void ACharacterBase::OnCharacterDied_Implementation(AActor* Killer)
{
// デフォルト実装
Destroy();
}
float ACharacterBase::GetMaxHealth_Implementation() const
{
return MaxHealthValue;
}
void ACharacterBase::SetCharacterColor(FLinearColor NewColor)
{
if (USkeletalMeshComponent* SkeletalMesh = GetMesh())
{
DynamicMaterial = SkeletalMesh->CreateAndSetMaterialInstanceDynamic(0);
if (DynamicMaterial)
{
DynamicMaterial->SetVectorParameterValue(FName("BaseColor"), NewColor);
}
}
}
float ACharacterBase::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent,
AController* EventInstigator, AActor* DamageCauser)
{
float ActualDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
Health -= ActualDamage;
if (Health <= 0.0f)
{
OnCharacterDied(EventInstigator ? EventInstigator->GetPawn() : nullptr);
}
return ActualDamage;
}
第6章:データ駆動型設計
6-1. データテーブルの使用
cpp
// ItemData.h
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataTable.h"
#include "ItemData.generated.h"
UENUM(BlueprintType)
enum class EItemType : uint8
{
Weapon = 0 UMETA(DisplayName = "Weapon"),
Armor = 1 UMETA(DisplayName = "Armor"),
Consumable = 2 UMETA(DisplayName = "Consumable"),
Misc = 3 UMETA(DisplayName = "Miscellaneous")
};
// データテーブルの行構造体
USTRUCT(BlueprintType)
struct FItemData : public FTableRowBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
FString ItemName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
FString Description;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
EItemType ItemType;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
float Value;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
int32 MaxStackSize;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
TSoftObjectPtr<class UTexture2D> Icon;
};
cpp
// ItemManager.h
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "ItemData.h"
#include "ItemManager.generated.h"
UCLASS()
class YOURPROJECT_API UItemManager : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
UFUNCTION(BlueprintCallable, Category = "Item")
FItemData* GetItemData(FName ItemID);
UFUNCTION(BlueprintCallable, Category = "Item")
TArray<FItemData*> GetAllItems();
UFUNCTION(BlueprintCallable, Category = "Item")
TArray<FItemData*> GetItemsByType(EItemType ItemType);
private:
UPROPERTY()
class UDataTable* ItemDataTable;
void LoadItemData();
};
cpp
// ItemManager.cpp
#include "ItemManager.h"
void UItemManager::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
LoadItemData();
}
void UItemManager::LoadItemData()
{
// データテーブルをロード
ItemDataTable = LoadObject<UDataTable>(
nullptr,
TEXT("DataTable'/Game/Data/DT_Items.DT_Items'")
);
if (!ItemDataTable)
{
UE_LOG(LogTemp, Error, TEXT("Failed to load ItemDataTable"));
}
}
FItemData* UItemManager::GetItemData(FName ItemID)
{
if (!ItemDataTable)
return nullptr;
return ItemDataTable->FindRow<FItemData>(ItemID, TEXT("GetItemData"));
}
TArray<FItemData*> UItemManager::GetAllItems()
{
TArray<FItemData*> Items;
if (!ItemDataTable)
return Items;
ItemDataTable->GetAllRows<FItemData>(TEXT("GetAllItems"), Items);
return Items;
}
TArray<FItemData*> UItemManager::GetItemsByType(EItemType ItemType)
{
TArray<FItemData*> FilteredItems;
TArray<FItemData*> AllItems = GetAllItems();
for (FItemData* Item : AllItems)
{
if (Item && Item->ItemType == ItemType)
{
FilteredItems.Add(Item);
}
}
return FilteredItems;
}
第7章:マルチプレイと レプリケーション
7-1. 基本的なリプリケーション設定
cpp
// MultiplayerCharacter.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MultiplayerCharacter.generated.h"
UCLASS()
class YOURPROJECT_API AMultiplayerCharacter : public ACharacter
{
GENERATED_BODY()
public:
AMultiplayerCharacter();
// レプリケートするプロパティ
UPROPERTY(Replicated)
float CharacterSpeed;
UPROPERTY(Replicated)
int32 PlayerHealth;
// オーナーのみが変更できるプロパティ
UPROPERTY(ReplicatedUsing = OnRep_PlayerName)
FString PlayerName;
// サーバーのみが実行する関数
UFUNCTION(Server, Reliable, WithValidation)
void Server_TakeDamage(float DamageAmount);
// すべてのクライアントで実行する関数
UFUNCTION(NetMulticast, Reliable)
void Multicast_PlayDamageEffect(FVector ImpactLocation);
// オーナーのクライアントのみで実行する関数
UFUNCTION(Client, Reliable)
void Client_ShowDamageNumber(int32 DamageAmount);
protected:
virtual void BeginPlay() override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
private:
UFUNCTION()
void OnRep_PlayerName();
};
cpp
// MultiplayerCharacter.cpp
#include "MultiplayerCharacter.h"
#include "Net/UnrealNetwork.h"
AMultiplayerCharacter::AMultiplayerCharacter()
{
PrimaryActorTick.bCanEverTick = false;
// リプリケーション有効化
bReplicates = true;
bReplicateMovement = true;
CharacterSpeed = 0.0f;
PlayerHealth = 100;
PlayerName = FString(TEXT("Player"));
}
void AMultiplayerCharacter::BeginPlay()
{
Super::BeginPlay();
}
void AMultiplayerCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// プロパティレプリケーション設定
DOREPLIFETIME(AMultiplayerCharacter, CharacterSpeed);
DOREPLIFETIME(AMultiplayerCharacter, PlayerHealth);
DOREPLIFETIME(AMultiplayerCharacter, PlayerName);
}
void AMultiplayerCharacter::Server_TakeDamage_Implementation(float DamageAmount)
{
if (!HasAuthority())
return;
PlayerHealth -= DamageAmount;
PlayerHealth = FMath::Max(PlayerHealth, 0);
// すべてのクライアントに通知
Multicast_PlayDamageEffect(GetActorLocation());
}
bool AMultiplayerCharacter::Server_TakeDamage_Validate(float DamageAmount)
{
// チートプレイヤーの検出(不正なダメージ値を拒否)
return DamageAmount > 0.0f && DamageAmount < 1000.0f;
}
void AMultiplayerCharacter::Multicast_PlayDamageEffect_Implementation(FVector ImpactLocation)
{
// ダメージエフェクト再生(すべてのクライアント)
UE_LOG(LogTemp, Warning, TEXT("Damage effect played at %s"), *ImpactLocation.ToString());
}
void AMultiplayerCharacter::Client_ShowDamageNumber_Implementation(int32 DamageAmount)
{
// ダメージ数字表示(自クライアントのみ)
UE_LOG(LogTemp, Warning, TEXT("Damage: %d"), DamageAmount);
}
void AMultiplayerCharacter::OnRep_PlayerName()
{
// プレイヤー名が変更された時の処理
UE_LOG(LogTemp, Warning, TEXT("Player name changed to: %s"), *PlayerName);
}
第8章:デバッグとプロファイリング
8-1. デバッグ出力とログ
cpp
// 基本的なログ出力
UE_LOG(LogTemp, Warning, TEXT("This is a warning message"));
UE_LOG(LogTemp, Error, TEXT("This is an error message"));
UE_LOG(LogTemp, Display, TEXT("This is a display message"));
// 変数値のログ出力
int32 MyInt = 42;
UE_LOG(LogTemp, Warning, TEXT("MyInt: %d"), MyInt);
float MyFloat = 3.14f;
UE_LOG(LogTemp, Warning, TEXT("MyFloat: %.2f"), MyFloat);
FVector MyVector = FVector(1, 2, 3);
UE_LOG(LogTemp, Warning, TEXT("MyVector: %s"), *MyVector.ToString());
FString MyString = FString(TEXT("Hello World"));
UE_LOG(LogTemp, Warning, TEXT("MyString: %s"), *MyString);
// 画面上デバッグ表示
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(
-1, // キー(-1で新規作成)
5.0f, // 表示時間(秒)
FColor::Green, // 色
FString::Printf(TEXT("Score: %d"), Score)
);
}
8-2. アサーションとチェック
cpp
// 開発環境でのみ実行(リリースビルドでは除去)
check(MyPointer != nullptr); // 条件が false なら停止
// 条件が false なら警告をログ出力(開発環境のみ)
checkf(Health >= 0, TEXT("Health should not be negative: %f"), Health);
// マルチプレイ検証(Serverで実行)
ensureMsgf(HasAuthority(), TEXT("This function should only run on server"));
// 常に有効(開発環境とリリースビルド両方)
verify(InitializeSystem());
8-3. プロファイリング
cpp
// スコープベースのパフォーマンス計測
{
SCOPE_CYCLE_COUNTER(STAT_MyExpensiveOperation);
// 時間がかかる処理
for (int32 i = 0; i < 1000000; ++i)
{
// ...
}
}
// 条件付きコンパイル(開発ビルドのみ)
#if UE_BUILD_DEBUG
UE_LOG(LogTemp, Warning, TEXT("Debug build detected"));
#endif
// CPU プロファイラー
FString ProfileName = FString(TEXT("MyFunction"));
double StartTime = FPlatformTime::Seconds();
// 処理...
double EndTime = FPlatformTime::Seconds();
double ElapsedTime = (EndTime - StartTime) * 1000.0; // ミリ秒
UE_LOG(LogTemp, Warning, TEXT("%s took %.2f ms"), *ProfileName, ElapsedTime);
第9章:C++の頻出パターン
9-1. Cast と Type チェック
cpp
// ダイナミックキャスト(安全)
AMyCharacter* MyCharacter = Cast<AMyCharacter>(SomeActor);
if (MyCharacter)
{
// キャスト成功
MyCharacter->DoSomething();
}
// C++ スタイルのキャスト
ACharacter* CharacterPtr = static_cast<ACharacter*>(SomeActor);
// IsA チェック
if (SomeActor->IsA<ACharacter>())
{
// ACharacter またはその派生クラス
}
// Safe Cast with nullptr チェック
if (AController* Controller = Cast<AController>(GetOwner()))
{
Controller->Possess(MyCharacter);
}
9-2. Timer の使用
cpp
// 一定時間後に関数実行
GetWorld()->GetTimerManager().SetTimer(
MyTimerHandle, // ハンドル
this, // オブジェクト
&AMyActor::MyFunction, // 関数ポインタ
5.0f, // 遅延(秒)
false // ループするか
);
// ループするタイマー
GetWorld()->GetTimerManager().SetTimer(
LoopTimerHandle,
this,
&AMyActor::UpdateFunction,
1.0f, // 1秒ごと
true // ループ有効
);
// タイマーをクリア
GetWorld()->GetTimerManager().ClearTimer(MyTimerHandle);
// Lambda を使用した Timer
GetWorld()->GetTimerManager().SetTimer(
TimerHandle,
[this]()
{
UE_LOG(LogTemp, Warning, TEXT("Timer fired!"));
},
3.0f,
false
);
9-3. デリゲート(イベント)
cpp
// シングルキャスト デリゲート
DECLARE_DELEGATE(FOnSimpleEvent);
UPROPERTY()
FOnSimpleEvent OnMyEvent;
// バインド
OnMyEvent.BindDynamic(this, &AMyActor::MyFunction);
// 実行
OnMyEvent.Execute();
// マルチキャスト デリゲート
DECLARE_MULTICAST_DELEGATE(FOnMultipleEvent, int32);
UPROPERTY()
FOnMultipleEvent OnMyMulticastEvent;
// 複数のリスナーをバインド
OnMyMulticastEvent.AddDynamic(this, &AMyActor::FunctionA);
OnMyMulticastEvent.AddDynamic(this, &AMyActor::FunctionB);
// すべてのリスナーに実行
OnMyMulticastEvent.Broadcast(42);
// バインド解除
OnMyMulticastEvent.RemoveDynamic(this, &AMyActor::FunctionA);
第10章:よくある問題とトラブルシューティング
10-1. コンパイルエラー
【エラー: Unresolved external symbol】
原因:関数の実装がない、または .cpp ファイルが参照されていない
対処:
├─ .h ファイルに宣言がある関数が .cpp ファイルにあるか確認
├─ Visual Studio の「Rebuild」を実行
├─ Unreal Engine のクリーンビルドを実行
│ File → Clean → クリーン実行
└─ Intermediate フォルダを削除して再コンパイル
【エラー: Undefined reference to 'SomeClass'】
原因:ヘッダーファイルが Include されていない
対処:
├─ #include "SomeClass.h" を追加
├─ ファイルパスが正確か確認
└─ クラス名が正確か確認(大文字小文字区別)
10-2. ホットリロードが失敗する
【原因と対処】
原因1:コンパイルエラーがある
対処:
├─ Visual Studio で完全にコンパイルする
├─ Unreal Editor のコンソールでエラー確認
└─ エラーを修正してから再度ホットリロード
原因2:アクティブなオブジェクトを修正した
対処:
├─ エディタプレイを停止
├─ コードを修正
├─ エディタを再起動
└─ もう一度プレイ開始
推奨:ホットリロードに頼らず、定期的にエディタを再起動
10-3. リプリケーションが機能しない
【原因と対処】
原因1:bReplicates が false に設定されている
対処:
├─ コンストラクタで bReplicates = true を設定
└─ BeginPlay() 直後の値を確認
原因2:GetLifetimeReplicatedProps() が呼ばれていない
対処:
├─ DOREPLIFETIME マクロを使用
├─ Super::GetLifetimeReplicatedProps() を呼び出し
└─ エディタを再起動してリコンパイル
原因3:Server/Client 関数に Reliable が設定されていない
対処:
├─ Server や Client 関数に Reliable キーワードを追加
└─ Validate 関数で不正な値をチェック
第11章:ベストプラクティス
11-1. 命名規則
【UE5 公式命名規則】
クラス名:
├─ A(Actor 派生):ACharacter, AGameMode
├─ U(Object 派生):UComponent, UWidget
├─ F(構造体):FVector, FRotator
├─ S(Slate ウィジェット):SButton
├─ I(インターフェース):ICharacterInterface
└─ E(列挙型):EGameState
関数名:
├─ PascalCase:GetHealth(), TakeDamage()
├─ 動詞で始まる:Initialize(), Update(), Destroy()
└─ Getter/Setter:GetScore(), SetScore()
変数名:
├─ プロパティ:PascalCase(MyHealth)
├─ ローカル変数:lowerCamelCase(myLocalVar)
├─ メンバー変数 プリフィックス:
│ ├─ b(bool):bIsAlive, bCanMove
│ ├─ f(float):fSpeed, fHealth
│ ├─ i(int):iScore, iLevel
│ └─ S(FString):SName, SPath
└─ ポインタサフィックス:なし(直接参照)
定数:
├─ Const before/after:const bool bIsConst
└─ ALL_CAPS(マクロのみ):MAX_PLAYERS
11-2. メモリ管理
cpp
// AActor は AddToRoot() で参照を保持
MyActor->AddToRoot(); // GC 対象外に
MyActor->RemoveFromRoot();
// UObject のポインタはデフォルトで GC 対象
UPROPERTY()
UMyComponent* MyComponent; // GC 対象
// 手動でクリア(必須ではない)
if (MyObject)
{
MyObject->ConditionalBeginDestroy();
MyObject = nullptr;
}
// Smart Pointers(推奨)
TSharedPtr<class FMyData> SharedData;
TUniquePtr<class FMyData> UniqueData;
11-3. パフォーマンス最適化
cpp
// オフスクリーンオブジェクトを無視
if (!IsVisible())
{
return;
}
// インスタンスごとの処理を最小化
if (GetWorld()->GetNetMode() == NM_Standalone)
{
// シングルプレイのみの処理
}
// 頻繁な計算結果をキャッシュ
if (!bCached)
{
CachedValue = ExpensiveCalculation();
bCached = true;
}
// 配列操作は Reserve() を使用
TArray<int32> Numbers;
Numbers.Reserve(1000); // 1000要素分のメモリを事前割り当て
for (int32 i = 0; i < 1000; ++i)
{
Numbers.Add(i);
}
まとめ
UE5でのC++開発は、以下のポイントを押さえることで効果的に進められます。
要点1:プロジェクト構成を統一する
ファイル構成を最初から整理しておくことで、プロジェクトの規模が大きくなった時も管理が容易です。
要点2:ブループリント・C++ の使い分けを理解する
C++はロジック、ブループリントはビジュアル・調整に使い分けることで、開発効率が向上します。
要点3:コンポーネント設計を活用する
再利用可能なコンポーネントを設計することで、コードの重複を減らし、保守性を向上させます。
要点4:デバッグ・プロファイリング機能を活用する
ログ、アサーション、Timer を活用することで、バグの早期発見とパフォーマンス問題の解決が容易になります。
要点5:ベストプラクティスに従う
命名規則、メモリ管理、最適化技法を守ることで、チーム開発を円滑に進められます。
参考資料・リンク集
公式ドキュメント
コミュニティリソース
おすすめ学習サイト
このガイドが、あなたのUE5 C++開発を加速させることを願っています。Happy Coding!






