UE5でC++を使用する方法

目次

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!

よかったらシェアしてね!
目次