====== コンソール版 Costco Man(コスコマン)の制作 ======
アメリカの卸業者COSTCOは、アメリカではカタカナで書くと(コスコ)と発音する。\\
ちなみにIKEAはアメリカではアイキアと読むのだが、スウェーデンではイケアと読む笑\\
ということで、倉庫の中の荷物を、目的の位置まで運ぶゲームを作ろうと思う。\\
とても似たようなゲームに倉庫番というゲームがあるらしい。。。\\
[[https://sdin.jp/outside/puzzle/sokoban.php|Web 倉庫番1]]
「大体」同じような流れのゲームを作りたいと思う。\\
{{:game-engineer:classes:2023:something-else:summertime-special-cource:costcomanconsole.png?300|}}
=== ルール ===
Web版をやってみるとわかると思うが、ルールは単純明快で、(来る日も来る日も、倉庫を片付ける仕事をする)倉庫番の労働者を操作して、荷物をすべて目的地まで片付けるパズルゲームである。\\
これをコンソール版で作ってみよう。いよいよキャラクターを操作するタイプのゲームである。\\
== 登場オブジェクト ==
登場人物を@$#. (最後は半角スペース)で表す。\\
* 人 --------- '@' (アットマーク)
* 荷物 ------ '$' (ダラー)
* 壁 --------- '#' (シャープ)
* ゴール --- '.' (ドット(ピリオド))
* 床 --------- ' ' (スペース)
{{:game-engineer:classes:2023:something-else:summertime-special-cource:スクリーンショット_2023-08-08_091646.png?300|}}
有名なゲーム画面
このようなステージは、
{{:game-engineer:classes:2023:something-else:summertime-special-cource:souko_stage.png?300|}}
コンソール版の表現
このような表示で表す。グラフィックがまだ使えないので、想像力でカバーしてほしい。\\
また、全角文字を使ったり、いろいろ表現を変えることもできるので見やすい表示にしたければやってみるといいと思う。\\
(全角使うと一番上の画像のように面白い表現法ができるけど、違うPCで実行したときに文字化けしたりしてとても面倒です。。。)\\
ちなみにこのようなコンソールのゲームは、Unix(Linuxの親玉)のシステム上でたくさん作られ、その後Windows(MS-DOS時代から)やその他のOSに移植された。\\
有名なものでは、Rogue、net-hackなどがあり、ストーリーや登場するアイテムや人物、モンスターなどは、トールキンの指輪物語や、D&D(Dangeons and Dragons)などを題材にしたものが多い。\\
これらのゲームはコンソール画面に出たキャラクターを、キーボードのエディタ操作を模した操作法で操作するため、遊んでいるうちに基本的な操作を覚えられたりと、いろいろ面白い要素がたくさんある。\\
軟派な派生品として、不思議のダンジョンシリーズなどがコンソールゲームとして発売されているが、ほぼ原作の丸パクリであることは有名である。\\
ということで、今回はこの想像力を鍛える画面で、ゲームを動かしていくことにする。\\
まずは、簡単なステージから作っていこう。\\
==== ステージの表現 ====
ステージは、先ほどの半角文字を2次元配列としてステージ型に並べちゃいます。\\
1次元配列と2次元配列の変換ができる人は1次元配列で持ってたほうが精神衛生上いい効果があります(笑)\\
[[https://blog.goo.ne.jp/odohuran/e/03dc15f69094c303e0a1ddbd44280571|2次元配列を1次元で表現(人のとこの記事だけど)]]
{{:game-engineer:classes:2023:something-else:summertime-special-cource:stagegrid.png?400|}}
2次元配列によるステージの表現法
これを、intの配列で並べてあつかいます。\\
対応付けとして登場オブジェクトすべての組み合わせを書けるように以下のように表現する\\
(ゴールの上の荷物と、ゴールの上の人は、表現する文字を変えないとオブジェクト同士が重なってるのがわからないよねぇ。。。どうしよう)\\
//0 床
//1 壁
//2 荷物
//3 ゴール
//4 人
//5 人 on the ゴール
//6 荷物 on the ゴール
//7 壁の外のゲームに使わない領域
ステージ用の2次元配列
const int MAX_STAGE_HEIGHT = 10;
const int MAX_STAGE_WIDTH = 10;
//行、列の順に並んでるから、HEIGHT,WIDTHの順に書くよ
int stageData[MAX_STAGE_HEIGHT][MAX_STAGE_WIDTH] =
{
{1, 1, 1, 1, 7, 7, 7, 7, 7, 7},
{1, 3, 0, 0, 1, 7, 7, 7, 7, 7},
{1, 0, 2, 0, 1, 7, 7, 7, 7, 7},
{1, 0, 4, 0, 1, 7, 7, 7, 7, 7},
{1, 1, 1, 1, 1, 7, 7, 7, 7, 7},
{7, 7, 7, 7, 7, 7, 7, 7, 7, 7},
{7, 7, 7, 7, 7, 7, 7, 7, 7, 7},
{7, 7, 7, 7, 7, 7, 7, 7, 7, 7},
{7, 7, 7, 7, 7, 7, 7, 7, 7, 7},
{7, 7, 7, 7, 7, 7, 7, 7, 7, 7}
};
後はこれを、表示する関数\\
void DrawStage(ステージのデータ)\\
を作ればいいんだけども、幅と高さの情報もステージに含めちゃうのと、配列を関数に渡すやり方をみんな知らんので、構造体に丸っと納めちゃいます。(あんまよくない)\\
== ステージを表す構造体 ==
ステージを表現する構造体
struct Map
{
int stage_width;
int stage_height;
int Dat[MAX_STAGE_HEIGHT][MAX_STAGE_WIDTH];
};
ステージの最大の大きさは、STAGE_HEIGHT、STAGE_WIDTHで表されるけど、毎回全部使うわけではないので、そのステージの最大幅と、最大高さを、構造体のメンバstage_width,stage_heightで表す。\\
これのメンバを書いておくことで、無駄に全部の配列を毎回ループせずに済む(しても大した手間ではないんだけどね)\\
== ステージの初期化 ==
ステージのデータは以下のように作成する。(10x10の適当なステージを作ってみる)\\
ステージの作り方(Mapの初期化)
Map sampleSatge = {
10,10,
{
{1,1,1,1,1,1,1,1,1,1},
{1,0,0,2,3,3,0,0,0,1},
{1,0,4,2,0,0,0,0,0,1},
{1,0,0,0,0,1,1,0,0,1},
{1,0,0,0,0,2,0,0,0,1},
{1,0,0,3,0,0,0,0,0,1},
{1,0,0,0,0,0,1,1,1,1},
{1,0,0,0,0,0,0,0,0,1},
{1,0,1,0,0,0,0,0,0,1},
{1,1,1,1,1,1,1,1,1,1}
}
}
== ステージ表示関数 ==
次に、ステージデータを受け取り、ステージを描画する関数**DrawStage()**を作成する。\\
戻り値は、表示するだけなのでvoidで、引数はMapの参照を受け取ることにする。\\
**void DrawStage(Map& _map);**
ここで、表示のことを考えなければいけないのでゴール上の人、ゴール上の荷物も、何の文字で表すかを決める。\\
と思ったけど、実は似たようなゲームの倉庫番は、データの標準形が決まっていてXsokoban形式というのがある。\\
これを使おうと思う。[[http://www2.tokai.or.jp/deepgreen/sokoban/term.htm|参考ここ]]\\
**マップデータの番号に対応したインデックスで、ゲームオブジェクトを表す文字の配列を作っておく**\\
ついでに、0が床で、5がゴールに乗ってる人で、とか毎回照合するのがめんどくさいので、文字に数字充てて使いやすくしたい。\\
const int FLOOR = 0;
const int WALL = 1;
・・・
const int MANONTHEGOAL = 5
とかでもいいけど、もっと楽ながあったよね!enumとかいうやつ。\\
ってことでenumで、番号中に名前つけていきます。\\
//0 床
//1 壁
//2 荷物
//3 ゴール
//4 人
//5 人 on the ゴール
//6 荷物 on the ゴール
//7 壁の外のゲームに使わない領域
enum OBJNAME {
FLOOR, WALL, LUGG, GOAL, HUMAN, HUMAN_ON_GOAL, LUGG_ON_GOAL
これで、マップの数字との比較などが楽になる。\\
もしpos.xの右隣の座標が壁だったら?という比較は、\\
//enumなしの場合
if(pos.x + 1 == 1){処理}
//とかになる
//enumを使ったとき
if(pos.x + 1 == WALL){処理}
こう書くと、隣が壁なのかどうか調べてるんだな、というのがわかりやすい\\
んじゃ、まとめると以下のようになるので\\
**void DrawStage(Map& _map);**を書いてみよう。
caption
//0 床
//1 壁
//2 荷物
//3 ゴール
//4 人
//5 人 on the ゴール
//6 荷物 on the ゴール
//7 壁の外のゲームに使わない領域
enum OBJNAME {
FLOOR, WALL, LUGG, GOAL, HUMAN, HUMAN_ON_GOAL, LUGG_ON_GOAL
};
const string OBJS[] = const char OBJS[] = { " ", "#", "$", ".", "@", "+", "*", " "};
void DrawStage(Map& _map)
{
for (int j = 0; j < _map.stage_height; j++)
{
for (int i = 0; i < _map.stage_width; i++)
{
cout << OBJS[マップデータの値];
}
cout << endl;
}
}
=== 動作確認 ===
ステージの表示
#include
#include
#include
#include
using std::cout;
using std::cin;
using std::endl;
using std::string;
const int MAX_STAGE_WIDTH = 10;
const int MAX_STAGE_HEIGHT = 10;
struct Map
{
int stage_width;
int stage_height;
int Dat[MAX_STAGE_HEIGHT][MAX_STAGE_WIDTH];
};
enum OBJNAME {
FLOOR, WALL, LUGG, GOAL, HUMAN, HUMAN_ON_GOAL, LUGG_ON_GOAL
};
const string OBJS[] = { " ", "#", "$", ".", "@", "+", "*", " " };
Map sampleSatge = {
10,10,
{
{1,1,1,1,1,1,1,1,1,1},
{1,0,0,2,3,3,0,0,0,1},
{1,0,4,2,0,0,0,0,0,1},
{1,0,0,0,0,1,1,0,0,1},
{1,0,0,0,0,2,0,0,0,1},
{1,0,0,3,0,0,0,0,0,1},
{1,0,0,0,0,0,1,1,1,1},
{1,0,0,0,0,0,0,0,0,1},
{1,0,1,0,0,0,0,0,0,1},
{1,1,1,1,1,1,1,1,1,1}
}
};
int main()
{
DrawStage(sampleSatge);
}
こんな感じでmainを組んで、実行してみよう。
2次元配列のステージが、キャラクターを表す文字に置き換われば成功!\\
{{:game-engineer:classes:2023:something-else:summertime-special-cource:stage_sample1.png?300|}}
実行例
;#;
[[game-engineer:classes:2023:something-else:summertime-special-cource:costcoman-console-2|その2 コスコマン大地に立つ(こいつ動くぞ)へ]]
;#;