改良案
===== 🎯 Character 分割 実行順おすすめ(3コミットで見栄えが出る) =====
目標: 1ファイル1000行超の Character.cpp を段階的に分割し、責務を明確化。
Visual Studio プロジェクト(CMake 不使用)で、見た目を変えずにコードの「整理力」「設計力」をアピールできる構成にします。
🥇 コミット1:VFX / Blink 抽出
- 🎨 CharacterVfx に VFX 関数群を移設。
- InitCSVEffect / SetChargingEffect / SetFullChargeEffect / SetAttackLocusEffect / SetHitEffect / SetFenceHitEffect
- 💡 CharacterModelBlink に DrawCharacterModel を移設。
- 無敵中の点滅描画などの「表現」責務をまとめる。
- ✨ まずビジュアル責務を独立させることで、レビュー映え&最初のインパクトが大きい!
🧩 Character(変更後)
Character::Draw() → CharacterModelBlink::Draw()
Character::Update() → CharacterVfx::Update()📈 効果:視覚表現が明確に分離。300行以上軽量化。
🥈 コミット2:Shadow / Air / Forward 抽出
- 🌑 CharacterShadow に InitShadow / ShadowSet / ShadowDraw を移設。
- 🪶 CharacterAir に CharacterGravity / SetJump を移設。
- 🧭 CharacterForward に RotateVecFront / FrontVectorConfirm を移設。
- 🔁 Character::Update / Draw は新モジュール呼び出しのみの薄い制御層へ。
🧩 Character(変更後)
Update() → shadow.tick() → air.tick() → forward.tick()
Draw() → shadow.draw()📈 効果:ランタイムの共通処理が整理され、Facade 化が進行。
🥉 コミット3:Movement / Rotate / Charge / Hit / Fence / Csv / Observer の順次抽出
- 🏃 CharacterMovement / Rotate / Charge
- 移動・回転・チャージ処理を担当。
- 💥 CharacterHit / Fence
- 被弾/ノックバック/柵反射をまとめる。
- 📄 CharacterCsvLoader
- CSV パラメータ読み込みを専用化。
- 👁️ CharacterObserver
- Add / RemoveObserver など監視者管理を独立。
🧩 Character(変更後)
Update() → movement.tick() → rotate.tick() → charge.tick() → hit.tick() → fence.tick()📈 効果:Player / Enemy 側の共通処理が削ぎ落とされ、状態遷移と入力処理に集中できる。
📦 ファイル移行マップ(Visual Studio での構成)
各
.cppを Visual Studio の「ソースファイル」フォルダに新規作成し、既存関数を下表のファイルに移動します。
移行元(Character.cpp内の関数) | 移行先(新ファイル) |
|---|---|
DrawCharacterModel | CharacterModelBlink.cpp / .h |
DrawCharacterImGui | CharacterDebugPanel.cpp / .h |
InitShadow / ShadowSet / ShadowDraw | CharacterShadow.cpp / .h |
CharacterGravity / SetJump | CharacterAir.cpp / .h |
RotateVecFront / FrontVectorConfirm | CharacterForward.cpp / .h |
CharacterMove / CreateMoveVector / MoveConfirm / IsOutsideStage / 減速系関数群 | CharacterMovement.cpp / .h |
RotateDirectionVector / MoveRotateX / FastRotateX / RotateXStop | CharacterRotate.cpp / .h |
Charging / ChargeRelease / ChargeReset / SetArrow / DrawArrow | CharacterCharge.cpp / .h |
Reflect / KnockBack / InvincibilityTimeCalculation / etc. | CharacterHit.cpp / .h |
GetWireNormal / FenceReflect / NotifyFenceHit | CharacterFence.cpp / .h |
SetCSVStatus | CharacterCsvLoader.cpp / .h |
AddObserver / RemoveObserver | CharacterObserver.cpp / .h |
💾 補足:
CharacterParams.hを作成し、MoveParam / JumpParam / ShadowParam などの構造体を移動。Character.hでは各モジュールを前方宣言して include を最小化。- Visual Studio の「ソリューションエクスプローラ」で
Characterフォルダを右クリック → 「追加」→「新しい項目」で上記を順次作成。
🧱 仕上げ(README / BookStack反映時)
🧩 Before / After の依存図
Before: Character.cpp(1000行超)
├─ 表示(VFX/点滅/影)
├─ 挙動(移動/回転/ジャンプ/チャージ)
├─ 当たり(被弾/ノックバック/柵反射)
├─ データ(CSV読み込み)
└─ 管理(Observer/ImGui)
After: Character Facade + 8モジュール
├─ CharacterVfx
├─ CharacterModelBlink
├─ CharacterShadow
├─ CharacterAir
├─ CharacterMovement / Rotate / Charge
├─ CharacterHit / Fence
├─ CharacterCsvLoader
└─ CharacterObserver🗂️ 責務表
モジュール | 主な役割 | 代表関数 |
|---|---|---|
CharacterVfx | VFX 発火・制御 | SetHitEffect() 他 |
CharacterModelBlink | 無敵中点滅描画 | Draw() |
CharacterShadow | 影生成・描画 | InitShadow(), ShadowDraw() |
CharacterAir | 重力・ジャンプ | CharacterGravity(), SetJump() |
CharacterMovement | 地上移動・減速処理 | CharacterMove(), MoveConfirm() |
CharacterRotate | 回転ユーティリティ | RotateDirectionVector() 他 |
CharacterCharge | チャージ・矢印描画 | Charging(), ChargeRelease() |
CharacterHit | 被弾・ノックバック | Reflect(), KnockBack() |
CharacterFence | 柵反射処理 | GetWireNormal(), FenceReflect() |
CharacterCsvLoader | パラメータ読込 | SetCSVStatus() |
CharacterObserver | 監視者管理 | AddObserver(), RemoveObserver() |
🧱 動作不変性の説明
- Update の呼び順を保持(
shadow → air → forward → movement → ...) - API 仕様を変更せず、Player / Enemy 側の呼び元はそのまま利用可能。
- 表現系(VFX / SE)は Assets / Vfx 経由に統一。
🤖 将来拡張例
- Enemy も CharacterMovement / Hit / Fence を共通化 → AI 部分だけ差分に。
- Player 側の OnCollision → CollisionCharacter → Reflect は CharacterHit に委譲し、入力とステート遷移だけに集中可能。
💡 TIP:
- まず「VFX/Blink → Shadow/Air/Forward」から着手するのが最も安全。
見た目を変えずに 300〜400行削減、レビューにも強い第一歩!
📄 新規 .h / .cpp 最小テンプレート一式(Visual Studio 追加用)
そのまま貼り付けて新規ファイルとして保存できます。必要に応じて型名(
Transform/Vec3など)を既存プロジェクトのものに合わせてください。
例)保存先:
Game/Actors/Character/配下
CharacterParams.h
#pragma once
// 既存の型に合わせて置換してください
struct Vec3 { float x{}, y{}, z{}; };
struct InitializeParam { /* TODO: 初期姿勢/速度など */ };
struct MoveParam { float maxSpeed{6.0f}; float accel{40.0f}; float decel{50.0f}; };
struct RotateParam { float yawSpeed{360.0f}; float pitchSpeed{0.0f}; };
struct JumpParam { float power{8.5f}; float coyoteTime{0.08f}; float bufferTime{0.12f}; };
struct HitParam { float iFrames{0.25f}; float knockback{6.0f}; };
struct FenceHitParam { float reflectScale{1.0f}; float cooldown{0.15f}; };
struct ShadowParam { float size{1.0f}; float offsetY{0.05f}; };CharacterModelBlink.h
#pragma once
struct Transform; // 前方宣言(既存のTransform型に合わせてください)
class CharacterModelBlink {
public:
void Draw(int modelHandle, const Transform& t) const; // 無敵点滅込みの描画
};CharacterModelBlink.cpp
#include "CharacterModelBlink.h"
// #include 必要に応じて: マテリアル/タイマー/無敵状態参照 など
void CharacterModelBlink::Draw(int modelHandle, const Transform& t) const {
// TODO: 旧 DrawCharacterModel() の中身を移設
(void)modelHandle; (void)t;
}CharacterVfx.h
#pragma once
class CharacterVfx {
public:
void InitFromCsv(const char* path);
void SetChargingEffect();
void SetFullChargeEffect();
void SetAttackLocusEffect();
void SetHitEffect();
void SetFenceHitEffect();
void Tick(float dt);
};CharacterVfx.cpp
#include "CharacterVfx.h"
void CharacterVfx::InitFromCsv(const char* ){}
void CharacterVfx::SetChargingEffect(){}
void CharacterVfx::SetFullChargeEffect(){}
void CharacterVfx::SetAttackLocusEffect(){}
void CharacterVfx::SetHitEffect(){}
void CharacterVfx::SetFenceHitEffect(){}
void CharacterVfx::Tick(float){ }CharacterShadow.h
#pragma once
struct Transform;
class CharacterShadow {
public:
void Init();
void Tick(const Transform& t);
void Draw() const;
};CharacterShadow.cpp
#include "CharacterShadow.h"
void CharacterShadow::Init(){}
void CharacterShadow::Tick(const Transform&){ }
void CharacterShadow::Draw() const { }CharacterAir.h
#pragma once
class CharacterAir {
public:
void TickGravity(float dt);
void RequestJump();
bool IsJumpBuffered() const { return false; }
};CharacterAir.cpp
#include "CharacterAir.h"
void CharacterAir::TickGravity(float){ }
void CharacterAir::RequestJump(){ }CharacterForward.h
#pragma once
struct Vec3; struct Transform;
class CharacterForward {
public:
void Tick(const Transform& t);
const Vec3& Front() const { return front_; }
private:
Vec3 front_{}; // 右手系/左手系は既存に合わせる
};CharacterForward.cpp
#include "CharacterForward.h"
void CharacterForward::Tick(const Transform&){ /* TODO: 回転→前方更新 */ }CharacterMovement.h
#pragma once
struct Vec3; struct Transform; struct MoveParam;
class CharacterMovement {
public:
void Tick(float dt, const MoveParam& prm, Transform& t);
void MoveConfirm(Transform& t);
bool IsOutsideStage(const Transform& t) const; // 必要ならステージ参照を注入
// 減速/加速系
static void Deceleration(float& v, float amount, float dt);
static void FrictionDeceleration(float& v, float amount, float dt);
static void AccelerationStop(float& v, float threshold);
static bool IsDashStop(float v, float threshold);
};CharacterMovement.cpp
#include "CharacterMovement.h"
void CharacterMovement::Tick(float, const MoveParam&, Transform&){ }
void CharacterMovement::MoveConfirm(Transform&){ }
bool CharacterMovement::IsOutsideStage(const Transform&) const { return false; }
void CharacterMovement::Deceleration(float& v, float a, float dt){ v -= a*dt; }
void CharacterMovement::FrictionDeceleration(float& v, float a, float dt){ v -= a*dt; }
void CharacterMovement::AccelerationStop(float& v, float th){ if(fabsf(v)<th) v=0.0f; }
bool CharacterMovement::IsDashStop(float v, float th){ return fabsf(v)<=th; }CharacterRotate.h
#pragma once
struct Transform; struct RotateParam;
class CharacterRotate {
public:
void Tick(float dt, const RotateParam& prm, Transform& t);
void RotateDirectionVector(Transform& t);
void MoveRotateX(Transform& t);
void MoveRotateXReverse(Transform& t);
void FastRotateX(Transform& t);
void FastRotateXReverse(Transform& t);
void RotateXStop(Transform& t);
};CharacterRotate.cpp
#include "CharacterRotate.h"
void CharacterRotate::Tick(float, const RotateParam&, Transform&){ }
void CharacterRotate::RotateDirectionVector(Transform&){ }
void CharacterRotate::MoveRotateX(Transform&){ }
void CharacterRotate::MoveRotateXReverse(Transform&){ }
void CharacterRotate::FastRotateX(Transform&){ }
void CharacterRotate::FastRotateXReverse(Transform&){ }
void CharacterRotate::RotateXStop(Transform&){ }CharacterCharge.h
#pragma once
struct Transform;
class CharacterCharge {
public:
void Charging(float dt);
void ChargeRelease();
void ChargeReset();
void SetArrow();
void DrawArrow(const Transform& t) const;
};CharacterCharge.cpp
#include "CharacterCharge.h"
void CharacterCharge::Charging(float){ }
void CharacterCharge::ChargeRelease(){ }
void CharacterCharge::ChargeReset(){ }
void CharacterCharge::SetArrow(){ }
void CharacterCharge::DrawArrow(const Transform&) const { }CharacterHit.h
#pragma once
struct Transform; struct HitParam;
class CharacterHit {
public:
void Reflect(const Transform& hitNormal);
void KnockBack(float power);
bool IsKnockBackEnd() const { return true; }
void KnockBackVelocityReset();
void InvincibilityTimeCalculation(float dt, const HitParam& prm);
};CharacterHit.cpp
#include "CharacterHit.h"
void CharacterHit::Reflect(const Transform&){ }
void CharacterHit::KnockBack(float){ }
void CharacterHit::KnockBackVelocityReset(){ }
void CharacterHit::InvincibilityTimeCalculation(float, const HitParam&){ }CharacterFence.h
#pragma once
struct Transform; struct FenceHitParam;
class CharacterFence {
public:
void GetWireNormal(/* args: 環境からの法線取得に必要な情報 */);
void FenceReflect(const FenceHitParam& prm);
void NotifyFenceHit();
};CharacterFence.cpp
#include "CharacterFence.h"
void CharacterFence::GetWireNormal(){ }
void CharacterFence::FenceReflect(const FenceHitParam&){ }
void CharacterFence::NotifyFenceHit(){ }CharacterCsvLoader.h
#pragma once
#include "CharacterParams.h"
class CharacterCsvLoader {
public:
bool Load(const char* csvPath, InitializeParam& init, MoveParam& move,
RotateParam& rot, JumpParam& jump, HitParam& hit,
FenceHitParam& fence, ShadowParam& shadow);
};CharacterCsvLoader.cpp
#include "CharacterCsvLoader.h"
bool CharacterCsvLoader::Load(const char*, InitializeParam&, MoveParam&, RotateParam&, JumpParam&, HitParam&, FenceHitParam&, ShadowParam&){
// TODO: 旧 SetCSVStatus() を分解して移設
return true;
}CharacterObserver.h
#pragma once
#include <vector>
class ICharacterObserver { public: virtual ~ICharacterObserver() = default; /* OnHit/OnDie など */ };
class CharacterObserver {
public:
void AddObserver(ICharacterObserver* o);
void RemoveObserver(ICharacterObserver* o);
private:
std::vector<ICharacterObserver*> obs_;
};CharacterObserver.cpp
#include "CharacterObserver.h"
#include <algorithm>
void CharacterObserver::AddObserver(ICharacterObserver* o){ obs_.push_back(o); }
void CharacterObserver::RemoveObserver(ICharacterObserver* o){
obs_.erase(std::remove(obs_.begin(), obs_.end(), o), obs_.end());
}CharacterAssets.h
#pragma once
class CharacterAssets {
public:
void Init(); // SE / モデル / マテリアルのロード
int ArrowModel() const { return arrowModel_; }
private:
int arrowModel_ = -1; // 例:DxLib/自作Engineのハンドル
};CharacterAssets.cpp
#include "CharacterAssets.h"
void CharacterAssets::Init(){ /* TODO: 旧 InitArrow()/SEロード等を移設 */ }必要なら 各クラス間の依存を注入する小さな Facade(Character 本体) の見本も追加できます。既存
Character.h/.cppにメンバとしてこれらを持ち、Update/Drawで順に呼ぶだけの薄い制御層にしてください。
🧰 Facade 見本:Character 本体(依存注入・薄い制御層)
既存のエンジンフック(Init/Update/FixedUpdate/Render/OnCollision 等)を想定。
依存は コンストラクタ注入 or Setter 注入で受け取り、各モジュールへ渡します。
Character.h(Facade)
#pragma once
#include <memory>
#include <string>
#include "CharacterParams.h"
#include "CharacterModelBlink.h"
#include "CharacterVfx.h"
#include "CharacterShadow.h"
#include "CharacterAir.h"
#include "CharacterForward.h"
#include "CharacterMovement.h"
#include "CharacterRotate.h"
#include "CharacterCharge.h"
#include "CharacterHit.h"
#include "CharacterFence.h"
#include "CharacterCsvLoader.h"
#include "CharacterObserver.h"
// ---- Engine 側(前方宣言にして依存を最小化) ----
struct Transform; // 既存の Transform 型
class IPhysics; // レイ/衝突問合せ(エンジン提供)
class IStageQuery; // 地形/柵/足場の問合せ
class IAudio; // SE 再生命令
class ITime; // deltaTime 提供(必要なら)
class Character /* : public GameObject 等 */ {
public:
struct Deps { IPhysics* physics{}; IStageQuery* stage{}; IAudio* audio{}; ITime* time{}; };
explicit Character(const Deps& deps);
~Character();
// --- ライフサイクル(エンジンのフックから呼ばれる想定) ---
bool Init(const char* csvPath); // パラメータ読み込み + アセット初期化
void Update(); // 1フレーム更新(順序制御のみ)
void FixedUpdate(float fixedDt); // 物理刻みが分かれている場合に使用
void Render() const; // 影 + モデル点滅描画など
// --- 衝突・イベント ---
void OnCollision(/* Engine の接触情報を薄く受ける */);
void OnFenceHit();
void OnTakeDamage(int dmg); // 被弾イベント入口
// --- 状態アクセス(UI 等から参照) ---
const Transform& GetTransform() const { return *transform_; }
int HP() const { return hp_; }
// --- Optional: Setter 注入(コンストラクタ後に差し替えたい依存がある場合) ---
void SetDeps(const Deps& d) { deps_ = d; }
private:
// ---- 依存/データ ----
Deps deps_{};
Transform* transform_{}; // 位置/回転/スケール:エンジン所有を生ポインタで参照(または保持)
InitializeParam init_{}; MoveParam move_{}; RotateParam rot_{}; JumpParam jump_{};
HitParam hitp_{}; FenceHitParam fencep_{}; ShadowParam shadowp_{};
int hp_{100};
// ---- モジュール(Composition) ----
CharacterModelBlink blink_;
CharacterVfx vfx_;
CharacterShadow shadow_;
CharacterAir air_;
CharacterForward forward_;
CharacterMovement movement_;
CharacterRotate rotate_;
CharacterCharge charge_;
CharacterHit hit_;
CharacterFence fence_;
CharacterCsvLoader csv_;
CharacterObserver observer_;
// ---- 内部ユーティリティ ----
void TickRuntime_();
void DrawModel_() const;
};Character.cpp(Facade)
#include "Character.h"
// 最小限の Engine ヘッダのみを include(Physics/Stage/Audio/Time/Transform 等)
Character::Character(const Deps& deps) : deps_(deps) {}
Character::~Character() = default;
bool Character::Init(const char* csvPath) {
// 1) CSV/データローディング
if (!csv_.Load(csvPath, init_, move_, rot_, jump_, hitp_, fencep_, shadowp_)) {
return false;
}
// 2) Transform はエンジンから取得(生成/アタッチ方法は既存に合わせる)
// transform_ = Engine::CreateTransform(init_.pos, init_.rot, ...);
// 3) アセット/VFX/影などの初期化
shadow_.Init();
vfx_.InitFromCsv(csvPath); // 必要に応じて別CSV
// Audio 初期化が必要なら deps_.audio を使う
return true;
}
void Character::Update() {
// 依存の取得(必要なら):float dt = deps_.time ? deps_.time->Delta() : 1.0f/60.0f;
TickRuntime_();
}
void Character::FixedUpdate(float fixedDt) {
// 物理刻みが別のときに Movement/Hit などの一部をここで進める設計も可
(void)fixedDt;
}
void Character::Render() const {
// 影→モデル点滅描画の順
if (transform_) {
shadow_.Draw();
DrawModel_();
}
}
void Character::OnCollision() {
// エンジンの接触情報から法線・相手属性などを得て、Hit/Fence へ振り分け
// 例:fence_.FenceReflect(fencep_); hit_.InvincibilityTimeCalculation(dt, hitp_);
}
void Character::OnFenceHit() {
fence_.FenceReflect(fencep_);
vfx_.SetFenceHitEffect();
}
void Character::OnTakeDamage(int dmg) {
(void)dmg; // 必要なら HP 減算・ノックバック決定
hit_.KnockBack(hitp_.knockback);
vfx_.SetHitEffect();
}
void Character::TickRuntime_() {
// ※ 呼び順は従来どおりに維持(動作不変性の鍵)
// shadow → air → forward → movement → rotate → charge → hit → fence → vfx
if (!transform_) return;
// 例: air_ は重力/ジャンプの内部タイマを進行
air_.TickGravity(/*dt*/ 0.016f);
forward_.Tick(*transform_);
movement_.Tick(/*dt*/ 0.016f, move_, *transform_);
rotate_.Tick(/*dt*/ 0.016f, rot_, *transform_);
charge_.Charging(/*dt*/ 0.016f);
hit_.InvincibilityTimeCalculation(/*dt*/ 0.016f, hitp_);
vfx_.Tick(/*dt*/ 0.016f);
// 影の位置更新:必要なら Transform を渡す
shadow_.Tick(*transform_);
}
void Character::DrawModel_() const {
// モデルハンドルなどは Engine/Assets 管理の取得方式に合わせる
const int modelHandle = /* Engine::GetModelHandle(...) */ 0;
blink_.Draw(modelHandle, *transform_);
}使い方(例)
Character::Deps deps{ physicsSys, stageQuery, audioSys, timeSys };
Character player(deps);
player.Init("Data/Character.csv");
// game loop:
player.Update();
player.Render();ポイント
- **依存注入(Deps)**で Engine 側と疎結合化。単体テスト時にはモックを注入可能。
- Update の呼び順を固定して動作不変性を担保。差分は各モジュール内で閉じる。
- Facade は“薄い制御のみ”。ロジックや I/O をここに戻さない(肥大化の再発防止)。