Appearance
第15回 CSVから作るステージ
1 / 4 CSVの値をStageCellへ変換する
このステップの目的
CSVファイル内に記述された整数値をスコープ付き列挙型 StageCell へ変換し、定義済みの値のみを安全に受け付ける設計を学びます。
生の整数値を直接比較しない設計
CSVファイルには、0, 0, 10, 10, 1 のようにカンマで区切られた数値が並んでいます。
プログラム側で if (cell == 10) や if (cell == 1) のように数値を直接比較してしまうと、以下の問題が発生します。
- 可読性の低下:
10や1が何を表すのか、コードを読む側には判別が困難になります。 - 誤入力の検知不可:
10と書くべき箇所に誤って1と書いても、コンパイラは検知できません。 - 不正値の混入: CSVに
999などの存在しない数値が含まれた際、意図しない挙動を引き起こします。
用途ごとに値域を分けた StageCell の定義
これらの問題を解決するため、すべてのマップチップの種類を enum class StageCell : int として定義します。
StageCell.hcpp
namespace dxgame::platformer
{
enum class StageCell : int
{
Empty = 0, // 空白(空間)
// 1〜9: 進行・ゲームルール上の目印
Goal = 1, // ゴール地点
PlayerSpawn = 2, // プレイヤーの出現位置
// 10〜19: 通常の地形(ブロック・足場)
Brick = 10, // レンガブロック
Moss = 11, // コケ付きブロック
// 20〜29: トラップ(危険物)
Spike = 20, // 棘トラップ(第23回で使用)
// 90〜99: 敵の出現配置
EnemySpawn = 90 // 敵の初期配置位置(第21回で使用)
};
}用途ごとに値域を分ける理由
列挙値を 0, 1, 2, 3... と詰めた連番にしてしまうと、後から新しい地形ブロックを追加した際に既存の番号がずれ、作成済みのCSVファイルを書き直す必要が生じます。
用途ごとに「10番台は通常地形」「20番台はトラップ」「90番台は敵」と値域をあらかじめ空けておくことで、既存のCSVデータとの互換性を保ちながら拡張できます。
検証関数 TryParseStageCell() の実装
C++では、単に static_cast<StageCell>(intValue) とするだけでは、列挙型に定義されていない不正な数値(例えば 37)も強制的に変換されてしまいます。
安全性を担保するため、定義されている列挙値のみを確実に受け付ける検証関数 TryParseStageCell() を用意します。
StageCell.cppcpp
bool TryParseStageCell(int value, StageCell& outCell)
{
switch (value)
{
case static_cast<int>(StageCell::Empty):
case static_cast<int>(StageCell::Goal):
case static_cast<int>(StageCell::PlayerSpawn):
case static_cast<int>(StageCell::Brick):
case static_cast<int>(StageCell::Moss):
case static_cast<int>(StageCell::Spike):
case static_cast<int>(StageCell::EnemySpawn):
outCell = static_cast<StageCell>(value);
return true;
default:
// 未定義の数値は拒否して false を返す
return false;
}
}この関数を通すことで、CSV内に未定義の数値が紛れ込んだ場合でも、プログラムの奥深くに不正なデータが入り込む前に検知できます。
チェックリスト
- CSVの数値を直接比較せず、
enum class StageCellを介して扱う理由を説明できる - 用途ごとに値域(0番台、10番台、20番台など)を空けておく設計意図を理解している
-
TryParseStageCell()によって、未定義の数値を安全に検出できる仕組みを説明できる