見出し画像

【C言語プログラミング7】クロノトリガーと構造体

C言語には構造体と言うものがあります。構造体を使うと色々な型の変数をまとめて自分好みのパッケージを作ることができます。自分で新しい変数の型を作ると言っても良いです。

ロールプレイングゲームのキャラを作る

例えば、ロールプレイングゲームのキャラを考えてみましょう。キャラを構成する情報って何がありますかね?名前、HP、MP、攻撃力、防御力・・等々色々な情報がありますよね。これらの情報を管理するためには、変数を宣言することになりますが、バラバラで宣言するとこんなことになります。

char name[16];
int hp;
int mp;
int attack;
int defense;
・・・
・・
・

キャラが一人だけならこれでも良いでしょう。しかし、通常複数の仲間が居て冒険するはずです。じゃー、こんな風に配列を使うか?

char name[5][16];    // 5人分の名前
int hp[5];           // 5人分のHP
int mp[5];           // 5人分のMP
・・・
・・

これはちょっとないでしょ。各キャラ毎にまとめて管理するのが分かり易いですね。そんな場合に使うのが構造体です。

クロノを構造体にする

1995年にスーパーファミコンでクロノトリガーと言うロールプレイングゲームがスクウェアから発売されました。200万本以上売れたソフトらしいですが、発売当初は12000円くらいだったかな?手が出ませんでした。非常に面白いソフトで、今ではスマホでもプレイできます。そんなゲームソフトの主人公のクロノを構造体にしてみます。クロノはこんなやつです。

画像1

懐かしー。さて、どんな情報があるかと言うと。名前、レベル、装備、力、命中力、素早さ、魔力、回避、体力、魔法防御、EXP、NEXTですね。これを構造体にしてみましょうか。構造体を作る時はこうします。

struct {
    変数の型名 変数名;
    ・・・
    ・・
};

具体的には以下の通りです。//はコメントです。/**/これと同じことです。

struct stCharStatus {
    char name[16];         // 名前
    char level;            // レベル
    char weapon;           // 武器
    char head;             // 防具(頭)
    char body;             // 防具(体)
    char accessory;        // アクセサリー
    char power;            // 力
    char aim;              // 命中力
    char speed;            // 素早さ
    char magic;            // 魔力
    char evade;            // 回避
    char strength;         // 体力
    char magicdefense;     // 魔法防御
    int exp;               // 経験値
    int next;              // 次のレベルアップまでに必要な経験値
};

上記の様に記述すると、stCharStatusと言う新しい型を定義したことになります。頭文字のstはstructの略で、これは構造体ですよというのがパッと見て分かるようにしています。私の好みですので、こうしないとダメということはありません。

クロノのレベルを1にする(構造体を使う)

構造体を使う時は、通常の変数と同様に宣言しなければなりません。宣言の方法は以下の通り。struct 構造体の型名 構造体変数名;です。

struct stCharStatus stChronoStatus;

そして、構造体の中にある変数に対して何か値を入れたい場合は次のようにします。構造体の中にある変数のことをメンバ変数と言います。構造体変数名.メンバ変数名のように構造体変数名とメンバ変数名の間にピリオドを入れます。そんなに難しい話ではないですね。これでクロノのレベルは1になりました。

stChronoStatus.level = 1;

typedefを使った構造体の作成

これまでの説明で構造体を作って、使うことまで出来ますが、もっと便利な記述方法があります。typedefを使うと構造体を使う際に楽ができます。さっき作った構造体をtypedefで再定義すると以下のようになります。structの前にtypedefが入って、最後にstCharStatusがあるだけで後は一緒です。

typedef struct {
    char name[16];         // 名前
    char level;            // レベル
    char weapon;           // 武器
    char head;             // 防具(頭)
    char body;             // 防具(体)
    char accessory;        // アクセサリー
    char power;            // 力
    char aim;              // 命中力
    char speed;            // 素早さ
    char magic;            // 魔力
    char evade;            // 回避
    char strength;         // 体力
    char magicdefense;     // 魔法防御
    int exp;               // 経験値
    int next;              // 次のレベルアップまでに必要な経験値
} stCharStatus;

typedefを使った構造体を使う

typedefを使って作った構造体を使う時も、構造体変数を宣言してから使います。具体的には以下の通り。

stCharStatus stChronoStatus;

何が違うか分かりますか?構造体変数を宣言する時に、structを省略することが出来ます。stCharStatusはtypedefによりchar、intのように一つの型として定義されているのです。なので、最初にこれは構造体ですよという意味のstructは要らず、いきなりstCharStatus stChronoStatus;としても大丈夫なのです。私が構造体を使う場合はほぼtypedefで定義します。

マールが仲間に加わった(構造体配列)

クロノの仲間にマールと言うキャラが居ます。マールが仲間に加わった場合、マールの情報も管理しないといけません。そんな場合は構造体配列を使うのがいいです。構造体も通常の変数と同様に配列にすることが出来ます。構造体配列の宣言は以下の通り。stCharStatusはtypedefで宣言した前提で話を進めます。

stCharStatus stPartyStatus[2];

charの配列を宣言する場合と同じですね。charの所がstCharStatusなだけです。これでstCharStatus型のstPartyStatusという名前の配列を宣言したことになります。

マールのレベルも1にしよう(構造体配列を使う)

構造体配列を使う場合は以下のようにします。配列の[0]をクロノ用、配列の[1]をマール用にするとします。

stPartyStatus[1].level = 1;

これでマールのレベルが1になりました。構造体配列stPartyStatusの[1]番目のlevelという変数に1を入れています。簡単な話ですね。

装備は別にしときたいなぁ(構造体の入れ子)

クロノのステータスを見た時に、キャラ自体の能力と装備ってちょっと意味合いが違うんじゃね?って思いませんか。そんな場合は、構造体の中に構造体を入れて管理するのが良いです。構造体の中の構造体のことを入れ子と言ったりします。typedefで構造体を定義したように、構造体もcharやintと同じく一種の型として扱えます。なので、構造体の中に構造体をメンバ変数として入れることができます。まずは、装備構造体を作りましょう。

typedef struct {
    char weapon;           // 武器
    char head;             // 防具(頭)
    char body;             // 防具(体)
    char accessory;        // アクセサリー
} stEquipment;

これで装備構造体が出来ました。これを使ってキャラ構造体を新しく定義するとこうなります。

typedef struct {
    char name[16];         // 名前
    char level;            // レベル
    stEquipement stEquipe; // 装備一式
    char power;            // 力
    char aim;              // 命中力
    char speed;            // 素早さ
    char magic;            // 魔力
    char evade;            // 回避
    char strength;         // 体力
    char magicdefense;     // 魔法防御
    int exp;               // 経験値
    int next;              // 次のレベルアップまでに必要な経験値
} stCharStatus;

少しすっきりしましたね。装備一式をstEquipeと言う構造体メンバ変数として定義しました。

クロノの装備を整える(構造体の入れ子に対するアクセス)

クロノの武器を別の武器に変える時はどうすれば良いのでしょうか?構造体の中にある構造体の中にある変数に対してアクセスするには以下のようにします。

stPartyStatus[0].stEquipe.weapon = 1;

stPartyStatus[0]番目はクロノなので、クロノの中の、装備の中の、武器に1を入れると言う感じです。○○の中の、○○の中のと言う指定は全てピリオドで行います。構造体の中の、構造体の中の、構造体の中の・・・とどれだけ構造体が深くなってもルールは同じで、ピリオドで指定して行きます。

構造体の変数を宣言した時のメモリイメージ

構造体の変数も通常の変数と同じく宣言するとメモリ上に配置されます。今回の構造体の場合は、次のようなイメージになります。

構造体

expとnextだけがint型なので、4バイト消費します。後は全てchar型なので1バイトです。

まとめ

今回は構造体についての話でしたが、結局構造体はデータを管理し易くするためにあると言うことです。普段の生活でも関連する情報は一つにまとめておきますよね?プログラミングでも同じ話です。このようにデータの構造を考えることをデータ設計と言います。expはint型で宣言しましたが、何故でしょうか?他と同じくchar型だとダメなの?ダメですね。expは225,5165らしいのでchar型には収まりません。char型には-128~127の範囲の値しか入りませんからね。shortでもダメですね。だからintなのです。データ構造を設計する際には、どんな値が入り得るのか、最低どれだけあれば良いのかを考える必要があります。以上、構造体の話でした。


この記事が気に入ったらサポートをしてみませんか?