コンテンツにスキップ

ゲームプログラミング/RPG/アイテム定義

出典: フリー教科書『ウィキブックス(Wikibooks)』

アイテムの定義

[編集]

一般に、ゲームにおいて、主人公などの所持品を管理するシステムのことを「インベントリ・システム」という[1]

アイテム設定

[編集]

よく子供がRPGの構想を考えるとき(けっして大人の企画会議などでこうするワケではないので混同しないように)、大量の武器や防具の「攻撃力」や「防御力」を、ノートに書き起こしたりする場合がある。

例えば

  • 竹やり: 攻撃力=1, 値段 = 50G,
  • 鉄のナイフ: 攻撃力=3, 値段 = 100G,
  • 鉄の剣: 攻撃力=10, 値段 = 700G,

(中略)

  • ミスリルのよろい: 防御力 = 40, 値段= 13000G,
  • ミスリルのたて: 防御力 = 70, 値段= 10000G,

(後略)

・・・・みたいに、何十個や何百個もの武器や防具のデータを、ノートに書き起こしたくなる人もいるだろう。

なぜなら、もし、武器や防具のプログラムを修正するとき、いままでプログラムした武器データ・防具データすべてを修正する場合がありうるからである。なお、データとプログラム自体を分離しておけば、データを変更するたびにソースコードを再コンパイルする必要がなくなるというメリットもある(ただし、デバッグなどのために、可能なら、最終的には全部コンパイルしなおしたバージョンを通しプレイするほうが安全である)。

もし、既にソースコードに大量のデータを入力してしまった人は、それらのデータを、ソースコードとは別のテキストファイルまたはExcelなどの表計算ファイルに移動しておくのが便利である。

特に、武器や防具などのデータは、表計算ソフトで

           

のような空白の表に、

名称 攻撃力 値段
竹やり 1 50
鉄のナイフ 3 100
鉄の剣 10 700

のように入力しておくと、後々で編集しやすく、また、必要に応じてテキストファイルなどに変換できるので便利である。

アルゴリズムとデータの分離
大規模なプログラムを開発するときは、データとソースコードを分離します。このことを、「ロジックとデータの分離」と言います。このように分離するべき理由として、データを変更してもソースコードは変更されず、ソースコードを変更してもデータが変更されないため、結果として結合度を下げることができることがあげられます。
CSVファイル

プログラムで読み込むための大量の数値データを取り扱うとき、CSVファイルで格納することがある。

簡単に説明すると、コンマ (,) でデータを区切り、改行でレコードを区切る方式である。

内部設計

[編集]

C言語には構造体が存在する。構造体の解説については、リンク先を参照されたい。

さて、ゲーム制作の最初では構造体を使わなくても良いのですが、将来的に構造体を使うことになるので、説明しておきます。 たとえばRPGなら、武器データベースや防具データベースなどの読み書きのコードは、構造体でまとめる事になると思います。 たとえば、武器名と、武器の攻撃力は、同じ武器グループなので、buki構造体にまとめる・・・ようなことになるでしょう。構造体の配列 ( array of struct ) を作ることで、アイテムを複数管理することができます。


さて、防具構造体(たとえばbouguKouzou)と武器構造体(bukiKouzou)とを流用したいと思われるかもしれませんが、しかし、それはなかなかC言語では困難です。なぜなら、C言語では、要素に 異なる型 をもつ構造体どうしを、コピーしたり代入する事が不可能または、コンパイラが動作未保障だからです。

もちろん、構造体の要素の型をすべて同じにしてしまえば、構造体をコピーする事は可能です。

たとえば、使用品(薬草)や武器や防具や特殊アイテムなどをすべてまとめて扱う構造体としてアイテム総合構造体 sougou を作ったとします。 変数 sougou.itemType

  • 1 なら使用品・道具[注 1]
  • 2 なら武器、
  • 3 なら盾、
  • 4 なら(かぶと)
  • 5 なら(よろい)

・・・・のような方式です。

ですが、それはコードの「パッと見」では、その構造体が何を表すのか、一見しただけでは分かりません。また、もしアイテム関連のバグが発生した際、デバッグのたびに、sougou.itemType がズレてないか調べなければいけないので、デバッグの手間が増えます。ですが、他によい方法がありません。なので、ある程度の長さのあるRPGなら、現代なら構造体の配列を使うことになるだろうと思われます。

プログラム内部的には所持しているアイテムの種類を数値などで判別する必要があるので、アイテムの種類に番号をつける必要自体は、けっして無駄にならないでしょう。

アイテム種類番号を使う方式とは、たとえば、「勇者ゴンザレスが『毒消し』と『銅の剣』を所持している」場合なら、毒消しがアイテム種類1番でID番号2番、銅の剣がアイテム種類2番でID番号4番、などなら、勇者ゴンザレスの所持している所持アイテムの構造体配列 syojiItemは

『毒消し』についてはsyojiItem[1].Type=1 かつ syojiItem[1].bangou=2
『銅の剣』についてはsyojiItem[2].Type=2 かつ syojiItem[2].bangou=4

のように管理する事で、異なる種類のアイテムを一人のアイテム所持欄でまとめて扱うことができます。

アイテム管理番号を振り直す場合、テストプレイを念の為もう一度行ったほうが良いですが、プロジェクトの規模に比例して大変なことになります。なので、開発の中期・後期などは、もう番号は振りなおさず、できるだけ未使用のうしろの番号にアイテムを追加していくほうが安全です。

また、開発の前期のうちから、ある程度は計画的にマジメに番号をつけていく必要があります。

コード例
#include <stdio.h>
#include <stdlib.h> // 「続行するには何かキーを押してください . . .」を表示するのに必要。
#include <string.h>

// アイテム例
struct ItemSougou {
    int Grouptype;
    int subID;
    char ItemName[20];
};

int main(void) {
    struct ItemSougou ItemYouso[5][7]; // 構造体配列の宣言

    // 1品目
    int GroupID;
    GroupID = 0;
    strcpy_s(ItemYouso[GroupID][0].ItemName, 10, "薬草");
    ItemYouso[GroupID][0].Grouptype = 0;
    ItemYouso[GroupID][0].subID = 0;

    strcpy_s(ItemYouso[GroupID][1].ItemName, 10, "毒消し");
    ItemYouso[GroupID][1].Grouptype = 0;
    ItemYouso[GroupID][1].subID = 1;

    // 2品目
    GroupID = 1;
    ItemYouso[GroupID][1].Grouptype = 1;
    ItemYouso[GroupID][1].subID = 1;
    strcpy_s(ItemYouso[GroupID][1].ItemName, 10, "鉄の剣");

    int i = 0;

    //ドラクエ式の持ち物の構造体
    struct motimono {
        int Grouptype;
        int subID;
    };

    struct motimono hinmoku[5]; // 構造体配列の宣言

    // 持ち物の定義
    // 1品目の定義
    // strcpy_s(  hinmoku[0].ItemName, 10 ,"毒消し");
    hinmoku[0].Grouptype = 0;
    hinmoku[0].subID = 1;

    // 2品目の定義
    // strcpy_s( ItemYouso[1][1].ItemName, 10 ,"鉄の剣");
    hinmoku[1].Grouptype = 1;
    hinmoku[1].subID = 1;

    // 表示コード
    for (int temp = 0; temp <= 1; temp = temp + 1) {
        printf(
            "アイテム: %s\n",
            ItemYouso[hinmoku[temp].Grouptype][hinmoku[temp].subID].ItemName);
    }

    system("pause"); // 「続行するには何かキーを押してください . . .」の待機命令
    return 0;
}


実行結果
アイテム: 毒消し
アイテム: 鉄の剣
※ なお、上記コード例は、MS-DOSプロンプトでの確認用である。windows版gccで確認してある。
実際にデスクトップアプリとしてゲーム用にする場合、strcpy_s 関数を lstrcpy_s 関数に変更するなどの、若干の変更の必要がある。

持ち物だけでなく、ゲーム中の武器防具屋などの品揃えにある商品リストなどでも、上記の例と同様に、異なるアイテム種類の商品を統一的に扱う必要もあるだろう。(たとえば武器防具屋は、武器と兜・鎧・盾という、異なる種類のデータベースを1つの商品リストに同時掲載している。)このため、ドラクエ式の持ち物管理のゲームでなくとも、武器防具屋などで上記コードのようなテクニックを使う可能性も考えられる。


さて別手法として、上記の(構造体変数としての)アイテム種類番号のテクニックとは異なる手法もあります。「構造体のネスト」という、構造体の中に別の新規の構造体を定義するというテクニックもありますが(※ wikibooksのC言語教の科書で解説済み)、たとえソレを使っても、内側の構造体は共通化しなければいけないので、上記のsougou構造体の例と似た制約があります。また、C言語は可変長配列を認めていないので、たとえば兜・帽子の種類が少ない場合などは上記と同様、メモリに多くの無駄が出ます。


なにより、構造体ネストの方式であってもデバッグが面倒になりますし、アイテム種類の番号の整合性を維持しなければならないのでデータベースを後から変更するのは、なかなか困難です(もしアイテム種類番号の仕様を変更したら、テストプレイ等は、やり直し)。

異なる装備品の連番方式はあまりオススメできない
[編集]

なお、鎧の数に比べて極端に兜・帽子の数が少ない作品が多い場合など、データベース管理方式について上述とは他の方式として、あまりオススメできない方式ですが、たとえば、鎧も兜も、同じ防具データベースで、「防具IDの20番までは鎧、21番から25番までは兜・帽子」のような連番にしてしまう方式でも、原理的には鎧・兜・盾などの統一的な扱いが可能です。

しかし、この連番方式の場合、もし将来的に仕様変更で「鎧の数を23種類まで増やしたい」のように仕様変更になったとき、すでに21~23番が兜に使われているので、追加の鎧はまだ未使用の番号に追加しなければならず、ソースコードおよびデータベース構造が複雑化します。あるいは仕様書(設計書)が複雑になります。なので、連番方式はあまりオススメできません。連番方式なら、メモリの負担だけなら少ないですが、しかしソースコードの管理および仕様書の管理が、とても面倒になるでしょう。

  1. ^ 『ゲーム業界に入るために必要なプログラミングスキルとは?業界直結型のC&R Creative Academyが定義する技術力と学習ロードマップ』2022.11.28、INTERVIEW & TEXT / 神山 大輝・ INTERVIEW / 佐々木 瞬、


引用エラー: 「注」という名前のグループの <ref> タグがありますが、対応する <references group="注"/> タグが見つかりません