🧭 第6.5章 縮小表示のステージ+当たり判定(完全版+イニシャライザ解説付き)

クラスのメンバ変数を、コンストラクタのかっこの後に「:」を使って初期化する方法です。
代入と違い、オブジェクトの生成時に同時に値を設定できるため、
定数・参照・クラス型メンバ などを正しく初期化できます。

class Player {
public:
    string name;
    int hp;
    double speed;
 
    // (例1)メンバイニシャライザを使った初期化
    Player(string n, int h, double s)
        : name(n), hp(h), speed(s)
    {
        // コンストラクタ本体
    }
 
    // (例2)通常の代入初期化
    //最近割と非推奨(生成後に代入するため)
    Player(string n, int h, double s) {
        name = n;
        hp = h;
        speed = s;
    }
};

項目 書き方 説明
構文
: 変数(初期値), 変数(初期値)
クラスのメンバを生成時に同時初期化する
記述位置 コンストラクタの () の後 { } の前に書く
利点 代入より高速・確実 特に const メンバや参照メンバで必須
初期化順 宣言順に実行される ソースコード内の並び順に依存しない

  • 実際の距離を縮小して「双六(すごろく)」形式で表示できるようにする
  • Player(P)と Enemy(E)を左右から動かし、Enterキーで1ステップずつ進める
  • 双六の下に現在の位置を表示する
  • 実距離で「距離 ≤ 半径合計」のときに接触(HIT)を判定する

🎯 目的

  • C++でクラスを使って、PlayerとEnemyを動かす基本構造を作る。
  • Vector2Dで位置・速度を扱えるようにする。

🧠 考え方

  • PlayerとEnemyはほぼ同じ構造なので、共通の仕組みをクラス化する。
  • update関数で座標を更新し、pos = pos + vel * DT という形で動かす。

🛠 コード

#include <iostream>
#include <vector>
#include <cmath>
#include <string>
#include <algorithm>
#include <cstdlib>   // system("cls")
#include <limits>    // numeric_limits
using namespace std;
 
const int    BOARD_CELLS = 28;
const double SCALE       = 10.0;
const double DT          = 1.0;
 
class Vector2D {
public:
  double x, y;
  Vector2D(double ax=0, double ay=0) : x(ax), y(ay) {}
  Vector2D add(const Vector2D& v) const { return Vector2D(x + v.x, y + v.y); }
  static double distance(const Vector2D& a, const Vector2D& b) {
    double dx = a.x - b.x, dy = a.y - b.y;
    return sqrt(dx*dx + dy*dy);
  }
};
 
class Player {
public:
  string name;
  Vector2D pos, vel;
  double radius;
  Player(string n, Vector2D p, Vector2D v, double r)
      : name(n), pos(p), vel(v), radius(r) {}
  void update() { pos = pos.add(Vector2D(vel.x * DT, vel.y * DT)); }
};
 
class Enemy {
public:
  string name;
  Vector2D pos, vel;
  double radius;
  Enemy(string n, Vector2D p, Vector2D v, double r)
      : name(n), pos(p), vel(v), radius(r) {}
  void update() { pos = pos.add(Vector2D(vel.x * DT, vel.y * DT)); }
};

🔍 確認

  • クラスの定義が正しいか確認する。
  • まだ実行しても何も起きないが、エラーが出なければOK。

🎯 目的

  • 実距離(worldX)を「マス番号(セル番号)」に変換する。

🧠 考え方

  • 1マス = SCALE(例: 10.0)と決める。
  • 実距離 ÷ SCALE でマス番号に変換できる。
  • 例:実距離 35 → 35 / 10 = 3.5 → 四捨五入で4マス目

🛠 コード

int toCell(double worldX) {
  int idx = static_cast<int>(worldX / SCALE + 0.5);
  if (idx < 0) idx = 0;
  if (idx >= BOARD_CELLS) idx = BOARD_CELLS - 1;
  return idx;
}

🔍 確認

  • worldX=0, 9.9, 10.0, 20.1 などを試してマス番号を確認。

🎯 目的

  • PlayerとEnemyの位置を「|P|_|_|E|」のように文字で表す。

🧠 考え方

  • vector<char> でマスを作り、PとEを配置する。
  • 同じマスに来たら「X」と表示する。

🛠 コード

string buildSugorokuLine(int pCell, int eCell) {
  vector<char> cells(BOARD_CELLS, '_');
  if (pCell == eCell) cells[pCell] = 'X';
  else {
    cells[pCell] = 'P';
    cells[eCell] = 'E';
  }
  string line;
  for (char c : cells) {
    line += '|';
    line += c;
  }
  line += '|';
  return line;
}

🔍 確認

  • buildSugorokuLine(0,3) → |P|_|_|E|

🎯 目的

  • 双六の下に「Enemy: E(距離)」「Player: P(距離)」を表示する。

🛠 コード

void render(const Player& p, const Enemy& e) {
  int pCell = toCell(p.pos.x);
  int eCell = toCell(e.pos.x);
  string line = buildSugorokuLine(pCell, eCell);
  cout << line << "\n";
  cout << "  Enemy:  E(" << e.pos.x << ")\n";
  cout << "  Player: P(" << p.pos.x << ")\n";
}

🔍 確認

  • PlayerとEnemyの位置がきちんと表示されるか。

🎯 目的

  • Enterを押すたびに動きを1ステップ進める。

🛠 コード

int main() {
  Player pl("Player", Vector2D(0,0), Vector2D(3,0), 2.0);
  Enemy en("Enemy", Vector2D(270,0), Vector2D(-2,0), 2.0);
 
  for (int step = 0; step < 200; ++step) {
    system("cls");
    cout << "=== Step " << step << " ===\n";
    render(pl, en);
 
    double d = Vector2D::distance(pl.pos, en.pos);
    cout << "  distance = " << d
         << " / threshold (rP+rE) = " << (pl.radius + en.radius) << "\n";
 
    if (d <= (pl.radius + en.radius)) {
      cout << ">>> HIT! 接触しました。\n";
      break;
    }
 
    cout << "\nEnterで次へ...";
    cin.ignore(numeric_limits<streamsize>::max(), '\n');
 
    pl.update();
    en.update();
  }
  return 0;
}

🔍 確認

  • Enterで1ステップ進み、HIT判定が出るまで動く。

🎯 目的

  • 判定ロジックを関数として分ける。

🛠 コード

bool hitTest(const Player& p, const Enemy& e) {
  double d = Vector2D::distance(p.pos, e.pos);
  return d <= (p.radius + e.radius);
}

🔍 確認

  • 近づいた時に「HIT!」が表示される。

🎯 目的

  • 各パラメータを変えて挙動を観察する。

🧠 観察のポイント

  • 速度が速いとどうなる?
  • 半径が大きいと早く当たる?
  • SCALEを変えると画面の縮尺がどう変わる?

💬 メモ例

  • 速度を上げると、画面上の動きが速くなりすぐに接触した。
  • SCALEを10→5にするとマップが長くなった。

=== Step 12 ===
|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|P|_|_|_|_|_|_|_|E|
  Enemy:  E(162.0)
  Player: P(162.0)
  distance = 0 / threshold (rP+rE) = 4
>>> HIT! 接触しました。

  • 実際の距離を縮小して画面に表示できるようになった。
  • PlayerとEnemyをクラスで表し、座標更新・距離計算を行った。
  • 当たり判定は「距離 ≤ 半径合計」で表せる。
  • Enterキーで1ステップずつ進むゲームループを体験した。
  • game-engineer/classes/2025/game-development-1/no-08-6_5.txt
  • 最終更新: 3カ月前
  • by root