vol.1 ゲームフィールドの実装、プレイヤーの移動

目次

はじめに

C言語を用いてゲームプログラムを書くことになった人のために、丹精込めて書きます。
今回はゲームのフィールド、およびプレイヤーの移動について解説していくよ。
統合開発環境としてVisual Studio Community 2017を使っています。

プログラムの書き出し

画面を表示し続けるために最低限のプログラムが成立するために必要なものを記述していきます。

#include<conio.h>

int main(void)
{
	//この部分にプログラムを書いていきます

	_getch();
	return 0;
}

_getch関数は文字入力を待機している状態なので、文字を入力するまでは画面が保持されます。
画面をとめておきたい場合にはこの_getch関数を使いましょう。

フィールド作成

ゲームではおなじみのフィールドを実装しましょう。
フィールドの実装には配列を活用していきます。今回は10×10マス分の□でフィールドを表現してみます。

#include<stdio.h>
#include<string.h>
#include<conio.h>

//縦・横を10で定義
#define WIDTH 10
#define HEIGHT 10

int main(void)
{
	int x, y;

	//配列の3は、全角文字2バイト+終了コード \0 1バイト 合計3バイト分
	char field[HEIGHT][WIDTH][3];

	//fieldの初期化 今回はすべて□
	for (y = 0; y < HEIGHT; y++){
		for (x = 0; x < WIDTH; x++){
			strcpy_s(field[y][x], 3,  "□");
		}
	}

	//fieldの表示
	for (y = 0; y < HEIGHT; y++){
		for (x =0; x < WIDTH; x++){
			printf("%s", field[y][x]);
		}
		printf("\n");
	}

	_getch();
	return 0;
}

この配列fieldの中身を操作することによって色々なことを実装していきます。
ひとまずはこれでフィールドが完成!

プレイヤーを配置する

単純にプレイヤーを左上に配置するだけです。
この考え方がフィールドで色々なことを実装していくのに役に立ちます。
左上というのは、x座標y座標がともに0の点を指します。プレイヤーを△で表現します。
なお、コードの中で変更した部分には //←←← を記入しておきます。

#include<stdio.h>
#include<string.h>
#include<conio.h>

//縦・横を10で定義
#define WIDTH 10
#define HEIGHT 10

int main(void)
{
	int x, y;
	//プレイヤーのx座標、y座標
	int px = 0, py = 0; //←←←

	//配列の3は、全角文字2バイト+終了コード \0 1バイト 合計3バイト分
	char field[HEIGHT][WIDTH][3];

	//fieldの初期化 今回はすべて□
	for (y = 0; y < HEIGHT; y++){
		for (x = 0;x < WIDTH; x++){
			//プレイヤーのx座標,y座標によってfieldの情報を変えます。
			if(x == px && y == py) strcpy_s(field[y][x], 3,  "△"); //←←←

			else strcpy_s(field[y][x], 3,  "□"); //←←←
		}
	}

	//fieldの表示
	for (y = 0; y < HEIGHT; y++){
		for (x = 0; x < WIDTH; x++){
			printf("%s", field[y][x]);
		}
		printf("\n");
	}

	_getch();
	return 0;
}

これで左上が△で、残りが□のフィールドになりました。

プレイヤーを移動させる(1)

プレイヤーを移動させられるようになると、一気に実装の幅が広がります。初めてのゲームプログラミングにとって、一番
わくわくするポイントだと思います。
w,s,a,d のキーでそれぞれ、上、下、左、右と移動するようにします。
px,pyを更新していくことによってプレイヤーが動くように実装します。
ここで、新しくstdlib.hをインクルードしますが、これは画面の更新のために必要なsystem("cls")を使うためです。
cls は clear screen の略です。

#include<stdio.h>
#include<string.h>
#include<conio.h>
#include<stdlib.h> //←←←

//縦・横を10で定義
#define WIDTH 10
#define HEIGHT 10

int main(void)
{
	int x, y;
	//プレイヤーのx座標、y座標
	int px = 0, py = 0; 
	//キー入力用
	int key; //←←←

	//配列の3は、全角文字2バイト+終了コード \0 1バイト 合計3バイト分
	char field[HEIGHT][WIDTH][3];

	//fieldの初期化 今回はすべて□
	for (y = 0; y < HEIGHT; y++){
		for (x = 0; x < WIDTH; x++){
			//プレイヤーのx座標,y座標によってfieldの情報を変えます。
			if (x == px && y == py) strcpy_s(field[y][x], 3, "△"); 

			else strcpy_s(field[y][x], 3, "□"); 
		}
	}
	//無限ループにすることによって、画面の更新を繰り返していきます。
	while(1){ //←←←
		
		//画面の更新
		system("cls"); //←←←
		
		//fieldの表示
		for (y = 0; y < HEIGHT; y++){
			for(x = 0 ; x < WIDTH; x++){
				printf("%s", field[y][x]);
			}
			printf("\n");
		}
		
		//プレイヤーのx座標、y座標が変更されてしまう前に、あらかじめ現在のfieldを初期化しておきます。
		strcpy_s(field[py][px], 3, "□"); //←←← 

		//キーを取得して、押されたキーによってプレイヤーのx座標、y座標を変更していきます。
		key=_getch(); //←←←
		if (key == 'w') py--; //←←←
		if (key == 's') py++; //←←←
		if (key == 'a') px--; //←←←
		if (key=='d') px++; //←←←

		//プレイヤーの新しいx座標、y座標を使ってfieldを更新します。
		strcpy_s(field[py][px], 3, "△"); //←←←
		
	}

	_getch();
	return 0;
}

プレイヤーを移動させる(2)

先ほどのプログラムで全然問題はないのですが、長いプログラムを書くようになったときに大量にif文が並んでいると
なんとなく格好悪いものです。その上、大量にif文があると動作が重くなる原因になるので、ここでは新しい条件分岐を
お教えします。
その名も switch文 です。
どのように分岐するかサンプルプログラムを見てみましょう。

このプログラムは、1,2,3,4,5の5つのコマンドを選択することによって分岐していくものです。
1:こうげき
2:まほう
3:どうぐ
4:ぼうぎょ
5:にげる

#include<stdio.h>
#include<conio.h>

int main(void)
{
	int n;
	printf("コマンド:");
	scanf("%d", &n);
	switch(n){
	case 1:
		printf("こうげき\n");
		break;
	case 2:
		printf("まほう\n");
		break;
	case 3:
		printf("どうぐ\n");
		break;
	case 4:
		printf("ぼうぎょ\n");
		break;
	case 5:
		printf("にげる\n");
		break;
	default: 
		printf("そんなコマンドはないぜ!\n");
		break;

	}

	_getch();
	return 0;
}

プログラムの上から順に解説していきます。
switch(n) ・・・nの内容について分岐していくという合図。
case 1:  ・・・nが1のとき、コロン以降の命令を実行。ここでのbreakはswitch文を抜け出すもの。
default: ・・・nが1,2,3,4,5のいずれでもなかった場合に、コロン以降の命令を実行

switch文は次のようにif文で書き換えられます。

if (n == 1) printf("こうげき\n");
else if (n == 2) printf("まほう\n");
else if (n == 3) printf("どうぐ\n");
else if (n == 4) printf("ぼうぎょ\n");
else if (n == 5) printf("にげる\n");
else printf("そんなコマンドはないぜ!\n");

ここで、switch文にはbreak文が書かれていましたが、もしbreak文が書かれていないとどのような動作になるのでしょうか。
もし、break文が書かれておらず、nとして4が入力されたとすると、

実行結果:
ぼうぎょ
にげる
そんなコマンドはないぜ!

となります。要するに、breakしないとそのまま下に命令が下りていく、ということです。
break文を入れるか入れないかは状況によるので、使いたいように使って下さい。

※大変便利でスマートなswitch文ですが、分岐には整数しか使えないので、注意して下さい。


さて、前置きが長くなりましたが、switch文を用いてプレイヤーを移動させてみましょう!

#include<stdio.h>
#include<string.h>
#include<conio.h>
#include<stdlib.h>

//縦・横を10で定義
#define WIDTH 10
#define HEIGHT 10

int main(void)
{
	int x, y;
	//プレイヤーのx座標、y座標
	int px = 0, py = 0; 
	//キー入力用
	int key;

	//配列の3は、全角文字2バイト+終了コード \0 1バイト 合計3バイト分
	char field[HEIGHT][WIDTH][3];

	//fieldの初期化 今回はすべて□
	for (y = 0; y < HEIGHT; y++){
		for (x = 0; x < WIDTH; x++){
			//プレイヤーのx座標,y座標によってfieldの情報を変えます。
			if (x == px && y == py) strcpy_s(field[y][x], 3, "△"); 

			else strcpy_s(field[y][x], 3, "□"); 
		}
	}


	//無限ループにすることによって、画面の更新を繰り返していきます。
	while(1){

		//画面の更新
		system("cls");

		//fieldの表示
		for (y = 0; y < HEIGHT; y++){
			for (x = 0; x < WIDTH; x++){
				printf("%s", field[y][x]);
			}
			printf("\n");
		}
		
		//プレイヤーのx座標、y座標が変更されてしまう前に、あらかじめ現在のfieldを初期化しておきます。
		strcpy_s(field[py][px], 3, "□");

		//キーを取得して、押されたキーによってプレイヤーのx座標、y座標を変更していきます。
		key = _getch();

		//switch文で表現します。
		switch(key){ //←←←
		case 'w': //←←←
			py--; //←←←
			break; //←←←
		case 's': //←←←
			py++; //←←←
			break; //←←←
		case 'a': //←←←
			px--; //←←←
			break; //←←←
		case 'd': //←←←
			px++; //←←←
			break; //←←←
		default: //←←←
			break; //←←←
		
		}

		//プレイヤーの新しいx座標、y座標を使ってfieldを更新します。
		strcpy_s(field[py][px], 3, "△");
		
	}

	_getch();
	return 0;
}

プレイヤーを移動させる(3) (壁判定)

プレイヤーを自由に動かせるようになったのはいいのですが、少々自由すぎますね。
実際に動かしてみるとわかりますが、壁について一切の記述をしていないのでプレイヤーはフィールドを超えると
あらぬところへとワープしてしまいます。
実際に壁判定を実装していきましょう。
壁の判定としては、プレイヤーのx座標、y座標がフィールドの端にいた場合、プレイヤーのx座標、y座標を変更しないという
考え方のもと、実装していきます。

#include<stdio.h>
#include<string.h>
#include<conio.h>
#include<stdlib.h>

//縦・横を10で定義
#define WIDTH 10
#define HEIGHT 10

int main(void)
{
	int x, y;
	//プレイヤーのx座標、y座標
	int px = 0, py = 0; 
	//キー入力用
	int key;

	//配列の3は、全角文字2バイト+終了コード \0 1バイト 合計3バイト分
	char field[HEIGHT][WIDTH][3];

	//fieldの初期化 今回はすべて□
	for (y = 0; y < HEIGHT; y++){
		for (x = 0; x < WIDTH; x++){
			//プレイヤーのx座標,y座標によってfieldの情報を変えます。
			if(x == px && y == py) strcpy_s(field[y][x], 3, "△"); 

			else strcpy_s(field[y][x], 3, "□"); 
		}
	}


	//無限ループにすることによって、画面の更新を繰り返していきます。
	while(1){

		//画面の更新
		system("cls");

		//fieldの表示
		for (y = 0; y < HEIGHT; y++){
			for (x = 0; x < WIDTH; x++){
				printf("%s", field[y][x]);
			}
			printf("\n");
		}
		
		//プレイヤーのx座標、y座標が変更されてしまう前に、あらかじめ現在のfieldを初期化しておきます。
		strcpy_s(field[py][px], 3, "□");

		//キーを取得して、押されたキーによってプレイヤーのx座標、y座標を変更していきます。
		key = _getch();

		switch(key){
		case 'w':
			if (py == 0) break; //←←←

			py--;
			break;
		case 's':
			if (py == HEIGHT - 1) break; //←←←

			py++;
			break;
		case 'a':
			if (px == 0) break; //←←←

			px--;
			break;
		case 'd':
			if (px == WIDTH - 1) break; //←←←

			px++;
			break;
		default:
			break;
		
		}

		//プレイヤーの新しいx座標、y座標を使ってfieldを更新します。
		strcpy_s(field[py][px], 3, "△");
		
	}

	_getch();
	return 0;
}

おわりに

今回は、ゲームにおけるフィールド、およびプレイヤーの描画・移動まで実装しました。
ゲームプログラムのイメージがついてきたのではないでしょうか。
コードが長い!見づらい!と思ったかもしれません。
今はmain関数にすべて記述しているのでどうしてもインパクトに押されてしまいますが、今後ポインタなどの知識が
ついてきたら関数で色々実装していこうと思います。
各動作を関数に委ねることによってずいぶんとプログラムが見やすくなると思います。
ゲームプログラミングの道は、まだ始まったばかりです。
一歩ずつを、着実に。
お疲れ様でした。