ゲームプログラミング/RPG/アイテム表示

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

アイテムの上詰めの自動化[編集]

原理[編集]

たとえば中世ファンタジー風RPGのアイテム欄で、「薬草」や「毒消し」などといったアイテムが、アイテム欄で、

薬草          5個
高級な傷薬    1個
毒消し        5個
気付け薬      3個
テント        1個

などと、道具欄にアイテム名と個数と併記して表示されているシステムだとしましょう。

さて、もし1個しか持ってなかった『高級な傷薬』を大怪我の治療のために1個使用して、『高級な傷薬』を使いきって0個になり、

薬草          5個
毒消し        5個
気付け薬      3個
テント        1個

みたいに、自動的に上詰めが実行される(たとえばドラクエ方式のアイテム表示の)システムのゲームの場合、

つまり、使いきった道具(『高級な傷薬』)の以降に並んでいた別道具(「毒消し」「気付け薬」「テント」」)が上につめられるような仕様のゲームの場合、

はたしてコレをどうやって実装すればいいのでしょうか?


答えは、C言語で言う『配列』を使う事です。

まず、アイテムの定義の配列を用意します(仮に itemTeigi[要素数] とする)。(アイテム名などや分類などの定義が必要です。構造体配列などでも書けますが、当面は単なる配列でも充分です。)

さらに、

どのアイテムを何個持っているかという配列(仮に itemKosuu[要素数])も必要です。

※ この定義配列と個数配列とは、自動上詰めをしないシステムのゲームの場合でも必要になります。


さらに、アイテムの定義や個数の配列とは別に、

所持アイテムの並び順の配列(仮に itemNarabi[要素数] とする)を用意します。


つまり、最低でも3種類の配列(アイテム定義配列、アイテム個数配列、アイテム並び配列)が必要でしょう。


もしかしたら配列を使わなくても出来る方法もあるのかもしれませんが、しかし、もっとも直感的で分かりやすい方法が、このアイテム自動上詰めシステムの実装の場合なら「配列を使う方法」だと思います。

なので、将来的な集団作業のことも想定して、配列で実装するのがオススメでしょう。(仕事は集団作業なので、他の人でも分かりやすいコードを書く必要があります。なので、わざわざ配列を使わない、分かりづらい方法を使う必要が無い。)


なお、原理だけなら簡単ですが、実はメンテナンス上の理由により、なるべく上詰めの実装は、後回しにしたほうが、ゲーム全体の実装がラクです。

なぜなら上詰めなどのように配列の中身を並べ替えてゆく機能は、メンテナンス性が悪いからです。

なので、たとい最終的に、上詰めなどの自動並べ替えを実装する場合であっても、例えば実はデバッグモードでは自動並べ替えをしない方式でゲームをプレイできるように、二層構造で設計していくのがラクでしょう。

まず先にデバッグモードでの自動並べ替えをしない方式で設計・実装できてから、あとから一般プレイヤー用に自動並べ替えをするインタフェースを追加するという二層構造で実装すればいいのです。


実際の手順[編集]

さて、もし実際に、上述のような上詰めシステムを実装する場合、設計の順序としては、実は先に、上詰めしないシステムから作るほうがラクです。

なぜなら、いきなり上詰めをするシステムから作るのは、けっこう難しいのです。なので、まず先に、下記のように、0個のアイテムを表示するシステムから作りましょう。

手順 1   0個のアイテムも表示

つまり、たとえば、まず先に、0個のアイテムでも、アイテム欄に表示してしまうように作ります。

薬草          5個
高級な傷薬    0個
毒消し        5個
気付け薬      3個
テント        1個


手順 2   カーソル操作やアイテム選択の実装

上記のような表示のできる状態で、まず、カーソル操作によってアイテム選択できるまで作ります。そして、選択したアイテムをとりあえず使用できて効果を発揮できるように作ります。(たとえば傷薬をつかったら、主人公のHPが回復するなどのシステムを、先に実装します。 上詰めシステムの設計は、後回しです。)


手順 3   0個アイテムの非表示化

カーソル選択やアイテム選択の実装まで出来たら、次は、0個のアイテムを非表示にするシステムを作ります。

(※ 商業ゲームでは、たとえばイース3(スーパーファミコン版)というアクションRPGのゲームがこういう方式(未入手アイテムが道具欄の空欄になる方式)だった。なお、イース3は入手アイテムが(名称でなく)アイコン画像で表示される方式。こういう未入手アイテムの非表示方式でも、面白いゲームは作れるので、初心者は気にせず下記のプログラミングを実行すればいい。)
薬草          5個

毒消し        5個
気付け薬      3個
テント        1個

単にif文などを使って、アイテム個数が0個のアイテムについては、表示しないように分岐すればいいだけです。


これが出来たら、次に、

手順 4   表示だけ上詰め自動化
薬草          5個
毒消し        5個
気付け薬      3個
テント        1個

のように、ようやく、上詰めをするような手順で作るという順序で設計していくとラクです。

※ この手順のいいところは、万が一スキル不足やなんらかの事情で、ドラクエ方式の上詰めが不可能になったとしても、イース3方式の空欄方式にもどせば、そのままゲーム製作を続行できる事である。


手順 5   カーソル表示とアイテム効果ズレのバグなどの修正

なお、上詰めを実装する際、よくあるバグで、表示内容のアイテム名と、実際のアイテム効果とは、よくズレます。(毒を消そうと「毒消し」をつかったのに、前にその位置に表示されてた『高級な傷薬』の効果が出たりするバグが、よくある。)

ですが、いきなり、このバグの無いプログラムを書こうと思っても難解なので、まず先に、とりあえず上詰めした状態の表示だけできるプログラムを作っておきましょう。

※ 前提として、カーソル選択などのできるまでにプログラムを書いておく必要があります。

そして、とりあえず表示だけ上詰め自動化をできたら、あとから、アイテム効果選択の位置ズレのバグを直していけばいいのです。


まとめ

このように、段階的に設計していくと、プログラムを思いつきやすくなります。

けっして、いきなり高度なプログラムを作るべきでは、ないのです。

そうではなく、まず先に、やや仕様が最終目的と違ってもいいので、まずは単純な仕組みの とりあえず動くプログラムを作ります。

とりあえずのプログラムが出来たら、そのあとに最終目的のより高度なプログラムに近づけて、機能を追加していくように設計していきましょう。


留意事項: 並べ替えの処理時間について[編集]

アイテム処理プログラムにかぎった事ではないのですが、並べ替え(ソート)のような処理をコンピュータにやらさると、並べ替え対象が多い場合には、時間が掛かることがあります。

よく表計算ソフトなどで、たとえば価格表なら、価格順に並べ替えたり、あるいは在庫数ごとに並べ替えたり、のような場合です。

お仕事の表計算ソフトなら、1秒くらいの時間が掛かっても気にしませんが、ゲームによっては気になる場合があります。

ゲームでも実際、『信長の野望』や『三国志』などのスーパーファミコンの時代ごろのバージョンのをプレイすると、もし武将の人数が多い国などでの操作で、能力値順(たとえば「武力」順に武将を替えたり、あるいは「政治」順に武将を並び替えたり、)に名簿を入れ替えたり、あるいは名簿の最後のほうの武将のページを見ようとすると、読み込み時間が数秒ほどですが掛かります。(名簿の最初のほうの表示なら時間の掛からない場合もありますが、おそらく、他の待ち時間のあいだに名簿の並び替えをするなどして、プレイヤーに気づかせない工夫などをしてるのだと思われます。シミュレーションゲームなので考える時間が掛かるので、そのあいだにカーソルの位置などから、今後の操作を予想して、先に読み込みなどをしているのでしょう。)

アイテム倉庫など[編集]

よくRPGには、持ちきれないアイテムなどを倉庫に預けたりなどのシステムがあります。いわゆる「預かり所」です。

たとえば、薬草を何十個もプレイヤーがもっていて、持ちすぎでこれ以上は持ちきれないので、預かってもらえる、というような施設です。


これはプログラミングでは、どうやって実装するのかというと、原理的には、アイテム所持配列を2つぶん用意して、片方はパーティの所持中のアイテムのための配列、もう片方は倉庫用の配列とすればイイだけです。


さて、プログラミング的に思いつくのが難しいのは、倉庫にアイテムを出し入れするプログラムです。

これは答えを言うと、預けたいものをコピーして倍増させたあと、古い方(渡す側)の所持数を減らせばいいのです。


たとえば、薬草を主人公パーティが手持ちで最初は10個もっているとします。まだ倉庫には何も預けていないとします。これを開始の状態とします。つまり、最初の状態は、

手持ちの薬草: 10個
倉庫の薬草: 0個

です。

最初は、10+0=10により、手持ちと倉庫の合計で10個です。


これから手持ちの薬草を3個預ける場合、コピー方式でのプログラム実装では、まず倉庫の薬草の個数を+3個するのです。この時点で、

手持ちの薬草: 10個
倉庫の薬草: 3個

です。この時点では、薬草の合計数が、一時的に10個を超えて、合計13個になっていますが、これで合ってるのです。

そのあと、即座に、パーティ側のそのアイテムを、預けた分の個数だけ消します。

手持ちの薬草: 7個
倉庫の薬草: 3個

です。つまり、薬草の個数を、預けた後にすぐにマイナス3個する、という事です。

こうすれば、薬草の個数は合計10個ですし、たしかに倉庫に3個の薬草があります。そして、最初の状態の合計数と同じように、合計10個になっています。


このようなコピー方式の受け渡しプログラムは、人間のアナログ的な直感には反しているので(一時的だが、預けたハズのアイテムが、手持ち側にも存在しているので)、直感ではなかなか思いつかないです。

ゲームにかぎらず、プログラム中の何かのオブジェクトの所属先グループなどを変える際、このようなプログラム手法が有効でしょう。

このコピー的なプログラム手法はもしバグがあった場合のリスクとして、データが重複して倍増したりします。

ですが、一方でもしコピーしない別手法の場合に、バグで情報の消失するリスクの大きさと比べたら、コピーによる情報の重複のほうが遥かに安全でマシなバグです。

名探偵シャーロック・ホームズの格言か何かですが(創作上の人物ですが)、本来あるべきハズのものが無い場合には、違和感に気づくには予備知識として「こういう場所には、こういうモノがあるはず」という知識が必要なので、普通の人間はなかなか、無いことに気づきません。一方、無いハズのものが存在している場合には、比較的に容易に人は気づくのだと、ホームズは述べています。

さて、上記のコピー的手法の別の利点として、このコピー的手法なプログラミングは、すでにあるアルゴリズムの使いまわしで済むので、作るのが比較的にラクです。


説明の都合上、「薬草」などのアイテムで説明しましたが、アイテムにかぎらず、たとえば日本の戦国時代ゲームで織田信長が国を移動したり(尾張の国から美濃の国に移動したり)とかの所属先を変えたりするような場合も同様です。とにかく、なにかのオブジェクト(「織田信長」みたいな人物オブジェクトでもいい)の所属先が受け渡される場合、一時的にコピーをする方式が簡単です。

コピー方式の受け渡しの長所は、バグ発生時の被害が小さいという長所であり、

たとえばバグが起きても織田信長が2人になるコピーバグと、
一方でコピーしない別方式で織田信長が死んでもないのに0人になるバグとを比べたら、

どちらがマシかと言われれば、マシなのは織田信長が2人になるほうが発見しやすくマシだという長所です。


またなお、別の観点としては、上記の「薬草」の例の説明では、説明の簡単化のため「薬草」3個を倉庫に預けても7個残るようにしたので、どのような方式で実装しても比較的に簡単ですが、もし1個しかもってないアイテムを倉庫に預ける場合を考えると、明らかにコピー方式のほうが簡単です。

もし、先に所持数を減らしてしまう方式だと、もし主人公が薬草を1個しかもって無い場合、その薬草を預かリ所に預ける実装において先に薬草の所持数を0個に減らしてしまうと、倉庫サイドでは主人公の0個所持の「薬草」を参照する事になってしまい、0個のアイテムを参照するのは実装が面倒だし、プログラマ-のアナログ的な直感にも大きく反します。(実装できない事は無いですが、実装が面倒になってしまう方式なので、わざわざ先に所持数を減らす方式を採用するほどの価値が無いのです。)


セーブ時に、預かり所の中身もセーブする

あと、「預かり所」機能のあるゲームにセーブ機能を作る際、預かり所に保管してあるアイテムの一覧とその個数も、セーブする必要があります。

ファミコンのドラクエ3にも「預かり所」があるので、つまりドラクエのセーブデータは、預かり所のアイテムのIDとその個数も保存している事になり、ファミコン時代の当時としては、けっこうサイズの大きいセーブデータだという事になります。

ともかくプログラミング時に、預かり所の保管品もセーブするようにプログラミングしないと、ロード後に預かり所に保管したアイテムが消失したりして、大きなバグです。歴史シミュレーションゲームの武将などの地域移動(尾張→美濃 みたいな)も同様であり、どの地域にどの武将がいるかをすべてセーブ時に記録する必要もあります。記録しないとロード後に武将が消失して(ロストして)しまいます。

冒険者ギルドのシステム[編集]

ドラクエでいう「ルイーダの酒場」や、ウィザードディでいう「ギルガメッシュの酒場」のような、パーティの仲間の出し入れの出来る、2000年以降のRPGでいう「冒険者ギルド」のシステムは、実は上述の「預かり所」システムとほぼ同じです。

むしろ、C言語でRPGを自作する場合、先に「冒険者ギルド」のシステムを作り始めるかもしれません。

説明の都合上、キャラクターメイキング機能などの説明は省略して、すでに作成されているキャラクターの、パーティの加入や、パーティから離脱してギルドの控えメンバーになる処理などの話に限定します。


これは上述の織田信長の例の「尾張 → 美濃」の移動の例と同じです。


たとえばパーティキャラの仲間の一人である「戦士ガーライン」というキャラが、パーティから離脱してギルドの控えに戻る場合のアルゴリズムの原理を考えて見ましょう。

戦士ガーラインをパーティから外す処理のプログラムは、たとえば戦士ガーラインがパーティの2番目の位置にいるなら、プログラムの処理内容はおおむね、

  1. パーティの2番目にいるキャラ(戦士ガーライン)のキャラIDを読み取る、
  2. そのキャラID(この例では「戦士ガーライン」)のパーティ/ギルド処理用フラグ変数(たとえば、パーティ加入中なら1番、ギルド控え中なら2番)を、「ギルド控え中」(つまり「2」番)にセットして更新する、
  3. ギルド控え中のキャラ合計人数の変数を +1 する、
  4. パーティ加入中メンバーの合計人数の変数を -1 する、

のような処理になるわけです。

というか、そもそも普通、C言語での自主RPG制作では、まず武将システムは作らないので、RPGでは先にギルド・パーティの機能を作ります。(※ 本wikiでは説明の簡単化の都合のため、現実世界に対応しやすい歴史シミュレーションゲーム風のたとえを先に説明しました。)


ギルド処理は原理的には「預かり所」システムとも同じですが、しかし変数名などをもしアイテム処理用の変数や配列と流用するとプログラミング時やデバッグ時に混乱を招きます。なので、プログラミングの最初は手間が掛かりますが、アイテム用の変数とは別に、ギルド処理用の変数や配列を別途で作成して、そのギルド処理専用の変数・配列でギルド機能を実装しましょう。

武器・防具の装備システム[編集]

武器・防具の装備システムも、預かり所システムと同じです。 ファンタジーRPGにおける、「銅の剣」を装備したり「棍棒」を装備したりなどの装備システムのコードの実装方法の原理も、上記の「預かり所」の手法と同じです。

というか、ゲームの歴史的には、装備システムのほうが「預かり所」よりも歴史が古いです。

本wikiでは単に説明の都合で、現実世界とも対応しやすい「預かり所」とか「織田信長の移動」とかの例で説明しました。(説明文でいきなり「勇者ゴメスの装備」とか説明しても、初心者には分かりづらいから。読者には「勇者ゴメスって誰だよ? だいたい、どういう装備システムのRPGかよ?」って疑問に思われかねないので。「ゴメス」は分からなくても「織田信長」なら義務教育で習うので。)


で、文章で上記のように「装備システムの原理は、預かり所システムと同じ」説明するだけなら簡単そうですが、初心者が実際にコードを書くとまず、バグが発生し、「アイテム無限増殖バグ」または「アイテム消失バグ」のいずれかが発生します。

ツクール製やウディタ製のフリーゲームRPGでも、作者が独自の装備システムなどを考案しているRPGでは、ベータ版などでときどき、アイテム無限増殖バグなどが発生している場合もあります。


アイテム無限増殖バグの原因は色々と考えられますが、やりがちなミスのひとつとして、

本来なら装備から外した武器IDの武器の所持数を+1だけ増やさなければならないのに、間違えて選択中武器の武器IDの武器を+1所持数してしまう、

のようなミスをしがちです。

また、この種のプログラミングやデバッグをするので、バグ予防としては、所持武器の配列の構造体を作成する際に、あらかじめ作成しておく構造体変数として、ID番号も保管できるように構造体変数を用意しておくとラクです。

さらに言うなら、武器の作成時に、すべての武器にIDを割り当てておくべきです。原理的にはID番号が無くても、一時変数などで代用できますが、しかし、かなり面倒です。


ともかく、装備システムのコードは、かなり難しいので、答えを書いておきます。手本にしてください。

int tempID; // 作業用の一時ID
tempID = (weapon_have_list[heros_def_list[partyNarabijyun[whomTargetID]].heros_weapon1]).have_weapon_id   ; // 一時IDに、選択中キャラの右手武器の装備品IDを代入している
int tempEquip = heros_def_list[partyNarabijyun[whomTargetID]].heros_weapon1;

// 外した装備の個数が1増える。
weapon_have_list[tempEquip].have_kosuu = weapon_have_list[tempEquip].have_kosuu + 1;
// 上記で [tempID - 1]としている理由は単に、仕様表では武器IDが1番から数えてるけど、プログラム内部ではゼロからカウントしているので1個ズレしてるから


// 装備したものは個数が1減る。 変数「sentakutyuu2」は装備画面での装備可能品リストで選択中のものが何行目かを表す数値。
weapon_have_list[itemHairetu[sentakutyuu2]].kosuu = weapon_have_list[itemHairetu[sentakutyuu2]].kosuu - 1;


// 上記で先に所持武器の個数を更新してから、あとから装備内容の更新。こうしないとロストする。
heros_def_list[partyNarabijyun[whomTargetID]].heros_weapon1 = itemHairetu[sentakutyuu2];

のようになります。

なお、上記コードでの、あらかじめ作成しておいた構造体変数や配列の意味は、それぞれ、

・所持武器の構造体変数: weapon_have_list
その所持武器の所持数: .kosuu
所持武器のID用メンバ変数: .have_weapon_id
・パーティキャラ定義の構造体変数: heros_def_list
そのパーティキャラの右手武器を表す構造体メンバ: .heros_weapon1
・パーティ並び順を保管した配列: partyNarabijyun[]

という意味です。

もしドラクエ1のように勇者1人だけのパーティのゲームなら、もう少し構造体などが減らせて、簡単になるのかもしれません。しかし、現代の一般的なRPGではパーティ内に多くの人数がいるので、そういった事も構造体変数や配列などでプログラミングする必要があるので、そのため、装備システムのプログラミングはけっこう難しくなります。


ともかく、文章だけで「預かり所のシステムと同じ」だと、読めば簡単そうですが、しかし実際のゲーム制作では、RPGの装備システムの実装は、諸々の事情により、かなり難しいです。

RPGプログラミングのもうひとつの山場の「戦闘モードにおける、各キャラの素早さ順に並べた行動順配列をつくるためのソート」のテクニックと同様に、上記の装備品システムでの、データ消失なき各・装備品の個数増減のプログラミングも、RPG制作プログラミングにおける山場のひとつでしょう。


なお、この装備システムを作る前の準備として、実務的にはセーブシステムが必要であり、現在の装備品やアイテム所持数(薬草などの消耗品だけでなく武器・防具などの装備可能品の所持数も含む)などをセーブできるようにしておく必要があります。なぜなら、テストプレイの際に、何度も、コード改修するたびにロードを繰り返して検証するからです。

このように、装備システムの実装には、前準備(各種の構造体配列の作成や、セーブやロードにおける所持アイテムや装備中アイテムなどのセーブ・ロード機能の対応)が多く必要です。

この前準備の多さのため、装備システムの実装は、けっこう大変です。山場です。もしC言語で自作RPGを作りたい人は、がんばりましょう。

本ページの別セクションで述べた「アイテム上詰め」システムなどとも関連するので、装備システムの実装は、かなり難しくなります。

上詰め表示の実装は、後回しに[編集]

説明の都合上、先にアイテム表示の自動上詰めを紹介しましたが、実は上詰め方式はメンテナンス性を悪くしますので、上詰め機能の実装は、後回しにすべき項目です。

なぜメンテナンス性を悪くするかというと、理由は、上詰めによる置き換わり先の位置が、一定ではないからです。位置が一定でないという事はつまり、配列(あらかじめ「アイテム配列」などを用意する)などのそれぞれの要素の値も一定ではないという事なので、デバッグの際、いくつも要素数のある配列の中身をつねに追跡しつづける手間が生じますが、このことはつまり、配列の要素数が増えるとメンテナンスの手間が増え、(不可能ではないですが)そこそこ煩雑になります。

単に所持アイテムだけを上詰めするのは、まだ簡単なほうですが、アイテム倉庫なども実装するとなると、倉庫アイテム配列と主人公所持アイテム配列との受け渡しになってしまい、それを両方とも上詰めすると、2種類ある配列の中身を追跡するのが、かなり煩雑になってしまいます。

しかも、その2種類のアイテム配列どうしを、矛盾のないようにアイテムの受け渡しをするように設計しなければいけないので、中身の追跡の手間が増えるのは、かなり面倒です。

なのでプログラミングの際、上詰めなどの並べ替えは極力、後回しにすべきであり、ゲームシステム実装の最後のほうの段階になってから行うようにすると、メンテナンスへの影響が減らせます。

たといアイテムの種類が何十個や何百個もあるゲームであっても、プログラミングの始めの段階では、上詰めを行わないほうが簡単です。


プレイヤー視点だと、アイテムなどの種類の多いゲームの場合は、普通はそういうゲームはアイテムが上詰めで紹介されるので、ついつい自分がプログラミングする場合もそのように早めに上詰めを実装したくなりますが、しかし実はメンテナンス上の理由により、上詰めのような自動並べ替え機能の実装は、後回しにすべきです。

上詰め表示の実装は、後回しに2[編集]

アイテムの上詰めシステムを導入する場合、所持アイテムの種類が合計で何種類か、などの変数が必要です。

なぜなら、上詰めによって空白になった何も無い(下部の)場所に、カーソルが移動したら、オカシイからです。

このため合計で何種類のアイテムを所持しているかの変数が必要です。


問題点・難所は、どうやってその、合計種類数を追跡を行うか、という事です。


方法は主に2種類あります。

  • たとえば『薬草』を使い切るなどして『薬草』が0個になった時など、アイテム数が0になった場合に合計種類数をマイナス1する方式。逆に、今まで0個だったアイテムを、そのあと入手した場合には、入手直後に合計種類数をプラス1していく。(仮に「0個カウント方式」と言いましょう。)
  • アイテム配列をもとに、実際に配列の中身をfor文などで走査的に調べてカウントしていく方式。(仮に「走査方式」と言いましょう。)


このうち、圧倒的にラクな方式は、アイテム数が0個になった場合に合計種類数をマイナス1していく方式です。初心者のうちは、まず0個のアイテムが生じたら合計種類数をマイナス1していく方式がラクです。

原理的には、アイテム配列をもとに毎回、配列の中身を走査していくほうが正確なので上級者にはバグ予防しやすいのですが、しかし大きな問題点として、コードが複雑化するので、初心者がデバッグ(バグの修正)をしづらい、という問題があります。

なので初心者のうちは、配列の中身を走査していく方式は、避けるほうが安全です。初心者にデバッグしやすいコードと、上級者にデバッグしやすいコードとは、異なります。


たとい正確な走査方式でバグの発生を防げる可能性が増えても、万が一にバグが発生した場合に、初心者レベルではバグの修正をしづらい、と言う問題点が、走査方式にはあります。

どうしても走査方式を試みるなら、せめて中級レベルのプログラミング力を獲得してから、走査方式を試みるべきです。

また、アイテム種類数が多いゲームだと、アイテム配列をもとに毎回走査するのは、計算量が増えていくので(指数関数ではなく一次関数的に増加する計算量ですが)、それなりに時間が掛かる可能性があります。


ハイブリッド方式はしない

また、アイテム種類カウントでは、上述の両方式のハイブリッド的なプログラムを試してイイトコどりしようとするのは、初心者のうちは、あまりしないほうが安全です。

なぜなら、ハイブリッド方式を初心者が試みた場合によく起きるバグとして、下記のようなバグが、よくあります。

アイテム種類数の変化も計算を、あやまって重複して、2倍の変化量で計算してしまうという、計算ミスがよくある。
0個カウント方式と走査方式の両方に書かないといけないコードを、あやまって片方にだけ書いてしまい、もう片方との食い違いによって、バグになる事がよくある。


ID番号は原則的に0番から数えよう

ハイブリッド関連の悩みでよく遭遇するので、配列の番号です。プログラミングでは配列の番号は0番からですが、しかし現実社会では1番から数えるので、悩みます。ついつい、配列の番号のスタイルがコード中ではハイブリッドになりがちです。

たとえば味方キャラ(主人公を含む)のID番号を主人公を1番、ヒロインを2番、(△ あまり、すすめられない)とかにしがちです。

しかし、配列の番号は、0番から使うべきですし、つまり、主人公のキャラ番号は0番、ヒロインを1番、にするべきです。


なぜなら、C言語が0番から数える文法なのに(仮に「0番方式」と呼ぶ)、なのに主人公を1番方式にすると、0番方式と1番方式が混在するのでプログラミングの手間が大幅に増えますので、避けるべきです。

実際、動画サイトなどで古いゲームのバグ動画などを見ると、キャラ番号が0番から振られている様子が伺えます。

具体的には、バグなどで仲間が加わった場合に、なぜかそのバグ仲間のキャラ画像と名前が主人公である事例も多く、加わったバグ主人公がHP0,MP0、すべてのパラメータが0だったりするので、つまり「じゃあ主人公のキャラ番号も0だな」だということが伺えます。


また現代でも、ゲーム製作ツールのウディタなどでは、(キャラ番号などの)配列を0番から使います。

ついついプログラミング初心者にあわせて1番からキャラ番号などをフリ勝ちですが、しかしそれは避けましょう。

そもそも、C言語程度の配列の「番号の数え方が0番から始まる」という程度の入門知識すら無い人には、そもそもRPGのプログラミングは到底は無理です。 そこまでの知識の無い人には、C言語によるプログラミングの仕事は、あきらめてもらいましょう。

番号の表示を避ける仕様で根本解決

また、根本解決としては、極力、ゲーム中では番号の表示を避ける仕様にするべきでしょう。 実際、市販の多くのゲームでも、番号は表示されません。

たとえゲーム中にパラメータ順などに整列できるメニューコマンドなどがあっても、 しかし多くのゲームで、番号はなにも表示されないのが一般的です。

おそらく上述のように、番号方式の混在(C言語的な方式と現実社会の方式の混在)を避けるために、 根本解決として番号そのものを画面表示しないという仕様に決めたのだろうと思います。


例外的に、「システム処理用に配列の0番を利用したい」などの事情で、1番以降からキャラクターのID番号などに割り振る場合も考えられますが、 しかしそういうのは、そういうシステム処理が必要になってからコードを改修すれば済むことです。

こういう後工程の手間が増えそうな処理(1番方式への改修)は、後回しにしましょう。


道具屋の商品メニュー表示のキャンセルなど[編集]

基本[編集]

RPGでは、道具屋に行くと、「買う」コマンドを選択後に商品メニューが出て

薬草         10 G
毒消し草     4 G 
ワープ石     10 G

とか商品メニューが出てきます。

駄目かもしれない例[編集]

さて、もしも、この商品メニューの最後に、コマンドを追加して

薬草         10 G
毒消し草     4 G 
ワープ石     10 G
店を出る
所持品を売る

とか追加するのは、どうでしょうか?

こういうゲームは、あまり見かけませんね。

普通の多くのRPG作品では、キャンセルボタン(PCゲームならXボタン、十字コントローラーならBボタンなど)を押すことで、

「買う」「売る」「店を出る」コマンドなどの選択画面に戻るという仕組みになっています。


答えを言うと、商品リストの末尾に「店を出る」コマンドを追加してしまうとデバッグの手間が増えるので、よってコマンド追加はやめたほうが良いです。

この末尾コマンド追加方式をしなくても、もっと安全に、プレイヤーに操作方法をわかりやすく伝える代替策がありますので、その代替策を実装したほうが安全です。(代替策については後述。)


では、なぜ末尾コマンド追加方式にしたらデバッグの負担が増えるかというと、理由は、

・商品メニューの種類数によって、末尾コマンドの位置が変わるので、まずそれのテストプレイ検証などが手間です
・しかも、商品メニューの一覧のためのプログラム命令文と、コマンド一覧のためのプログラム命令文とを、相互に関連しあって不整合の無い様にコード記述しなければならず、だいぶ手間が増えます。

たとえば、いくつか前の節でも説明しましたが、まず道具屋での商品リスト掲載の前提となるアイテムの上詰め処理の実装だけでも、けっこうデバッグの負担を増やします(しかし、この上詰めは仕方無い)。

その上、さらに道具屋の商品メニュー末尾に別のコマンドを末尾に追加するというのは、つまり、購入モードにおいて 商品/コマンド の選択時に、いちいち、「プレイヤーは、いま何行目を選択しているか?」をもとに条件分岐(if文など)で「商品選択中」または「コマンド選択中」などとフラグ分岐の内部処理をプログラムに追加する必要が生じるので、だいぶ設計の手間が増え、しかもバグの発生しやすい高リスクな設計になります。

そして、手間が掛かる割りには、目立ちません。しかも、他のゲームでも使わないので、教育的な応用も利かなそうです。デバッグ防止という工学的な理由もあるので、ゲーム以外の一般的なIT産業にも、上記のような「何かのデータベース表示の末尾にコマンド追加方式」が採用される確率は低いので、あまり他産業にも応用が利かないでしょう。(一般のIT産業でも、何かのメニューコマンド欄と、データベース表示欄とは、分離する事になろだろうと思われます。)

特に商業ゲーム作品では、バグ発生が品質問題として大幅に嫌われるので、コマンド追加方式はさけたほうが安全です。

代替策[編集]

キー・アサイン

それでも、どうしても初心者への操作の説明の分かりやすさを考えている設計をしたいなら、たとえば

「Xボタン で 買う/売る コマンド画面に もどります。」

などの説明を画面のどこかに表示するだけにして、末尾コマンドは追加しないようにすると良いでしょう。

なお、キーの機能割り当てのことをIT業界の専門用語で「キー・アサイン」と言います。アサイン(assign)とは「割り当て」と言う意味の英語です。キーマップやキーバインドとも言いますがニュアンスが微妙に異なる場合もあるので、とりあえず「キーアサイン」の言い回しを本ページでは採用します。

キーアサインを、たとえば、ゲーム中の「決定」=Z、「キャンセル」=X、 (推奨の例)

のようにゲーム中の判断や指令ごとに割り当てる仕組みにするといいでしょう。

一方、あまり「第1ボタン」=Z、第2ボタン=X、 (ダメな例)

のような、具体性のとぼしい仕組みは、メンテを難しくするので避けたほうが良いでしょう。

なぜなら、デバッグしやすさ的な理由もあります。もしゲーム中で場面ごとにキーアサインをコロコロと変えるシステムだと、キーアサインの変更し忘れなどの余計なデバッグ作業の手間を増やし、バグを誘発する原因になります。(プレイヤーの設定カスタムなどは、とりあえずここでは考えないとする)しかも、そのバグは重度の高いバグです(操作性を大きく損なうバグになるので)。

「第1ボタン」=Zのような方式は、開発効率的に、まったく割に合いません。

こういうデバッグ上の理由もあり、いっそのこと、最初から「決定」=Z、「キャンセル」=X、 (推奨の例)のように機能ごとにアサインを指定する方式にすれば、余計なバグの混入を未然防止できます。


また、ゲームに限らずITを使った業務一般で、たとえばファイル名が「file1」とかフォルダ名が「folder1」みたいなのは、大きく嫌われます。(どこの業界でも新人がよくこういう命名をヤラかし、上司に文句を言われます。)なぜこの「folder1」みたいな方式が嫌われるかと言うと、後工程の人がフォルダ内容の確認をする必要がある場合が多いので、確認時間を取られるからです。そのぶん、会社組織の仕事が遅れかねません。

同様、「第1ボタン」、「第2ボタン」のような方式も、「第1ボタンの機能って何だっけ?」という確認の手間がプログラム時やら各種の集団作業時に発生してしまうのです。

どうしても番号をつける必要があるなら「1_決定ボタン」=Z、「2_キャンセル」=X

のように、目的の機能を併記した命名にしましょう。


プレイヤーにとっては、どうせ(このwikiを読んでるアナタ以外の)他の作家のRPGのプレイでも、キャンセルボタンのプレイ技術は習得する必要があるので(デバッグ的な理由もあるので、他のゲームでもキャンセルボタン方式が採用されている確率が高い)、プレイヤーへの教育も兼ねて、そういうキャンセルボタン方式でコマンド画面に戻れる設計にしましょう。

RPGツクールやウディタなどでも、道具屋や武器屋・防具屋などからの退出は、ボタン方式です。もっとも、ツクールの制作会社がクリエイターのデバッグの負担まで考えたか分かりませんが(単にウィザードリィ(昔のツクールの開発元のアスキーがウィズの日本移植してた)やドラクエなど市販の有名ゲームの多くを統計的に参考にしたのだろう)、経緯はともかく結果的にツクールのデフォルト(標準設定時)での機能は、バグの発生しづらい設計になっていますので、そういうプログラマー的な視点でツクールなどのデフォルト設定も参考にしてみると良いでしょう。


なお、この工学発想を一般のIT産業に応用するなら、「データベースの表示およびアクセス手段と、メニューの表示とアクセス手段は、なるべく分離しろ」という原則になるでしょう。分離することでバグの発生を抑えられる利点があるし、テスト(バグの無いことを確認するための試験)などの検証もモジュールごとに分割して行いやすくなるので、デバッグの負担が減ります。

ただし例外的に、もしかしたらマウス操作のゲームや、タッチパネルのゲームなど、ゲームの操作デバイスによっては、ツクールやウディタのデフォルト設定のようにはいかないかもしれません。ですが、とりあえず設計の際には、上述のような工学的なモジュール分離の発想を参考に、うまくモジュール分割してバグを抑える設計をすると良いでしょう。

今後もメンテナンスしやすい設計を[編集]

もし、むりやり末尾コマンド追加方式で道具屋システムを作ろうと思えば、とりあえず一時的には試作品を作れてしまうでしょう。しかし、問題点は、その後のメンテナンスの手間と費用です。改修やブラッシュアップなどのメンテナンスのたびに、いちいち上記のバグ混入リスクのような点に注意するのは、だいぶ非効率です。

しかも、もしゲームが集団制作の場合には、共同プログラマーどうしで、いちいち上記のバグ混入リスクの高い部分の仕様のすり合わせをしなければならず、だいぶ手間が増えます。

つまり、集団制作な大作ほど、手間が増えてしまいます。

そして、手間が大幅に増える割に、しかし末尾コマンド追加方式は、プレイヤーにとってプレイで得られるメリットが少ないのです。


とにかく、RPG作家は、道具屋の商品メニューなどでのコマンド追加方式はさけるのが安全です。


アイテム構造体配列の関数表示[編集]

アイテムに限りませんが、ゲームでは関数を使って配列形式の構造体データを表示する必要に迫られる場合があります。

構造体の配列を関数で呼び出す方法については、一般的な市販のC言語入門書ではあまり説明されませんので、このページでサンプルコードを示しておきます。

(具体的なイメージがないと読者が想像しづらいので)先に表示結果を示しておきます。

例として、道具コマンドの使用時の処理の実装を考えます。

表示結果
道具欄を見ますか?(※道具欄に決定したとします)
ポーション x 2
毒消しハーブ x 5
火炎石 x 8
ワープ石 x 2

道具欄を選んでいます(※実際のゲームではここにカーソル表示などが加わります)
ポーション x 2
毒消しハーブ x 5
火炎石 x 8
ワープ石 x 2

使用後の道具表示
ポーション x 2
毒消しハーブ x 4  使用したよ
火炎石 x 8
ワープ石 x 1  使用したよ

ゲームでは、上記の表示結果のようになるとしましょう。

では、このようなコードを関数と構造体と配列を用いてシンプルに実装する方法を考えましょう。

先に結果のコードを示すと、下記のようになります(解説は後述します)。

コード例
#include <stdio.h>

typedef struct {
  char *name; // 道具名
  int kosuu; // 個数
  int siyouFlag; // 使用したアイテムを見やすくするために目印を表示するために使うフラグ
} dougu_kouzou;

// 道具一覧を表示するための関数
void hyouji(
    dougu_kouzou dougu
        [5]) { //  Linuxの場合、main側の配列の要素数(4)に合わせないと警告アリ
  int i = 0;

  for (int i = 0; i <= 3; i = i + 1) {
    printf("%s x %d", dougu[i].name, dougu[i].kosuu);

    if (dougu[i].siyouFlag == 1) {
      printf("  使用したよ");
    }
    printf("\n");
  }
}

void siyou(dougu_kouzou dougu[5]) {
  // 1番目と3番目の道具を1個使用したとする
  int tagetNum = 1; // 何番目の道具を使うか
  dougu[tagetNum].kosuu = dougu[tagetNum].kosuu - 1;
  dougu[tagetNum].siyouFlag = 1; // 使用したアイテムに目印表示させたいので

  tagetNum = 3;
  dougu[tagetNum].kosuu = dougu[tagetNum].kosuu - 1;
  dougu[tagetNum].siyouFlag = 1; // 使用したアイテムに目印表示させたいので
}

int main(void) {
  dougu_kouzou dougu[] = {
      {.name = "ポーション", .kosuu = 2, .siyouFlag = 0}, // const 除去
      {.name = "毒消しハーブ", .kosuu = 5, .siyouFlag = 0},
      {.name = "火炎石", .kosuu = 8, .siyouFlag = 0},
      {.name = "ワープ石", .kosuu = 2, .siyouFlag = 0}};

  printf("道具欄を見ますか?(※道具欄に決定したとします)\n");
  hyouji(dougu);
  printf("\n");

  printf("道具欄を選びました(※実際のゲームではここにカーソル表示などが加わります)\n");
  hyouji(dougu);
  printf("\n");

  // 道具の使用シーン
  siyou(dougu); // ここで道具を数個ほど消費
  printf("使用後の道具表示\n");

  hyouji(dougu);
}

上記の例としては、アイテム構造体の配列で道具コマンド時の一連の表示で説明しましたが、もちろん他の構造体(キャラクター構造体や装備品構造体など)でも同様のプログラミング手法で記述可能です。

コツとしては、関数を呼び出すときにhyouji(dougu); のように引数を使って呼び出す亊です。対応する関数定義文でも、引数が必要です。引数を使わないと、これらの処理の実装は困難になると思われます。