📄 Particle.h
#pragma once
#include "Common.h"
class Particle {
private:
float m_x;
float m_y;
float m_vx; // X方向速度
float m_vy; // Y方向速度
int m_lifetime; // 残り寿命(フレーム数)
char m_glyph; // 表示文字 ('*', '+', '.')
public:
Particle(float x, float y, float vx, float vy, int lifetime, char glyph);
// 毎フレーム座標を動かし、寿命を減らす
void update();
// 寿命が尽きたか(消滅判定)
bool isDead() const { return m_lifetime <= 0; }
int getX() const { return (int)m_x; }
int getY() const { return (int)m_y; }
char getGlyph() const { return m_glyph; }
};
📄 Particle.cpp
#include "Particle.h"
Particle::Particle(float x, float y, float vx, float vy, int lifetime, char glyph)
: m_x(x), m_y(y), m_vx(vx), m_vy(vy), m_lifetime(lifetime), m_glyph(glyph) {}
void Particle::update() {
m_x += m_vx;
m_y += m_vy;
m_lifetime--; // 1フレームごとに寿命を消費(老化プロセス)
}
📄 Game.h
#pragma once
#include <vector>
#include "Common.h"
#include "Player.h"
#include "Invader.h"
#include "Bullet.h"
#include "Particle.h"
class Game {
private:
Player m_player;
std::vector<Bullet> m_bullets; // 動的な弾リスト(連射対応)
std::vector<Invader> m_invaders; // 動的な敵リスト
std::vector<Particle> m_particles; // 動的な火花パーティクルリスト!
int m_score;
int m_invaderDir;
int m_invaderMoveTimer;
bool m_isRunning;
bool m_gameClear;
// 爆発火花をヒープ上に動的生成するファクトリメソッド
void spawnExplosion(int x, int y);
void processInput();
void update();
void render();
public:
Game();
void run(); // ゲーム全体の起動窓口
};
📄 Game.cpp
#include "Game.h"
#include <iostream>
#include <algorithm>
#include <cmath>
#include <windows.h>
#include <conio.h>
void setCursorPosition(int x, int y) {
COORD coord = { (SHORT)x, (SHORT)y };
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
Game::Game()
: m_player(SCREEN_WIDTH / 2 - 1, SCREEN_HEIGHT - 2),
m_score(0), m_invaderDir(1), m_invaderMoveTimer(0),
m_isRunning(true), m_gameClear(false) {
// 敵を動的に配置
for (int col = 0; col < 6; col++) {
m_invaders.push_back(Invader(4 + col * 4, 2));
}
}
// =============================================================
// 【動的生成フェーズ】弾が命中した瞬間、10個の火花実体を生み出す
// =============================================================
void Game::spawnExplosion(int x, int y) {
const char glyphs[] = { '*', '+', '.', 'x', '#' };
for (int i = 0; i < 10; i++) {
// 360度放射状のベクトルを計算
float angle = (float)(i * (3.1415926 * 2.0 / 10.0));
float speed = 0.5f + (float)(rand() % 5) * 0.1f;
float vx = std::cos(angle) * speed * 1.5f;
float vy = std::sin(angle) * speed * 0.8f;
int life = 4 + rand() % 5; // 4〜8フレームの寿命
char g = glyphs[rand() % 5];
// vectorのメモリ上に直接Particleインスタンスを追加(生成)
m_particles.push_back(Particle((float)x, (float)y, vx, vy, life, g));
}
}
void Game::processInput() {
if (_kbhit()) {
char key = _getch();
if (key == 'a' || key == 'A') m_player.moveLeft();
if (key == 'd' || key == 'D') m_player.moveRight();
if (key == ' ') {
// 連射制限:画面内に最大3発まで動的追加
if (m_bullets.size() < 3) {
m_bullets.push_back(Bullet(m_player.getX() + 1, m_player.getY() - 1));
}
}
if (key == 'q' || key == 'Q') m_isRunning = false;
}
}
// 【C++03 述語関数】erase-remove で使用する消去条件の判定関数
static bool isParticleDead(const Particle& p) { return p.isDead(); }
static bool isBulletInactive(const Bullet& b) { return !b.isActive(); }
static bool isInvaderDead(const Invader& inv) { return !inv.isAlive(); }
void Game::update() {
// 1. 弾の更新
for (size_t i = 0; i < m_bullets.size(); ++i) {
m_bullets[i].update();
}
// 2. 敵の更新(タイマー制御)
m_invaderMoveTimer++;
if (m_invaderMoveTimer >= 5) {
m_invaderMoveTimer = 0;
bool hitWall = false;
for (size_t i = 0; i < m_invaders.size(); ++i) {
if ((m_invaderDir == 1 && m_invaders[i].getX() >= SCREEN_WIDTH - 2) ||
(m_invaderDir == -1 && m_invaders[i].getX() <= 1)) {
hitWall = true;
break;
}
}
if (hitWall) {
m_invaderDir = -m_invaderDir;
for (size_t i = 0; i < m_invaders.size(); ++i) {
m_invaders[i].move(0, 1);
if (m_invaders[i].getY() >= m_player.getY()) {
m_isRunning = false;
}
}
} else {
for (size_t i = 0; i < m_invaders.size(); ++i) {
m_invaders[i].move(m_invaderDir, 0);
}
}
}
// 3. 当たり判定(Bullet vs Invader)
for (size_t b = 0; b < m_bullets.size(); ++b) {
if (!m_bullets[b].isActive()) continue;
for (size_t inv = 0; inv < m_invaders.size(); ++inv) {
if (m_invaders[inv].isAlive() && m_bullets[b].getX() == m_invaders[inv].getX() && m_bullets[b].getY() == m_invaders[inv].getY()) {
m_invaders[inv].destroy();
m_bullets[b].deactivate();
m_score += 100;
// ド派手な爆発火花を大量動的生成!
spawnExplosion(m_invaders[inv].getX(), m_invaders[inv].getY());
break;
}
}
}
// 4. パーティクルの更新(物理移動と寿命減算)
for (size_t p = 0; p < m_particles.size(); ++p) {
m_particles[p].update();
}
// =========================================================
// 【最重要:寿命管理と自動消滅フェーズ】
// C++標準イディオム「erase-remove」により、死んだ実体を完全解放!
// =========================================================
// 寿命が尽きた火花粒子を消去
m_particles.erase(
std::remove_if(m_particles.begin(), m_particles.end(), isParticleDead),
m_particles.end()
);
// 画面外に出た弾を消去
m_bullets.erase(
std::remove_if(m_bullets.begin(), m_bullets.end(), isBulletInactive),
m_bullets.end()
);
// 撃破された敵を消去
m_invaders.erase(
std::remove_if(m_invaders.begin(), m_invaders.end(), isInvaderDead),
m_invaders.end()
);
if (m_invaders.empty()) {
m_gameClear = true;
m_isRunning = false;
}
}
void Game::render() {
char buffer[SCREEN_HEIGHT][SCREEN_WIDTH];
for (int y = 0; y < SCREEN_HEIGHT; y++) {
for (int x = 0; x < SCREEN_WIDTH; x++) {
buffer[y][x] = (y == 0 || y == SCREEN_HEIGHT - 1 || x == 0 || x == SCREEN_WIDTH - 1) ? '#' : ' ';
}
}
// 自機
buffer[m_player.getY()][m_player.getX()] = '_';
buffer[m_player.getY()][m_player.getX() + 1] = 'A';
buffer[m_player.getY()][m_player.getX() + 2] = '_';
// 弾
for (size_t i = 0; i < m_bullets.size(); ++i) {
if (m_bullets[i].getY() > 0 && m_bullets[i].getY() < SCREEN_HEIGHT - 1) {
buffer[m_bullets[i].getY()][m_bullets[i].getX()] = '|';
}
}
// 敵
for (size_t i = 0; i < m_invaders.size(); ++i) {
buffer[m_invaders[i].getY()][m_invaders[i].getX()] = 'V';
}
// 火花パーティクル(最前面に美しく散乱!)
for (size_t i = 0; i < m_particles.size(); ++i) {
int px = m_particles[i].getX();
int py = m_particles[i].getY();
if (px > 0 && px < SCREEN_WIDTH - 1 && py > 0 && py < SCREEN_HEIGHT - 1) {
buffer[py][px] = m_particles[i].getGlyph();
}
}
setCursorPosition(0, 0);
for (int y = 0; y < SCREEN_HEIGHT; y++) {
for (int x = 0; x < SCREEN_WIDTH; x++) {
std::cout << buffer[y][x];
}
std::cout << "\n";
}
std::cout << "SCORE: " << m_score
<< " | PARTICLES: " << m_particles.size()
<< " | BULLETS: " << m_bullets.size() << " \n";
}
void Game::run() {
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursorInfo;
GetConsoleCursorInfo(hConsole, &cursorInfo);
cursorInfo.bVisible = FALSE;
SetConsoleCursorInfo(hConsole, &cursorInfo);
while (m_isRunning) {
processInput();
update();
render();
Sleep(33);
}
setCursorPosition(0, SCREEN_HEIGHT + 2);
if (m_gameClear) {
std::cout << "=========================================\n";
std::cout << " ★★★ VICTORY! ALL INVADERS DESTROYED! ★★★\n";
std::cout << "=========================================\n";
} else {
std::cout << "=========================================\n";
std::cout << " GAME OVER \n";
std::cout << "=========================================\n";
}
}