グローバル変数を全廃!Player(自機)、Invader(敵)、Bullet(弾)をそれぞれヘッダ(.h)と実装(.cpp)に分離。C言語の「構造体ポインタを関数に渡す設計」とC++の「クラス」の決定的な違いと、カプセル化の真の意図を解き明かします。
2.4 第2章の教材コード:複数ファイル構成プロジェクト一式
ヘッダと実装に分割され、カプセル化された実戦的なコードです。タブを切り替えて各ファイルの役割を確認してください。
📄 Common.h
#pragma once
// ゲーム画面のサイズ定義
const int SCREEN_WIDTH = 30;
const int SCREEN_HEIGHT = 15;
📄 Bullet.h
#pragma once
#include "Common.h"
class Bullet {
private:
// 【カプセル化】外部からの改ざんを許さない非公開データ
int m_x;
int m_y;
bool m_active;
public:
Bullet();
// 弾を発射する
void spawn(int startX, int startY);
// 弾を前進させ、画面外で自ら消滅する(責任の自己完結)
void update();
// 外部に安全に状態を教えるゲッター関数(const修飾で中身を変えないことを保証)
bool isActive() const { return m_active; }
void deactivate() { m_active = false; }
int getX() const { return m_x; }
int getY() const { return m_y; }
};
📄 Bullet.cpp
#include "Bullet.h"
Bullet::Bullet() : m_x(0), m_y(0), m_active(false) {}
void Bullet::spawn(int startX, int startY) {
m_x = startX;
m_y = startY;
m_active = true;
}
void Bullet::update() {
if (!m_active) return;
m_y--; // 上方向へ移動
if (m_y < 1) {
m_active = false; // 天井に達したら自ら消滅
}
}
📄 Player.h
#pragma once
#include "Bullet.h"
class Player {
private:
int m_x;
int m_y;
public:
Player(int startX, int startY);
void moveLeft();
void moveRight();
void shoot(Bullet& bullet);
int getX() const { return m_x; }
int getY() const { return m_y; }
};
📄 Player.cpp
#include "Player.h"
#include "Common.h"
Player::Player(int startX, int startY) : m_x(startX), m_y(startY) {}
void Player::moveLeft() {
// 境界チェックは自機クラスの責任!
if (m_x > 1) {
m_x--;
}
}
void Player::moveRight() {
// 壁の手前で自律的にストップ
if (m_x < SCREEN_WIDTH - 4) {
m_x++;
}
}
void Player::shoot(Bullet& bullet) {
// 弾オブジェクトに「発射せよ」とメッセージを送る
if (!bullet.isActive()) {
bullet.spawn(m_x + 1, m_y - 1);
}
}
📄 Invader.h
#pragma once
class Invader {
private:
int m_x;
int m_y;
bool m_alive;
public:
Invader(int x, int y);
void move(int dx, int dy);
void destroy();
int getX() const { return m_x; }
int getY() const { return m_y; }
bool isAlive() const { return m_alive; }
};
📄 Invader.cpp
#include "Invader.h"
Invader::Invader(int x, int y) : m_x(x), m_y(y), m_alive(true) {}
void Invader::move(int dx, int dy) {
if (!m_alive) return;
m_x += dx;
m_y += dy;
}
void Invader::destroy() {
m_alive = false;
}
📄 main.cpp
#include <iostream>
#include <windows.h>
#include <conio.h>
#include "Common.h"
#include "Player.h"
#include "Invader.h"
#include "Bullet.h"
void setCursorPosition(int x, int y) {
COORD coord = { (SHORT)x, (SHORT)y };
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
int main() {
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursorInfo;
GetConsoleCursorInfo(hConsole, &cursorInfo);
cursorInfo.bVisible = FALSE;
SetConsoleCursorInfo(hConsole, &cursorInfo);
// =========================================================
// 【オブジェクトの実体化】グローバル変数は完全ゼロ!
// =========================================================
Player player(SCREEN_WIDTH / 2 - 1, SCREEN_HEIGHT - 2);
Bullet bullet;
const int INVADER_COUNT = 6;
Invader invaders[INVADER_COUNT] = {
Invader(4, 2), Invader(8, 2), Invader(12, 2),
Invader(16, 2), Invader(20, 2), Invader(24, 2)
};
int invaderDir = 1;
int moveTimer = 0;
int score = 0;
bool isRunning = true;
bool gameClear = false;
while (isRunning) {
// --- 1. 入力受付:オブジェクトにメッセージを送るだけ ---
if (_kbhit()) {
char key = _getch();
if (key == 'a' || key == 'A') player.moveLeft();
if (key == 'd' || key == 'D') player.moveRight();
if (key == ' ') player.shoot(bullet);
if (key == 'q' || key == 'Q') isRunning = false;
}
// --- 2. 弾の自律更新 ---
bullet.update();
// --- 3. 敵の移動 ---
moveTimer++;
if (moveTimer >= 5) {
moveTimer = 0;
bool hitWall = false;
for (int i = 0; i < INVADER_COUNT; i++) {
if (!invaders[i].isAlive()) continue;
if ((invaderDir == 1 && invaders[i].getX() >= SCREEN_WIDTH - 2) ||
(invaderDir == -1 && invaders[i].getX() <= 1)) {
hitWall = true;
break;
}
}
if (hitWall) {
invaderDir = -invaderDir;
for (int i = 0; i < INVADER_COUNT; i++) {
invaders[i].move(0, 1);
if (invaders[i].isAlive() && invaders[i].getY() >= player.getY()) {
isRunning = false;
}
}
} else {
for (int i = 0; i < INVADER_COUNT; i++) {
invaders[i].move(invaderDir, 0);
}
}
}
// --- 4. 当たり判定(安全なゲッターで問い合わせ) ---
if (bullet.isActive()) {
for (int i = 0; i < INVADER_COUNT; i++) {
if (invaders[i].isAlive() &&
bullet.getX() == invaders[i].getX() &&
bullet.getY() == invaders[i].getY()) {
invaders[i].destroy();
bullet.deactivate();
score += 100;
break;
}
}
}
// クリア判定
bool hasAliveInvader = false;
for (int i = 0; i < INVADER_COUNT; i++) {
if (invaders[i].isAlive()) { hasAliveInvader = true; break; }
}
if (!hasAliveInvader) {
gameClear = true;
break;
}
// --- 5. 描画処理 ---
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[player.getY()][player.getX()] = '_';
buffer[player.getY()][player.getX() + 1] = 'A';
buffer[player.getY()][player.getX() + 2] = '_';
if (bullet.isActive() && bullet.getY() > 0 && bullet.getY() < SCREEN_HEIGHT - 1) {
buffer[bullet.getY()][bullet.getX()] = '|';
}
for (int i = 0; i < INVADER_COUNT; i++) {
if (invaders[i].isAlive()) {
buffer[invaders[i].getY()][invaders[i].getX()] = 'V';
}
}
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: " << score << " [第2章: クラス設計版] \n";
Sleep(33);
}
setCursorPosition(0, SCREEN_HEIGHT + 1);
std::cout << (gameClear ? "=== VICTORY! GAME CLEAR! ===\n" : "=== GAME OVER ===\n");
return 0;
}