ゲームプログラミング/RPG

出典: フリー教科書『ウィキブックス(Wikibooks)』
ナビゲーションに移動 検索に移動

RPG[編集]

ここでいう「RPG」とは、特に断りのないかぎり、ターン制RPG(Turn-based RPG)のこととする。(アクションRPGやシミュレーションRPG、アクティブバトルなどを語る場合には、その際にそのことを注記する。)

いわゆるドラクエ シリーズやや昔のFF(ファイナルファンタジー)1~3あたりのようなシステムのターン戦闘システムのゲームを念頭に、このページでは「RPG」と読んでいる。

現代では『RPGツクール』やウディタなどのパソコン用のゲーム製作ツールで、手軽にパソコンでターン制RPGをつくることもできる。


なお、JRPGとは英語では慣用的に、ドラクエのようなターン制RPG(英: Turn-based RPG)のことを英語(和製英語でなく実際の国際英語)のゲームのこと、またはせいぜいスーパーファミコン程度(またはそれに毛の生えたレベル)のような雰囲気の2Dグラフィックなどを駆使したRPG、またはドラクエや昔のファイナルファンタジーなどの典型的なRPGと類似したシステム、あるいは日本製ゲームのようにストーリー重視やキャラクター重視のゲームのことを「JRPG」や「J-RPG」と言っている。

あまり「JRPG」に明確な定義は無い。たとえば欧米人・外国人の作ったインディーズ系のターン制RPGの自己紹介文を見ても、アメコミみたいな筋肉ムキムキの主人公や女性ヒロインのデザインがアメコミ風(美少女でなく)美女なビジュアルのゲームでも「JRPG」や「J-RPG」という表現が見られる場合もある[1]一方で、アクションRPGでも何故かJRPGという場合もあったりと、あまりJRPGとは意味が統一されていない。


いっぽう、もし「TRPG」というと、いわゆるテーブルトークRPG(和製英語。英語ではテーブルトップRPGという)などと混同される可能性もあるだろう。日本製RPGにターン制RPGの多いことなどもあってか、ターン制RPGのシステムのことを英語では(TRPGではなく)JRPGという場合もある。

このページでいうRPGとは当然、けっしてテーブルトークRPGではなくて、コンピューターゲ-ムのRPG、古い言い方ならビデオゲームのRPGのことである。


設計の方針[編集]

初期段階ではデータの個数は少なめに[編集]

細かな設計方法を述べる前に、まず、設計時の大まかな注意事項を述べる。

ゲーム好きの子供などがRPGの企画を考えるとき、よく、自由帳などのノートに、いきなり、大量の武器や防具の「攻撃力」や「防御力」などのデータを、ノートに書き起こし足りする場合がある。

例えば

1. 竹やり: 攻撃力=1, 値段 = 50G,
2. 鉄のナイフ: 攻撃力=3, 値段 = 100G,
3. 鉄の剣: 攻撃力=10, 値段 = 700G,
(中略)
130. ミスリルのよろい: 防御力 = 40, 値段= 13000G,
131. ミスリルのたて: 防御力 = 70, 値段= 10000G,
(後略)


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


しかし、プログラミングでは、けっして、プログラミングの最初に、いきなり大量のデータを搭載するようなプログラミングは、してはならない。 また、可能なら、データはソースコードとは分離するほうが、大規模な作品では管理しやすくなります(ゲームに一般に、プログラミングでは、なるべうアルゴリスムとデータを分離すべきだと言われています)。

なお、Windowsの場合、データのファイルはソースコードのファイルとは、実行ファイルにデータを同梱する機能がある。


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

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

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

┏━━━━━┳━━━━┳━━━━━┳
┃       ┃       ┃       ┃          
┣━━━━━╋━━━━╋━━━━━╋
┃     ┃     ┃      ┃      
┣━━━━━╋━━━━╋━━━━━╋
┃       ┃     ┃          ┃
┣━━━━━╋━━━━╋━━━━━╋━
┃        ┃     ┃       ┃            
┣━━━━━╋━━━━╋━━━━━╋

のような空白の表に、

┏━━━━━┳━━━━┳━━━━━┳
┃名称   ┃攻撃力 ┃ 値段  ┃          
┣━━━━━╋━━━━╋━━━━━╋
┃竹やり  ┃  1  ┃  50  ┃      
┣━━━━━╋━━━━╋━━━━━╋
┃鉄のナイフ┃  3  ┃  100  ┃
┣━━━━━╋━━━━╋━━━━━╋━
┃鉄の剣  ┃  10 ┃  700  ┃            
┣━━━━━╋━━━━╋━━━━━╋

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

なお、表計算ソフトの入手には、有料ソフトのマイクロソフトExcelのほか、無料のフリーソフトのw:LibreOfficeのCalcなどがある。


参考
アルゴリズムとデータの分離

一般に、大規模なプログラムを開発するときは、なるべくデータを、ソースコードからは分離します。このことを、「ロジックとデータの分離」と言いますが、この手法で分離されるべき対象物は、アルゴリズムとデータです。

このように分離すべき理由として、もしデータを調整などのために値や文字列などを変更しても、アルゴリズムへの影響が無くなるので、アルゴリズムに故障を及ぼすような致命的バグが発生しづらくなる等の利点があります。

一般に仕事などでプログラムを作る場合、もしかしたら開発当初などの試作の段階では、データをソースコード中にベタ書きする場合もあるかもしれませんが、しかし最終的にはデータはソースコードとは別ファイルにするのが一般的です。


参考: CSVファイル[編集]

プログラムで読み込むための大量の数値データを取り扱うとき、素のテキストファイル(.txt)を使うよりも、CSVファイルという形式で取り回す場合が多い。

上記の武器の表の場合、CSVファイルとして出力すると、

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

のように出力される。

C言語には、テキストファイルを読み取る関数 (fgets) もあるので、それらとCSVファイルを組み合わせることで、ゲーム用のデータを読み込むことができる。

いきなり構造体や関数を使わない[編集]

  • 方針

C言語では、何度も利用する処理をまとめるのに「関数」という機能があります。

また、同じパターンのデータを複製するために「構造体」という機能があります。さらにC++では、これらと似たような機能をもつ「クラス」というものもあります。

RPGのプログラミングでも、最終的には関数や構造体や(C++なら)クラスなどを使うことになるでしょう。しかし、あまり最初から構造体や関数を使わないほうが、便利です。

なぜかというと、もし、いきなり関数や構造体を使おうにも、アプリの実行状態を見てない段階なので、具体的にどんな処理をまとめれば良いのかなどのイメージがわかないので、プログラミングが頓挫しがちになるでしょう。

もし、アプリの実際の動作をまだ見てない状態で、せっかく関数などのプログラミングをしても、たいてい、見当はずれのプログラムになりがちです。


そもそも、たいていの場合の初心者は、はじめて書くプログラムでは、なんなかのプログラムミスがありコンパイルエラーになり、実行自体を開始できない段階なので、関数や構造体などをいきなり作っても無駄です。

関数や構造体を作りに行くのは、せめて、エラーの無い状態でとりあえず動かせるようになった後からです。

なので初心者は、ともかく通常の変数ばかりのような初歩的な機能ばかりを使っての(いわゆる)「ベタ書き」でもいいので、まずは動作するプログラミングをするほうが、手っ取り早いです。


最終的には構造体や関数などをつかって処理をまとめる目標は必要ですが、しかし、あるアプリの開発の初期段階のプログラムでは、あえて「関数」や「構造体」などの機能を利用しないのがコツです。


  • 判断基準

初心者のアプリ開発初期では、ちょっとくらい繰り返し処理があっても(たとえば「ある3行の命令を、4回繰り返す」くらいなら)、関数なんて使わずに、1行づつ処理を書いてしまいましょう。初心者には、せいぜいFOR文やIF文を使う程度でも、十分でしょう。

関数を使うほうが便利になる場合とは、もっと行数が多い場合や(関数内の処理が何十行以上もある場合)、もしくは繰り返し回数が多い場合です(何十回以上も繰り返す場合など)。


なぜ、関数や構造体を開発初期では使わないほうがよいかというと、たいていのプログラム言語では、関数などの定義の宣言場所と、その関数を呼び出す場所とが、文法上、けっこう離れた位置になります。

このため、関数や構造体が多いと、かえってプログラム全体の見通しが悪くなりますし、修正などのさいに、スクロールバーをスクロールする手間が生じます。

そして、実際のアプリは、学校などで習う十行ていどで終わるプログラムとは違い、実務のプログラムでは何千行も行数がある場合もあるので、スクロールの手間は、けっこう面倒です。 (また、ファイル分割などをすると、1ファイルあたりのコード数は減るが、今度は、仕様変更などによる修正などをするときに、修正対象の複数のファイルを起動する手間が生じるので、とても手間が増える。 実際のプログラミングでは、仕様の変更などによる修正があるので、けっして、いきなりファイル分割はしないのがオススメです。)

学校などで習う関数の命令文は、初心者でも理解しやすいように関数内の処理が数行で終わる例がほとんどですが、しかし実際のアプリ開発では、3行×3回ていどなら関数を使わないで、1行づつ書いたほうが分かりやすくなるしょう。


このように、学校(大学や専門学校)などで習うプログラミングの授業とは、違います。大学などでは、どんどんと「構造体」やら「関数」やら「クラス」やらの高度な機能を習いますが、しかし、ゲームプログラミングでは、そういうのは後回しです。


「毎回、この処理を1行ずつ書くのがメンドウだな~。関数にしたいな~~(もしくは「構造体にしたいな」)。」と思ったら、そこで初めて、以降に作成する処理を、自作の関数や構造体に置き換えればいいのです。

つまり、すでに作った部分は、まだ関数や構造体にしないでおきます。

とりあえず、これから作る部分だけ、関数や構造体に置き換えるのです。

関数を増やし過ぎないように[編集]

ゲームプログラミングの場合、関数を増やし過ぎないようにすべきです。


ゲームの場合、必ずしも、関数や構造体などを使って共通化されたコードは必ずしも便利とは限りません。なぜなら、特殊イベントなどの処理のために、不規則な動作をさせたいシーンも多く(その不規則さがイベントのドラマ性などの理由にもなっています)、そういうシーンを作る際に、規則的なシーンだけを前提にアルゴリズムを共通化してしまうと、システムの柔軟性がなくなり硬直化してしまい、むしろゲーム開発が非効率になってしまいます。

ゲーム全体のまとまりのための共通化には、パラメータを通して共通化すればいいのであり、必ずしもアルゴリズムまでも共通化する必要は無いのです。


  • RPGの場合、具体的には

たとえばRPGの場合、「戦闘モード」とか「マップモード」とか「買い物モード」とか、いろんなモードがあるので、つい、「どのモードでも使いそうな共通の処理は、あらかじめ関数にしておこう。」と勘違いして、設計しがちです。

たとえば、どのモードでもカーソル操作があるだろうから、つい「共通カーソル操作関数」みたいなのを先に設計しようとしがちです。あるいは、どのモードでも文字表示があるから「共通文字表示関数」とか。

つまり、どのモードでもカーソルやウィンドウなどのUIがあるので、ついつい先にそちらを作りこみたくなりがち、です。

ですが、はじめにそういったUI(ユーザーインターフェース)の作りこみは、実は後半のほうの段階で仕上げをするべきなのです。なぜなら、プレイヤーに「よかれ」と思って細かく作ったUIが、動作速度の問題やデバッグ効率などの理由で、古いバージョンに戻すことになったりする場合もあるからです。


UI用の関数にかぎらず、こった関数を作ると、こっているぶんに比例して、デバッグなどの手間も増えていきます。なので、関数はけっして「一度、関数をつくって終わり」ではないのです。その関数を今後もデバッグし続けることも考える必要があり、なるべくデバッグしやすい直感的な関数だけを作るべきです。


UI用の関数を書いてもいいですが、どの関数を書くにしても、あまり複雑になりすぎないようにしましょう。


ともかく、ゲーム全体を先に作るのが先決です。プレイヤーが見たいのは、ゲーム全体のストーリーやテンポといったゲームの全体像です。カーソルの動きとかウィンドウの動きとかは、あくまで補助的であり、そういったUIに対するプレイヤーの興味も、あくまでオマケの範囲でしかないのです。

ゲームの開発当初のUIは、とりあえず、操作がワカリやすく、邪魔にならなければいいのです。


では、どういった関数が直感的かというと、たとえば、マップモードなら「マップ関数」を1個(または、画像用とパラメータ用に分けて2個)だけ、みたいにそのモードの代表的な小数の関数を作るのが、直感的です。

「どうしても、マップ関数1個だけではマップ処理を書きづらい」となったら、そこで初めて、新たに最低限に必要な関数を追加していけば十分でしょう。

むやみにリファクタリングしない[編集]

リファクタリングとは、とりあえず動くようになったプログラムの内部構造を整理することです。

よくある方法として、プログラム中の似た動作をする複数のアルゴリズムを、ひとつの関数にまとめたりする等して、プログラム中の複数の似たコードの共通部分などをひとつのモジュールにまとめたりする事が、リファクタリングとして行われます。

ですが、むやみに似たコードをひとつの関数にまとめると、将来的な仕様変更に対応しづらくなる欠点が発生しやすいため、一長一短です。なので、むやみやたらにコード共通化によるリファクタリングをする事は、さけるべきです。

ゲーム開発ではテストプレイヤーなどからの報告を受けることにより、プロトタイプ時の過去の仕様のままではツマラナイ場合には仕様を変更することも多々あります。なので、仕様変更にも対応できる開発スタイルにて、ゲーム作家は開発していく必要があるのです。

上記のような理由から、ゲームの場合、コード共通化リファクタリングをするべきタイミングは、開発の中期の段階であり、さらに、あまりにもベタ書きのコードが多くなって作業効率が落ちてからです。(開発後期にリファクタリングを避ける理由は後述)

あるいはある程度規模が大きくなって、どこがボトルネックになっているかを把握してからでも遅くはありません。


ベタ書きを途中で使用頻度の多いコードを関数に置き換える手法で開発を進めていくと、関数を使わずに当初は自作関数を使わずに命令を1行ずつ書いていた処理と、自作関数を使って書いた処理とが、混在することになります。

しかし、混在を気にする必要はありません。

有名フリーゲーム作家なども、リファクタリングしてない状態でゲームを発表しています。(ツイッターなどで、そう発表しているクリエイターもいます。)


ゲームの場合、販売価格などの低さなどの理由もあり、あまり開発期間を長くできない等の理由もあり、デバッグ期間もあまり長く出来ないのです。

リファクタリングをしてしまうと、デバッグの手間が再度、増えます。

リファクタリングは、ゲーム全体に影響を確実に及ぼすので、デバッグのためのテストプレイの手間も増えます。特にRPGの場合、デバッグの手間が多く(「RPGのデバッグはハードコア」などと主張している洋書もあります)、(アドベンチャーゲームやアクションゲームなど)他ジャンルに比べて多いと言われるほどです。

このため、開発後期ではリファクタリングは避けるべきです。


一般にIT業界では

デバッグされてないコードは負債である

と言われます。

リファクタリングは、デバッグ労力も大きく要求しますので、開発後期・終盤などのデバッグ時間を取りづらい時期は、せっかくリファクタリングのコードを書いてもデバッグ不足になりかねないので、開発後期ではリファクタリングを避けるべきです。


なお、情報科学でも、こういった格言があります。

性急な最適化は、諸悪の根源である。

w:リファクタリングについて、ドナルド・クヌースリファクタリング


どうしても混在が今後の作業の懸念として気になるなら、コードをいじるのではなくて、// TODO: 〇〇の混在を解消することとでもコメントし、マークしておきましょう。

このコメントの意味は、今後は、リファクタリングがこの部分に必要だという、後輩などへのためのメモ書きです。


  • コメントの習慣をつけよう

初心者に必要なノウハウは、コメントをプログラム中に書く習慣をつける事です。

    // 武器の強さ
    // 短剣の強さ
    int tanken_tuyosa = 10; 
    // 長剣の強さ
    int tyouken_tuyosa = 25;

などのように、コメントを書けば、いちいち構造体やクラスにしなくても、第三者が見ても用途がわかります。いちいち、武器の構造体やら武器クラスとかを考える手間が減ります。

学校では、採点の手間が掛かるのでコメントをあまり練習しないでしょうが、しかし実務ではコメントの習慣は必要でしょう。

たとえ自分1人で開発しているアプリであっても、日数が経つと過去のプログラム時に思いついたアイデアを忘れることも多いので、未来の自分が思い出せるようにコメントで情報を残しておきましょう。

異なる関数どうしでのパラメータ共有[編集]

おおまかな内容

グローバル変数やポインタなどを使って、別々の関数どうしでパラメータを共有できることを紹介。

普通のC言語の知識があれば、それで足りる知識の紹介なので、すでにC言語をある程度 知っている人は読まなくてもいい。

モードの管理手法について[編集]

基礎[編集]

ターン制RPGの場合、モードの切り替え方法は、 たとえば、モード番号 mode_number みたいな変数を用意して、

マップ移動モード: 1000番
会話モード: 2000番
メニューモード: 3000番
戦闘モード: 4000番

のように、番号で管理したほうがラクでしょう。

マップ移動フラグ map_flag (trueならマップ表示) や会話中フラグ talk_flag (trueなら会話ウィンドウ表示) みたいな変数を用意しないほうがラクでしょう。

その理由は、

たとえば戦闘モードへの突入のためのモードの切り替え時に、いちいちmap_flagをオフ(0にセット)にして、talk_flagをオフにして、menu_flagをオフにして、・・・などのプログラム作業を、モード番号管理の方式ならば1回の作業で省略できます。

また、もし、管理するフラグの個数が増えると、フラグの切り替えのし忘れなどのバグも発生しやすくなります。

このように、モード番号方式にすると、自然と、各モードへの遷移時に他モードを排他することができ、モードの混在を防げるので、ゲームシステムをモジュール化できます。

よって、モード番号の方式で管理するのが無難でしょう。

これはつまり、何かのオン/オフのフラグは、なるべく特別なイベント(例: 中ボスを倒した。伝説の武器を手に入れた。)などの例外的な処理の管理のみで活用するのが無難ということです。


なお、1番、2番、3番、・・・などのように1つおきではなく、1000番、2000番、のように間を置くのは、途中で追加するモードを挿入しやすくするためです。

たとえば、マップ移動でも、通常の歩行での移動を1000番として、ダッシュ移動を1010番、船による海上の移動を1020番のように、モードを追加したくなるかもしれません。なので、ある程度の間を、事前に基本モード間に空けとくと便利です。

特に、メニューモードや戦闘モードなど、コマンドをいろいろとプレイヤーが選ぶことになるので、それぞれのコマンドに対応したモードを多く追加することになるでしょう。

メニューモードの内部にコマンド(たとえば「装備」コマンド)に対応した中モードがあって、さらにコマンド(武器を装備? それとも防具を装備?)があって、選んだコマンドに対応した小モードに遷移する・・・みたいな3段階のモードの存在する可能性もあるので、基本のモード番号は3桁以上(つまり100番以上、できれば1000番ほど)ないと、不便でしょう。

このため、最低でも

マップ移動モード: 100番
会話モード: 200番
メニューモード: 300番
戦闘モード: 400番

くらいの3桁以上で管理するべきです。

変数を媒介としてモード番号の数値の定義[編集]

モード番号は、開発中に番号の数値の仕様を変更したくなる可能性があるので(例えば、オープニング画面のモード番号を追加したくなったとか)、後から番号を変更しやすいように、変数を媒介して定義するとラクです。

まず、単純な方法で、

// モード番号の定義
int mode_map1 = 100; // マップ画面のモード番号
int mode_talk1 = 200; // 会話画面のモード番号
int mode_menu1 = 300 // メニュー画面のモード番号
int mode_battle1 = 400 // 戦闘画面のモード番号

みたいに定義する方法があります。

こうすれば、もしモード番号の数値を変更するときには、対応する宣言文 int mode_〇〇 = △△の1個の文だけを変更するだけで済みます。

なお、C言語には定数値の宣言の就職子 const がありますので、

// モード番号の定義
const int mode_map1 = 100; // マップ画面のモード番号
const int mode_talk1 = 200; // 会話画面のモード番号
const int mode_menu1 = 300 // メニュー画面のモード番号
const int mode_battle1 = 400 // 戦闘画面のモード番号

と書くと、よりいっそう、意味が明確になります。


ほかにも、defineマクロを宣言する方法もあります。

// モード番号の定義
#define MODE_MAP1 100 // マップ画面のモード番号
#define MODE_TALK1 200 // 会話画面のモード番号
#define MODE_MENU1 300 // メニュー画面のモード番号
#define MODE_BATTLE1 400 // 戦闘画面のモード番号

マクロ名が大文字になってることに、特に深い理由は無く、単なるC言語業界での慣習です。


数字を「1」と後につけている理由は、のちのち、それぞれのモードのサブメニュー画面が追加されていくから、それとの区別のためです。


あらかじめ「1」とか「main」とかをつけておかないと、あとでサブ画面との区別のために、変数名をコードエディタの置換機能で一括変換する際に面倒なことになります。 たとえば

MODE_MAP
MODE_MAP_sub

でMODE_MAPだけを名前変更したくて「MODE_MAP_main」にしようとしても、 「MODE_MAP_sub」も一緒に「MODE_MAP_main_sub」になってしまう。

なので、あらかじめ、「1」でも「main」でもいいので、モード名の定義のさい、定義名称のうしろにサブ画面モードとの区別のための追加文字を付けて起きましょう。


列挙型 enum[編集]

なお、モード番号を使う代わりに、C言語の列挙型 enum の機能を使うことでも、上記のような排他的な制御は出来ます。そもそもC言語の列挙型の正体そのものが、上述のモード番号のように、整数(int型)の値を条件分岐用データに割り当てる命令です。

wikibooksの『C言語/データ型と変数の高度な話題#enum』のページにもenumの解説がありますが、このゲームプログラミングの教科書で大まかにenumのコード例を書くと

enum mode { battle , map , menu };

みたいな宣言方法になります。

しかし、C++と無印C言語でenumの文法が違っていたり、市販の入門書ではenumについて触れてないことも多く、列挙型はあまり初心者向けではありません。


標準C言語での enum の大まかな使いかたは、下記のとおりです。

コード例
#include "stdio.h"
#include "string.h"

enum mode { battle, map, menu };

int main(void) {
  enum mode modevar = map;

  if (modevar == battle) {
    printf("いまバトル画面 \n");
  }

  if (modevar == map) {
    printf("いまマップ画面 \n");
  }

  if (modevar == menu) {
    printf("いまメニュー画面 \n");
  }
}
※ なお、このコードは、Fedora Linux で確認したものなので、Windows や visual C++ では動かないです。さらに、コマンドライン(WindowsでいうDOSプロンプト)から使うためのコードです。
文法の解説
enum mode { battle, map , menu };

のように、enumで列挙体を宣言する。

書式は

enum タグ名 { 列挙体定数1, 列挙体定数2, 列挙体定数3 };

です。


さらに、(定数でなく変数として)列挙体変数を別途、宣言する必要があり、

enum mode modevar = map;

によって列挙体変数 modevar を宣言しており、初期値として map を代入している。

書式は

enum タグ名 変数名 = 初期値 ;

です。


なお、余談ですが、enumで上述のようにif文と組み合わせてモード管理の手法を使うと、情報科学でいう「状態遷移」(じょうたい せんい)という管理手法が自然と実装できます(『オートマトン』などの教科書に書いてある『状態遷移図』)。ゲーム制作よりも、自作プログラム言語(インタプリタ方式)などのコンパイラ制作などでenumが便利かもしれません。

モード番号方式の限界[編集]

ただし、上記の一連のノウハウは、あくまでターン制RPGの場合だけでしょう。

もしアクションRPGの場合なら、マップ移動中でも戦闘したり会話したりする可能性もあるので、モード番号方式ではなくフラグ方式にせざるを得ないでしょう。

このことは、ターン制RPGとアクションRPGとでは、コードを共有する事が、なかなか難しい事にも、つながるでしょう。


階層構造にしない[編集]

モード方式で管理するとき、似たようなモードをつい、まとめたくなるかもしれません。

たとえば、『アイテムのメイン画面モード』と、『アイテムの使用者対象者の選択モード』とを、『アイテム大モード』の中の小分類として分類したり・・・、みたいなピラミッド型というか樹形図みたいな分類手法です。

ですが、あまりこういうふうに、大分類・中分類とか作らないほうが、簡単です。


特に、しないほうがイイこととして、「ある大分類の中での共通変数」みたいな中間的なローカル変数は、まず最初のうちは作らないでおくのが安全です。


なぜ、大分類の共通変数やら中分類の共通変数みたいなのを作らないほうがイイかというと、理由は、その変数を使う際に、いちいち所属先を記述しないとイケナクなり、手間が増えるからです。


なんだか、役所仕事で手続きが色々な機関を通すと煩雑なのを非効率だと新自由主義者から批判されるように、プログラミングにおいて自作の変数を使う際に色々な所属先をいつも調べてから記述するのは、なんだか煩雑です。

それでも行政なら慎重さも必要かもしれませんが、少なくてともアマチュアのゲーム製作において、そういう手間は当面は不要でしょう。


なので、変数はなるべく、コード冒頭だけのグローバル変数と、もひとつはローカル変数を使う直前に定義するという、たった2階層だけで管理するほうがラクです。


それでも、どうしてもモードの数が膨大になるなどの理由で、そのため「大分類」とかで似ているモードどうしをまとめて整理したいなら、(けっしてコード中ではなく、)仕様書などドキュメントでの説明文のほうで、モードや変数の説明文といった文章だけを階層構造にしましょう。

コードそのものの管理は、グローバル変数と直前ローカル変数だけの2種類という、フラット構造にしたほうが安全です。

Windowsによる画面消去[編集]

Windowsでは、プログラマーが画面クリアの命令を書いてなくても、OSがなんらかの理由で強制的にいったん画面クリアする場合があります。

典型的なのが、アプリをウィンドウ右上の最小化ボタンを押してタスクバーに格納すると、いったん画面が消去されてから、WM_PAINTを実行するという再描画が始まります。


モードの管理方式だと、この際、前のモードの描画はすべて消えてしまいます。


たとえば、(ドラクエ1みたいに)戦闘モード中にマップ画面を背景で表示しようと思って、マップモードの画像を残していても、しかしWindowsの場合、なんらかの理由でWindowsが強制的に画面クリアすることがあり、そのあとにはマップモード時に描画された画像は、このままでは消去されてしまい再描画されません。


こういったWindowsによる強制消去の対策としては、戦闘画面モード側にもマップ背景の描画をコード記述するしか、方法はありません。(他の方法があるかどうかは、一般には知られていません。)


結局、それぞれのモードで、それぞれ別個に、同じ背景画面の描画を指定することになります。

なお、関数などを使えば、背景などの描画はコードの使いまわしが出来ます。


たとえば、戦闘画面モードで、マップ画面描画の関数を呼び出せば、追加する必要のあるコードはその呼出し命令だけで済むという事です。


グローバル変数でも画像を保管できない

なお、これとは別の方法を試みても、グローバル変数などで画像を保有する方法は、うまくいかないです。(標準c言語の文法どおりの仕様には、Windows APIの仕様は従っていないのです。)

たとえば、あらかじめグローバル変数として(コード冒頭の部分の)グローバル領域で

HDC hbackDC; 

など、裏画面用の画面変数を宣言しておいて、ためしにローカル関数サイドでグローバル変数のhbitmapやHDCなどに代入コピーしようとしてイコール記号「=」でプログラム中で代入コピーしても、(コンパイルはできるのですが、)なぜかモード変更時にはコピー内容を残せない仕様になっています。(おそらく、メモリのオーバーブローを防ぐための仕様として、Winodowsが自動で勝手に、代入命令の意味をメモリ内容の複製ではなく(コピーではなく)、参照リンクにしていると思われます。)

(なお、HDCは型名である。Windowsではデバイスコンテキストの型名がこう決まっている。 hbackDCは単なる変数名なので、自由に命名できるし、別の変数名でも構わない。)

どうやらWindowsAPIにおけるhbitmapやHDCといった型のグローバル変数宣言は、実態は単に、各ローカル関数ごとに宣言をする手間を減らすために、一括でグローバル領域で宣言できるという仕様でしかないようです。

もし、上述の説明とは別のうまい方法があったとしても、つまり複数のローカル関数どうしでhbitmapやHDCの内容を共有できる方法があったとしても、その方法はけっして、上述のような直感的な方法ではないので(上述のような直感的な(標準C言語のような)方法では、共有不可能です)、メンテナンス上の理由であまり用いないほうが安全でしょう。

このようなWindowsAPIの非直感的な仕様のため、どうしても背景を複数のモードで使いまわしたい場合にはWindowsの場合、やむを得ず、CPUに負担は掛かってしまうのですが、結局は前の節で説明したように、関数などを用いて各モードごとに同じ描画をする方法しか、IT業界ですら、この関数の方法しか世間的にはよく知られていません。

static でも保存できない

静的変数 static としてグローバル変数として、

static HDC hbackDC; 

とグローバル領域で宣言しても、同様の結果です(共有できない)。なお、これとは別件で、ローカル関数側で static HDC と宣言しても、何も表示されなくなりますので、ローカルで static 宣言しても役立ちません。


ポインタ変数でも保存できない

画像の描画の際、グローバル変数でアドレス変数やポインタ変数を作成しても、通常変数の場合と同様に、異なるモード間では一行に画像を共有できないです。

たとえば、下記のようにポインタなどを使ったコードを書いてもコンパイルは出来るのですが、しかし画像がモード遷移時に消去され、複数のモード間では画像を共有できないです。

例えばグローバル変数としてグローバル領域で

HDC* point_dc;

と宣言してみて、

描画時に

point_dc = &hbackDC;

などと宣言をしてみて、

BitBlt(hdc, 0, 0, 600, 400, *point_dc, 0, 0, SRCCOPY);

としても、単に普通に通常変数で描画した場合と同様のことが起きるだけで(つまり、宣言したモード内だけで描画が出来るだけ)、異なるモード間では何も画像は共有されません。

また、

HBITMAP*

で試しても、同様の結果です。

2Dマップ[編集]

方針だけ述べる。具体的なコードは、上手い人のコードを参考にしよう(コードがけっこう長くなり、紹介がメンドウくさい)。

2Dマップがあると、俄然、RPGっぽく見えるようになって、ヤル気が出るので、さあ作ろう。

マップのデータは、ふつう、2次元配列で書く。

まず、グローバル領域で、たとえば

static int maptable[10][10] = {
	{ 0,0,0,0,0,0,0,0,0,0 }, //0 y
	{ 0,0,0,0,0,0,0,0,0,0 }, //1
	{ 0,0,0,0,0,0,0,0,0,0 }, //2
	{ 0,0,0,0,0,0,0,0,0,0 }, //3
	{ 0,0,0,0,0,0,0,0,0,0 }, //4
	{ 0,0,0,0,0,0,0,0,0,0 }, //5
	{ 0,0,0,0,0,0,0,0,0,0 }  //6
};

のように、とりあえず配列を確保しよう。


ここで重要なのは、 確保した配列のタテとヨコは、ヨコの並びをx方向として、タテの並びをy方向としたとき、

maptable[y][x]

のように確保されていることに注意する。


さて、各配列に「0」を入れても、はたして0番が何を意味するか、まだ何も決めていない。

ゲームにもよるが、とりあえず、何もマップチップを上書きしない領域だと定義しよう。

背景色と同じ色のベタ塗りのマップチップを作っておけば、それで上書きすれば、あたかも何も上書きしてないように見える。

いちいちif文などで、何も上書きしないように場合わけをするのもメンドウであるので、どっちの方式にするか、決めておこう。


さて、マップ用の配列は、作成したいマップで最低限必要なマス目よりも、やや大きめの領域を確保しておく必要がある。

たとえば、5×5のマップを作りたいなら、配列ではもっと大目に、たとえば 8×7 とか確保しておく必要がある。


もし、なんらかのバグで確保されていない領域を呼び出してしまうと、プログラムがエラーで異常停止してしまう。


たとえば、一番左端をもしx=0とした場合、もしこの場所に主人公がいると、さらにプレイヤーが「左端に行こう」と考えて、左ボタンを押したときに、エラーで異常停止してしまう。

こういう停止はメンドウくさいので、だったら念のため、最初から、マップを移動可能な場所よりも何マスか大目に確保しておくと安全である。


さて、まだ配列を確保しただけなので、「0」が何かとか、「1」が何かとか、まったく定義していない。

とりあえず、

0番は、進入不可能の暗闇。
1番は、床とか、草原とか平地とか、とにかく歩ける場所

としよう。 だと思ってればイイ。


たとえば、もし配列 maptable の宣言が、

static int maptable[10][10] = {
	{ 0,0,0,0,0,0,0,0,0,0 }, //0 y
	{ 0,0,1,1,1,1,1,1,0,0 }, //1
	{ 0,0,1,1,1,1,1,1,0,0 }, //2
	{ 0,0,1,1,1,1,1,1,0,0 }, //3
	{ 0,0,1,1,1,1,1,1,0,0 }, //4
	{ 0,0,0,0,0,0,0,0,0,0 }, //5
	{ 0,0,0,0,0,0,0,0,0,0 }  //6
};

のような宣言だったら、このマップでは真ん中のほうに4マス×5マスの移動可能な地帯がある。 その周囲は移動不可能になっている。

ともかく、このように、プログラマーは、まず、何番のマップチップが何を現すかということを、脳内で設計しておく必要がある。


配列の範囲外の読み込みエラー防止のためも兼ねて、0番は、すべてのゲームキャラが進入禁止としておくのは安全だろう。


あとはもう、win32 API などの機能を使って、マップチップの画像を配列にもとづいて各マスごとに表示すればいい。


前提として、まずマップチップ画像の読み込みを行う必要がある。あるいは、画像をあらかじめ実行ファイルに組み込んでおく必要がある。

画像の実行ファイルへの組み込みは、Visual C++ にそういう機能があるので、それを使えばいい。

画像を読み込みたいなら、ゲーム起動時にでも世も込んでおけばいい。



イメージ的にコードの雰囲気(ふんいき)を書くと、たとえば case WM_PAINT: の節に、下記のように for文などを使って画像ハンドらに画像を代入するコードを裏画面に書いていき、最後にまとめて本画面に描画することになる。


(イメージ)
(※ あくまでイメージです。このままでは、動きません。)
       int iTemp;

	for (x_map = 0; x_map <= 9; ++x_map)
	{
		for (y_map = 0; y_map <= 6; ++y_map)
		{			
			iTemp = maptable[y_map][x_map]; // 配列の文字が長いので、いったんiに置き換え
			hbmp = hbmp_mapchip_list[iTemp].hbmp_mapchip;

			SelectObject(hMdc, hbmp);
			BitBlt(hbackDC, 225 + x_map * 32, 140 + y_map * 32, 32, 32, hMdc, 0, 0, SRCCOPY);

			// DeleteDC(hMdc); // これを入れると、マップが表示されない。
		}
	}


	// 裏画面から本画面に転送
	BitBlt(hdc, 0, 0, 700, 500, hbackDC, 0, 0, SRCCOPY);

	hbmp = NULL; // 初期化。


	// 中間ハンドルからhbmpを解除
	SelectObject(hMdc, NULL);
	SelectObject(hbackDC, NULL);

	// 不要になった中間物を削除	
	DeleteDC(hbackDC);
	DeleteDC(hMdc);

	DeleteObject(hbmp);
}



SelectObject や BitBlt は、Win32 API の専用の関数なので、分からなければ読者で調べてください。


なお、マップチップ画像そのものの作成は、単にWindowsアクセサリ『ペイント』などの画像作成アプリでビットマップ画像を用意すればいい。

ツクールやウディタなどだと、いくつかのマップチップをまとめた「タイルセット」などを取り扱うが、実はプログラミング的には、わざわざタイルセットを作らなくても、マップチップ1個ずつを読みとりすることも可能である(単にwin32APIのビットマップ画像の読みとりの機能を使えばいい)。

(win32 API の初期設定のままではビットマップしか表示できないハズ。PNGなどを使いたいなら、GDI+の設定を行うこと。GDI+については説明を省略。)。


なお、マップチップのサイズの規格は、よくある規格は16ピクセルの倍数で、「px」をピクセル単位の意味として16px×16pxまたは32px×32pxまたは64px×64pxのマップチップ規格がよくある(ツクールやウディタのマップチップのサイズ規格も、この系統)。

とりあえず、私たちにとって作るのがラクなのは、小さいほうが作成がラクなので、とりあえず16×16のマップチップを作ろう。


プログラム技術としてはマップ表示は単なる二次元配列なので難しいことは無い。しかし、その他の準備が忙しく、たとえばマップチップ用のビットマップを用意したりとか、あるいはキーボード操作のプログラムを作ったりとか、そういう準備が難しい。


これだけだと、まだマップを表示しただけなので、さらに主人公キャラのキャラチップとか、主人公がx=何マス目、y=何マス目にいるかの変数とかも宣言する必要がある。

戦闘中のアルゴリズム[編集]

素早さ順行動のアルゴリズム[編集]

ターン制RPGでは、戦闘中、敵と味方が入り乱れて、素早さ順に行動するシステムが、一般的です。

このアルゴリズムが、けっこう難しいです。

ドラクエ1のように、敵と味方が1対1の戦いなら、けっこう簡単ですが、ドラクエ2以降や『ファイナルファンタジー』シリーズのように、味方が複数人、敵も複数匹の場合、それらの行動準の決定アルゴリズムは、けっこう難しいです。

さて、まずアナタは、ドラクエ1のように敵と主人公が1対1のバトルのプログラムを、あなたの自作ゲームのソースコードに書いてください。そして、その1対1のバトルのプログラムを実際に自作ゲームに組み込んで、とりあえずプレイ可能な状態まで持っていってください。

それが出来たら、次に、その1対1のプログラムを拡張して 多数 対 多数 のプログラムに改造していきましょう。

では、そのような多数対多数での素早さ順行動アルゴリズムのための考えかたを述べます。


まず、敵と味方の両方とも、それぞれのキャラ(または敵モンスター)の素早さの値を、登場の順番ごとに(まだ並べ替えはしない)1つの配列に入れます。

けっして「敵どうしだけ」、「味方どうしだけ」でなく、敵と味方の両方を比較する必要があります。


たとえば、味方3人、敵2匹なら、配列の要素の5個を使います。

あらかじめ、要素数20や30など、十分に要素数の大きい配列を1つ用意しておきましょう。

そして、この、たとえば要素数20の配列の先端5個ぶんの要素に、素早さの値を入れるわけです。

たとえば

for (id = 0; i < partyNinzu; id = id + 1)  // まず味方パーティのメンバー順に味方人数分だけ素早さを配列に代入
{
  sankaSubayasa[id] = mikata_Subayasa[id];  // 味方の人数分、味方の素早さを代入
}

for (id = 0; id < tekiNinzu; id = id + 1)  // つづけて敵パーティの人数分を代入
{
  sankaSubayasa[partyNinzu + id] = teki_Subayasa[id];  // つづけて、敵の人数分、敵の素早さを配列に代入
}

のようになります。今後の説明のため、とりあえずこの配列の名前を「素早さ配列」とでも名づけておきます。

※ なお、この作業のために、あらかじめ、キャラのステータスを配列または構造体配列などにしておく必要があります。少なくとも、各キャラの素早さは配列化しておかなければ、並べ替えが困難(ほぼ不可能)になります。なので、事前に、構造体配列などの配列にしておきましょう。wikibooksのC言語の教科書でも構造体配列について解説してあります。
※ 上のコード例では、説明の簡単化のため、構造体配列を使わないで説明しています。実際のゲーム開発では、もしかしたら構造体配列で書いたほうがラクかもしれません。

ともかく、このように、for文などを使って、まとめて入れましょう。


さて、並べ替えたい対象物は、けっして「素早さ」そのものではないです。なので、上で作った配列(「素早さ配列」)そのものは、並べ替えません。

行動するキャラクター・敵の順番を並べ替えたいわけです。


なので、そのためには、キャラクターまたは敵のIDが入った配列が新たに必要です。なので、そのキャラクターIDまたは敵IDの入った配列の呼び名をとりあえず「行動順配列」と言いましょう。


たとえば、この行動順配列の内容がたとえば「3, 1, 5, 4, 2」だったら、

まず ID=3番 の奴が最初に行動し、
その次に ID=1番 の奴が行動し、
その次に ID=5番 の奴が行動し、
その次に ID=4番 の奴が行動し、
さいごに ID=2番 の奴が行動する.

というような仕組みになります。


要するに私たちは最終的な目標として、「行動順配列」を作れればいいのです。そして、そのための手段として、「素早さ配列」を使います。

そして、「行動順配列」を作るために、「素早さ配列」の各要素の数値を「ソート」技術で比較します。ソートの技術は、一般的な情報科学の本に書いてあるので、それを参照してください。


一般に、2つの変数の数値を入れ替えるアルゴリズムがあります。コレを使うとラクですし、そもそもソートのアルゴリズムはコレを応用します。

まず、X=3, Y=5 を入れ替えたい場合、

int X=3;
int Y=5;

int irekaeA = X;
int irekaeB = Y;

そして、すばやさ比較をして、「素早さ配列のソートの結果と連動して行動順配列を変更する」のがテクニックです。(※ 一般のソートの教科書では、ひとつの配列しか入れ替えないので、なかなかコレに思い至らない。)

イメージ的にプログラムを書くと、

if (subayasaHairetu[2] >= subayasaHairetu[5]) {
 // (ifカッコの中の)前の数が大きい場合には入れ替えしない。
}
if (subayasaHairetu[2] < subayasaHairetu[5]) {
  // 素早さ配列の入れ替え
    int irekae_Subayasa_A ;
    int irekae_Subayasa_B ;

    irekae_Subayasa_A = subayasaHairetu[2];
    irekae_Subayasa_B = subayasaHairetu[5];

    subayasaHairetu[2] = irekae_Subayasa_B;
    subayasaHairetu[5] = irekae_Subayasa_A;

  // 行動順配列の入れ替え
    int irekae_Junjo_A ;
    int irekae_Junjo_B ;

    irekae_Junjo_A = junjoHairetu[2] ;
    irekae_Junjo_B = junjoHairetu[5] ;

    junjoHairetu[2] = irekae_Junjo_B;
    junjoHairetu[5] = irekae_Junjo_A;
}

みたいな感じの作業を、すべてのキャラ同士の組み合わせで、for文を使って網羅的に行えばよいのです。

上記コード例の場合、そのときの行動順配列で2番目と5番目のキャラを素早さ比較しており、もし素早さが高ければ、そのキャラを先にもってきています。

ただし、配列の「2」番と「5」番なので、日常の数え方では3番と6番のことです(配列は0から数え始めるので)。


さて、for文の走査は単に、w:リーグ戦方式で、単に片っ端から走査していけばいいだけです。この方法をw:バブルソートといいます。

戦闘中の各ターンの進行の自動化[編集]

戦闘で各ターンのコマンド入力が終わったら、今度は、自動的にそのターンの戦闘が始まらなければいけません。

そして、1~2秒くらいおきに、たとえば

「戦士の攻撃!ゴブリンに5のダメージを与えた!」 (0~2秒のあいだ表示)
「ゴブリンの攻撃!魔法使いは2のダメージを受けた!」(2~4秒のあいだ表示)
「魔法使いの攻撃!ゴブリンは4のダメージを受けた!」(4~6秒のあいだ表示)

みたいに秒の経過によってメッセージが自動進行する必要があります。

※ 説明の単純化のため、全員、通常攻撃で肉弾攻撃だとします。

このような自動進行の処理のため、タイマーを呼び出す必要があります。

タイマーは、オペレーティングシステムが管理しています。

オペレーテイングシステムの種類によって、タイマーの呼び出し方は異なります。

Windowsの場合、Windows API にある SetTimer 関数でタイマーを呼び出せます。

SetTimer 関数で指定した時間が経過するごとに、case WM_TIMER ラベルが呼び出され、ラベル内のプログラムを実行します。

この case WM_TIMER ラベルをWindows API の、WM_PAINT などのある位置の前後に作る必要があります。


さて、case WM_TIMER ラベルが書けたら、


今度はそこに、さきほどの素早さの処理で作成した行動順配列の順番に基づき、戦闘に参加している各キャラが行動する必要があります。

コードは、書きのようなイメージになります。(イメージですので、下記のままでは動きません。)

case WM_TIMER:

if (mode_scene == MODE_BATTLE_NOW ) {			
			TimeCount++; // バトル時以外はカウントしない. 戦闘開始期はゼロになっている

				if (monster_alive == 1 && TimeCount >= 2 && timerFlag == 0)  // 自動進行の開始時に timerFlagをゼロにセットしておく
				{
					timerFlag = 1;
					ID = 0; // 戦闘参加キャラの通し番号. TimerFlag-1でも代用できるが、意味を考えて別の変数を用意した

					// 行動者が味方側の場合
					if (junjoHairetu[0] < partyNinzu) {
						
						heroside_attack(hWnd);

						InvalidateRect(hWnd, NULL, FALSE);
						UpdateWindow(hWnd);
					}

					// 行動者が敵側の場合					
					if (junjoHairetu[0] >= partyNinzu) {
						
						if (encount_mons_alive == 1) {
							enemy_attack(hWnd);
						}
						InvalidateRect(hWnd, NULL, FALSE);
						UpdateWindow(hWnd);
					}
				}

				if (monster_alive == 1 &&  TimeCount >= 4 && timerFlag == 1) 
				{
					timerFlag = 2;
					ID = 1;

					// 行動者が味方側の場合
					if (junjoHairetu[1] < partyNinzu) {

						heroside_attack(hWnd);

						InvalidateRect(hWnd, NULL, FALSE);
						UpdateWindow(hWnd);
					}

					// 行動者が敵側の場合					
					if (junjoHairetu[1] >= partyNinzu) {

						if (encount_mons_alive == 1) {
							enemy_attack(hWnd);
						}

						InvalidateRect(hWnd, NULL, FALSE);
						UpdateWindow(hWnd);
					}
				}
	


				if (monster_alive == 1 && TimeCount >= 6 && timerFlag == 2) 
				{					
					timerFlag = 3;
					ID = 2;

					// 行動者が味方側の場合
					if (junjoHairetu[2] < partyNinzu) {

						heroside_attack(hWnd);

						InvalidateRect(hWnd, NULL, FALSE);
						UpdateWindow(hWnd);
					}

					// 行動者が敵側の場合					
					if (junjoHairetu[2] >= partyNinzu) {

						if (encount_mons_alive == 1) {
							enemy_attack(hWnd);
						}

						InvalidateRect(hWnd, NULL, FALSE);
						UpdateWindow(hWnd);
					}

	                        }
				if ( TimeCount >= 9) {
					mode_scene = MODE_BATTLE_COMMAND; // コマンド画面に復帰するためのモード設定
					TimeCount = 0;
					timerFlag = 0;
					ID = 0;
				}
				InvalidateRect(hWnd, NULL, FALSE);
		} 	
		break;


説明の簡単のため、for文を使わないでコードを書いています。また、味方の死亡を無視しています。

敵の死亡だけ考慮してあるのは、戦闘を有限ターンで終了させるためです。本当は上記コードにさらに敵の死亡判定のためのコードが加わったり、敵全滅時の自動進行のコードが加わりますが、説明の単純化のために除去してあります。

そのほか、クリティカルヒットやミスなどの判定も無視しています。敵のAIも無視して、たとえば、敵の(味方への)攻撃対象は、あらかじめパーティ最前列の戦士だけを攻撃するように決めてあるとします。


さて上記コード例で「行動者が味方側の場合」と「行動者が敵側の場合」とでif文や関数を別々に分けている理由は、

参照先のデータベース(または構造体)が、それぞれ異なっているからです。

味方キャラのデータベースを参照するか、それともモンスターのデータベースを参照するかの違いがあるからです。

このように、文章だけなら一見すると同じ動作であっても、参照データベース(あるいは参照先の構造体など)が異なっていれば、実質的に別々の命令文になります。


上記コードの重要ポイントは、たとえば TimerFlag という変数を用意して、指定時間の経過ごとに case を呼び出した時に、このTimerFlag フラグと同じ番号(行動順配列での通し番号)のキャラだけが動くように設計している事です。通し番号の異なる別番号のキャラのIf文ブロックはマスク(遮蔽)されており、別キャラは行動しないようになっています。

そして、このTimerFlagが、行動順配列の各キャラの行動ごとに、TimerFlagの値が順番どおりに1アップしていくようにしておけば、よいのです(説明が難しい。要するに、こういう感じでRPG戦闘シーンでの自動処理が実装できる。実際に Visual C++ でやればワカル)。


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

原理[編集]

たとえば中世ファンタジー風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であるイース3(ファルコム製)がアイテム管理では並べ替え処理をしない方式である事も、こういう背景もありうるだろう(取材したワケではないので未確認。)(もっとも、別企業のスクウェア製(現スクウェア・エニックス)の聖剣伝説シリーズではアイテムの上詰めのような処理をしていたので、それでも作れないことも無い。)

2020年代の私達は、ドラクエ方式・ファイナルファンタージ方式のインターフェースに見慣れていますが、必ずしもそれが全てのRPGにおいて適切とは、かぎりません。作成したいゲームの特徴にあわせて、適切にインターフェースを選びましょう。


作品によっては、RPGではアイテム類の種類の個数が膨大になる作品もあるので、並べ替えのような機能を実装すると、アクションRPGとしては処理速度が低下して不適切になる場合もあります。留意しておきましょう。

あるいは、処理速度を向上させようとして、並べ替えの頻度を下げたり間引きしたりすると、今度はバグ発生原因になりかねず、たとえば表示内容と実際のアイテム配置がズレるというバグなどが置きかねません。


なので、特別な事情のないかぎり、アイテムなどの表示順は、プログラム側の登録ID順に表示するのが、ラクです。プログラマー側にとってもラクだし、ユーザーも、たいして気にしません。

アイテムにかぎらず、ポケットモンスター(任天堂)みたいに作中にモンスター図鑑などのようなものがある場合も、なるべく、プログラム側の登録ID順にモンスターなどを表示するのがラクです。


仲間になるキャラクターが膨大にいるゲームの場合も、なるべく登録ID順に表意するのがラクでしょう。(しかし例外として『伝説のオウガバトル』のような例外もあるので、登録ID順の表示にしなくても、作れないことも無い。)


例外として『信長の野望』(コーエーテクモ)や『三国志』みたいな、お勉強要素のありそうな歴史シミュレーションゲームなどでも無い限り、少なくともファンタジーRPGでは、あまり並べ替え処理を細かく実装する利点は無いと思います。


ゲームの場合、アルゴリズムの管理などの保守の手間も考えて、アルゴリズムを設計する必要があります。そのため、いきなり複雑なアルゴリズムは作らないほうが安全です。

なぜなら、アルゴリズムはいったん実装するだけでは、けっして終わりではないのです。とりあえずのアルゴリズム実装後のあとも、そのアルゴリズムとの整合性のあるように他の各所を設計する手間が生じるからです。複雑・多様な処理のあるアルゴリズムは、それだけデバッグなどの手間も増えます。


なので、設計方針としては、比喩的に言うなら、アルゴリズムの実装の場合であっても、まずはゲーム全体の骨格を先につくり、まずはゲーム全体に血を回すほうが先です。細かな作りこみや整えは、後回しです。

しかし、アルゴリズムの作りこみのほうが全体像の実装よりも楽しかったりするので(なぜなら高校~大学の数学っぽいので. 数学好きの人がプログラマーになるワケですから)、ついつい作りこみたくなりますが、それはあくまで息抜きです。


小学校で「遠足は、(行くだけでなく)家に帰るまでが遠足です」と習ったでしょうが、アルゴリズムの設計も、デバッグなどの維持管理をするまでが お仕事 です。

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

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

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


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


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

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


たとえば、薬草を主人公パーティが手持ちで最初は10個もっているとします。

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

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

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

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

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

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

こうすれば、薬草の個数は合計10個ですし、たしかに倉庫に3個の薬草があります。


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

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

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

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

名探偵シャーロック・ホームズの格言か何かですが(創作上の人物ですが)、本来あるべきハズのものが無い場合には、人間はなかなか、そのことに気づきません。

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


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

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

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

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


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

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


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

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

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

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

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

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

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


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

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

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

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

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


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


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

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


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

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

なので初心者のうちは、配列の中身を走査していく方式は、避けるほうが安全です。

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

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

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


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

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

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

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

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

基本[編集]

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

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

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

駄目な例[編集]

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

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

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

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

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

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


答えを言うと、コマンドを追加してしまうとデバッグの手間が増えるので、よってコマンド追加はやめたほうが良いです。

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


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

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

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

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

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

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

代替策[編集]

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

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

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


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


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


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

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

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

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

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

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

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


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

(※: 未確認)戦闘中のモンスター隊列[編集]

さて、戦闘中のモンスター隊列の構成を定義するための配列(または同等の内容の数列)も、必要です。

まず、画面に敵を、表示したい順に表示するためも必要です。

また、上述の「素早さ順」での行動処理のためにも、前提として、敵パーティの構成を表すための配列が必要になります。


説明の単純化のため、敵は横一列に、左から右に、並んでいるとしましょう(ドラクエ2~3みたいな方式だとしましょう)。


たとえば敵が左から順に

ホブゴブリンが1匹、毒々スライムが1匹、ゾンビ兵士が1匹

出現したとしましょう。(説明の単純化のため、モンスターは各種類ごとに1体だけとする。)


そして、ホブゴブリンのモンスター用データベース中でのIDが13番だとして、 毒々スライムのIDが8番、 ゾンビ兵士のIDが25番、 だとしましょう。

すると、この敵パーティを表すための配列として、

{13,8,25,-99}

のような配列が必要です。(もしくは、配列に格納できるような数値の列「13,8,25,-99」)

末尾の-99は、これはその配列の読み終わりとして使用するための数字です。この数字は、別に「-10」でも「-99」でもいいですが、モンスターのidとけっして重ならないようにする必要があります。

通常、なんらかのデータベースのidの値は0以上の正の整数ですので、マイナスの整数を「終了」の意味で使用しておけば、安全でしょう。


このように、敵側にも、配列が必要になります。

しかも、この敵側の配列は、そのゲーム中で出現する敵パーティの全パターンを用意する必要があります。


たとい1体だけしか出現しない敵でも(たとえばボス敵や強敵)、その1体分の敵パーティの配列が必要です。

たとえば、その1体だけで出現する、あるボス敵「暗黒大王」のモンスター用データベース中でのIDが205番だとしたら、

{205,-99}

のような、敵1体だけの配列を用意する必要があります。


余談: マイナスを含む並べ替え[編集]

なお、もしマイナスの数を含む数をふくめて並べ替えをしたいのでしたら、 上記の「-99」など終了処理を負数で区別する方法は、そのままでは使えないです。

たとえば、方向でもし東向きをプラス、西向きをマイナスとした場合、終了コードのつもりの「-99」は、誤解で「西に99マス」などと誤解される恐れがあります。

たとえば、

-3 , 5 ,2, -6, -2 , 0 , 1

の並べ替えをしたい場合を考えましょう。


解決策としては、いろいろあります。

解決策1[編集]

一番ラクな方法は、要素数を新たな変数として追加し、それと組み合わせて配列を使うことでしょう。


たとえば、

-3 , 5 ,2, -6, -2 , 0 , 1

は7個の数が並んでいるので、

youso = 7;

みたいに新たに変数を用意します。

そして、たとえば最初の「-3」から順番意読み取りの際に、数えおわった個数を+1していき、+7になったら読み取りを終了することです。

この方法は、拡張性が高いのでオススメですが、ただし、要素を書く前に個数を宣言する必要があります。

実際の配列データは

-3 , 5 ,2, -6, -2 , 0 , 1, 0 ,0 ,0 , 0 ,0 ,0 , (以下略)

のようになっているので、けっして数え終わって欲しいところでキリよく要素が終わるわけではないので、なので、要素数をあらかじめ宣言する必要があるのです。

さもないと、8番目の「0」が、読み取りたい数値としてゼロなのか、それとも、単にデータなしのためゼロなのか、不明になります。


終了コードなどとして負数を使用するのは、マイナスを含む並べ替えでは、あきらめましょう。それが安全でしょう。


解決策2[編集]

最初の解決策1(要素数を新たな変数として追加)がもっとも安全かつ簡単ですが、比較のため、別の解決策も紹介しておきます。


次の解決策2は、数学的にはエレガントですが、しかしプログラミング的には、煩雑になり、また、バグ時などのエラーの波及をしやすい欠点があります。


さて、並べ替えのもうひとつの解決策として、

一例は、符号と絶対値を分離して、その組み合わせとして処理することです。


たとえば、

-3 , 5 ,2, -6, -2 , 0 , 1

の並べ替えをしたい場合、


プレイヤーからは表面的には「-6」は1つの数に見えまずが、これをあえて、「フラグhugouが状態2(マイナスに相当)である」「絶対値 zetai が6である」というように、2つの変数hugou と zetaiに分けます


符号がプラスなら、hugou = 1 にでもしておきましょう。また、ゼロは便宜上、hugou = 1 にしておきましょう。


すると、上記の数の並びは、(hugou, zetai)のベクトルで考えると、

(2,3) , (1,5) ,(1,2), (2,6), (2,2) , (1,0) , (1,1)

という組み合わせに変換するので、マイナスが使われない形になります。


なので、終了処理に、「-99」などの負数を割り当てても、並べ替え対象の数値と重なる心配がなくなります。: (2, 6,) , (2, 3) , (2, 2) , (1,0) , (1,1) ,(1,2), (1,5)

(2,3) , (1,5) ,(1,2), (2,6), (2,2) , (1,0) , (1,1),(-99,0)

などのように、終了コード「(-99,0)」を末尾に追加しても、なんの混同の心配もなくなります。


さて、いったん上記のように、数を、符号と絶対値の組み合わせに分解して、同じ符号どうしで並びかえたなら、

あとは、符号が同じものどうしで、並べ替えをするだけですみます。


たとえば、

(2,3) , (1,5) ,(1,2), (2,6), (2,2) , (1,0) , (1,1)


の並べ替えをしたい場合、ベクトルの第一引数に注目し、※ (第一引数、第二引数)

まず、

(2,3) , (2,6), (2,2) のグループ
(1,5) ,(1,2), (1,0) , (1,1)のグループ

に分けます。


そして、絶対値(例では第二引数)だけに注目すれば、

マイナス数の絶対値は 3,6,2 → 2,3,6 と並べ替え → 逆順にして6,3,2 → マイナス符号をつけて -6,-3,-2
プラス数の絶対値は 5,2,0,1, → 0,1,2,5 と並べ替え

となるので、簡単に並べ替えできます。


あとは、これを合成し、

-6, -3 , -2 , 0 , 1 ,2, 5

と、並べ替えできます。


なお、説明の都合上でベクトルを蒸気の解説で使ったが、しかしC言語にベクトルの機能は無いので、もし上記の機能を実装するなら配列や構造体配列を使って、ベクトルと似たような処理を実装することになる。

たとえば配列で実装するなら、符号用の配列 hugou[id] と、絶対値用の配列 zetai[id] のようなものを用意したりすることになるだろう。

もし1番目の最初の数が「-3」なら、符号フラグは2、絶対値は3だからベクトル表現では

(2,3)

であるが、これは配列で表現するなら、たとえば

hugou[0]=2; zetai[0]=3;

のように記述できる(C言語では配列の番号は0から数える)。


欠点

この方法は、一見すると数学的にエレガントに見えるかもしれませんが、しかし、もしバグやタイプミスなどによって数え間違えると、数え落としや重複があると、以降の符号が1個ずつズレてしまうなどの大きな影響があります。

たとえば、説明のため上記では

-3 , 5 ,2, -6, -2 , 0 , 1

の分解を

(2,3) , (1,5) ,(1,2), (2,6), (2,2) , (1,0) , (1,1)

とマトメて書きましたが、 実際には

hugou 2,1,1,2,2,1,1
zatai 3,5,2,6,2,0,1

のように別々の変数に分かれて保管されるのです。

もし、たとえば hugou の最初に、バグなどで「1」が加わると、

hugou 1,2,1,1,2,2,1,1
zatai 3,5,2,6,2,0,1,0

のようになってしまい、すべての符号が1個分、ズレてしまい、

3, -5, 2, 6 ,-2 , 0, 1 ,0

となってしまいます。

正しい元々の数字の並びの

-3 , 5 ,2, -6, -2 , 0 , 1

と比べると、符号がいくつも間違っており(1個目と2個目と4個目が違う)、解決策2ではエラーの波及が大きいことが分かります。

画像関係[編集]

半透明のレイヤー合成を使いたい場合[編集]

たとえばRPGでは、戦闘に入ったときなど、マップ画面の上に半透明の黒レイヤーでマップをやや隠して、その上に戦闘画面の画像を描画したいような場合もあるかと思います。

RPGツクール作品とかウディタ作品とかの戦闘シーンでも、そういう描画のゲームがよくあります。

もし、自前のプログラムでこういう半透明レイヤーを描画したい場合は、GDI+ のインクルードで出来ます。または、最初から同等の命令が組み込んである C# などを使えば出来ます。

Win32API で半透明レイヤーを使いたい場合の方法については、wikibooks では『Windows API/画像の操作』に説明があります。

Direct X を使わなくても、Windows 製のゲームで半透明レイヤーの画像合成をする事は可能です。


なお、実際にそのまま半透明レイヤーを乗せると、描画に少し時間(コンマ数秒ほど)が掛かります。


このため、もしも、キー入力するたびに背景ごと毎回描画してしまうと、画面がチラついてしまいます。


なので、対策として、たとえば、

裏画面の描画を使うとか、
または、(win32 API やGDI + だけのプログラミングをあきらめて) Direct X を使うなど、

いろいろと対策しましょう。


脚注・参考文献など[編集]

  1. ^ たとえば欧米製のフリーのRPG 『Valyria Tear』(2020年2月13日に閲覧して確認) では同ゲームを「J-RPG」と謳っているが、どこにも日本風の萌え美少女アニメのようなデザインの女性ヒロインなんて出てこない。