Appearance
第20回 アニメーションと音声イベント
3 / 4 プレイヤーイベントによる演出の疎結合化
このステップの目的
プレイヤーの内部で成立した出来事(ジャンプや着地)を通知構造体(PlayerEvents)として返し、キャラクターの物理運動ロジックとゲーム演出(音響やエフェクト)の依存関係を疎結合に保つ設計を実装します。
プレイヤーが音声を直接再生しない理由
ジャンプが成立した瞬間や地面に着地した瞬間を判定できるのは、物理状態を管理しているプレイヤー自身です。しかし、プレイヤー自身が効果音を直接再生する設計には以下の課題があります。
- 責務の過多: プレイヤーの責務は「入力に応じた移動・地形衝突の解決・アニメーションの更新」です。ここに音響ハンドルの管理や再生命令が混ざるとクラスの役割が肥大化します。
- 結合度の増大: プレイヤーが
SoundManagerに直接依存することになり、プレイヤー単体の動作確認やテストが難しくなります。 - 拡張性の低下: 将来「着地時に砂煙エフェクトを出す」「ジャンプ時に画面をわずかに振動させる」といった演出を追加する際、プレイヤーにエフェクトマネージャやカメラインスタンスまで渡す必要が生じます。
text
【密結合な設計】
Player ---> SoundManager(音を鳴らす)
---> ParticleManager(煙を出す)
---> Camera(画面を揺らす)
【イベント通知による疎結合な設計】
Player ---> [PlayerEvents](起きた出来事を返す)
|
v
Game ---> SoundManager / ParticleManager / Camera へ配分PlayerEvents によるイベント通知パターン
この設計上の課題を解決するため、そのフレームで発生した出来事を通知する軽量な構造体 PlayerEvents を定義します。
Player.hcpp
struct PlayerEvents
{
bool jumped = false;
bool landed = false;
};プレイヤーの Update() は、更新処理の過程で成立した出来事をこの構造体に記録し、戻り値として返却します。
Player.cppcpp
PlayerEvents Player::Update(const Input& input, const Stage& stage,
float deltaTime)
{
PlayerEvents events;
UpdateInput(input, deltaTime, events);
UpdatePhysics(stage, deltaTime, events);
UpdateAnimation();
return events;
}これらのフラグは、条件が成立した1フレームのみ true となり、次フレームにはリセットされます。
イベントの受け取りと演出の振り分け
ゲーム統括層である Game クラスは、プレイヤーの更新結果として返された PlayerEvents を受け取り、演出処理(効果音の再生など)へ振り分けます。
Game.cppcpp
const PlayerEvents events = player_.Update(input_, stage_, deltaTime);
HandlePlayerEvents(events);Game.cppcpp
void Game::HandlePlayerEvents(const PlayerEvents& events)
{
if (events.jumped)
sounds_.PlaySe(SoundId::Jump);
if (events.landed)
sounds_.PlaySe(SoundId::Land);
}プレイヤーは「ジャンプが成立した」「着地した」という事実のみを通知し、それに対してどのような演出(効果音やエフェクト)を付与するかはゲーム統括側が担当します。これにより、物理移動と演出の責務が分離され、コードの保守性が向上します。
チェックリスト
- プレイヤーが音響システムに直接依存せず、イベント通知構造体を介して出来事を伝達する利点を説明できる
- ジャンプ成立時および着地時の1フレーム限定フラグを正しく設定し、呼び出し元へ返却できる
-
Gameクラス側でイベントを受信し、適切な効果音再生処理へディスパッチできる