はじめに
C/C++・C#・Unity・Unreal Engine を対象としたコーディング規約のダイジェストです。
共通規約を土台に、言語別 → エンジン別の順で上書きする 3 層構成になっています(競合時はより具体的な層が優先)。
共通(common) < 言語別(C++ / C#) < エンジン別(Unity / Unreal)
共通規約
ファイル形式
| 項目 |
ルール |
| エンコード |
UTF-8(BOM 付き) |
| ファイル末尾 |
空行を 1 行 |
| インデント |
スペース 4 文字 |
| 1 行の最大文字数 |
100 文字以内 |
ファイル先頭には専用書式のヘッダーを付ける。
//---------------------------------------
/// @file ファイル名
///
/// @brief 何を行うクラスか
///
/// @date 年月日
///
/// @author 著者名
//---------------------------------------
命名規則
| 種別 |
規則 |
例 |
| クラス・構造体・型 |
PascalCase |
PlayerController |
| 関数・メソッド |
PascalCase(動詞始まり) |
GetPlayerCount() |
| ローカル変数 |
camelCase |
playerCount |
| bool 型 |
is / has / should / can を先頭に |
isAlive |
| 定数 |
UPPER_SNAKE_CASE |
MAX_PLAYER |
| 値渡し引数 |
_ + PascalCase(全言語・全エンジン) |
_PlayerId, _IsAlive
|
| 参照・ポインタ引数 |
プレフィックスなし |
target |
| 列挙定数 |
UPPER_SNAKE_CASE(定数と同ルール)+ 最後に必ず MAX
|
TITLE, MAIN_GAME, MAX
|
int GetPlayerCount(); // ✅ 動詞始まり + PascalCase.
Enemy FindEnemyById(int _Id); // ✅ 検索処理は Find を含める.
void OnButtonClicked(); // ✅ イベントハンドラーは On で始める.
Task LoadDataAsync(); // ✅ 非同期関数は Async で終わる.
// ❌ どうやるか(実装手段)を名前に含めている.
void SaveToFile();
// ✅ 何をするか(役割)で命名する.
void Save();
名前の品質
| ❌ |
✅ |
理由 |
int c; |
int playerCount; |
略語・1 文字変数は禁止(ループカウンタ i、j のみ例外) |
UserInfo userInfo; |
UserInfo player; |
型名の繰り返しは禁止 |
bool isNotAlive; |
bool isDead; |
否定形の bool 名は禁止 |
型・値
float speed = 0.5f; // ✅ float リテラルには必ず f をつける.
int count = GetCount(); // ✅ 型は明示する(C++ は型が自明な場合のみ auto 可).
// ❌ マジックナンバー. 30 が何を指すか読み取れない.
if (count > 30) { Reject(); }
// ✅ 意味を持つ数値は命名定数に切り出す.
const int MAX_PLAYER_COUNT = 30;
if (count > MAX_PLAYER_COUNT) { Reject(); }
// ❌ スコープ内で一度しか使わないローカル変数.
int total = basePrice * quantity + shippingFee;
return total;
// ✅ そのまま返す. 式が複雑なら関数に切り出す.
return CalculateTotalPrice(basePrice, quantity, shippingFee);
簡潔な記述の優先
条件に基づく変換・集計は制御構文ではなく LINQ / STL アルゴリズムに置き換える。
// ✅ C#: LINQ.
List<string> names = players.Where(player => player.IsAlive)
.Select(player => player.Name)
.ToList();
// ✅ C++: STL アルゴリズム.
auto it = std::find_if(players.begin(), players.end(),
[](const Player& player) { return player.IsAlive(); });
制御構文
-
波括弧必須:
if / else / for / while / do-while は 1 文でも必ず波括弧
-
早期 return: 入れ子が深くなったらガード節で防ぐ
-
条件式の統合: 同じ結果に至る条件は一つに(
if (isDead || hitPoint <= 0))
-
ループ: 事前に計算できる値はループの外へ
-
三項演算子: 1 行 100 文字未満に収まる場合のみ使用可
-
switch:
default は一番上。複数行の処理は関数に切り出す。状態ごとに処理が大きく異なるなら State パターンを検討
-
while:
while (true) は無限ループ専用。それ以外は for / do-while
// ✅ ガード節(早期 return)で入れ子を防ぐ.
void Process()
{
if (!isAlive)
{
return;
}
if (!hasItem)
{
return;
}
Execute();
}
関数・クラスの設計
- 引数が多い場合は構造体・クラスにまとめる
- 二次元配列のインデックス計算(
cells[y * width + x])は関数化する(GetCell(x, y))
- オーバーライドする関数には
override を明示する
- メンバ変数は最小限に。他のメンバから導出できる値はプロパティ・ゲッターで返す
設計原則
| 原則 |
概要 |
| 不変性 |
既存オブジェクトを書き換えず、新しいオブジェクトを返す(エンジンのホットパスは例外 — 後述) |
| KISS |
動く最もシンプルな解を選ぶ。巧妙さより明快さ |
| DRY |
繰り返しが実際に発生したら共通化する(先回りの抽象化はしない) |
| YAGNI |
必要になるまで作らない |
| SOLID |
単一責任 / 開放・閉鎖 / リスコフ置換 / インターフェース分離 / 依存関係逆転 |
// ❌ デメテルの法則違反: 関係のないオブジェクトの内部に依存(ドット 2 回以上).
player.GetInventory().GetWeapon().GetDamage();
// ✅ 直接知っているオブジェクトのみ呼び出す.
player.GetWeaponDamage();
// ※ LINQ / STL のチェーンは対象外(同一コレクションの変換のため).
空行
| 位置 |
空行 |
| メンバ変数・メンバ関数の宣言の前後 |
1 行 |
| 型・メンバ定義レベルの波括弧の前後 |
1 行 |
波括弧の内側({ 直後・} 直前) |
入れない |
| 制御構文と非制御構文の境界 |
1 行 |
| 制御構文どうしの間 |
入れない |
コメント
初心者でも理解できる平易な言葉で、「なぜそうするか」が伝わるように書く。
| ルール |
例 |
| 1 行の末尾はピリオド |
// プレイヤー数. |
| 全角と半角の間に半角スペース |
プレイヤー ID |
-er / -or / -ar 語尾は長音符を付ける |
Player → プレイヤー |
- 変数・関数の宣言には概要コメントを直上に 1 行
- 関数定義・クラス・構造体・列挙体には XML 形式(
/// <summary>)
-
// TODO: はやるべきこと、// NOTE: は備忘録・注意書き
C++ 規約
モダン C++(C++17/20/23)
- C スタイルの構文よりモダン C++ の機能を優先する
- 型が文脈から自明な場合は
auto を使ってよい
- コンパイル時定数には
constexpr
- 構造化束縛を使う:
auto [key, value] = map_entry;
リソース管理(RAII)
- 手動の
new / delete は禁止
- 排他的所有権には
std::unique_ptr、共有が本当に必要な場合のみ std::shared_ptr
- 生の
new より std::make_unique / std::make_shared
シグネチャにおける所有権
| 状況 |
使用する型 |
| 関数実行後にアクセスしない |
参照で渡す |
| 所有権を共有する(内部でコピーして保持) |
const shared_ptr<T>& |
| 所有権を要求しない |
const weak_ptr<T>& |
| 所有権を受け取り以降管理する |
shared_ptr<T>&&(ムーブ) |
| 戻り値(所有権を譲渡しない) |
weak_ptr<T>(原則) |
| 戻り値(所有権を譲渡する) |
shared_ptr<T> |
初期化・列挙体
// ✅ 変数宣言時の初期化はブレース初期化 {} で統一する.
int tmp{}; // = 0
std::vector<int> values{}; // = 空のベクター.
MyClass* node{}; // = nullptr
// ✅ enum class のみ使用する(スコープ無し enum は名前空間を汚染する).
// ✅ 列挙定数は定数と同じ UPPER_SNAKE_CASE、最後に必ず MAX.
enum class SceneType { TITLE, MAIN_GAME, MAX };
インクルード
自ファイルに対応するヘッダーを先頭に置き、1 行空けてから他の include を並べる。
| 優先順位 |
ルール |
| 1 |
階層が浅い順(/ の数が少ないものを上に) |
| 2 |
同一階層内はアルファベット順 |
| 3 |
相対パス(../)は絶対パスの後 |
- インクルードガードは
#pragma once
- ヘッダーで使う型は前方宣言に統一し、実体が必要な
.cpp でのみインクルード
引数・クラス
- 基本型は値渡し(
_PlayerId 形式)、基本型以外は const T& / T&
- メンバ変数を変更しない関数(特にゲッター)は
const メンバ関数にする
- メンバの宣言順: public 関数 → public 変数(やむを得ない場合のみ)→ private 関数 → private 変数
- メンバ変数は
snake_case_(末尾アンダースコア)または m_ プレフィックス
- フォーマットは clang-format に委ねる
C# 規約
型とモデル
- null 許容参照型(nullable reference types)を有効にする
- 不変の値的モデルには
record / record struct、エンティティには class、抽象化には interface
-
dynamic は避ける
// ✅ 不変モデルは record + with 式で更新する.
public sealed record UserProfile(string Name, string Email);
public static UserProfile Rename(UserProfile _Profile, string _Name) =>
_Profile with { Name = _Name };
変数の初期化
| 型カテゴリ |
初期化 |
| 値型(プリミティブ・構造体・enum) |
default |
| 参照型(クラス) |
new() |
| 配列・コレクション |
[](コレクション式) |
その他の規則
-
using は System 名前空間を最上部、それ以外はアルファベット順
-
private は省略してよい(public / internal は明示)
- コンパイル時に確定する定数は
const、それ以外のみ readonly
- 大きい構造体は
in T(変更しない)/ ref T(変更する)で参照渡し
アロー演算子(式形式メンバー)
// ✅ 一行で表せるメソッドとゲッタープロパティはアロー演算子で実装する.
// ✅ => は関数名・プロパティ名の次の行に記述する.
public int Count
=> items.Count;
// ❌ 一行で表せるのに本体を波括弧で書いている.
public int Count
{
get { return items.Count; }
}
非同期
-
.Result / .Wait() などのブロッキングより async / await
- public な非同期 API には
CancellationToken を通す
- フォーマットは
dotnet format に委ねる
Unity 規約
対象: Assets/ 配下の C#。C# 規約を土台に、競合時は本規約が優先。
MonoBehaviour の作法
- 1 ファイル 1
MonoBehaviour。ファイル名はクラス名と一致させる
- Inspector 公開は
public フィールドではなく [SerializeField] private
- コンポーネント参照は
Awake / Start でキャッシュ — Update 内で GetComponent を呼ばない
-
GetComponent + null チェックより TryGetComponent
public sealed class PlayerMovement : MonoBehaviour
{
[SerializeField] private float moveSpeed = 5f;
private Rigidbody cachedRigidbody;
private void Awake()
{
cachedRigidbody = GetComponent<Rigidbody>();
}
}
ライフサイクル
Awake → OnEnable → Start → Update → LateUpdate → OnDisable → OnDestroy
↑
FixedUpdate(物理)
| タイミング |
用途 |
Awake |
自己完結する初期化 |
Start |
他コンポーネントの参照解決 |
- Unity の標準メッセージ関数(
Awake、Update 等)には XML コメント不要
不変性の例外
共通規約の「常に新しいオブジェクトを生成」は Unity のホットパスでは緩和される。
-
MonoBehaviour / ScriptableObject は new せず Instantiate / AddComponent / ScriptableObject.CreateInstance
- 毎フレームのコードでは GC 圧を避けるため構造体やプールされたオブジェクトを再利用してよい
- フレームループ外のゲームロジックは不変性を維持する
null 判定・非同期
// ❌ ?. / ?? は破棄済みオブジェクトの判定をすり抜ける.
target?.DoSomething();
// ✅ UnityEngine.Object には == で判定する.
if (target == null)
{
return;
}
- フレームベースの待ちはコルーチンまたは
Awaitable(Unity 6+)
-
async void はイベントハンドラー以外で使わない(例外が握りつぶされる)
- コルーチン停止・イベント購読解除は
OnDisable / OnDestroy で必ず行う
パフォーマンス・リソース
-
Update 内の new・文字列連結は GC Alloc の原因 — 値が変わったときだけ更新する
-
GameObject.Find / FindObjectOfType は初期化時のみ。毎フレーム呼び出しは禁止
-
Resources.Load は使わない(レガシー API)— Addressables を優先
-
[RequireComponent] / [Header] / [Tooltip] / [Range] などの補助属性を活用する
Unreal Engine 規約
対象: UE5 の Source/ 配下の C++。C++ 規約を土台に、Epic のコーディング標準が汎用の命名規則を上書きする。
命名(Epic 規約 — 必須)
| 種別 |
規則 |
例 |
UObject 派生 |
U 接頭辞 |
UInventoryComponent |
| Actor 派生 |
A 接頭辞 |
APlayerCharacter |
| 通常の構造体 |
F 接頭辞 |
FItemData |
| インターフェース |
I 接頭辞 |
IInteractable |
| 列挙体 |
E 接頭辞 + 列挙定数は UPPER_SNAKE_CASE + MAX
|
ESceneType { TITLE, MAIN_GAME, MAX } |
| bool 型 |
b 接頭辞 |
bIsAlive |
| 関数・変数・引数 |
PascalCase(値渡し引数は _ を付ける) |
_DamageAmount |
メモリ管理(GC)
std:: スマートポインタはエンジン型には使わない。
// ❌ new で生成し、delete で解放している.
UWeapon* Weapon = new UWeapon();
delete Weapon;
// ✅ 生成は NewObject / CreateDefaultSubobject / SpawnActor.
// ✅ 解放は GC に委ねる. UObject を delete しない.
UPROPERTY()
TObjectPtr<UWeapon> Weapon;
Weapon = NewObject<UWeapon>(this);
- メンバ保持は
UPROPERTY() + TObjectPtr<T>(UPROPERTY 無しの生ポインタは GC 後にダングリング)
- 非所有参照は
TWeakObjectPtr<T>
- 非
UObject 型には TUniquePtr / TSharedPtr / TSharedRef
コンテナ・文字列
| 汎用 C++ |
Unreal Engine |
std::vector<T> |
TArray<T> |
std::map<K, V> |
TMap<K, V> |
std::string |
FString(可変)/ FName(識別子)/ FText(表示・ローカライズ) |
// ✅ 文字列リテラルは TEXT("...") で囲む.
FString Name = TEXT("Player");
その他の規則
-
UObject 派生型の引数は参照ではなく生ポインタで渡す(AActor* Target)
-
#include "X.generated.h" は必ず最後のインクルード
- ログは
UE_LOG(LogTemp, Warning, TEXT("..."), ...)
- 波括弧は Allman スタイル(Epic 標準)
| 用途 |
マクロ |
| 不変条件(回復不可能) |
check() / checkf()
|
| 回復可能な検証 |
ensure() / ensureMsgf()
|
| Blueprint 公開イベント |
DECLARE_DYNAMIC_MULTICAST_DELEGATE* |
| C++ 内部のみのイベント |
DECLARE_MULTICAST_DELEGATE* |
Visual Studio の設定
書式設定
- 「ツール」タブ →「オプション」をクリック
- 「テキストエディター」→「C/C++」または「C#」→「コードスタイル」→「書式設定」→「改行」を開く
- チェックをすべて付ける(C/C++ の場合はすべて「新しい行に追加」を選択)
コードのクリーンアップ
- ソリューションエクスプローラーを右クリック
- 「コードのクリーンアップ」→「コード クリーンアップを実行」
上記の手順を行うと、インデントや改行を設定どおりに整えてくれる。
GitHub
コミット時
コミットメッセージは Conventional Commits 形式で、以下の 8 種のうちどれかを必ず先頭に付ける。
| type |
用途 |
feat |
新しい機能の追加・既存機能の変更 |
fix |
バグを修正した場合 |
refactor |
バグ修正・機能追加を伴わないコード変更 |
docs |
ドキュメントのみの変更 |
test |
テストの追加・修正 |
chore |
ビルド設定・依存関係など機能に関係しない雑務的変更 |
perf |
パフォーマンス改善 |
ci |
CI 設定・スクリプトの変更 |
プルリクエスト時
- 題名には、大まかに何を行ったかを明記する
- コメントには、何のファイルにどのような変更・追加を行ったかを明記する
参考
UnityでのC#コーディング規約
Unreal EngineでのC++コーディング規約