Skip to main content

改良案

===== 🎯 Character 分割 実行順おすすめ(3コミットで見栄えが出る) =====

目標: 1ファイル1000行超の Character.cpp を段階的に分割し、責務を明確化。
Visual Studio プロジェクト(CMake 不使用)で、見た目を変えずにコードの「整理力」「設計力」をアピールできる構成にします。


  • 🎨 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:


📄 新規 .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 をここに戻さない(肥大化の再発防止)。