vol.4 構造体によるステータス実装

目次

はじめに

今回は新しく「ステータス」について実装していこうと思います。
新しい概念として「構造体」というものを使っていくので、今回は実際のゲームプログラム作成というよりは、構造体について理解してもらう回になります。

構造体とは

構造体とは、複数の項目をひとかたまりにしたものです。
複数の項目をひとかたまりにするのは、配列でも行っていたことですが、決定的に構造体と配列が異なるのは、「異なる型であってもひとかたまりにできる」ということです。

配列では、例えば
int x[5]={1,3,5,7,9};
のように宣言したとすると、xというひとかたまりにはint型変数しか入っていません。
一方、構造体を使うと、例えば
player_name(プレイヤー名:char型ポインタ)、lv(レベル:int型変数)、statue(身長:double型変数)
のように様々な型をひとかたまりにすることができます。
結局のところ、複数の異なるデータ型の変数を一つにまとめたデータ型を構造体と言います。
これは非常に便利なものなので、是非しっかり把握して使えるようにしたいところです。
実際に販売されているようなゲームでは、この構造体が沢山使われています。

構造体に関していくつか用語があるので、説明します。

メンバ変数(メンバ) ・・・構造体にある項目のこと。
        今回ならplayer_name, lv, statue がそれにあたります。
実体 ・・・いつも変数を宣言するときに使っているような名前こと。

構造体の扱い

宣言

typedef struct {
	メンバ1;
	メンバ2;
    ・・・
} 構造体名;

構造体では、int型やchar型のような型を自作することができます。
例えば、

typedef struct{
	char *name;
	int lv;
	double statue;
} person;

とすれば、新しく「person」型を作る事になります。

実体の生成

構造体名 実体名;

自作した変数型で新しい実体を生成しています。
例えば、

person player={"SHION",1,175.0};

とすれば、person型のplayerという実体が生成され、同時に名前(SHION)、レベル(1)、
身長(175.0)というデータも実体playerに格納されます。
※{}で行っているメンバ変数への一括代入は、実体の宣言時のみ可能です。
それ以外では一括代入はできません。

メンバ変数へのアクセス

実体名.メンバ名

ドット演算子(「.」)を使ってアクセスします。直接代入したいときに
例えば、

player.lv = 100;

とすれば、プレイヤーのレベルが100に書き換えられます。
ここでのplayer.lvはint型変数としてふるまうので、

printf("レベルは%dです。\n",player.lv);

とすれば、

レベルは100です。

と出力されます。

ポインタと構造体

構造体の実体のポインタ変数名->メンバ名

構造体の実体はポインタを使って操作することが多いです。
実体のポインタからメンバ変数にアクセスするのには、
アロー演算子(「->」)を使います。

通常のポインタの考え方で、ポインタが指すデータへアクセスするときは
間接演算子(「*」)を使いますが、構造体の時はアロー演算子を使います。
意味合いとしては似たようなものです。

例えば、プレイヤーの身長を180.0に変えるプログラムを書くとするなら、

person player={"SHION",1,175.0};
person *p;
p=&player;
p->statue=180.0;

とすればいけます。
つまるところ、p->statue でplayerの身長を表すということですね。
因みに、数値をただ代入するだけの処理ならアロー演算子を使わずに

player.statue = 180.0;

でもよいです。

もう一つ、例えばプレイヤーのレベルを1つ増やして、
身長を2倍にするプログラムを書くなら、

p->lv++;
p->statue*=2;

とすればいけます。
p->lvでプレイヤーのレベル、p->statueでプレイヤーの身長を表しています。
この時、++,--,+=,-=,*=,/=,%=が使えます。

構造体を配列で扱う

構造体では、これまで同様配列を扱うことができます。
まずは通常通り構造体を宣言して、実体を生成するときに配列を準備しましょう。

例えば、複数人のプレイヤーの情報を格納したいとき、

typedef struct{
	char name[80];
	int lv;
	double statue;
}person;

person player[5]={
	{"ライアン",40,175.0},
	{"アリーナ",38,163.0}
	{"クリフト",5,170},
	{"ブライ",3,289.5},
	{"トルネコ",99,1000}
};

とすれば、player[0]からplayer[4]までの5人のプレイヤーが一気に準備されます。

キャラクター作成

実際のゲームでは初めてゲームをプレイしたときに、キャラクター設定をしますよね。
名前や性別、種族などいろいろなものを最初に決定していくことと思います。
その状況を実際に実装してみましょう。
キャラクター生成時には名前と性別、種族をこちらから指定して、
HP(体力)やATK(攻撃力)などは乱数で決定しましょう。
項目としては、
name(名前)、gender(性別)、race(種族)、HP(体力)、MP(魔力)、ATK(攻撃力)、
DEF(防御力)、DEX(すばやさ)
を考えていきたいと思います。

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


//構造体でPERSON型を作成
typedef struct {
	char name[80]; //名前
	char gender[80]; //性別  男・女
	char race[80]; //種族 人間・エルフ・ドワーフ
	int HP; //体力 18~22
	int MP; //魔力 6~8
	int ATK; //攻撃力 8~12
	int DEF; //防御力 4~6
	int DEX; //すばやさ 4~6
}PERSON;

//ステータス振り用乱数関数
int status_rand(int, int);

int main(void)
{
	PERSON player1;

	printf("冒険を始める前に、あなたの性別を教えてください。\n");
	while (1){
		
		printf("男?女? : ");
		scanf("%s", player1.gender);
		if (strcmp(player1.gender, "男") == 0 || strcmp(player1.gender, "女") == 0)
			break;
	}

	printf("次に、あなたの名前を教えてください。\n");
	scanf("%s", player1.name);

	printf("最後に、あなたの種族を選んでください。\n");
	while (1){
		printf("人間・エルフ・ドワーフ : ");
		scanf("%s", player1.race);
		if (strcmp(player1.race, "人間") == 0 || strcmp(player1.race, "エルフ") == 0 || strcmp(player1.race, "ドワーフ") == 0)
			break;
	}

	srand((unsigned int)time(NULL));

	player1.HP = status_rand(18, 22);
	player1.MP = status_rand(6, 8);
	player1.ATK = status_rand(8, 12);
	player1.DEF = status_rand(4, 6);
	player1.DEX = status_rand(4, 6);

	printf("あなたの名前は%s\n", player1.name);
	printf("あなたの性別は%s\n", player1.gender);
	printf("あなたの種族は%s\n", player1.race);
	printf("あなたのHPは%d\n", player1.HP);
	printf("あなたのMPは%d\n", player1.MP);
	printf("あなたのATKは%d\n", player1.ATK);
	printf("あなたのDEFは%d\n", player1.DEF);
	printf("あなたのDEXは%d\n", player1.DEX);

	_getch();
	return 0;
}

int status_rand(int s_point, int e_point)
{
	return rand()%(1+e_point-s_point)+s_point;
}

構造体については問題ないと思いますが、strcmpや乱数について追記しておきます。

strcmp関数は、文字列を比較する関数で、
二つの引数が同じ文字列であれば0を返します。
今回であれば、

strcmp(player1.race,"人間")

という記述がありますが、これはplayer1.raceに人間と書かれているかどうかを調べています。

次に乱数についてですが、

srand((unsigned int)time(NULL));

は乱数を生成するseed値を変化させるために記述しています。
これがあることによっていつプログラムを実行しても良い乱数が得られます。
status_rand関数を今回自作しているのでこれも少し解説を入れます。

int status_rand(int s_point, int e_point);

で作成していますが、これはs_point以上e_point以下の整数乱数を返す関数となっています。
プログラム内部の説明は具体的に数字を入れて考えてみるとわかりやすいと思います。
例えば、

status_rand(10,100);

とするなら、10以上100以下の整数が返ってくるはずです

return rand() % (1 + e_point - s_point);

とすることで、rand()を91で割った余りを計算します。
よってこの段階では0以上90以下の整数が計算される可能性があります。
実際に欲しいのは10以上100以下の整数ですから、そこに10足したものを結果として返したらいいということです。

本来はもっと厳密な乱数生成の方法があるのですが、そこまで正確な乱数を今回求めていないので、簡略的なものを使いました。

おわりに

今回は新しい概念を追加したので日本語が多くなってしまいましたが、とにかく構造体は非常に便利なものなので是非使えるようにしていきたいところです。
次回は改めて構造体でキャラクターを作成し、戦闘シーンのようなものを実装していきたいと思います。
お疲れさまでした。