第05回 プレイヤーを狙う敵弾
4 / 4 敵弾の動作確認
このステップの目的
敵弾が発射時の自機を狙うこと、速さが一定であること、プールで再利用されることを確認します。次回は被弾処理を追加します。
ゲームループへの組み込みと描画順序
完成したEnemyShotManagerをGameクラスの更新・描画ループへ接続します。
Game.cppcpp
void Game::Update(float deltaTime)
{
player.Update(input, deltaTime);
playerShots.Update(deltaTime);
enemies.Update(deltaTime);
enemyShots.Update(deltaTime);
ResolveCollisions();
Vector2 playerPosition = player.GetHitbox().GetCenter();
for (Enemy& enemy : enemies.GetAll())
{
enemy.TryShoot(enemyShots, playerPosition);
}
player.TryShoot(input, playerShots);
}
void Game::Draw() const
{
// 描画順はプレイヤー、プレイヤーの弾、敵、敵の弾の順
player.Draw(images);
playerShots.Draw(images);
enemies.Draw(images);
enemyShots.Draw(images);
DrawFormatString(16, 16, GetColor(255, 255, 255), L"SCORE %02d", score);
}敵弾を敵より手前、または自然に見える層へ描くと、どの敵から撃たれたかが分かりやすくなります。
動作確認の手順
プロジェクトをビルドして実行し、以下の項目を順に確認します。
- 敵弾の発射: 画面上から出現した敵が、機体の先端から敵弾を発射することを確認します。
- 発射時の狙い撃ち: 敵弾が発射された瞬間のプレイヤー位置へ向かってまっすぐ進むことを確認します。
- 追尾しないことの確認: 弾が発射された後に自機を左右へ移動させても、すでに飛んでいる敵弾が方向を変えて追いかけてこない(直線移動を保つ)ことを確認します。
- 斜め移動時の速度の均一性: 近い位置から撃たれた弾も、遠い位置から撃たれた弾も、すべて同じスピードで飛んでくることを確認します(正規化の確認)。
- 画面外での消滅: 画面の左右や下端を越えた敵弾が安全に消滅し、プールの空きスロットに戻ることを確認します。
この段階では、敵弾に触れてもダメージを受けずにすり抜けます。次回追加する被弾処理が、まだ実装されていないためです。
チェックリスト
- 敵が発射時点のプレイヤー位置を狙って弾を発射することを確認できる
- 敵弾が追尾せず、発射時に決まった方向へ等速直進することを確認できる
- 画面外へ出た敵弾が停止し、プールへ戻って再利用されることを確認できる
- プレイヤー弾と敵弾が独立したクラス・管理クラスで安全に管理されていることを説明できる
まとめと次回への引き継ぎ
第05回では、2点間の差分ベクトルと正規化を用いた自機狙い弾を実装しました。発射後に自機を動かして、弾が発射時の方向へ進み続けることを確認します。接触によるダメージは次回に追加します。
第06回では、プレイヤーの被弾処理と無敵時間を実装します。敵や敵弾に当たるとHPを減らし、その後の一定時間はダメージを受けないようにして連続被弾を防ぎます。
参考実装
今回追加・変更するファイルの一例です。宣言や補助処理を確認したいときに開いてください。自分のコードへ必要な変更を反映し、既存の調整値や素材をそのまま上書きしないようにします。
Source/Shooting/Enemy.cpp
Source/Shooting/Enemy.cppcpp
#include <cmath>
#include "Shooting/ImageManager.h"
#include "Shooting/Tuning/GameTuning.h"
#include "Shooting/EnemyShotManager.h"
#include "Shooting/Enemy.h"
namespace dxgame::shooting
{
void Enemy::Spawn(const Vector2& startPosition, float speed, Behaviour spawnBehaviour)
{
position = startPosition;
baseX = startPosition.x;
moveSpeed = speed;
age = 0.0f;
shotTimer = tuning::enemy::ShotDelay;
hp = tuning::enemy::MaxHp;
behaviour = spawnBehaviour;
isActive = true;
}
void Enemy::Update(float deltaTime)
{
if (!isActive)
return;
age += deltaTime;
shotTimer -= deltaTime;
position.y += moveSpeed * deltaTime;
if (behaviour == Behaviour::Wave)
{
float waveOffset = std::sin(age * tuning::enemy::WaveSpeed)
* tuning::enemy::WaveAmplitude;
position.x = baseX + waveOffset;
}
if (GetHitbox().y > tuning::ScreenHeight)
Deactivate();
}
void Enemy::Draw(const ImageManager& images) const
{
if (!isActive)
return;
images.Draw(ImageManager::Id::Enemy, GetDrawRect());
}
void Enemy::TakeDamage(int damage)
{
hp -= damage;
if (hp <= 0)
Deactivate();
}
bool Enemy::TryShoot(EnemyShotManager& shots, const Vector2& targetPosition)
{
if (!isActive || shotTimer > 0.0f)
return false;
if (!shots.Spawn(GetMuzzlePosition(), targetPosition))
return false;
shotTimer = tuning::enemy::ShotInterval;
return true;
}
bool Enemy::IsActive() const
{
return isActive;
}
void Enemy::Deactivate()
{
isActive = false;
}
Rect Enemy::GetDrawRect() const
{
return tuning::enemy::DrawRect.MovedBy(position);
}
Rect Enemy::GetHitbox() const
{
return tuning::enemy::Hitbox.MovedBy(position);
}
Vector2 Enemy::GetMuzzlePosition() const
{
Rect hitbox = GetHitbox();
return
{
hitbox.GetCenter().x,
hitbox.y + hitbox.height + tuning::enemyShot::Hitbox.height * 0.5f
};
}
}Source/Shooting/Enemy.h
Source/Shooting/Enemy.hcpp
#pragma once
#include "DxGame/Rect.h"
#include "DxGame/Vector2.h"
namespace dxgame::shooting
{
class ImageManager;
class EnemyShotManager;
// 敵一体の位置、移動、発射間隔、表示、実行状態を管理する
// 敵1体の位置、移動、発射間隔、表示、実行状態を管理する
class Enemy
{
public:
// 下方向へ直進するか左右へ波打ちながら進むかを表す
enum class Behaviour
{
Straight,
Wave
};
// 中心座標、速度、移動方法を設定して敵を出現させる
void Spawn(const Vector2& startPosition, float speed, Behaviour spawnBehaviour);
// 敵を移動させ、画面の下へ出たら停止する
void Update(float deltaTime);
// 実行中の敵を画像で描く
void Draw(const ImageManager& images) const;
// 発射間隔を確認し、敵弾を生成できたらtrueを返す
bool TryShoot(EnemyShotManager& shots, const Vector2& targetPosition);
// HPからdamageを減らし0以下になったら停止する
void TakeDamage(int damage);
// 更新と描画の対象ならtrueを返す
bool IsActive() const;
// 敵を更新と描画の対象外にする
void Deactivate();
// 表示用Rectを現在位置へ移動して返す
Rect GetDrawRect() const;
// 現在位置から求めた命中判定用Rectを返す
Rect GetHitbox() const;
private:
// 敵の下端から発射する弾の中心座標を返す
Vector2 GetMuzzlePosition() const;
// 敵の中心座標
Vector2 position = {};
float baseX = 0.0f;
float moveSpeed = 0.0f;
float age = 0.0f;
float shotTimer = 0.0f;
int hp = 0;
Behaviour behaviour = Behaviour::Straight;
bool isActive = false;
};
}Source/Shooting/EnemyShot.cpp
Source/Shooting/EnemyShot.cppcpp
#include <cmath>
#include "Shooting/ImageManager.h"
#include "Shooting/Tuning/GameTuning.h"
#include "Shooting/EnemyShot.h"
namespace dxgame::shooting
{
void EnemyShot::Spawn(const Vector2& startPosition, const Vector2& targetPosition)
{
position = startPosition;
Vector2 direction = {
targetPosition.x - position.x,
targetPosition.y - position.y
};
float distance = std::sqrt(direction.x * direction.x + direction.y * direction.y);
// 敵とプレイヤーの位置が異なるためdistanceは0にならない
// 方向を長さ1にそろえ角度によらず同じ速さにする
velocity = {
direction.x / distance * tuning::enemyShot::Speed,
direction.y / distance * tuning::enemyShot::Speed
};
isActive = true;
}
void EnemyShot::Update(float deltaTime)
{
if (!isActive)
return;
position.x += velocity.x * deltaTime;
position.y += velocity.y * deltaTime;
// Hitbox全体が画面外へ出た弾を再利用できる状態に戻す
Rect hitbox = GetHitbox();
if (hitbox.x + hitbox.width < 0.0f ||
hitbox.x > tuning::ScreenWidth ||
hitbox.y + hitbox.height < 0.0f ||
hitbox.y > tuning::ScreenHeight)
Deactivate();
}
void EnemyShot::Draw(const ImageManager& images) const
{
if (!isActive)
return;
images.Draw(ImageManager::Id::EnemyShot, GetDrawRect());
}
bool EnemyShot::IsActive() const
{
return isActive;
}
void EnemyShot::Deactivate()
{
isActive = false;
}
Rect EnemyShot::GetDrawRect() const
{
return tuning::enemyShot::DrawRect.MovedBy(position);
}
Rect EnemyShot::GetHitbox() const
{
return tuning::enemyShot::Hitbox.MovedBy(position);
}
}Source/Shooting/EnemyShot.h
Source/Shooting/EnemyShot.hcpp
#pragma once
#include "DxGame/Rect.h"
#include "DxGame/Vector2.h"
namespace dxgame::shooting
{
// 敵が発射する1発の弾の位置、速度、実行状態を管理する
class EnemyShot
{
public:
// 中心をstartPositionへ移動し発射時点のtargetPositionへ向かう速度を設定する
// 発射後はtargetPositionを追尾しない
void Spawn(const Vector2& startPosition, const Vector2& targetPosition);
// 発射時の速度で移動し画面外へ出たら停止する
// deltaTimeは1フレームの経過秒数
void Update(float deltaTime);
// 実行中の弾を表示用Rectに合わせて描く
void Draw(const class ImageManager& images) const;
// 更新、描画、命中判定の対象ならtrueを返す
bool IsActive() const;
// 弾を更新、描画、命中判定の対象外にする
void Deactivate();
// 表示用Rectを現在位置へ移動して返す
Rect GetDrawRect() const;
// 現在位置から求めた命中判定用Rectを返す
Rect GetHitbox() const;
private:
// 弾の中心座標
Vector2 position = {};
Vector2 velocity = {};
bool isActive = false;
};
}Source/Shooting/EnemyShotManager.cpp
Source/Shooting/EnemyShotManager.cppcpp
#include "Shooting/EnemyShotManager.h"
namespace dxgame::shooting
{
bool EnemyShotManager::Spawn(const Vector2& startPosition, const Vector2& targetPosition)
{
// 先頭から停止中の弾を探して再利用する
for (EnemyShot& shot : shots)
{
if (shot.IsActive())
continue;
shot.Spawn(startPosition, targetPosition);
return true;
}
return false;
}
void EnemyShotManager::Update(float deltaTime)
{
for (EnemyShot& shot : shots)
shot.Update(deltaTime);
}
void EnemyShotManager::Draw(const ImageManager& images) const
{
for (const EnemyShot& shot : shots)
shot.Draw(images);
}
void EnemyShotManager::Reset()
{
for (EnemyShot& shot : shots)
shot.Deactivate();
}
}Source/Shooting/EnemyShotManager.h
Source/Shooting/EnemyShotManager.hcpp
#pragma once
#include <array>
#include <cstddef>
#include "Shooting/EnemyShot.h"
namespace dxgame::shooting
{
// 敵弾を固定数所有し生成、更新、描画、再利用を管理する
class EnemyShotManager
{
public:
// 停止中の弾の中心をstartPositionに合わせtargetPositionへ向けて生成する
// 空きがない場合は生成せずfalseを返す
bool Spawn(const Vector2& startPosition, const Vector2& targetPosition);
// 所有するすべての敵弾を更新する
// deltaTimeは1フレームの経過秒数
void Update(float deltaTime);
// 所有するすべての敵弾を描く
void Draw(const class ImageManager& images) const;
// 所有するすべての敵弾を停止して空の状態へ戻す
void Reset();
private:
// Gameだけが命中判定とデバッグ表示のために内部配列を参照できる
friend class Game;
// 停止中の要素を再利用しゲーム中にはメモリを確保しない
static constexpr std::size_t Capacity = 48;
// Gameが命中判定で状態を変更できるようすべての弾への参照を返す
std::array<EnemyShot, Capacity>& GetAll() { return shots; }
// Gameが状態を変更せず参照できるようすべての弾へのconst参照を返す
const std::array<EnemyShot, Capacity>& GetAll() const { return shots; }
std::array<EnemyShot, Capacity> shots = {};
};
}Source/Shooting/Game.cpp
Source/Shooting/Game.cppcpp
#include <DxLib.h>
#include <string>
#include <Windows.h>
#include "Config/ApplicationConfig.h"
#include "Shooting/Tuning/GameTuning.h"
#include "Shooting/Game.h"
namespace
{
void ShowResourceLoadError(
const wchar_t* resourceType,
const std::wstring& failedPath)
{
std::wstring message = resourceType;
message += L"の読み込みに失敗しました。\n\n対象: ";
message += failedPath;
message += L"\n\nAssetsフォルダーとファイル名を確認してください。";
MessageBoxW(
nullptr,
message.c_str(),
L"リソース読み込みエラー",
MB_OK | MB_ICONERROR);
}
}
namespace dxgame::shooting
{
namespace config = dxgame::config;
bool Game::Run()
{
const ImageManager::LoadResult imageResult = images.LoadAll();
if (!imageResult.succeeded)
{
ShowResourceLoadError(L"画像", imageResult.failedPath);
return false;
}
player.Reset();
playerShots.Reset();
enemies.Reset();
enemyShots.Reset();
score = 0;
// 小数分の待ち時間を次フレームへ持ち越す
double nextFrame = GetNowCount();
while (ProcessMessage() == 0)
{
input.Update();
if (input.IsDown(Input::Action::Exit))
break;
Update(config::DeltaTime);
ClearDrawScreen();
Draw();
ScreenFlip();
nextFrame += config::FrameMilliseconds;
int waitMilliseconds = static_cast<int>(nextFrame - GetNowCount());
if (waitMilliseconds > 0)
WaitTimer(waitMilliseconds);
else
nextFrame = GetNowCount();
}
return true;
}
void Game::Update(float deltaTime)
{
player.Update(input, deltaTime);
playerShots.Update(deltaTime);
enemies.Update(deltaTime);
enemyShots.Update(deltaTime);
ResolveCollisions();
Vector2 playerPosition = player.GetHitbox().GetCenter();
for (Enemy& enemy : enemies.GetAll())
{
enemy.TryShoot(enemyShots, playerPosition);
}
player.TryShoot(input, playerShots);
}
void Game::Draw() const
{
// 描画順はプレイヤー、プレイヤーの弾、敵、敵の弾の順
player.Draw(images);
playerShots.Draw(images);
enemies.Draw(images);
enemyShots.Draw(images);
DrawFormatString(16, 16, GetColor(255, 255, 255), L"SCORE %02d", score);
}
void Game::ResolveCollisions()
{
for (PlayerShot& playerShot : playerShots.GetAll())
{
if (!playerShot.IsActive())
continue;
for (Enemy& enemy : enemies.GetAll())
{
if (!enemy.IsActive())
continue;
if (!playerShot.GetHitbox().Overlaps(enemy.GetHitbox()))
continue;
enemy.TakeDamage(tuning::playerShot::Damage);
playerShot.Deactivate();
if (!enemy.IsActive())
++score;
break;
}
}
}
}Source/Shooting/Game.h
Source/Shooting/Game.hcpp
#pragma once
#include "Shooting/ImageManager.h"
#include "Shooting/Input.h"
#include "Shooting/EnemyManager.h"
#include "Shooting/EnemyShotManager.h"
#include "Shooting/Player.h"
#include "Shooting/PlayerShotManager.h"
namespace dxgame::shooting
{
// ゲームに必要なオブジェクトを所有し1フレームの処理を取りまとめる
class Game
{
public:
// 必須画像を読み込んでゲームループを開始する
// 画像を読み込めない場合はループを開始せずfalseを返す
bool Run();
private:
// プレイヤーを1フレーム分更新する
// deltaTimeは移動計算で1フレーム分として使う秒数
void Update(float deltaTime);
// プレイヤーを描く
void Draw() const;
// プレイヤー弾と敵の命中を判定する
void ResolveCollisions();
ImageManager images;
Input input;
Player player;
PlayerShotManager playerShots;
EnemyManager enemies;
EnemyShotManager enemyShots;
int score = 0;
};
}Source/Shooting/ImageManager.cpp
Source/Shooting/ImageManager.cppcpp
#include <array>
#include <cstddef>
#include <DxLib.h>
#include "Shooting/ImageManager.h"
namespace dxgame::shooting
{
namespace
{
using ImageId = ImageManager::Id;
struct ImageEntry
{
ImageId id;
const wchar_t* path;
};
constexpr std::array<ImageEntry, static_cast<std::size_t>(ImageId::Count)> ImageTable =
{
ImageEntry { ImageId::Player, L"Assets/Images/Player.png" },
ImageEntry { ImageId::PlayerShot, L"Assets/Images/Bullet.png" },
ImageEntry { ImageId::Enemy, L"Assets/Images/Enemy.png" },
ImageEntry { ImageId::EnemyShot, L"Assets/Images/EnemyShot.png" }
};
std::size_t ToIndex(ImageId id)
{
return static_cast<std::size_t>(id);
}
}
ImageManager::ImageManager()
{
handles.fill(-1);
}
ImageManager::~ImageManager()
{
for (int handle : handles)
{
if (handle != -1)
DeleteGraph(handle);
}
}
ImageManager::LoadResult ImageManager::LoadAll()
{
for (const ImageEntry& entry : ImageTable)
{
std::size_t index = ToIndex(entry.id);
handles[index] = LoadGraph(entry.path);
if (handles[index] == -1)
return { false, entry.path };
}
return { true, {} };
}
void ImageManager::Draw(Id id, const Rect& rect) const
{
int handle = handles[ToIndex(id)];
DrawExtendGraph(
static_cast<int>(rect.x),
static_cast<int>(rect.y),
static_cast<int>(rect.x + rect.width),
static_cast<int>(rect.y + rect.height),
handle,
TRUE);
}
}Source/Shooting/ImageManager.h
Source/Shooting/ImageManager.hcpp
#pragma once
#include <array>
#include <cstddef>
#include <string>
#include "DxGame/Rect.h"
namespace dxgame::shooting
{
// 画像の読み込み、ハンドルの所有、拡大縮小描画、解放を管理する
class ImageManager
{
public:
// ゲーム内で使う画像を種類ごとに識別する
enum class Id
{
Player,
PlayerShot,
Enemy,
EnemyShot,
Count
};
// 全ハンドルを未取得の状態で初期化する
ImageManager();
// 取得済みの画像ハンドルをすべて解放する
~ImageManager();
// ハンドルを二重に解放しないようコピー構築を禁止する
ImageManager(const ImageManager&) = delete;
// ハンドルを二重に解放しないようコピー代入を禁止する
ImageManager& operator=(const ImageManager&) = delete;
// 画像の読み込み結果と、失敗した場合の相対パスを表す
struct LoadResult
{
bool succeeded = true;
std::wstring failedPath;
};
// 対応表にある画像をすべて読み込む
// 1つでも読み込めない場合は、その画像の相対パスとともに失敗を返す
LoadResult LoadAll();
// idの画像をrectの大きさへ拡大縮小して描く
void Draw(Id id, const Rect& rect) const;
private:
std::array<int, static_cast<std::size_t>(Id::Count)> handles = {};
};
}Source/Shooting/Player.cpp
Source/Shooting/Player.cppcpp
#include <algorithm>
#include <cmath>
#include "Shooting/ImageManager.h"
#include "Shooting/Input.h"
#include "Shooting/PlayerShotManager.h"
#include "Shooting/Tuning/GameTuning.h"
#include "Shooting/Player.h"
namespace dxgame::shooting
{
using Action = Input::Action;
void Player::Reset()
{
position = tuning::player::StartPosition;
shotTimer = 0.0f;
}
void Player::Update(const Input& input, float deltaTime)
{
Vector2 direction;
if (input.IsDown(Action::MoveLeft))
direction.x -= 1.0f;
if (input.IsDown(Action::MoveRight))
direction.x += 1.0f;
if (input.IsDown(Action::MoveUp))
direction.y -= 1.0f;
if (input.IsDown(Action::MoveDown))
direction.y += 1.0f;
// 斜め入力を長さ1に収める
// 方向と長さの計算は第05回で詳しく扱う
float length = std::sqrt(direction.x * direction.x + direction.y * direction.y);
if (length > 1.0f)
{
direction.x /= length;
direction.y /= length;
}
position.x += direction.x * tuning::player::MoveSpeed * deltaTime;
position.y += direction.y * tuning::player::MoveSpeed * deltaTime;
// 指定の範囲全体が画面内に残るよう位置を制限する
const Rect& bounds = tuning::player::Bounds;
float minX = -bounds.x;
float minY = -bounds.y;
float maxX = tuning::ScreenWidth - bounds.x - bounds.width;
float maxY = tuning::ScreenHeight - bounds.y - bounds.height;
position.x = std::clamp(position.x, minX, maxX);
position.y = std::clamp(position.y, minY, maxY);
shotTimer = std::max(0.0f, shotTimer - deltaTime);
}
void Player::Draw(const ImageManager& images) const
{
images.Draw(ImageManager::Id::Player, GetDrawRect());
}
bool Player::TryShoot(const Input& input, PlayerShotManager& shots)
{
if (!input.IsDown(Action::Shoot) || shotTimer > 0.0f)
return false;
if (!shots.Spawn(GetMuzzlePosition()))
return false;
shotTimer = tuning::player::ShotInterval;
return true;
}
Vector2 Player::GetMuzzlePosition() const
{
return
{
position.x,
position.y - 32.0f
};
}
Rect Player::GetDrawRect() const
{
return tuning::player::DrawRect.MovedBy(position);
}
Rect Player::GetHitbox() const
{
return tuning::player::Hitbox.MovedBy(position);
}
}Source/Shooting/Player.h
Source/Shooting/Player.hcpp
#pragma once
#include "DxGame/Rect.h"
#include "DxGame/Vector2.h"
namespace dxgame::shooting
{
class Input;
class ImageManager;
class PlayerShotManager;
// プレイヤーの中心座標、移動、発射間隔を管理する
class Player
{
public:
// プレイヤーを開始位置へ戻す
void Reset();
// 入力から移動方向を求めプレイヤーを画面内で移動させる
// deltaTimeは移動計算で1フレーム分として使う秒数
void Update(const Input& input, float deltaTime);
// プレイヤー画像を表示用Rectに合わせて描く
void Draw(const ImageManager& images) const;
// 入力と発射間隔を確認し、弾を生成できたらtrueを返す
bool TryShoot(const Input& input, PlayerShotManager& shots);
// 表示用Rectを現在位置へ移動して返す
Rect GetDrawRect() const;
// 現在位置から求めた命中判定用Rectを返す
Rect GetHitbox() const;
private:
// 発射する弾の中心座標を返す
Vector2 GetMuzzlePosition() const;
// プレイヤーの中心座標
Vector2 position = {};
float shotTimer = 0.0f;
};
}Source/Shooting/Tuning/GameTuning.h
Source/Shooting/Tuning/GameTuning.hcpp
#pragma once
#include "DxGame/Rect.h"
#include "DxGame/Vector2.h"
#include "Config/ApplicationConfig.h"
namespace dxgame::shooting::tuning
{
// ゲーム画面の横幅 単位はピクセル
// ゲーム画面の横幅(単位:ピクセル)
inline constexpr int ScreenWidth = 640;
// ゲーム画面の縦幅 単位はピクセル
// ゲーム画面の縦幅(単位:ピクセル)
inline constexpr int ScreenHeight = 480;
// 座標、距離、Rectの各値はピクセル単位で指定する
// 時間は秒、速度は1秒あたりの移動量で指定する
// プレイヤーのpositionは中心座標を表す
// 各Rectはpositionからの相対範囲として指定する
namespace player
{
// ゲーム開始時のプレイヤー中心座標
inline constexpr Vector2 StartPosition = { 320.0f, 424.0f };
// プレイヤー中心を基準にした画像の表示範囲
inline constexpr Rect DrawRect = Rect::Centered(48.0f, 58.0f).MovedBy({ 0.0f, 3.0f });
inline constexpr Rect Bounds = Rect::Centered(28.0f, 28.0f);
// プレイヤー中心を基準にした命中範囲
inline constexpr Rect Hitbox = Rect::Centered(28.0f, 28.0f);
// プレイヤーの速度 単位はピクセル/秒
// プレイヤーの速度(単位:ピクセル/秒)
inline constexpr float MoveSpeed = 240.0f;
// 弾を発射してから次に発射できるまでの時間 ここでは8フレーム間隔の意味
// 弾を発射してから次に発射できるまでの時間(ここでは8フレーム間隔)
inline constexpr float ShotInterval = 8.0f * config::DeltaTime;
}
namespace playerShot
{
// 弾の中心を基準にした画像の表示範囲
inline constexpr Rect DrawRect = Rect::Centered(10.0f, 28.0f);
// 弾の中心を基準にした命中範囲
inline constexpr Rect Hitbox = Rect::Centered(8.0f, 20.0f);
// 弾の速度 単位はピクセル/秒
// 弾の速度(単位:ピクセル/秒)
inline constexpr float Speed = 480.0f;
// 敵へ与えるダメージ
inline constexpr int Damage = 1;
}
namespace enemy
{
inline constexpr int LaneCount = 8;
inline constexpr float LaneStart = 64.0f;
inline constexpr float LaneSpacing = 72.0f;
inline constexpr Rect DrawRect = Rect::Centered(42.0f, 42.0f);
inline constexpr Rect Hitbox = Rect::Centered(32.0f, 32.0f);
inline constexpr float BaseSpeed = 120.0f;
inline constexpr float SpeedStep = 12.0f;
inline constexpr int SpeedLevels = 4;
inline constexpr float SpawnInterval = 0.60f;
inline constexpr float SpawnDelay = 0.40f;
inline constexpr float ShotDelay = 0.80f;
inline constexpr float ShotInterval = 1.80f;
inline constexpr float WaveAmplitude = 30.0f;
inline constexpr float WaveSpeed = 3.0f;
inline constexpr int WaveInterval = 3;
inline constexpr int MaxHp = 1;
}
namespace enemyShot
{
inline constexpr Rect DrawRect = Rect::Centered(14.0f, 14.0f);
inline constexpr Rect Hitbox = Rect::Centered(10.0f, 10.0f);
inline constexpr float Speed = 180.0f;
}
}