ケーススタディ 15: 照準制御¶
TPS/アクションの「狙う」機能——エイムアシスト、ロックオン、カメラ追従の設計です。数学とゲームフィールの絡む題材で、Update順序(Game Loop)が品質を左右します。
素朴な実装¶
// C++20
void Player::UpdateAim(const FrameInput& in, float dt) {
aimYaw_ += in.lookX * sensitivity_ * dt;
aimPitch_ = std::clamp(aimPitch_ + in.lookY * sensitivity_ * dt, -80.f, 80.f);
}
素の視点操作。ここまでは全ゲーム共通です。
機能追加で破綻する過程¶
- エイムアシスト(敵に吸い付く)→ 入力補正がUpdateAimに直書きされ、アシストの強さ調整のたび操作感全体が壊れる
- ロックオン(ボタンでターゲット固定)→ 「自由視点とロックオンの排他」がフラグ分岐に。ロックオン対象の死亡・画面外離脱の処理が散らばる(dangling参照問題の実戦形)
- 「乗り物では感度・制限が違う」「弓は偏差射撃補助」→ モード×補正の組み合わせがif爆発
- カメラ・キャラ回転・照準UIの更新順序が絡み、1フレーム遅れの照準ズレ(カメラが先か狙いが先か)
案A: 照準モードのFSM+補正パイプライン(定石)¶
2つの直交する軸を別の仕組みに分けます。
軸1: モード(排他的・状態遷移あり)→ [FSM](../04_game_patterns/state_machines.md)
FreeAim ⇄ LockOn ⇄ VehicleAim(遷移条件: ボタン、対象喪失、搭乗)
軸2: 入力補正(重ねがけ・調整対象)→ パイプライン(関数の列)
生入力 → [感度カーブ] → [エイムアシスト(吸着)] → [クランプ] → 最終角度
class IAimMode { // モード=State
public:
virtual ~IAimMode() = default;
virtual AimResult Update(const AimContext& ctx, float dt) = 0;
// 戻り値で遷移先を返す形は [State](../03_design_patterns/state.md) と同じ
};
// 補正はStrategyのリスト(調整はデータで)
using AimModifier = std::function<Vec2(Vec2 input, const AimContext&)>;
std::vector<AimModifier> pipeline_; // [Decoratorのリスト方式](../03_design_patterns/decorator.md)と同型
- アシストの調整=パイプラインの係数(データ)。モード追加=クラス1つ
- ロックオン対象は弱参照/ハンドルで保持し、毎フレーム有効性を確認(死亡・遮蔽・距離で解除)——ロックオンカメラ問題の本番
案B: 継承案(比較のため)¶
Player → LockOnPlayer は論外(実行時に型を変えられない)。AimController 基底からモード別派生は案AのFSMと実質同じで、健全(浅い継承+差し替え)。「Playerの派生」ではなく「Playerが持つ部品の差し替え」にするのが分かれ目(コンポジション)。
案C: パターンを使わない簡潔案¶
モードが「通常/ロックオンの2つだけ」なら:
関数2つ+対象ハンドル1つ。エイムアシストなしのアクションならこれで完成です。
更新順序の設計(破綻4への回答)¶
照準は「入力→照準→キャラ回転→カメラ→UI」の依存の鎖であり、順序を明示しないと1フレーム遅れが混入します。
- エンジンの更新枠(UnityのUpdate/LateUpdate、UEのTickGroup)にこの鎖を意図的に割り付ける: カメラはLateUpdate/PostUpdateWork——「カメラはLateUpdate」という慣習の理由がこの鎖です
- 前フレーム値を読む箇所(意図的な遅延)はコメントで明示——Double Buffer的な「どの時点の値か」の意識
規模別の判断¶
| 規模 | 推奨 |
|---|---|
| 小(視点操作のみ〜ロックオン1種) | 素朴版 → 案C |
| 中(アシスト・複数モード) | 案A(FSM+パイプライン)。係数はデータ化(調整が毎日入るため) |
| 大(対戦FPS級) | 案A+入力の固定レート処理(感度の再現性)+アシストのチューニング環境(リプレイ+可視化) |
| Unity | 案A+Cinemachine(カメラ側の状態遷移はCinemachineが持ってくれる) |
| UE | 案A+SpringArm/CameraManager。Enhanced Inputの修飾子(Modifier)が入力パイプラインの一部を担う |
この題材の教訓¶
- 「モード(排他)」と「補正(重ねがけ)」という直交する2軸を見抜いて別の構造に載せる——ステート異常(排他でない→リスト)と敵AI(排他→FSM)で学んだ判断の複合問題
- 操作感の品質は構造だけでなく更新順序で決まる。設計図に「1フレームの中の順序」を描く習慣を