Blueprintでひと通りゲームが動くようになると、C++という選択肢が視界に入ってきます。ただ、入門記事を開くと UCLASS() や GENERATED_BODY() が並んでいて、どこから手をつけるものなのかが見えません。
UEのC++は、Blueprintを捨てて全部書き直すためのものではありません。土台だけをC++で作り、その上はこれまで通りBlueprintで組む、という形が一般的です。この記事では、最初のC++クラスを作る手順と、UCLASS / UPROPERTY / UFUNCTION の3つのマクロでC++の変数・関数をBlueprintへ公開する方法を解説します。
この記事でわかること
- C++を使う理由は速度より 「Blueprintでは書きづらいものがある」 こと
- Tools > New C++ Class... から最初のクラスを作る手順
- 3つのマクロ の役割:
UCLASS(登録)/UPROPERTY(変数の公開)/UFUNCTION(関数の公開)- EditAnywhere と BlueprintReadWrite は別の役割 という指定子の読み方
- Live Coding で反映できる変更と、できない変更
- 実践: C++で親クラスを作り、Blueprintで子を作って値を調整する
なぜC++を使うのか
「C++は速いから」という説明をよく見かけますが、個人開発で最初にC++が欲しくなる場面は、速度とは別のところにあります。 Blueprintのままだと素直に書けないもの が出てくるからです。
| 困ること | Blueprintでの状況 |
|---|---|
| 入れ子のデータ構造 | 変数の型に「配列の配列」や「Mapの値に配列」を直接作れない |
| 差分を見る・マージする | Blueprintはバイナリのアセット。Gitで中身の差分が読めず、競合すると手作業で直せない |
| まとめて直す | 「この関数名だけ全プロジェクトから探す」ができない。テキスト検索や一括置換が効かない |
| 共通の土台を配る | 20個のBPに同じ変数を足すには、20回同じ作業をする |
速度の話が効いてくるのは、 毎フレーム大量に回る処理 に限られます。ボタンを押したら扉が開く、といったイベント駆動の処理をC++に移しても体感は変わりません。ボトルネックの探し方はStatコマンドの記事で扱っています。

逆に言えば、上の表に当てはまらないうちは、Blueprintのままで問題ありません。「共通の土台を配りたくなったとき」が、最初のC++クラスを作るタイミングです。
最初のC++クラスを作る
エディタのメニューから Tools > New C++ Class... を選びます。ウィザードが開くので、順に決めていきます。
手順1:親クラスを選ぶ
よく使う親クラスがボタンで並んでいます。用途で選んでください。
| 親クラス | 何になるか |
|---|---|
| Actor | レベルに配置・スポーンできるもの(アイテム、扉、ギミック) |
| Character | 歩く・ジャンプするキャラクター |
| Actor Component | Actorに付けて機能を足す部品(→ Componentの記事) |
| None | どれにも当てはまらない素のオブジェクト |
手順2:名前と置き場所を決める
名前を入力します。UEには クラス名の頭文字のルール があります。
- A: Actorの派生(
AItemPickup) - U: Actor以外のUObject派生(
UMyComponent) - F: 構造体などの素のC++型(
FEnemyStats)
ウィザードで ItemPickup と打つと、AItemPickup として作られます。Public / Private の選択は、迷ったら Public で構いません。
補足: Blueprintだけで作ってきたプロジェクトに初めてC++クラスを追加すると、そのプロジェクトはC++プロジェクトになります。ビルド環境(WindowsならVisual Studioと「C++によるゲーム開発」ワークロード)が必要です。作成後にエディタの再起動を求められることがあります。
手順3:生成されたファイルを見る
ItemPickup.h と ItemPickup.cpp の2つができます。ヘッダー(.h)が「何を持っているかの宣言」、ソース(.cpp)が「実際の処理」です。
// ItemPickup.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ItemPickup.generated.h" // 必ず 最後のinclude
UCLASS()
class MYPROJECT_API AItemPickup : public AActor
{
GENERATED_BODY()
public:
AItemPickup();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
};
初見で気になる部分だけ拾っておきます。
#include "ItemPickup.generated.h"は 必ずincludeの最後 に置きます。順番を変えるとビルドが通りませんMYPROJECT_APIはプロジェクト名から作られる文字列で、他のモジュールから使えるようにする印です。触る必要はありませんGENERATED_BODY()は、UEが裏で生成するコードの差し込み口です。これも消さずに置いておきます
3つのマクロでBlueprintに橋を架ける
C++で書いただけでは、Blueprintからは何も見えません。 見せたいものにマクロで印を付ける 必要があります。この印を読み取る仕組みを リフレクションシステム と呼びます。

| マクロ | 付ける対象 | 付けると何ができるか |
|---|---|---|
UCLASS() | クラス | このクラスをBlueprintの親にできる。エディタが認識する |
UPROPERTY() | メンバ変数 | 詳細パネルでの編集、Blueprintでの読み書き、セーブ対象になる |
UFUNCTION() | メンバ関数 | Blueprintのノードとして呼べる。タイマーやデリゲートに登録できる |
印の付いていない変数には、もう1つ落とし穴があります。 UPROPERTY を付けないUObjectのポインタは、ガベージコレクションの対象から守られません 。参照しているつもりのオブジェクトが回収され、クラッシュにつながります。UObject系のメンバ変数には、Blueprintに出す予定がなくても UPROPERTY() を付けておくのが作法です。
UPROPERTY()
TObjectPtr<UStaticMeshComponent> MeshComponent; // GCから守られる
指定子の読み方:EditAnywhereとBlueprintReadWriteは別物
UPROPERTY のカッコの中に書くものを 指定子(Specifier) と呼びます。ここが最初のつまずきどころです。 EditAnywhere と BlueprintReadWrite は、別々のドアを開けています 。
- Edit系: 詳細パネル(Details)で 値を編集 できるか
- BlueprintRead系: Blueprintのグラフで Get / Set ノード を使えるか

Edit系(詳細パネル側)
| 指定子 | どこで編集できるか |
|---|---|
EditAnywhere | Blueprintのクラスデフォルトでも、レベルに置いた個体でも編集できる |
EditDefaultsOnly | Blueprintのクラスデフォルトのみ。個体ごとには変えられない |
EditInstanceOnly | レベルに置いた個体のみ。クラス側では変えられない |
VisibleAnywhere | 表示されるが編集できない(デバッグ表示向き) |
BlueprintRead系(グラフ側)
| 指定子 | グラフでできること |
|---|---|
BlueprintReadWrite | Getノードも Setノードも使える |
BlueprintReadOnly | Getノードのみ。BP側から書き換えられない |
| (書かない) | グラフに一切出てこない |
組み合わせの実例です。
// 設計者が調整する値。個体ごとに変えたいので EditAnywhere、
// 実行中にBPから書き換える必要はないので BlueprintReadOnly
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Pickup")
float HealAmount = 25.0f;
// 実行中に変わる状態。エディタでは見るだけ、BPからは読み書きしたい
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Runtime")
bool bIsConsumed = false;
Category はグループ名です。詳細パネルで折りたためる見出しになるので、変数が増えてきたら必ず付けておきます。
UFUNCTION側の指定子
関数側は、まず2つ覚えれば足ります。
| 指定子 | ノードの見た目 |
|---|---|
BlueprintCallable | 白い実行ピンのあるノード。処理として呼ぶ |
BlueprintPure | 実行ピンのない緑のノード。値を返すだけ(const にする) |
UFUNCTION(BlueprintCallable, Category = "Pickup")
void Consume(AActor* Taker);
UFUNCTION(BlueprintPure, Category = "Pickup")
float GetRemainingHeal() const;
もう1つ、C++からBlueprintを 呼ぶ 側の指定子もあります。BlueprintImplementableEvent を付けた関数は、C++では宣言だけして中身を書かず、実装をBlueprintのイベントとして書きます。「拾われた瞬間の演出はBP側で自由にやってほしい」という橋渡しに使います。
// ItemPickup.h — C++は「呼ぶタイミング」だけを決める
UFUNCTION(BlueprintImplementableEvent, Category = "Pickup")
void OnPickedUpEffect();
ビルドとLive Codingの現実
C++を書いたら、Blueprintのコンパイルボタンとは別に ビルド が必要です。UE5には Live Coding が標準で有効になっており、エディタを開いたまま Ctrl + Alt + F11 でコードを再ビルドして反映できます。
ただし、Live Codingには 反映できる変更とできない変更 があります。ここを知らないと「書き換えたのに反映されない」で時間を溶かします。

| 変更内容 | Live Codingで反映できるか |
|---|---|
.cpp の関数の中身を書き換える | できる |
| ログの追加、計算式の修正 | できる |
変数を追加する / UPROPERTY を書き換える | できない。エディタを閉じてビルドし直す |
| クラスを追加する / 親クラスを変える | できない。同上 |
判断はシンプルで、 ヘッダー(.h)に手を入れたらエディタを閉じる と覚えておけば大きく外しません。エディタを閉じた状態でIDEからビルドし、終わってから エディタを開き直します。
補足: 「ビルドは通ったのに詳細パネルに変数が出てこない」ときは、たいていLive Codingでヘッダーの変更を通そうとしています。一度エディタを閉じて、フルビルドしてください。
実践:C++で親を作り、Blueprintで子を量産する
ここまでの3つのマクロを、実際の形にします。作るのは 拾うと効果が出るアイテム です。FPSの回復パック、ローグライクの床落ちアイテム、レースゲームのブーストパネル。「近づくと消えて、何かが起きる」という構図は、ジャンルを問わず同じです。
C++には「回転する」「重なったら消える」という 共通の骨組み だけを書き、回復量や回転速度、拾ったときの演出は Blueprintの子クラス で決めます。
完成形

再現条件
AItemPickup(親クラス:Actor)に、次のメンバを用意します。
| メンバ名 | 型 | 初期値 | 指定子 |
|---|---|---|---|
MeshComponent | UStaticMeshComponent* | なし | VisibleAnywhere |
CollisionSphere | USphereComponent* | 半径 100.0 | VisibleAnywhere |
RotationSpeed | float | 90.0f | EditAnywhere, BlueprintReadOnly |
HealAmount | float | 25.0f | EditAnywhere, BlueprintReadOnly |
OnPickedUpEffect() | 関数(引数なし・戻り値なし) | — | BlueprintImplementableEvent |
C++側:骨組みを書く
// ItemPickup.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ItemPickup.generated.h"
class USphereComponent;
UCLASS()
class MYPROJECT_API AItemPickup : public AActor
{
GENERATED_BODY()
public:
AItemPickup();
virtual void Tick(float DeltaTime) override;
protected:
virtual void BeginPlay() override;
// 見た目。BP側でメッシュを差し替える
UPROPERTY(VisibleAnywhere, Category = "Components")
TObjectPtr<UStaticMeshComponent> MeshComponent;
// 拾う判定の範囲
UPROPERTY(VisibleAnywhere, Category = "Components")
TObjectPtr<USphereComponent> CollisionSphere;
// 1秒あたりの回転角度。子BPで調整する
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Pickup")
float RotationSpeed = 90.0f;
// 拾ったときの回復量。子BPで調整する
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Pickup")
float HealAmount = 25.0f;
// 拾った瞬間の演出。実装はBlueprint側に任せる
UFUNCTION(BlueprintImplementableEvent, Category = "Pickup")
void OnPickedUpEffect();
UFUNCTION()
void OnSphereOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
bool bFromSweep, const FHitResult& SweepResult);
};
// ItemPickup.cpp
#include "ItemPickup.h"
#include "Components/SphereComponent.h"
#include "Components/StaticMeshComponent.h"
#include "GameFramework/Character.h"
AItemPickup::AItemPickup()
{
PrimaryActorTick.bCanEverTick = true;
CollisionSphere = CreateDefaultSubobject<USphereComponent>(TEXT("CollisionSphere"));
CollisionSphere->InitSphereRadius(100.0f);
RootComponent = CollisionSphere;
MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComponent"));
MeshComponent->SetupAttachment(RootComponent);
// 判定はSphereに任せるので、メッシュ側は当たらないようにする
MeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
void AItemPickup::BeginPlay()
{
Super::BeginPlay();
CollisionSphere->OnComponentBeginOverlap.AddDynamic(this, &AItemPickup::OnSphereOverlap);
}
void AItemPickup::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// 1秒間に RotationSpeed 度ずつ回す
AddActorLocalRotation(FRotator(0.0f, RotationSpeed * DeltaTime, 0.0f));
}
void AItemPickup::OnSphereOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
bool bFromSweep, const FHitResult& SweepResult)
{
// プレイヤー(Character)以外は無視する
if (!OtherActor->IsA(ACharacter::StaticClass()))
{
return;
}
UE_LOG(LogTemp, Log, TEXT("%s picked up. Heal: %.1f"), *GetName(), HealAmount);
// 演出はBlueprint側の実装を呼ぶ
OnPickedUpEffect();
Destroy();
}
Blueprint側:子クラスを作って値を入れる
コンテンツブラウザの C++ Classes フォルダで ItemPickup を右クリックし、 Create Blueprint class based on ItemPickup を選びます。名前を BP_HealthPotion にしてください。
開くと、詳細パネルに Pickup カテゴリができています。ここで値を入れます。
| 子BP | RotationSpeed | HealAmount | Mesh |
|---|---|---|---|
BP_HealthPotion | 90.0 | 25.0 | ポーションのメッシュ |
BP_AmmoBox | 0.0 | 0.0 | 箱のメッシュ |
BP_SpeedBoost | 360.0 | 0.0 | パネルのメッシュ |
イベントグラフでは、 右クリック > Event On Picked Up Effect を探して置きます。C++で BlueprintImplementableEvent にした関数が、赤いイベントノードとして出てきます。ここに Spawn System at Location(Niagara)と Play Sound at Location を繋げば、演出だけをBP側で自由に組めます。
BP_HealthPotion(子クラス)
Event On Picked Up Effect
→ Spawn System at Location(System: NS_Heal, Location: Get Actor Location)
→ Play Sound at Location(Sound: SFX_Heal, Location: Get Actor Location)
確認する
3つの子BPをレベルに置いてPlayします。
BP_HealthPotionは 4秒でちょう ど1回転 します(90度/秒 × 4秒 = 360度)BP_SpeedBoostは 1秒で1回転 します(360度/秒)BP_AmmoBoxはRotationSpeedが0なので 止まったまま です- プレイヤーで触れるとアクターが消え、Output Logに
BP_HealthPotion_C_0 picked up. Heal: 25.0が出ます
回転しない場合は、PrimaryActorTick.bCanEverTick = true; がコンストラクタにあるかを確認してください。触れても消えない場合は、CollisionSphere の Collision Presets が OverlapAllDynamic になっているか、プレイヤーのカプセルが Overlap を返す設定かを見ます(→ コリジョンプリセットの記事)。
ポイントは2つです。
- C++側は「変わらない手順」だけを持つ: 回して、触れたら消して、演出を呼ぶ。この骨組みは全アイテム共通です。4種類目を足す作業は、子BPを1つ作って数値を入れるだけになります
BlueprintImplementableEventで演出を明け渡す: C++が「いつ呼ぶか」を決め、Blueprintが「何をするか」を決めます。エフェクトの差し替えのたびにビルドを待つ必要がなくなり、Event Dispatcherと同じ疎結合が、C++とBPの間にも作れます
おまけ:先に知っておくと良いこ と
.generated.hは最後のinclude: 順番を変えるとビルドエラーになります。エラーメッセージが.generated.hを指していたら、まずinclude順を疑ってください- ヘッダーでは前方宣言を使う:
class USphereComponent;のように名前だけ宣言しておき、実体のincludeは.cppで行います。ヘッダーのincludeが増えるほどビルドが遅くなるためです - Blueprint側の変更はC++に戻せない: 子BPで足した変数はC++からは見えません。あとから親に上げたくなったら、C++に書き直して子BPの変数を消す作業が必要です。「共通になりそうなもの」は最初からC++に置いておくと楽です
- 既存のBlueprintの親をC++に差し替えられる: Blueprintを開いて File > Reparent Blueprint から、作ったC++クラスを選べます。ただし変数名が衝突すると値が飛ぶことがあるので、作業前にバックアップを取ってください
UDataAsset派生の型定義から始めるのも手: Actorをいきなり書くより、データの型だけをC++で定義する方が影響範囲が小さく、失敗しても戻しやすいです(→ Data Assetの記事)
まとめ
- C++を使う理由は速度より Blueprintでは書きづらいものがある こと。差分・一括修正・共通の土台がその代表
- 作成は Tools > New C++ Class... から。親クラスを選び、
A/Uの命名ルールに沿って名前を付ける - Blueprintへの橋は 3つのマクロ 。
UCLASS(クラス)/UPROPERTY(変数)/UFUNCTION(関数) - 指定子は Edit系=詳細パネル、BlueprintRead系=グラフ 。別々のドアなので、両方欲しければ両方書く
- ヘッダーを触ったらエディタを閉じてビルド 。Live Codingが通るのは関数の中身までと覚える
- 基本形は C++で親、Blueprintで子 。骨組みをC++に、調整と演出をBPに置く
どこまでをC++に持たせ、どこからをBlueprintに残すか。その線引きはBlueprintとC++の役割分担で詳しく扱います。
いまのプロジェクトで、同じ変数を何度もコピーして回っているBlueprintはありませんか。その共通部分が、最初のC++クラスの中身になります。