ゲームプログラミング

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

概観[編集]

「ゲーム」とは[編集]

ゲームと一言でいっても、この教科書では、コンピュータを用いたゲームのプログラミングを扱います。

いわゆる「テレビゲーム」やコンピュータゲームについて述べます。

ここでは家庭用ゲーム機か、家庭用のコンピュータで扱える範囲の事柄をここでいうゲームとし、それらのゲームソフトをつくるためのプログラミングについて扱います。

解説にゲーム用語を使うので、もし用語の意味が分からなければ『ゲームプログラミング/コンピュータゲームの種類』などを参照してください。


そもそもプログラミングが必要か?[編集]

自分でゲームを作る際、必ずしも、C言語などプログラム言語で記述する必要はありません。

プログラミングをせずに、ほぼマウス操作と会話メッセージなどの文章のキーボード入力だけでゲーム開発をできるようにするソフトウェアが、有料または無料で発表されています。

たとえば、RPGを作りたいなら、日本で発表されているソフトでは、w:RPGツクールw:WOLF RPGエディターなどのように、RPG製作に特化された開発ソフトがあり、大幅に開発の手間を減らせます。なお、RPGツクールは有料製品です。WOLF RPGエディターは無料ソフトです。

アクションゲームを作りたいなら、w: 2D格闘ツクール2nd.w:アクションゲームツクールなどがあります。これらツクール製品は有料製品です。

また、ノベルゲームを作りたいなら、フリーソフトのw:吉里吉里Zなどがあります。吉里吉里Zはソースコードが公開されており、オープンソースになっています。


C言語などによるプログラムは、上記のゲーム開発ツールを使わない場合に、必要になる可能性があります。

既存のゲーム開発ツールの仕様に、あなたが不満を感じる場合に、「じゃあ自分でプログラムして作ろう」となり、プログラムが必要になるわけです。


なお、上記の開発ツールはほとんどがWindows用のソフトです。MacやLinuxでは動かない場合がほとんどです。なので、MacやLinuxでも動作するゲームを作りたい場合などに、プログラムが必要になる場合も多くあります。


ゲームを、既存のゲーム開発ソフトを使わずにプログラムで自作する場合、必ずしも、既存のゲーム開発ツールのような、ゲーム作品と開発ツールが分離された仕組みを再現する必要がありません。

というか、そもそも初心者には、ゲーム開発ツールを作るのは、ほぼ不可能です。

なので初心者は開発ツールを作ることは考えずに、とにかくゲームを1本、とりあえず完成させるのが優先です。

ゲーム1本を完成させたあとに、開発ツールとゲーム作品の分離などに取り掛かりましょう。


ゲームのプログラム言語[編集]

ゲームを書くために利用される言語は多岐にわたっています。歴史的にはC言語や、特に計算機のスピードが重要になる場面ではアセンブラを利用してプログラミングを行うことが普通に行われていました。現在では計算機がある程度速くなったことや、ゲームプログラムの開発を複数人で行うことでテクニカルなプログラミングが避けられるようになったことにより、ゲームプログラミングは普通のプログラミングに近いスキルとなっています。しかし、特にアクションゲームなどのリアルタイムでの画面書き換えが必要なゲームで、プログラムのスピードが重視されることは変わっていません。また、コンピュータの性能があがるにつれ、それらの性能を全て引き出すように表現手段が変化してきたため(3Dポリゴンなどを参照)、状況によっては複雑なプログラミングが必要になることもあります。

実際のゲームプログラミング[編集]

どのようなゲームを作るときでも、ゲームプログラミングでは、

  1. プログラミング言語を扱うことができる。
  2. ゲームが提示する内容を表示することができる。
  3. プレイヤーからの入力を扱うことができる。

などの技術が必要になります。

プログラミング言語とプレイヤーからの入力については歴史的にもあまり変化がありません。プログラミング言語ではC言語C++などが用いられます。ただし、携帯電話向けのゲームではJavaが利用されますが、これは携帯電話を提供する各社がJavaをアプリケーションの言語として選んだことによります(iアプリEZアプリS!アプリなどを参照)。

パソコンゲームでは、プレイヤーからの入力にはコンピュータでは通常キーボードかマウスを利用します。他にジョイスティックゲームパッドが利用される場合もあります。家庭用ゲーム機ではコントローラが利用されることが多いのですが、ニンテンドーDSWii UなどではタッチパネルWiiでは複数の入力機器が提供されることが発表されています。幸いにもそれらをプログラムから扱う手法はそれほど入力機器ごとにそれほど大きく変化することはありません(プログラムから周辺機器を扱う方法については、デバイスドライバ高等学校情報Bなどを参照)。

残念なことに画面を表示することのうちで3Dの表現は割合難しく、ある程度の数学(少なくとも高校卒業レベルくらい)を理解する事が必要になります。2Dに関してはプログラムの面ではさほど難しい部分はありません。しかし、特に最近のゲームでは非常に多くの画像データが必要となる為、それらを用意する事は困難です。最も画像データの作成については、プログラムによって解決できることではないのでここでは扱いません。

プログラミング言語[編集]

ゲーム開発において、一般にゲームショップなどで流通している商業ゲーム作品において、現在よく利用されているプログラミング言語として、C言語C++Javaがあげられます。これらの言語の詳細については、対応する項を参照してください。ここでは主にC言語とC++を紹介します。

しかし、ネット上のフリーゲームでは、これ以外の言語が使われることも、よくあります。

例えば、JavaScriptが、webブラウザで動かせるゲームを作る際に、よく使われます。歴史的な経緯により、webブラウザ用のプロググラム言語では、画像の表示や音声や動画などの取り扱いが、なるべく初心者にも簡単なように設計されてきたので、ブラウザ上で動くゲームが発表されることも、しばしば、あります。なお、このようなwebブラウザ上で動くゲームのことを一般に「ブラウザゲーム」などと言います。


さて、ゲームプログラミングに限らず、プログラミングを行うには、プログラミング言語を習得する事が必要になります。情報科学や計算機科学などにプログラミングの理論もありますが、しかし、さいわいにも計算機科学などの専門知識について詳しく知らなくとも、プログラミングを行う事はある程度可能です。


入力機器からの入力[編集]

プラットフォームごとの入力の差異[編集]

入力機器は、利用者からの入力を計算機に与えるものを指します。代表的なものには、キーボードとマウスがあげられます。これらの機器に入力を行うと、計算機のCPUに割り込み(ハードウェア割り込み)を行い、計算機はこれらの機器から何らかの入力が行われた事を知ります。実際には機器から送られてくる信号は入力デバイスなどの種類によって異なる為、入力機器と計算機に共通の意味づけを持った信号を使う事で、計算機は実際に利用者によって行われた入力を知る事ができます。

キーボードやマウスからの入力はPC/AT互換機と呼ばれる種類のコンピュータでは統一されており、計算機はその仕様に従って信号を解釈します。しかし、西暦2000年以降の現代のコンピュータではこのような入力機器からの入力はOSによって扱われるのが一般的であり、ゲームプログラマーが入力信号の仕様を意識する必要は、あまりありません。これは入力機器からの信号を直接扱うよりも、人間にとってわかりやすい形にしてから入力を提示した方が、利用者にとって便利であることによります。

もし、これらの入力の仕様の詳細が必要ならOADG[1]の文献を参照してください。


なお、残念なことに、OSが入力機器からの入力を利用者に提示する方法は、OSごとに異なっています。 このため、ゲーム作品の発表において、どのOSで作品を発表するかを考える必要があります。

マイクロソフト社ウィンドウズのプログラム開発ソフト Visual Stuido でも、アップル社スマホOSのiOSやグーグル社スマホOSのAndroidなど他OS用の開発環境も提供していますが、最終的に、iOSやAndroidなどの目的のOSのインストールされたスマートフォンなど現物のハードウェアを用意して、プログラマは動作確認を取る必要があります。

ゲームの開発ではこれらの非互換性が厄介な問題になります。


技術的に専門的なことを言えば、(ウィンドウズでない)UNIX系のOSでは入力機器からの信号を利用するために、デバイスファイルを利用します(UNIX/Linux入門も参照)。一方、Windowsでは、デバイスファイルは存在せず、別の方法を使います。

これは、各OSごとに入力を扱う方法を変えなくてはいけないからです。実際にはこの問題はいくつかの方法で回避されます。1つの方法としてプラットフォームによらない入力を提供するJavaのような言語を利用する事が出来ます。もしくは、プラットフォームごとの入力の差異を吸収するようなライブラリを利用する事です。C言語を利用する場合にはこのようなライブラリはいくつか知られています。代表的な例ではGTK+QtSDLが挙げられます。これらはどれもWindows, Mac OS X, Linuxなどをサポートしており、これらに共通の入力を与えます。複数のプラットフォームで動作するようなプログラムを書きたい時にはこのようなライブラリを利用することを考えると良いでしょう。


なお、webブラウザ上で動くゲームの場合、ブラウザ側が入出力を処理してくれるので、OSにあまり依存しません。しかし、代わりにブラウザに依存します。

つまり,マイクロソフト社のwebブラウザ Internet Explorer 上で動いたJavaScriptプログラムは、必ずしも、FireFox や Google Chorome などの他ブラウザで動くとは、かぎりません。

このほか、ゲームエンジンのw:SDLのような、どのOS上でも動くツールもありますが、当然、ほかのゲームエンジン上ではSDL用プログラムは動きません。

結局、どのようなプラットフォーム上でゲームを発表するのかを、事前に考えておく必要があります。

入力の例[編集]

C言語の関数を利用した例[編集]

非常に簡単な入力は、C言語のgetchar関数などを利用して処理する事が出来ます。例えば、次の例は入力した文字をそのまま表示します。

注意
このプログラムは2.6系列のLinux上で実行できることを確認していますが、他のプラットフォームでは異なった振舞いをするかもしれません。
#include <stdio.h>
int main(){
    int c;
    c = getchar();
    printf ("あなたは%cと入力しました。\n", c);
    return 0;
}

このプログラムの実行結果は次の様になります。これはgetcharの時点で'g'と入力した場合です。

$./a.out
g
あなたはgと入力しました。

この方法は簡単ですがいくつか欠点があります。まず、getchar関数が実行された時点でこのプログラムは一旦停止して利用者が入力を行うのを待ちます。この性質は、例えばアクションゲームでは利用者の入力を待つためにゲームの動きが止まってしまう事に対応しており、厄介な問題です。また、上の出力からは分からないのですが、上の例ではgを入力した後にEnterキーを押して入力が終了したことを計算機に知らせています。これは例えばキャラクターを動かす時に常にEnterキーで確定する必要がある事に対応しています。通常のゲームではこのような事は起こらないので、この事も問題です。

UNIX系のシステムでは(Linuxはこれに含まれる)これらを回避するための方法としてtcsetattr関数を使った方法が知られています。この方法を説明する事も出来るのですが、この方法には1つ厄介な問題があります。tcsetattr関数でカノニカルモードを取り止め、c_cc配列のMINとTIMEを0に設定します。この時上の2つの問題は解決され、Enterキーを押すことなく入力が受け付けられる上、プログラムが利用者の入力を待つこともありません。しかし、上の方法では、ShiftキーやCtrlキーなどの特殊なキーが押されたことを読み取る事が出来ません。また、矢印キーを押した時の結果はおそらくシステムごとに異なった結果を与えます。結局この方法では数字キーと英字キー以外の入力についてはうまく扱う事が出来ません。一方計算機上のゲームでは矢印キーを利用する例も多いので、これは問題です(例えば、SuperTux、gnibblesなど)。

実際には上であげたゲームはそれぞれSDLGTK+というライブラリを使っているので、どのように入力を得ているのかを見るのは困難です。Linuxの場合について結果を述べると、上のライブラリはLinux上では、どちらもX Window Systemを利用して入力を得ています。Linux上のXはShiftキーなどの入力を扱うために、プログラム中でioctlコールを利用してデバイスファイル(ここではttyファイル)の性質を変更しています。実際にはLinux上でのtcsetattr関数もioctlコールの1つとして実装されているため、このioctl命令を使うことでtcsetattr関数を使った場合よりも多くの事ができます[2][3]

これらのライブラリはそれぞれのプラットフォーム上で全てのキーを利用する手立てを提供しています。大抵のゲームではこれらのライブラリのいずれかを利用しているので、ここではそれらを用いて入力を扱うことにします。個々のライブラリの入力の扱いかたについては対応するドキュメントを参照してください。

2Dの画面出力[編集]

画面出力の場合にも入力機器の場合と同じで、これらを操作する方法はOSごとに異なっています。先ほどあげたGTK+, Qt, SDLなどのライブラリはクロスプラットフォームの画面出力を提供しているため、これらを利用することで全てのプラットフォームで動くプログラムを作ることができます。

具体的な例:ブロックくずし[編集]

ここまでで2次元の描画と入力機器の扱いかたについて述べてきました。ここではこれらを使った具体的な例として、ブロックくずしをあげます。

ブロック崩しは有名なゲームですが、一応内容について説明します。このゲームでは、プレイヤーはバットと呼ばれる板状のものを操作します。バットは画面下方を左右に移動することができます。プレイヤーは画面上方に向けてバットからボールを発射します。ボールは画面の端で跳ね返り、やがて画面下方に戻ってきます。この時ボールが画面下方に向けてバットよりも下に行かないように、プレイヤーはバットでボールを打ち返します。画面上方にはボールを当てることで壊れるブロックが複数配置されており、プレイヤーは破壊できるブロックを全て破壊することを目的とします。

[編集]

実際にこのことをプログラムとして書こうとするとおおよそ1,000行程度のCプログラムになるようです。ここでは既に完成している例として、gnome-breakoutを利用し、2Dゲームのプログラミングとして代表的な部分について解説していきます。ここでは特に、gnome-breakout-0.5.3を利用しました。ここからの解説ではこれらのコードを参照することが多いので、ゲームのソースを入手しておくとよいでしょう。一般的なソースの読み方については、OSS開発ツールなどを参照してください。

前提として、gnome-breakoutではゲームのライブラリとしてGNOMEを利用しています。GTK+はGnomeのGUIツールキットなので多くの描画はGTK+の関数を利用してなされます。GTK+の関数についてはGnomeのサイトOSS開発ツール GUIツールキットを参照してください。

メインループ[編集]

このゲームはボールやバットの操作を行うため、定期的に画面の書き換えを行います。画面の書き換えは繰り返し実行されますが、この繰り返される部分をメインループと呼ぶ事があります[1]。メインループは一部のパズルゲームを除くあらゆるゲームで用いられる技術です。基本的にはメインループは、

while(state == RUNNING){
...;
}

のように無限ループを使って書かれます。RUNNINGは大抵の場合ゲームの状況を表すenum(列挙子)で、

typedef enum {
RUNNING, STOPPED
} state;

などと定義されます。今回のゲームに関しては、メインループはsrc/game.c内のiterate_gameで定義されています。ただし、ここで使っているソースは見やすいように編集してあります。

while (game->state == STATE_RUNNING) {
  // 中略
  iterate_bat(game);
  iterate_balls(game);
  iterate_powerups(game);
  iterate_blocks(game);
  // 中略
  gui_update_game(game);
  // 中略
  gettimeofday(&end_tv, &tz);
  diff_t = ((start_tv.tv_sec - end_tv.tv_sec) * USEC_PER_SEC)
       + (start_tv.tv_usec - end_tv.tv_usec)
       + USEC_PER_FRAME;
  if (diff_t > 0)
    usleep(diff_t);
}

STATE_RUNNINGは、src/breakout.hで定義されたenumです。この例はいくつかの重要な技法を含んでいるので、ここで紹介します。

物体ごとの処理[編集]

上の例ではwhileループ(メインループ)内でiterate_xxxで書かれるいくつかの関数が実行されていますが、この関数名は自己を説明しています。これらの関数はそれぞれbatやballなど対応するデータの移動や衝突判定などを行います。実はiterate_blocksではブロックが破壊される際のアニメーションを行っているのですが、ここでは詳しく述べません。いずれにせよメインループ内で実行される関数はゲーム内で対応するキャラクターなどを持っており、それらに操作をするための関数である場合がほとんどです。オブジェクト指向を使ってプログラムを書くなら、上の例は、

bat->iterate(game);
balls->iterate(game);
...

などとなるでしょう。この場合おそらくbat, ballなどのクラスはiterateをvirtual(C++の時)なメソッドとして持つクラスを継承します。

画面の書き換え[編集]

上の例は、gui_update_gameという関数が続いています。この関数はいくつかの処理をしますが、ここで注目する距離は、関数の最後の処理で

  // 中略
  gnome_canvas_update_now(gui->canvas);
  return;
}

の部分です。gnome_canvas_update_nowはgnomeが提供する関数で、ゲームの描画を行う領域全体を書き換えます。上のiterate_xxx内でボールの移動などの処理は既に実行されているのですが、ここで変更したのはあくまでボールの位置を表す数値であり、それだけで画面の表示は変更されません。そのため、画面の変更をこの部分で一律に行っていることを示しています。

実際には例えば、iterate_ballの中で、

old_pos = ball->pos;
move_ball(ball);
update_rect(old_pos, ball_width, ball_height);
update_rect(ball->pos, ball_width, ball_height);

のように画面の書き換えを行うこともできます。ここで、update_rectは、

update_rect(pos, width, height);

で表され、posで表される場所から、 (width, height) で表される長方形で囲われる領域の描画を行う関数としました。実際には画面の描画は計算機に取って手間のかかる作業で、扱う領域が広いほど画面の再描画には多くの時間がかかります。そのため、上の例では2回の書き換えを行う必要があることを考えても、上の書き換えの方が高速で行われるでしょう。ただし、扱う画面が小さいなら書き換えは十分速く行われると期待されるので、どのように書き換えを行うかは状況によります。

fpsの管理[編集]

fpsはframe per secondの略で、1秒間に何コマ動くかという意味で、ゲームの時間制御の要となる概念です。計算機だけでなく、テレビもそうですが、これらの機器は1秒間に複数回の画面の書き換えを行います。例えば、日本のテレビではおおよそ30回/秒の書き換えを行います。ただし、テレビの書き換えはインターレース方式で行われるので、実質的に60回/秒の書き換えを行うことになります。詳しくはテレビを参照してください。ここで、画面の書き換えは例えば1/60秒で行われるとすると、これは非常に短い時間に見えます。しかし、例えば現在の計算機のクロック周波数が1GHZ程度とすると、計算機が1度の計算を行うのにかかる時間は、秒です。これは画面の書き換えを行うのに必要な時間よりはるかに速いため、何も工夫をしなければ、計算機は画面の書き換えより速く物体の動作などの処理をしてしまいます。これは例えば、描画が行われないままボールが多くの距離を移動するなどの事につながるため、何らかの対策を講ずる必要があります。

ここで用いる方法は、メインループ1回分の処理を終えた後、次の画面の書き換えが行われるまで、プログラムの実行を止めておくという方法です。この方法には、プログラムの実行を一時的に止めるusleep関数を使います。

usleep(int microseconds);

usleep関数はmicrosecondsで表されるマイクロ秒だけ、プログラムの実行を止めます。実際にどの程度の間実行を止めるかは、プログラムが処理にかかった時間と、その結果を画面に送るのにかかった時間の和がどの程度だったかによります。上の例ではdiff_tが実際に実行を止める時間に対応します。gettimeofdayは、現在の時刻をマイクロ秒単位で取得する関数です。最後のgettimeofdayではend_tvの名の通り、処理が終わった時刻を取得しています。実際には上の例でiterate_xxxが行われる前に、gettimeofdayでstart_tvを取得している場面があり、これら2つを用いて処理にかかった時刻を得ることができます。画面の書き換えを1秒に実際に行う回数をfpsと呼びます。ここでは書かれていないのですが、このゲームは50fpsで動いているので、各々の書き換えの間に、20ミリ秒=20000マイクロ秒が経過します。処理にかかった時間をnマイクロ秒とすると、

diff_t = 20000 - n

で実行を止める時間が計算されます。上の例では何故か -n + 20000となっていますが意味は同じです。

ここからはballやbatなどそれぞれの処理を見ていきます。batの処理では、キーボードやマウスからの入力を受け取る方法について説明します。

バットの処理[編集]

このゲームではキーボードかマウスのどちらかを使ってバットの操作をします。ここではまず、キーボードやマウスの入力を読み取る方法について述べます。

バットの動作の処理はおおよそ次のようになっています。

if (key_right_is_pressed){
  if (bat_x + bat_width + bat_move < GAME_WIDTH)
    bat_x += bat_move;
}
else if (key_left_is_pressed){
  if (bat_x - bat_move > 0)
    bat_x -= bat_move;
}

ここで、それぞれの変数は

bat_x: バットのx座標 (0 < bat_x < GAME_WIDTH)
GAME_WIDTH: 扱うウィンドウの幅
bat_move: 1度の処理でバットが動く距離

を表します。ここで、key_right_is_pressedとkey_left_is_pressedはキーの状態を表す変数です。ここでキーボードを扱う関数はこれらの変数の値を操作する必要があります。これらの変数はsrc/game.c内のkey_right(left)_pressedとkey_right(left)_releasedの4つで変更されています。それぞれの関数はおおよそ

void key_right_pressed(Game *game){
  right_ispressed = TRUE;
}

のように対応する変数の中身を書き換えています。

注意
実際のコードでは、上でいうbat_moveの値に正負の値を取らせてバットの移動の処理を簡略化しています。また、右キーと左キーの両方が押された場合の処理も行っています。ここでは、簡単のためispressedの変数だけを扱います。
C++と違い、C言語ではboolean型が存在しないため、TRUEやFALSEは普通のC言語プログラムでは扱われません。ここでのTRUEは、GLibで与えられているマクロであり、値は
FALSE
(0)
TRUE
(!FALSE)
です。

上で紹介した4つの関数は、GTK+の関数にコールバック関数として渡されます。ただし、GTK+はkey_press_eventとkey_release_eventの2つの操作を与え、押されたキーごとに操作を与えるわけではありません。そのため、2つのイベントに対して1つずつのコールバックを与え、コールバック関数内でどのキーが押されたかを判断する必要があります。実際にコールバックとして扱われる関数は,src/gui-callbacks.c内のcb_keyupとcb_keydownです。これらはほとんど同じ処理を行うので、cb_keydownだけを紹介します。

gint cb_keydown (GtkWidget *widget, GdkEventKey *event, gpointer data) {
// 中略
if(event->keyval == game->flags->left_key) {
  key_left_pressed(game);
} else if(event->keyval == game->flags->right_key) {
  key_right_pressed(game);
}
// 中略

まずGTK+はキーボードからの入力をイベントとして扱います。イベントは機器からの入力などを一般化したもので、その多くはGTK+が依存しているライブラリから提供されます。例えばlinux上ではキーボードからの入力に関するイベントは対応するX Window Systemの処理から与えられ、Mac OS X上ではquartzの処理から与えられます。これらのイベントの詳細はGTK+の立場から見ることはできないので、詳細を見るには、対応するライブラリの説明を参照してください(Xプログラミングも参照)。GTK+からはあるウィジェットにフォーカスがある時に、何らかのキーが押されたときには、そのウィジェットに対して"key_press_event"が与えられます。このとき、このイベントに対応するコールバック関数には、イベントが発行されたウィジェットに加えて、GdkEventKey型の引数が与えられます。この型は押されたキーの種類などの情報を含んでいる構造体で、この引数を使って押されたキーを判別することができます。

上のcb_keydownも"key_press_event"のコールバックとして与えられる関数で、その引数は順に

 gint cb_keydown (GtkWidet *, GdkEventKey *, void *)

となっています。ここで最初のGtkWidget *と、GdkEventKey *は上で述べたイベントが与えられたウィジェットと押されたキーの詳細を与える情報に対応します。最後のvoid * = gpointerは他に与えたい引数がある時に使うことができる変数ですが、ここではGame型のgameという引数を与えています。これはkey_left(right)_pressedの引数で、バットが動く方向などを記録しておく変数です。

ここまででキーが押されたときに実行させる関数を定義しました。次に実際にこの関数をキーが押された時の関数として登録する処理を見ます。この処理はsrc/gui.cのgui_init内で行われ、対応する部分は

// 中略
g_signal_connect(GTK_OBJECT (gui->app), "key_press_event",
       GTK_SIGNAL_FUNC (cb_keydown), gui);
g_signal_connect(GTK_OBJECT (gui->app), "key_release_event",
       GTK_SIGNAL_FUNC (cb_keyup), gui);
// 中略

です。g_signal_connectはGTK+で"イベント"に対するコールバックを登録する一般的な関数です。この関数の引数は、

g_signal_connect(GtkWidget *widget, char *event_name, GCallback cb_func, gpointer data);

で与えられ、それぞれイベントを扱わせたいウィジェット、ウィジェットが持つイベントのうちで対応するイベントを選ぶための文字列、コールバック関数、 コールバック関数に与えるデータとなっています。

注意
最初の引数は実際にはGtkWidget *である必要は無く、GObjectを継承したクラスのポインタならなんでも可能です。

ここで、GTK+のkey_press_eventとkey_release_eventが現れるタイミングについて説明します。key_press_eventとkey_release_eventはそれぞれキーが押され始めた瞬間と、キーが放された瞬間に与えられます。あるキーを押しつづけた場合key_press_eventが現れ続けるのではないため、キーが押しつづけられているのを知るためには、先ほど述べたkey_is_pressedなどの変数の値を設定しておき、key_release_eventが現れたときにこの値を書き換える必要があります。

ボールの動作[編集]

ボールはバットと違い、プレイヤーによる操作を受けないので、その意味ではボールの動作は単純です。しかし、このゲームではボールとバット、ボールとブロック等の間の衝突の判定を全てiterate_balls内で行っているため、iterate_ballsは割合複雑です。

iterate_ballsは、ball.cで定義されています。この処理はおおよそ

void iterate_balls(){
move_ball();
ball_block_collision();
ball_bat_collision();
ball_wall_collision()
}

となっています。ここではこれらの処理を順に見て行きます。一般に、物体を動作させその後に他の物体との衝突判定を行うという一連の手順はシューティングゲームやアクションゲームでは必ず現れる手順です。

ボールの移動[編集]

このゲームではボールの特性を表す構造体として、Ball (src/breakout.h) が定義されており、その内容は、およそ

typedef struct {
  double x1, y1, x2, y2, speed, direction
} Ball;

で与えられます。

注意
実際にはこれ以外にもいくつかの要素が定義されていますが、ここではこれらだけを使います。

x1, y1, x2, y2はそれぞれボールが含まれる長方形の左下と右上の座標を指します。これはボールに限らず、物体の絵を保存する際には一般的に、長方形の領域が用いられることによります。speedとdirectionはそれぞれボールが動く速度とその方向を表します。x方向の速度をv_x, y方向の速度をv_yと表すなら、それぞれ

v_x = speed * cos(direction);
v_y = speed * sin(direction);

が成り立ちます。ここまでの結果を用いるとmove_ballの動作はほぼあきらかで、その操作はおよそ

void move_ball(Ball *ball){
 double v_x, v_y;
 v_x = speed * cos(direction);
 v_y = speed * sin(direction);
 ball->x1 += v_x;
 ball->x2 += v_x;
 ball->y1 += v_x;
 ball->y2 += v_x;
}

となります。

一般的な衝突判定[編集]

ほとんどの場合物体と物体の衝突はそれぞれの物体を囲む長方形が重なることを用いて検出されます。このとき、物体を囲む長方形はできる限り物体の大きさと等しい大きさにしておくことが重要です。ここでは、ともにx1,y1,x2,y2を持つaとbという2つの物体があるとき、それらの衝突を判定する方法についてまとめます。実際に、このゲームでもボールとバットの衝突判定に、この手順が用いられています。

注意
ボールとブロックの衝突判定はブロックが動かないことを利用した簡単化された手順を用いています。

まずx座標とy座標は互いに関係なく動かせるので、x座標だけについて考えます。この場合問題は2つの2次不等式で表される領域xが存在するかどうかを確かめる問題に帰着します。2次不等式については高等学校数学Iを参照して下さい。

まず、a、bの各値が並ぶ順番が何通りあるかを計算します。a、bはそれぞれ2つのx座標を持っていますが、aの中での大小関係、bの中での大小関係は定まっており、常にx1 < x2です。そのため、例えばababのような並びを書けば、その順序は自動的にa.x1 < b.x1 < a.x2 < b.x2と定まります。この事を考えれば、a、bの値は単にa, a, b, bの4つの文字を並びかえる場合の数と等しい事が分かります。場合の数については高等学校数学Aを参照して下さい。

実際に取り得る値を書き並べると、

aabb, abab, abba, baab, baba, bbaa

の6つが挙げられ、全部で6つである事が分かります。この結果は計算法としては、互いに区別できない要素を含んだ順列の計算に対応しており、その場合には

に対応します。詳しくは高等学校数学Aを参照して下さい。

実際にはこれらの並びのうち4つが衝突が起こっている場合に対応します。具体的には4つの図が対応します。これらを一律に検出するには、例えば次のような試験が用いられます。

(b.x1 < a.x1 && a.x1< b.x2)||(b.x1 < a.x2 && a.x2 < b.x2)||(a.x1 < b.x1 && b.x2 < a.x2)

src/collision.cのcheck_collisionでは、これと同じ手順が書かれています。

上の操作をx座標とy座標について行えば、衝突の判定は完了します。

ボールと壁、ブロックの衝突判定[編集]

ボールや他の物体(ここではブロック)との衝突が確認されたら、次にボールがどちらの方向からぶつかったのかを確認します。この確認にはsrc/collision.cのfind_hit_side関数が使われます。この関数では、まずボールの速度と方向を使って、ボールが移動する前の位置まで戻します。ここでボールの位置とブロックの位置を調べる事で、ボールがどちらの方向からぶつかってきたのかを知る事が出来ます。

実際に物体が衝突したときどちらの方向へ反射するかはsrc/collision.cのrecalculate_ball_trajectoryで判定されます。仮にボールの速度をvxとvyで記録させるなら、右、左からの衝突では

vx *= -1;

上、下からの衝突では

vy *= -1;

で結果が得られることに注意が必要です。今回は速度と方向で記録している為、各方向毎の場合分けが必要です。

ブロック以外でゲームで利用するウィンドウの端にある壁と衝突する時にも上の関数は利用されます。壁との衝突判定は一般的な衝突判定より単純で

  1. ball.x<0 ではボールが左の壁に衝突した
  2. ball.x>GAME_WIDTH では、ボールが右の壁に衝突した

などとなります。

注意
実際のコード中では、実際には壁と壁の継ぎ目に衝突した場合の判定も行っています。

ボールとバットの衝突判定[編集]

ボールとバットの衝突は、src/collision.c内のball_bat_collisionで扱われます。この場合、ボールが反射する方向はバットとの位置関係で変化するようになっており、(おそらく)上級者はこれを用いてボールを狙った方向に送る事が出来ます。ここでは詳しくは扱いません。

最後に[編集]

ここまでで簡単なブロック崩しゲームに必要な手法について述べました。実際にはこのゲームではパワーアップの概念やレベル毎のブロックの配置の違いなど、説明していないいくつかの要素があります。それらも調べてみるとよいでしょう。

テトリス:gnometris[編集]

テトリスは代表的な落ち物ゲームで、非常に有名なゲームです。いくつかのフリーな実装があるのですが、ここではgnome-gamesの実装であるgnometrisを利用します。

アクション:xjump[編集]

xjumpROYALPANDA氏によって書かれた簡単なアクションゲームです。xjumpはXlibを用いて書かれており、Windows上でコンパイルするのはやや難しいのですが、同じ種類のゲームでsdljumpというゲームもあるので、ビルドが困難と感じたらそちらを用いると良いでしょう。

ここではアクションゲームを作るうえで基本的になる重力やジャンプの扱いについて解説します。


3Dグラフィック[編集]

RPG[編集]

未分類[編集]

Visual C++ でのプログラム文による文字画像の出力[編集]

上述の報告と考察により、Visual Studio付属のフォームデザイナ(VSの用意するGUI自動作成ソフト)によるGUIオブジェクト作成では、RPG用には使いづらいことが分かった。

では、どうすれば、Visual C++で、なるべくフォームデザイナを使わずに、文字や映像を出力できるようになるのか?

方法は、下記のとおり。

1
まず、最低限、ウィンドウフォームのGUIを、フォームデザイナなどで作成する。(この方法は、一般の市販の入門書に書いてある。例えば日経BP社『アプリを作ろう! Visual C++ 入門』など。) なんと、2017年版の Visual C++ ではウィンドウすら自動作成してくれない。ウィンドウが無いと、文字画像の描き込み場所そのものが無いので、さすがに何も表示できない。
2
最低限、「パネル」というGUIオブジェクトを、フォームデザイナで作成する。さきほどの手順1で作成したウィンドウ単体では、ウィンドウは画像の入出力の機能を持っておらず、よってウィンドウ内部に何も画像表示できない。なので、私たちは作業として、画像の入出力の機能をもった「パネル」オブジェクトを、ウィンドウ内部に貼り付ける必要がある。(※ 下記の画像出力コードをウィンドウ単体用のソースコードの関数ブロックに入れても、画像出力しない事を確認ずみ。)
下記のコードでは座標 (150, 150 )の位置に文字画像を出力するので、貼り付ける際のパネル幅はそれよりも大きくすること。初期表示ウィンドウの大きさギリギリにパネルを作成しておけば、問題ない。
3
ここまで準備すると、Visual Stuido が、下記のようなコードを自動生成するので、
private: System::Void panel1_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e) {

	}


その関数ブロック( { と } の内側)に、下記のようにコードを記述する。(※ 参考文献: マイクロソフト社開発者コミュニティwebサイト(Developer NetWorks)ドキュメント(操作マニュアル等の文書より引用) 『Graphics::DrawString メソッド (String^, Font^, Brush^, PointF)』 [4] 日本人の初心者ユーザ用にコメント文を追加し、コード中のコメントをいくつか意訳。)


private: System::Void panel1_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e) {

		// 文字列を描くためのオブジェクト作成。
		String^ drawString = "サンプル テキスト";

		// フォント(MSゴシックやArialなど)とブラシ(線太さや色など指定)のオブジェクト作成。
		System::Drawing::Font^ drawFont = gcnew System::Drawing::Font("Arial", 16);
		SolidBrush^ drawBrush = gcnew SolidBrush(Color::Black);

		// 出力位置の座標オブジェクトを作成。座標150,150に文字画像を貼り付ける予定。
		PointF drawPoint = PointF(150.0F, 150.0F); // Fは浮動小数点

		// 実際に文字列を出力する命令。
		e->Graphics->DrawString(drawString, drawFont, drawBrush, drawPoint);
	}

そして、コンパイルして実行すると、パネル内の座標 (150, 150 )の 場所に「サンプル テキスト」の文字画像を表示することができる。あなたはプログラム文による文字画像表示に成功しました。


キーボードからの入力を受け取る[編集]

(※ この節の内容は間違っています。しかし、修正後の記事が未完成のため、当面のあいだ、掲載しておきます。正しい内容に訂正してくださる人を募集しています。)


ゲームでは、キーボードからの入力を受け取る必要があります。たとえば「矢印キーの右キーを押したら、キャラが右に動く」などの仕様が必要です。

このような機能を実現するには、

GetAsyncKeyState() という命令を使います。これは、押されたキーの状態を調べ、カッコ内で指定されたキーが押されていれば、真値(True)を返す関数です。

例えば、GetAsyncKeyState('Z') なら、Zキーが押されているときにTrueになります。

If文などの中にこの命令文を組み込めば、Zキーを押したときに反応します。RPGなら、たとえば、マップ上でZキーを押したときにメニュー画面を開くようにしたい場合とか、あるでしょう。そういう場合に使えます。

コード例
private: System::Void panel1_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e) {

		while (true) {
			if (GetAsyncKeyState('Z')) {
				MessageBox::Show("Zが押されました。");
			}
		}

	}

このように、While文の中でまず無限ループを実現し、その中で、上記コードのように書けば、Zキーを押したときにメッセージボックスが開きます。

ただし、標準設定ではライブラリ等が不足しているため、このコードをすぐに追加しても、まだコンパイルできずにエラーになります。

コンパイルできるようにするため、まず、ファイルの冒頭に、

#include <windows.h>

を追加する必要があります。


追加した結果、たとえば、

#pragma once

#include <windows.h>

namespace wikiGame {

	using namespace System;
	using namespace System::ComponentModel;
	using namespace System::Collections;
	using namespace System::Windows::Forms;
	using namespace System::Data;
	using namespace System::Drawing;

のように、冒頭のコードはなります。

なお、上記のwikiGameの部分には、あなたのプロジェクト名が入ります。あなたがファイル作成時につけたプロジェクト名ですので、それに合わせて、自動的に作成されます。

しかし、このインクルード文の追加だけでは、まだコンパイルエラーになります。


何故なら、まだ、Visual Stuido のコンパイラに、ライブラリ User32.lib を使うという指示が入ってないからです。

GetAsyncKeyState() 命令など、いくつかの命令が、ライブラリ User32.lib を使用しています。(どの命令がどのライブラリを使用してるかは、マイクロソフトの開発者サイト(MSDNなど)で調べられますので、暗記の必要はないです。)

このライブラリは、Windowsに入ってるライブラリですが、しかし、マイクロソフト社は、Visual Stuido のコンパイラのリンカからは User32.lib を外しています。

マイクロソフト社は、一般の多数のプログラマはコード中で普段は使わないライブラリは、リンカなどから外していますので、もしアナタがそれらのライブラリを使用したい場合には、リンカに、そのライブラリの使用指示を追加します。

ライブラリの追加作業は、画面右(または左)にあるソリューションエクスプローラーのプロジェクトのプロパティから行います。

ソリューションエクスプローラーの上から2行目にプロジェクトのファイルが置かれてると思うので、それを右クリックして出てくるメニュー欄のなかに、最下段に「プロパティ」があると思うので、それをクリックしてください。

表示されるウィンドウ左にある項目から、構成プロパティ → リンカ → コマンドライン と開いていって「追加のオプション」項目の下にあるテキストボックスに「 User32.lib 」とカギカッコ内を入力します。カギカッコは含めないでください。

これでコンパイルが通るようになります。


リンカへのライブラリの追加作業の知識は、当ページで紹介した以外の他の方法を使う場合にも必要となる可能性の高い知識ですので、ぜひ習得しておいてください。


乱数[編集]

ゲームを作ってると、サイコロのような乱数の機能も必要になる場合が多い。

しかし、無印C言語で用意されている、標準的な乱数関数 rand() が機能不足であり、いろいろと不便である。

rand 関数では、ランダムに 「34521」(例) とかの数が生成されるだけなので、「1以上から6以下の整数値が欲しい」などの指定すら、直接には出来ない。

rand関数で生成された乱数を、割り算(÷6)で割って、あまりは0以上5以下なので、サイコロにするには、余りに +1 すれば、rand関数でサイコロを作れる。

このように、rand関数の使いかたは間接的であり、いまいち使いづらい。

そこでIT業界では対策として、より直接的に乱数の範囲指定のできる等の新機能もある、さまざまな乱数関数が、のちの各種のプログラム言語で追加された。Visual C++ などに採用されている Random関数グループには、名前がrand関数と似ているが、しかし、この新しいRandom関数グループの中には「1以上から7未満の整数値がランダムに欲しい」のような指定が直接的に出来る「Next」命令がある。

Random関数のこの直接指定の機能により、バグの混入率が減る。


しかし、Visual C++ や Visual C# では、これらの命令を使うための予備的な宣言が必要であり、そっちの宣言の手間で、やや不便である。


でも、現実的に、Windows向けのゲームプログラムでは Visual C++ や Visual C# などを利用せざるを得ない。

Visual C++ にある高機能な乱数命令グループ Random や、そのRandomグループに属するNext命令などを使いたい場合、サイコロのプログラムをつくるだけですら、下記のような数行のコードが必要になる。

	Random^ saikoro1; // Random^ でRandom関数の使用を宣言しなければならない。
	saikoro1 = gcnew Random(); // ここでの gcnew は命令をつくるための演算子
	int detame; // 出た目
	detame = saikoro1->Next(1, 7); // Next 命令で「〇〇以上△△未満」の乱数を指定できる。「->」はアロー演算子というもの。
	MessageBox::Show("目" + detame.ToString() + "が出ました");


画像の ちらつき[編集]

画像がひんぱんに変化するアプリでは、画面が、ちらつく事がある。画面のちらつきは、ゲームのように、頻繁に画像が変わるアプリでは、かなり利便性を損なう。

キャラクターが1歩移動するだけで、画面全体がチラついたりする場合もあり、かなり、プレイヤーの不満になる。

これは、ダブルバッファという技術で、解決できる。(日本のゲームプログラマーに「裏画面」と言われる技術を使えばいい。)


なお、Direct Xの用語では「スワップ チェーン」という。しかし、web検索で「スワップチェーン」としらべても、具体的なコードのある解説がほとんど出てこないし、何よりDirect Xでしか通用しない技術になってしまうので、あまり「スワップチェーン」には深入りしなくていい。

名目的には、.Net Framework開発環境のC++でもC#でもダブルバッファの機能はある事になっている。いくつかのGUIオブジェクトのプロパティで、ダブルバッファの設定項目がある。しかし、.Net Frameworkの不具合か設計ミスか何か知らないが、これら.Net Framework開発環境で、マイクロソフトの公式ヘルプどおりの方法(のはず)でダブルバッファをオンにしても、うまく動作しない。(仮に間違った操作をしてたとしても、.Net Framework開発環境の公式ヘルプをきちんと整備してきてないマイクロソフト社が悪い。)

.Net Framework が頼りにならないので、残念ながら、Win32 API を使う必要がある。 Win32 APIは、古くからある開発環境のため、ネット上に情報が充実している。


フォームデザイナによる開発は、頼りにならない。

フォームデザイナで、ユーザーコントロールなどを使って、そこから画像書き込みをすれば、おそらくユーザーコントロールが裏画面として扱われるためか、ちらつき自体は解決できる場合があるが、しかしユーザーコントロールの使用上、グローバル変数の共有が困難だったり、アプリ内での終了コマンドが無いなどの欠点がある。特に、グローバル変数の共有が困難な点は、ゲーム性およびゲーム開発効率を致命的に損ない、使い物にならない。

また、困ったことに、GUIフォームの「パネル」オブジェクトに、ダブルバッファの機能が無い。(もしくは不十分。)「パネル」なら、グローバル変数の共有は比較的に簡易だが、今度はダブルバッファの機能が不十分なのである。

一方、GUIフォームの「ピクチャーボックス」オブジェクトでは、関数ブロック内に書ける処理命令は、画像をクリックしたときの処理しか書けない。我々は、クリックしてない時にも画像(静止画だけでなく時には動画)を表示したいし、マウスクリック以外のキーボード操作にも画面を反応させたいのであるから、「ピクチャーボックス」は機能不足である。

結局、どのGUIオブジェクトでも、ゲームのような動画を描画できない。

だから、Win32 API を使う必要がある。フォームデザイナは使えない。


ゲーム用の書類の書き方[編集]

説明書や仕様書(しようしょ)などの書き方については、『ゲームプログラミング/書類』で解説する。



ゲーム用の独自プログラミング言語[編集]

日本でネット上で流通しているゲーム開発ソフトには、ゲーム開発用に、独自のプログラミング言語を持っている場合があります。

このような機能の実現方法は、C言語で、ファイル入出力の関数を使い、テキストファイルの文字列を読み取って、文字ごとに条件分岐を設定する機能があるので、そのような機能を使って、独自の言語を作成することで、独自のプログラミング言語を作成することで可能なのです。

(たとえば、文字列 IF を読みとたっら、あらかじめC言語プログラムで作成しておいた if文で書かれた関数を呼び出せば良い。)


なお、原理的には、 Java Script や Python などでも、文字読み取りの機能を使って、独自のプログラミング言語を作ることができます。


というか、そもそも、C言語以外のプログラム言語の、プログラム言語そのものの作成方法は、このようにして作成しています。

つまり、そもそも、C言語以外のコンパイラやインタプリタというのは、このような方法で作られています。(なお、C言語そのもののコンパイラを作りたいなら、OS作成時にアセンブラ言語を使って、文字読み取りの関数を作って、C言語を作ればいい。)


さて、ときどき、ゲーム製作ソフトなどで、独自のプログラミング言語を使用している場合があります。たいてい、コンパイル作業を必要としないので、そのようなソフトは、インタプリタ方式でしょう。

そもそもWindowsの場合、実行ファイルに変換するには、Visual Studio というマイクロソフト社の配布しているソフトがないと、通常は、コードの実行ファイルへの変換は不可能です。

なので、必然的に、WVisual Studio が開発環境を提供していない独自言語は、(その言語開発者が、よほど技術力が高度でないかぎり、)たいてい、その独自言語の実行方式はインタプリタ方式となります。


脚注[編集]

  1. ^ メインループを繰り返し文とも呼ぶ。

関連項目[編集]