欲しい本がいっぱいあります。el(える)です。
今回はコンソール上で動く、超簡単な迷路ゲームを作っていきます。
ここまで学んできた人なら作るのは大丈夫なはずです。
目次
作るゲームについて
作るゲームのイメージです。
#が壁、Pがプレイヤ、Gがゴールを表しています。
キーボードのAが左、Wが上、Dが右、Sが下としてプレイヤを動かし、
ゴールまでたどり着くとゲームクリアと表示してプログラムを終了します。
01ゲーム用のプロジェクトを作成しよう
新しいプロジェクト→画面左側の項目でVisual C#→Windowsクラシック木偶ストップを選択、
次に中央の項目からコンソールアプリ(.NETFramework)を選択。
プロジェクト名は「MazeGame」としましょう。
プロジェクトの保存先を任意の場所にして、OKボタンを押します。
02モノを表示しよう
ステージ上に#や空白などあらなる文字列を5行分表示することが必要です。
Console.WriteLine(“#####”);
Console.WriteLine(“# #”);
Console.WriteLine(“# # #”);
Console.WriteLine(“#P#G#”);
Console.WriteLine(“#####”);
この表示の仕方だとPを動かすだけで文字列をごっそり動かさなければいけません。
そこで、1文字ずつ管理する方法を考えます。
char [] map {‘#’,’#’,’#’,’#’,’#’,’#’,’ ‘,’ ‘,’ ‘,’#’,・・・};
map配列には25個のステージ情報を格納して、
ステージの左上から右下に向かって5行に分けて並べます.
ステージを1行だけ表示させてみましょう。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MazeGame { class Program { static char[] map = { '#','#','#','#','#', '#',' ',' ',' ','#', '#',' ','#',' ','#', '#','P','#','G','#', '#','#','#','#','#', }; static void Main(string[] args) { for (int i = 0; i < 5; i++) { Console.Write(map[i]); } } } }
Char型でmap配列を初期化しています。
map配列の中身は、ステージの形が分かりやすいように5文字ごとに改行しています。
横1列で表示させたかったので、Console.Wrieメソッドを使っています。
正方形のステージを表示するには同様の方法を繰り返せばよさそうです。
改行を入れるタイミングを考えてみましょう。
赤字で書いたインデックスの時に改行をいれます。
if(i == 4 || i == 9 || ・・・)
としていけば改行できますが、ステージが多くなった時に大変なので、
1を足すと5の倍数になるので、5の倍数の時に真となる条件にします。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MazeGame { class Program { static char[] map = { '#','#','#','#','#', '#',' ',' ',' ','#', '#',' ','#',' ','#', '#','P','#','G','#', '#','#','#','#','#', }; static void Main(string[] args) { for (int i = 0; i < map.Length; i++) { Console.Write(map[i]); if ((i + 1) % 5 == 0) { Console.Write(System.Environment.NewLine); } } } } }
(i + 1)が5で割り切れるか%演算子(割り値の余りを求める)を使っています。
System.Environment.NewLineは改行文字を表します。
ステージ描画をメソッド化しよう。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MazeGame { class Program { static char[] map = { '#','#','#','#','#', '#',' ',' ',' ','#', '#',' ','#',' ','#', '#','P','#','G','#', '#','#','#','#','#', }; static void DrawMap() { for (int i = 0; i < map.Length; i++) { Console.Write(map[i]); if ((i + 1) % 5 == 0) { Console.Write(System.Environment.NewLine); } } } static void Main(string[] args) { DrawMap(); } } }
読みやすいプログラムを作るためにも、処理や機能のまとまりに気づいたらメソッド化する習慣をつけておきましょう。
03プレイヤを動かそう
まずは左に1歩動かすプログラムを考えてみましょう。
- プレイヤのいるマス目を探す。
- 現在プレイヤがいる左のマス目をPにする
- プレイヤがいたマス目を空白にする
この3つのステップをプログラムにします。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MazeGame { class Program { static char[] map = { '#','#','#','#','#', '#',' ',' ',' ','#', '#',' ','#',' ','#', '#','P','#','G','#', '#','#','#','#','#', }; static void DrawMap() { for (int i = 0; i < map.Length; i++) { Console.Write(map[i]); if ((i + 1) % 5 == 0) { Console.Write(System.Environment.NewLine); } } } static void Main(string[] args) { //プレイヤの現在地のインデックスを探す int playerPos = Array.IndexOf(map, 'P'); //プレイヤの現在地の左のマス目を'P'にする map[playerPos - 1] = 'P'; //プレイヤの現在地を' 'にする map[playerPos] = ' '; DrawMap(); } } }
プレイヤがいる位置をインデックスに入れておくplayerPos変数を宣言しています。
map配列からプレイヤのいる位置を探すためIndexOfメソッドを使っています。
配列中に該当する値があった場合、その値が入っている要素のインデックスを返し、 なかった場合はマイナスの値を返します。
現在プレイヤがいる位置から1マス左のインデックスは「playerPos – 1」と表せるので、
「playerPos – 1」の要素をPに書き換えます。
プレイヤがいたインデックスに空白を代入しています。
04プレイヤを入力に応じて動かそう
ユーザーから入力を受け取ってプレイヤを移動させるプログラムを作りましょう。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MazeGame { class Program { static char[] map = { '#','#','#','#','#', '#',' ',' ',' ','#', '#',' ','#',' ','#', '#','P','#','G','#', '#','#','#','#','#', }; static void DrawMap() { for (int i = 0; i < map.Length; i++) { Console.Write(map[i]); if ((i + 1) % 5 == 0) { Console.Write(System.Environment.NewLine); } } } static void Main(string[] args) { DrawMap(); string key = Console.ReadLine(); int playerPos = Array.IndexOf(map, 'P'); int playerNextPos = 0; if(key == "a") //プレイヤを左に移動 { playerNextPos = playerPos - 1; } else if(key == "d") //プレイヤを右に移動 { playerNextPos = playerPos + 1; } else if(key == "w") //プレイヤを上に移動 { playerNextPos = playerPos - 5; } else if(key == "s") //プレイヤを下に移動 { playerNextPos = playerPos + 5; } map[playerNextPos] = 'P'; map[playerPos] = ' '; playerPos = playerNextPos; DrawMap(); //更新後マップを表示 } } }
プレイヤの移動をメソッド化する
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MazeGame { class Program { static char[] map = { '#','#','#','#','#', '#',' ',' ',' ','#', '#',' ','#',' ','#', '#','P','#','G','#', '#','#','#','#','#', }; static void DrawMap() { for (int i = 0; i < map.Length; i++) { Console.Write(map[i]); if ((i + 1) % 5 == 0) { Console.Write(System.Environment.NewLine); } } } static void MovePlayer(string key) { int playerPos = Array.IndexOf(map, 'P'); int playerNextPos = 0; if (key == "a") //プレイヤを左に移動 { playerNextPos = playerPos - 1; } else if (key == "d") //プレイヤを右に移動 { playerNextPos = playerPos + 1; } else if (key == "w") //プレイヤを上に移動 { playerNextPos = playerPos - 5; } else if (key == "s") //プレイヤを下に移動 { playerNextPos = playerPos + 5; } else { return; } map[playerNextPos] = 'P'; map[playerPos] = ' '; playerPos = playerNextPos; } static void Main(string[] args) { DrawMap(); string key = Console.ReadLine(); MovePlayer(key); //プレイヤの移動 DrawMap(); //更新後マップを表示 } } }
05壁を突き破らないようにする
当たり判定処理を追加しましょう
- 移動先のマス目が#かどうか調べる。
- 壁ならプレイヤを移動しない。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MazeGame { class Program { static char[] map = { '#','#','#','#','#', '#',' ',' ',' ','#', '#',' ','#',' ','#', '#','P','#','G','#', '#','#','#','#','#', }; static void DrawMap() { for (int i = 0; i < map.Length; i++) { Console.Write(map[i]); if ((i + 1) % 5 == 0) { Console.Write(System.Environment.NewLine); } } } static void MovePlayer(string key) { int playerPos = Array.IndexOf(map, 'P'); int playerNextPos = 0; if (key == "a") //プレイヤを左に移動 { playerNextPos = playerPos - 1; } else if (key == "d") //プレイヤを右に移動 { playerNextPos = playerPos + 1; } else if (key == "w") //プレイヤを上に移動 { playerNextPos = playerPos - 5; } else if (key == "s") //プレイヤを下に移動 { playerNextPos = playerPos + 5; } else { return; } //移動先のマス目が#ではない場合にプレイヤを移動する if (map[playerNextPos] != '#') { map[playerNextPos] = 'P'; map[playerPos] = ' '; playerPos = playerNextPos; } } static void Main(string[] args) { DrawMap(); string key = Console.ReadLine(); MovePlayer(key); //プレイヤの移動 DrawMap(); //更新後マップを表示 } } }
if文で移動先のマス目が#かどうか調べています。
移動先のマス目の値はmap[playerNextPos]で調べられます。
06何回も入力できるようにしよう
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MazeGame { class Program { static char[] map = { '#','#','#','#','#', '#',' ',' ',' ','#', '#',' ','#',' ','#', '#','P','#','G','#', '#','#','#','#','#', }; static void DrawMap() { for (int i = 0; i < map.Length; i++) { Console.Write(map[i]); if ((i + 1) % 5 == 0) { Console.Write(System.Environment.NewLine); } } } static void MovePlayer(string key) { int playerPos = Array.IndexOf(map, 'P'); int playerNextPos = 0; if (key == "a") //プレイヤを左に移動 { playerNextPos = playerPos - 1; } else if (key == "d") //プレイヤを右に移動 { playerNextPos = playerPos + 1; } else if (key == "w") //プレイヤを上に移動 { playerNextPos = playerPos - 5; } else if (key == "s") //プレイヤを下に移動 { playerNextPos = playerPos + 5; } else { return; } //移動先のマス目が#ではない場合にプレイヤを移動する if (map[playerNextPos] != '#') { map[playerNextPos] = 'P'; map[playerPos] = ' '; playerPos = playerNextPos; } } static void Main(string[] args) { DrawMap(); while (true) { string key = Console.ReadLine(); MovePlayer(key); //プレイヤの移動 DrawMap(); //更新後マップを表示 } } } }
何歩でゴールにたどり着くか分からないので、while文を使っています。
while文の条件をtrueにすることで無限ループになっています。
終了するさいは×で閉じましょう。
07ゴール判定をしよう
プレイヤがゴールしたかどうかを判定し、ゴールしていれば、ゲームクリアと表示させましょう。
プレイヤがゴールに到着するとGの文字がなくなります。
map配列の中にGの文字がない場合はゴールしたと判断できます。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MazeGame { class Program { static char[] map = { '#','#','#','#','#', '#',' ',' ',' ','#', '#',' ','#',' ','#', '#','P','#','G','#', '#','#','#','#','#', }; static void DrawMap() { for (int i = 0; i < map.Length; i++) { Console.Write(map[i]); if ((i + 1) % 5 == 0) { Console.Write(System.Environment.NewLine); } } } static void MovePlayer(string key) { int playerPos = Array.IndexOf(map, 'P'); int playerNextPos = 0; if (key == "a") //プレイヤを左に移動 { playerNextPos = playerPos - 1; } else if (key == "d") //プレイヤを右に移動 { playerNextPos = playerPos + 1; } else if (key == "w") //プレイヤを上に移動 { playerNextPos = playerPos - 5; } else if (key == "s") //プレイヤを下に移動 { playerNextPos = playerPos + 5; } else { return; } //移動先のマス目が#ではない場合にプレイヤを移動する if (map[playerNextPos] != '#') { map[playerNextPos] = 'P'; map[playerPos] = ' '; playerPos = playerNextPos; } } static bool CheckGoal() { //mapにGがない場合はtrue、 //Gがある場合はfalseを返す。 if(Array.IndexOf(map,'G') < 0) { return true; } else { return false; } } static void Main(string[] args) { DrawMap(); while (true) { string key = Console.ReadLine(); MovePlayer(key); //プレイヤの移動 if (CheckGoal()) //ゴールしたらwhile文を抜ける { break; } DrawMap(); //更新後マップを表示 } Console.WriteLine("ゲームクリア"); } } }
CheckGoalメソッドを追加しています。
map配列中にGがあればfalseを返し、なければtrueを返します。
IndexOfメソッドを使い、第一引数に指定した配列に第二引数に指定した値がない場合、
マイナス値を返します。
そのため、マイナス値ならtrue、そうでなければfalseとなります。
CheckGoalメソッドの戻り値がtrueだった場合、break文が実行され、
無限ループを抜け、ゴールクリアと表示して処理を終了します。
プログラムを作るときの考え方まとめ
- 作りたいものを具体的に考える
- 作りたいものはどんな処理が必要なのか考える
- 処理を細かく分割する
- 分割した処理をどうすればプログラムできるか考える
- 分割したプログラムが簡単に作れそうと感じたものから作ってみる
- 上手くいかなかったら、処理を細かく分割して~を考え直しながら作るを繰り返す
終わりに
ちょっと難しかったですかね。
ゆっくりゆっくり考えながらやれば問題はないと思います。
一個一個のメソッドの内容はそこまで難しいものではないので。
でもちょっとプログラミングを舐めてました。すみません。
ただ簡単なゲームが作れたことで自信もついたと思います。
C#の欲しい本
amazonにレビューがないので、どんな本なんだろうって興味があります。
読んでみたいなぁ。
アプリ・ゲーム開発にも使える基礎を習得って言葉にとても惹かれます。
それではまた次回。