Appearance
第20回 アニメーションと音声イベント
4 / 4 ジャンプと着地に音を付ける
このステップの目的
音声の読み込みを SoundManager へまとめ、発生したイベントに応じて音を再生します。
音声の対応表を用意する
SoundManager.h へ SoundId を定義し、SoundManager.cpp の対応表へ音声を登録します。第20回で使う音は PlayingBgm、Jump、Land の3種類です。
SoundManager.cpp(名前空間内の定義)cpp
struct SoundDefinition
{
SoundId id;
const wchar_t* path;
int volume;
};
constexpr SoundDefinition soundDefinitions[] =
{
{ SoundId::PlayingBgm, L"Assets/Sounds/PlayingBgm.wav", 140 },
{ SoundId::Jump, L"Assets/Sounds/Jump.wav", 200 },
{ SoundId::Land, L"Assets/Sounds/Land.wav", 180 }
};volume は 0〜255 の音量です。件数の static_assert と登録内容を調べる補助関数は、参考実装を利用します。BGMとSEの区別は、再生するときに PlayBgm() か PlaySe() を選んで指定します。
Gameから音声を利用する
Game.h で SoundManager.h をインクルードし、sounds_ をメンバへ追加します。Initialize() で sounds_.LoadAll() の結果を確認し、初期化成功後に sounds_.PlayBgm(SoundId::PlayingBgm) を呼び出します。
Game::Update() で Player::Update() の戻り値を受け取り、HandlePlayerEvents() へ渡します。
Game.cppcpp
void Game::HandlePlayerEvents(const PlayerEvents& events)
{
if (events.jumped)
sounds_.PlaySe(SoundId::Jump);
if (events.landed)
sounds_.PlaySe(SoundId::Land);
}ジャンプキーを押しただけでは音を鳴らしません。Player がジャンプを成立させた場合のみ鳴らします。
再生方法を確認する
PlayBgm() は再生中のBGMを停止してループ再生を開始します。PlaySe() は1回再生を開始してゲーム処理へ戻ります。同じ音を短い間隔で鳴らした場合の聞こえ方も確認します。
SoundManager.cppcpp
void SoundManager::PlayBgm(SoundId id)
{
if (currentBgm_ == id)
return;
StopBgm();
PlaySoundMem(handles_[static_cast<std::size_t>(id)], DX_PLAYTYPE_LOOP);
currentBgm_ = id;
}
void SoundManager::PlaySe(SoundId id) const
{
PlaySoundMem(handles_[static_cast<std::size_t>(id)], DX_PLAYTYPE_BACK);
}チェックリスト
- プレイ開始時にBGMが鳴る
- ジャンプと着地の瞬間に、それぞれの音が1回鳴る
- 空中で Z キーを押してもジャンプ音が鳴らない
参考実装
今回追加・変更するファイルの一例です。宣言や補助処理を確認したいときに開いてください。自分のコードへ必要な変更を反映し、既存の調整値や素材をそのまま上書きしないようにします。
Source/Platformer/Game.cpp
Source/Platformer/Game.cppcpp
#include <Windows.h>
#include <string>
#include "DxLib.h"
#include "Config/ApplicationConfig.h"
#include "Platformer/Game.h"
#include "Platformer/Tuning/GameTuning.h"
namespace dxgame::platformer
{
bool Game::Initialize()
{
if (!images_.LoadAll())
return false;
const ImageResource& playerImage = images_.Get(ImageId::Player);
if (playerImage.width % tuning::animation::PlayerFrameCount != 0)
{
MessageBoxW(nullptr,
L"Player.pngの横幅を8で割り切れません。",
L"Player Image Error", MB_OK);
return false;
}
if (!sounds_.LoadAll())
return false;
constexpr wchar_t StagePath[] = L"Assets/Maps/Stage01.csv";
if (!stage_.Initialize(StagePath))
{
const std::wstring message = std::wstring(StagePath) + L"\n" +
stage_.GetLastLoadError();
MessageBoxW(nullptr, message.c_str(), L"Map Load Error", MB_OK);
return false;
}
ResetStage();
sounds_.PlayBgm(SoundId::PlayingBgm);
return true;
}
void Game::MainLoop()
{
// 小数分の待ち時間を次フレームへ持ち越す
double nextFrame = GetNowCount();
while (ProcessMessage() == 0)
{
input_.Update();
if (input_.IsHeld(Key::Escape))
break;
Update(dxgame::config::DeltaTime);
ClearDrawScreen();
Draw();
ScreenFlip();
nextFrame += dxgame::config::FrameMilliseconds;
const int waitMilliseconds =
static_cast<int>(nextFrame - GetNowCount());
if (waitMilliseconds > 0)
WaitTimer(waitMilliseconds);
else
nextFrame = GetNowCount();
}
}
void Game::Update(float deltaTime)
{
UpdateDebugToggle();
const PlayerEvents events = player_.Update(input_, stage_, deltaTime);
HandlePlayerEvents(events);
ResetAfterFall();
camera_.Update(player_.GetPosition(), stage_.GetWorldWidth());
}
void Game::Draw() const
{
DrawBackground();
stage_.Draw(camera_.GetX(), images_);
player_.Draw(camera_, images_);
DrawHud();
DrawDebug();
}
void Game::DrawBackground() const
{
images_.Draw(ImageId::Background, { 0.0f, 0.0f,
static_cast<float>(dxgame::config::ScreenWidth),
static_cast<float>(dxgame::config::ScreenHeight) });
}
void Game::DrawHud() const
{
const int textColor = GetColor(255, 255, 255);
DrawString(12, 10, L"LEFT/RIGHT: MOVE", textColor);
DrawString(12, 30, L"Z: JUMP", textColor);
DrawString(12, 50, L"F1: DEBUG ESC: QUIT", textColor);
}
void Game::DrawDebug() const
{
if (!debugVisible_)
return;
stage_.DrawCollisionDebug(camera_.GetX());
const dxgame::Rect playerDrawRect =
camera_.WorldToScreen(player_.GetDrawRect());
DrawBox(static_cast<int>(playerDrawRect.x),
static_cast<int>(playerDrawRect.y),
static_cast<int>(playerDrawRect.x + playerDrawRect.width),
static_cast<int>(playerDrawRect.y + playerDrawRect.height),
GetColor(80, 240, 255), FALSE);
const dxgame::Rect playerRect =
camera_.WorldToScreen(player_.GetHitbox());
DrawBox(static_cast<int>(playerRect.x), static_cast<int>(playerRect.y),
static_cast<int>(playerRect.x + playerRect.width),
static_cast<int>(playerRect.y + playerRect.height),
GetColor(255, 80, 80), FALSE);
const dxgame::Rect goalDrawRect =
camera_.WorldToScreen(stage_.GetGoalDrawRect());
DrawBox(static_cast<int>(goalDrawRect.x),
static_cast<int>(goalDrawRect.y),
static_cast<int>(goalDrawRect.x + goalDrawRect.width),
static_cast<int>(goalDrawRect.y + goalDrawRect.height),
GetColor(80, 240, 255), FALSE);
const dxgame::Rect goalRect =
camera_.WorldToScreen(stage_.GetGoalTrigger());
DrawBox(static_cast<int>(goalRect.x), static_cast<int>(goalRect.y),
static_cast<int>(goalRect.x + goalRect.width),
static_cast<int>(goalRect.y + goalRect.height),
GetColor(255, 220, 40), FALSE);
DrawBox(8, 76, 300, 178, GetColor(0, 0, 0), TRUE);
const int textColor = GetColor(255, 255, 255);
const dxgame::Vector2& position = player_.GetPosition();
const dxgame::Vector2& velocity = player_.GetVelocity();
DrawFormatString(14, 84, textColor,
L"pos: %.1f, %.1f", position.x, position.y);
DrawFormatString(14, 104, textColor,
L"velocity: %.2f, %.2f", velocity.x, velocity.y);
DrawFormatString(14, 124, textColor,
L"ground: %d camera: %d", player_.IsOnGround() ? 1 : 0,
camera_.GetX());
DrawFormatString(14, 144, textColor, L"csv: %d",
stage_.IsLoadedFromCsv() ? 1 : 0);
DrawString(14, 164,
L"cyan: draw red/yellow: hit blue: solid", textColor);
}
void Game::UpdateDebugToggle()
{
if (input_.IsPressed(Key::Debug))
debugVisible_ = !debugVisible_;
}
void Game::ResetAfterFall()
{
if (player_.GetPosition().y <=
stage_.GetWorldHeight() + tuning::stage::FallResetMargin)
{
return;
}
ResetStage();
}
void Game::ResetStage()
{
player_.Reset(stage_.GetPlayerSpawn());
camera_.Update(player_.GetPosition(), stage_.GetWorldWidth());
}
void Game::HandlePlayerEvents(const PlayerEvents& events)
{
if (events.jumped)
sounds_.PlaySe(SoundId::Jump);
if (events.landed)
sounds_.PlaySe(SoundId::Land);
}
}Source/Platformer/Game.h
Source/Platformer/Game.hcpp
#pragma once
#include "Platformer/Camera.h"
#include "Platformer/ImageManager.h"
#include "Platformer/Input.h"
#include "Platformer/Player.h"
#include "Platformer/SoundManager.h"
#include "Platformer/Stage.h"
namespace dxgame::platformer
{
// 入力、プレイヤー、ステージ、描画順を調整する
class Game
{
public:
// 必須画像、音声、CSVを読み込む
bool Initialize();
// ウィンドウ終了まで更新と描画を繰り返す
void MainLoop();
private:
void Update(float deltaTime);
void Draw() const;
void DrawBackground() const;
void DrawHud() const;
void DrawDebug() const;
void UpdateDebugToggle();
void ResetAfterFall();
void ResetStage();
void HandlePlayerEvents(const PlayerEvents& events);
Input input_;
ImageManager images_;
SoundManager sounds_;
Stage stage_;
Camera camera_;
Player player_;
bool debugVisible_ = false;
};
}Source/Platformer/Player.cpp
Source/Platformer/Player.cppcpp
#include <algorithm>
#include <cmath>
#include "Platformer/Camera.h"
#include "Platformer/ImageManager.h"
#include "Platformer/Input.h"
#include "Platformer/Player.h"
#include "Platformer/Stage.h"
#include "Platformer/Tuning/GameTuning.h"
namespace dxgame::platformer
{
void Player::Reset(const dxgame::Vector2& position)
{
position_ = position;
velocity_ = {};
remainderX_ = 0.0f;
remainderY_ = 0.0f;
onGround_ = false;
animation_ = PlayerAnimation::Idle;
animationFrame_ = 0;
animationTimer_ = 0;
facingRight_ = true;
}
PlayerEvents Player::Update(const Input& input, const Stage& stage,
float deltaTime)
{
PlayerEvents events;
UpdateInput(input, deltaTime, events);
UpdatePhysics(stage, deltaTime, events);
UpdateAnimation();
return events;
}
void Player::Draw(const Camera& camera, const ImageManager& images) const
{
const dxgame::Rect screenRect = camera.WorldToScreen(GetDrawRect());
images.DrawFrame(ImageId::Player, GetAnimationFrame(),
tuning::animation::PlayerFrameCount, screenRect, !facingRight_);
}
dxgame::Rect Player::GetHitbox() const
{
return tuning::player::Hitbox.MovedBy(position_);
}
dxgame::Rect Player::GetDrawRect() const
{
return tuning::player::DrawRect.MovedBy(position_);
}
const dxgame::Vector2& Player::GetPosition() const
{
return position_;
}
const dxgame::Vector2& Player::GetVelocity() const
{
return velocity_;
}
bool Player::IsOnGround() const
{
return onGround_;
}
PlayerAnimation Player::GetAnimation() const
{
return animation_;
}
int Player::GetAnimationFrame() const
{
switch (animation_)
{
case PlayerAnimation::Idle:
return animationFrame_;
case PlayerAnimation::Run:
return 2 + animationFrame_;
case PlayerAnimation::Jump:
return 6;
case PlayerAnimation::Fall:
return 7;
}
return 0;
}
void Player::UpdateInput(const Input& input, float deltaTime,
PlayerEvents& events)
{
const bool moveLeft = input.IsHeld(Key::Left);
const bool moveRight = input.IsHeld(Key::Right);
if (moveLeft && !moveRight)
{
velocity_.x -= tuning::player::Acceleration * deltaTime;
facingRight_ = false;
}
else if (moveRight && !moveLeft)
{
velocity_.x += tuning::player::Acceleration * deltaTime;
facingRight_ = true;
}
else if (velocity_.x > 0.0f)
{
velocity_.x = std::max(0.0f,
velocity_.x - tuning::player::Friction * deltaTime);
}
else if (velocity_.x < 0.0f)
{
velocity_.x = std::min(0.0f,
velocity_.x + tuning::player::Friction * deltaTime);
}
velocity_.x = std::clamp(velocity_.x,
-tuning::player::MaxSpeed, tuning::player::MaxSpeed);
if (onGround_ && input.IsPressed(Key::Jump))
{
velocity_.y = tuning::player::JumpPower;
remainderY_ = 0.0f;
onGround_ = false;
events.jumped = true;
}
}
void Player::UpdatePhysics(const Stage& stage, float deltaTime,
PlayerEvents& events)
{
const bool wasOnGround = onGround_;
const float gravity = velocity_.y < 0.0f
? tuning::player::RiseGravity
: tuning::player::FallGravity;
velocity_.y += gravity * deltaTime;
velocity_.y = std::min(velocity_.y,
tuning::player::MaxFallSpeed);
MoveHorizontal(stage, deltaTime);
MoveVertical(stage, deltaTime);
dxgame::Rect groundProbe = GetHitbox();
groundProbe.y += 1.0f;
onGround_ = stage.IsSolidAtRect(groundProbe);
if (onGround_ && velocity_.y >= 0.0f)
{
velocity_.y = 0.0f;
remainderY_ = 0.0f;
}
if (!wasOnGround && onGround_)
events.landed = true;
}
void Player::UpdateAnimation()
{
PlayerAnimation nextAnimation = PlayerAnimation::Idle;
if (!onGround_)
{
nextAnimation = velocity_.y < 0.0f
? PlayerAnimation::Jump
: PlayerAnimation::Fall;
}
else if (std::abs(velocity_.x) > 0.1f)
{
nextAnimation = PlayerAnimation::Run;
}
if (nextAnimation != animation_)
{
animation_ = nextAnimation;
animationFrame_ = 0;
animationTimer_ = 0;
}
const int frameCount = animation_ == PlayerAnimation::Idle
? tuning::animation::IdleFrameCount
: animation_ == PlayerAnimation::Run
? tuning::animation::RunFrameCount
: 1;
const int frameDuration = animation_ == PlayerAnimation::Idle
? tuning::animation::IdleFrameDuration
: tuning::animation::RunFrameDuration;
animationTimer_++;
if (animationTimer_ >= frameDuration)
{
animationTimer_ = 0;
animationFrame_ = (animationFrame_ + 1) % frameCount;
}
}
void Player::MoveHorizontal(const Stage& stage, float deltaTime)
{
remainderX_ += velocity_.x * deltaTime;
const int pixelMove = static_cast<int>(remainderX_);
remainderX_ -= static_cast<float>(pixelMove);
if (pixelMove == 0)
{
const int probeMove = velocity_.x > 0.0f ? 1
: velocity_.x < 0.0f ? -1 : 0;
if (probeMove != 0)
{
dxgame::Rect probe = GetHitbox();
if (stage.ResolveHorizontal(probe, probeMove))
{
velocity_.x = 0.0f;
remainderX_ = 0.0f;
}
}
return;
}
dxgame::Rect movedRect = GetHitbox();
if (stage.ResolveHorizontal(movedRect, pixelMove))
{
velocity_.x = 0.0f;
remainderX_ = 0.0f;
}
position_.x = movedRect.GetCenter().x;
}
void Player::MoveVertical(const Stage& stage, float deltaTime)
{
remainderY_ += velocity_.y * deltaTime;
const int pixelMove = static_cast<int>(remainderY_);
remainderY_ -= static_cast<float>(pixelMove);
if (pixelMove == 0)
return;
dxgame::Rect movedRect = GetHitbox();
if (stage.ResolveVertical(movedRect, pixelMove))
{
velocity_.y = 0.0f;
remainderY_ = 0.0f;
}
position_.y = movedRect.GetCenter().y;
}
}Source/Platformer/Player.h
Source/Platformer/Player.hcpp
#pragma once
#include "DxGame/Rect.h"
#include "DxGame/Vector2.h"
namespace dxgame::platformer
{
class Camera;
class ImageManager;
class Input;
class Stage;
// Player内で成立した出来事を、音や演出へ接続するためにGameへ返す
struct PlayerEvents
{
bool jumped = false;
bool landed = false;
};
enum class PlayerAnimation
{
Idle,
Run,
Jump,
Fall
};
// プレイヤーの位置、速度、接地状態、表示アニメーションを管理する
class Player
{
public:
// プレイ開始時の状態へ戻す
void Reset(const dxgame::Vector2& position);
// 入力、重力、地形衝突、アニメーションを1フレーム更新する
PlayerEvents Update(const Input& input, const Stage& stage,
float deltaTime);
// カメラを考慮してプレイヤーを描画する
void Draw(const Camera& camera, const ImageManager& images) const;
dxgame::Rect GetHitbox() const;
dxgame::Rect GetDrawRect() const;
const dxgame::Vector2& GetPosition() const;
const dxgame::Vector2& GetVelocity() const;
bool IsOnGround() const;
PlayerAnimation GetAnimation() const;
int GetAnimationFrame() const;
private:
void UpdateInput(const Input& input, float deltaTime,
PlayerEvents& events);
void UpdatePhysics(const Stage& stage, float deltaTime,
PlayerEvents& events);
void UpdateAnimation();
void MoveHorizontal(const Stage& stage, float deltaTime);
void MoveVertical(const Stage& stage, float deltaTime);
dxgame::Vector2 position_ = {};
dxgame::Vector2 velocity_ = {};
float remainderX_ = 0.0f;
float remainderY_ = 0.0f;
bool onGround_ = false;
PlayerAnimation animation_ = PlayerAnimation::Idle;
int animationFrame_ = 0;
int animationTimer_ = 0;
bool facingRight_ = true;
};
}Source/Platformer/SoundManager.cpp
Source/Platformer/SoundManager.cppcpp
#include <Windows.h>
#include <array>
#include <string>
#include "DxLib.h"
#include "Platformer/SoundManager.h"
namespace dxgame::platformer
{
namespace
{
struct SoundDefinition
{
SoundId id;
const wchar_t* path;
int volume;
};
constexpr SoundDefinition soundDefinitions[] =
{
{ SoundId::PlayingBgm, L"Assets/Sounds/PlayingBgm.wav", 140 },
{ SoundId::Jump, L"Assets/Sounds/Jump.wav", 200 },
{ SoundId::Land, L"Assets/Sounds/Land.wav", 180 }
};
constexpr std::size_t SoundIdCount =
static_cast<std::size_t>(SoundId::Count);
constexpr std::size_t SoundDefinitionCount =
sizeof(soundDefinitions) / sizeof(soundDefinitions[0]);
static_assert(SoundDefinitionCount == SoundIdCount,
"Register one soundDefinitions entry for every SoundId.");
bool HasCompleteSoundDefinitions()
{
std::array<bool, SoundIdCount> registered = {};
for (const SoundDefinition& definition : soundDefinitions)
{
const std::size_t index =
static_cast<std::size_t>(definition.id);
if (index >= SoundIdCount || registered[index] ||
definition.path == nullptr ||
definition.path[0] == L'\0' ||
definition.volume < 0 || definition.volume > 255)
{
return false;
}
registered[index] = true;
}
for (bool isRegistered : registered)
{
if (!isRegistered)
return false;
}
return true;
}
}
SoundManager::SoundManager()
{
handles_.fill(-1);
}
SoundManager::~SoundManager()
{
Release();
}
bool SoundManager::LoadAll()
{
Release();
if (!HasCompleteSoundDefinitions())
{
MessageBoxW(nullptr,
L"SoundIdと音声定義の登録に重複、不足、または不正な音量があります。",
L"Sound Definition Error", MB_OK);
return false;
}
for (const SoundDefinition& definition : soundDefinitions)
{
const std::size_t index = static_cast<std::size_t>(definition.id);
handles_[index] = LoadSoundMem(definition.path);
if (handles_[index] == -1)
{
std::wstring message = definition.path;
message += L" の読み込みに失敗しました。";
MessageBoxW(nullptr, message.c_str(), L"Sound Load Error", MB_OK);
Release();
return false;
}
ChangeVolumeSoundMem(definition.volume, handles_[index]);
}
return true;
}
void SoundManager::PlayBgm(SoundId id)
{
if (currentBgm_ == id)
return;
StopBgm();
PlaySoundMem(handles_[static_cast<std::size_t>(id)], DX_PLAYTYPE_LOOP);
currentBgm_ = id;
}
void SoundManager::PlaySe(SoundId id) const
{
PlaySoundMem(handles_[static_cast<std::size_t>(id)], DX_PLAYTYPE_BACK);
}
void SoundManager::StopBgm()
{
if (currentBgm_ == SoundId::Count)
return;
StopSoundMem(handles_[static_cast<std::size_t>(currentBgm_)]);
currentBgm_ = SoundId::Count;
}
void SoundManager::Release()
{
StopBgm();
for (int& handle : handles_)
{
if (handle != -1)
DeleteSoundMem(handle);
handle = -1;
}
}
}Source/Platformer/SoundManager.h
Source/Platformer/SoundManager.hcpp
#pragma once
#include <array>
#include <cstddef>
namespace dxgame::platformer
{
enum class SoundId
{
PlayingBgm,
Jump,
Land,
Count
};
// DXライブラリの音声ハンドルを所有し、BGMとSEを管理する
class SoundManager
{
public:
SoundManager();
~SoundManager();
SoundManager(const SoundManager&) = delete;
SoundManager& operator=(const SoundManager&) = delete;
// 必須音声をすべて読み込む
bool LoadAll();
// BGMをループ再生する。同じBGMは再起動しない
// BGMをループ再生する(同一BGMの多重再生を防止)
void PlayBgm(SoundId id);
// SEを一度再生する
void PlaySe(SoundId id) const;
// 再生中のBGMを停止する
void StopBgm();
private:
void Release();
static constexpr std::size_t soundCount =
static_cast<std::size_t>(SoundId::Count);
std::array<int, soundCount> handles_ = {};
SoundId currentBgm_ = SoundId::Count;
};
}Source/Platformer/Tuning/GameTuning.h
Source/Platformer/Tuning/GameTuning.hcpp
#pragma once
#include "Config/ApplicationConfig.h"
#include "DxGame/Rect.h"
namespace dxgame::platformer::tuning
{
// 距離はピクセル、時間は秒、速度はピクセル/秒、
// 加速度と重力はピクセル/秒²を基本単位とする。
// 加速度と重力はピクセル/秒²を基本単位とする
namespace player
{
// 中心座標を基準にした表示範囲と地形判定範囲
// 画像本来の大きさとは独立して調整する
inline constexpr Rect DrawRect =
Rect::Centered(32.0f, 48.0f).MovedBy({ 0.0f, -8.0f });
inline constexpr Rect Hitbox = Rect::Centered(24.0f, 30.0f);
// 左右入力中の加速度、最大速度、無入力時の減速度
// 速度の単位はピクセル/秒、加減速度はピクセル/秒²
inline constexpr float Acceleration = 1260.0f;
inline constexpr float MaxSpeed = 270.0f;
inline constexpr float Friction = 900.0f;
// 上昇中と落下中の重力、および最大落下速度
// 重力の単位はピクセル/秒²、速度はピクセル/秒
inline constexpr float RiseGravity = 1620.0f;
inline constexpr float FallGravity = 2700.0f;
inline constexpr float MaxFallSpeed = 720.0f;
// ジャンプ開始時の上向き速度 単位はピクセル/秒
// ジャンプ開始時の上向き速度(単位:ピクセル/秒)
// 画面上方向はY座標が減るため負の値にする
inline constexpr float JumpPower = -660.0f;
}
namespace stage
{
// タイル画像の元サイズとは独立した、ワールド上の1マスの大きさ
inline constexpr int TileSize = 32;
// マップ下端からこの距離を越えたら落下とみなす
inline constexpr float FallResetMargin = 128.0f;
}
namespace camera
{
// 追従対象を画面内のどのX座標へ置くか
inline constexpr float TargetScreenX =
dxgame::config::ScreenWidth * 0.5f;
}
namespace animation
{
// Player.pngに横一列で並ぶフレームの総数
inline constexpr int PlayerFrameCount = 8;
// 待機と走行に使うフレーム数
inline constexpr int IdleFrameCount = 2;
inline constexpr int RunFrameCount = 4;
// 各1枚を表示し続ける更新フレーム数
inline constexpr int IdleFrameDuration = 20;
inline constexpr int RunFrameDuration = 6;
}
namespace enemy
{
// 敵の中心座標を基準にした表示範囲と命中範囲
inline constexpr Rect DrawRect = Rect::Centered(32.0f, 32.0f);
inline constexpr Rect Hitbox = Rect::Centered(28.0f, 28.0f);
}
namespace goal
{
// ゴール画像の表示範囲と、クリア成立に使う範囲
inline constexpr Rect DrawRect = Rect::Centered(32.0f, 32.0f);
inline constexpr Rect Trigger = Rect::Centered(20.0f, 28.0f);
}
}