P/ECE用初心者向けプログラミングのコーナー
ここでは、「Game Creators 2002 Summer」号に掲載された記事に
加筆、修正を行ない、紹介します。
それでは、行ってみましょう!
―――――――――――
「P/ECE」でプログラミング
昨年11月30日の発売以来たくさんのゲームやツールが公開されているP/ECE、とっても楽しいですね。
付属のソフトやユーザーさんの作ったゲームで遊ぶのも良いのですが、
せっかく開発ツールも付属している事ですし、思い切ってP/ECEでプログラミングに挑戦してみるのはいかがでしょうか?
プログラミングはけっして簡単ではありませんが、
パズルゲームで遊ぶような感覚で、気軽に楽しんでみましょう。
もしあなたが既にプログラミングに挫折してしまっていたとしてもあきらめないで、
もう一度いっしょにがんばってみませんか?
今回は簡単なシューティングゲームを作成し、その手順やプログラムの解説を行ないます。
誌面の都合上細かい解説は出来ませんが、
付属のAPIリファレンスとチュートリアルもあわせて参考にして、ゆっくりと読み進めてくださいね。
「アステロイドゲーム」を作ろう
これから作成するゲームは、宇宙船を操作し、
飛来する隕石を上手に避けながらレーザー砲で撃ち落すという簡単なシューティングゲームです。
最初に、プログラミング作業を行なう為に、
P/ECEの「app」フォルダ(チュートリアル参照)の中に作業用フォルダ「asteroid」を新規作成しましょう。
通常に開発環境をインストール済みでしたら「c:\usr\piece\app\asteroid」の状態になります。
画像データを用意しよう
図1を参考に、適当なペイントソフトを使って画像データを作成しましょう。
ここで気をつけなければならないのは、パレットの状態です。
パレットの変更の仕方がわからないのであれば、チュートリアルで使用されている「A.bmp」を作業用フォルダにコピーして、
それを元に、キャンバスサイズを縦16*横64ピクセルのサイズ変更して描きましょう。
図1の外周の黒い部分はマスクなので、黄緑色に変更して作業してください。
描き終わりましたらファイル名を「asgr_01」として保存し、
BMPコンバータを使用してP/ECE上で使用する為のデータ形式に変換します。
「asgr_01.c」というファイルが出来あがれば成功です。
BMPコンバータのくわしい使い方はチュートリアルをご覧ください。
―――――――――――
図1
―――――――――――
プログラムファイルを用意しよう
次に作業用フォルダの中に「asteroid.c」という名前のファイルを作成しましょう。
まず適当な名前でテキストファイルを作成し、「名前を付けて保存」を選び、
「ファイルの種類」を「すべてのファイル」に変更してからファイル名を「asteroid.c」と入力して保存してください。
プログラムファイルは適当なテキストエディタで開きます。
何も持っていなければ、メモ帳でも大丈夫です。
『メモ帳でプログラミング!?』と思われるかも知れませんが、これで十分なのです。
いよいよプログラミング
早速ですが、プログラムファイルにプログラムリストの内容を書きうつしてみましょう(プログラムリストその1)。
ただし、/*〜*/の箇所は、プログラムを理解しやすくする為の説明文ですので入力する必要はありません。
英数字や記号、空白などは半角文字で入力してください。
まとまった空白を入力するときはTabキーを、改行するときはEnterキーを使用します。
同じような箇所はコピー&貼り付けを上手に利用すると、入力時間が短くなります。
ある程度まで入力したら、万が一の事態のために定期的にプログラムファイルをセーブするようにしましょう。
―――――――――――
―――――――――――――――――――――――――――――――――――――
プログラムリストその1
―――――――――――――――――――――――――――――――――――――
/* アステロイドゲーム サンプル */
/* ↓↓初期設定↓↓ */
/* 基本的な標準関数を使う為のファイルをインクルード */
#include <stdio.h>
#include <piece.h>
/* グローバル変数の宣言、他 */
/* 画面描画の為の変数 */
unsigned char vbuff[128*88];
static unsigned char draw;
/* 画像データを参照し、情報を保持 */
extern unsigned char ASGR_01[];
DRAW_OBJECT obj;
PIECE_BMP pbmp1;
/* ゲームモードを判断する為の変数 */
static unsigned char gMode;
/* 自機の表示座標用変数 */
static int myshipx;
/* 自弾の表示座標用変数(配列変数)*/
static int myshootx[3],myshooty[3];
/* 自弾発射の入力判定用変数 */
static unsigned char myshootc;
/* 隕石の表示座標変数(配列変数)*/
static int astx[10],asty[10],astc[10];
/* 隕石の同時表示数を決める変数 */
static char astmax;
/* 得点用の変数 */
static long myscore;
/* 星(背景)表示用の変数(配列変数)*/
static unsigned char hoshix[20],hoshiy[20];
/* ゲームスタート時の文字表示カウント用の変数 */
static unsigned int mCount;
/* これから作って使う予定の関数を宣言 */
void moveHoshi( void );
void moveMain( void );
/* ↑↑初期設定終わり↑↑ */
―――――――――――――――――――――――――――――――――――――
―――――――――――
プログラムの内容を理解しよう
さて、プログラムを入力しながらその内容を少しづつ理解していきましょう。
ほとんどの箇所に簡単な説明文がついていますので参考にしてくださいね。
まずは「/* 初期設定 */」からです。
一番最初に「/* 基本的な標準関数を使う為のファイルをインクルード */」という箇所がありますが、
これはプログラミングをする際の「お約束」です。
「/* 画面描画の為の変数 *//* 画像データを参照し、情報を保持 */」という箇所も
今の所は画像データを扱う為の「お約束」と考えてください。
ここで注目する点は、extern unsigned char ASGR_01[]の「ASGR_01」が、
先に用意した画像データのファイル名の大文字であるという事です。
変数って何?
変数は情報をしまっておく事ができる箱や器のようなもので、様々な用途で使用されます。
残念ながら一言で表現できるものではありませんのでプログラムのしくみを理解しながら、
少しづつ使い方を憶えていきましょう。
とりあえず変数の種類を簡単に解説しますので後で参考にしてください。
まず、変数には「グローバル変数」と「ローカル変数」があり、
その名の通りプログラム全体で使用できる変数と、一部(ひとつの関数の中)でのみ使える変数です。
どちらも使用する為には、きちんとした書式で「宣言」しなくてはいけません。
プログラムの冒頭で宣言するのはグローバル変数です。
例として「/* ゲームモードを判断する為の変数 */」の箇所を見てみましょう。
static unsigned char gModeの「static」は静的変数を意味しますが、まだ深く考えなくてもかまいません。
かなり乱暴ですが、グローバル変数を宣言するときは「static」を付ける、と思っても大丈夫です。
(逆に、ローカル変数を宣言するときは「static」は不要です。)
次に「unsigned」ですが、これは負(マイナス)の値を使わないことを意味します。
自信が無いときは、とりあえず「unsigned」を付けないほうが良いでしょう。
更に「char」の箇所ですが、これは箱や器の「形と大きさ」をあらわします(表1)。
なるべくなら、その変数に合った大きさの器を用意するべきなのですが、
これも自信がないときは「大は小を兼ねる」で、とりあえず「long」宣言してしまいましょう。
最後の「gMode」は変数の名前で、例外もありますが自分でわかりやすい名前を自由に付けられます。
―――――――――――
―――――――――――――――――――――――――――――――――――――
表1(今回は浮動小数点数型は除外しています)
―――――――――――――――――――――――――――――――――――――
型と範囲(unsignedは負が使えない代わり、正の数が2倍になります)
―――――――――――――――――――――――――――――――――――――
char -127〜127
int -32767〜32767
long -2147483647〜2147483647
―――――――――――――――――――――――――――――――――――――
―――――――――――
配列変数って何?
「/* 自弾の表示座標用変数(配列変数)*/」の箇所を見てみましょう。
カッコ書きにある通り、static int myshootx[3],myshooty[3]は配列変数です。
といっても難しいものではなくて、
myshootx[0]、myshootx[1]、myshootx[2]とmyshooty[0]、myshooty[1]、myshooty[2]の
それぞれ3個づつの変数をまとめて宣言しているのです。
1〜3ではなく0〜2ですので注意してください。
配列変数を意味も無く使用すると分かりづらいプログラムになってしまいますが、
使い方によってはとても便利なものです。
なぜ便利なのかは後できっと分かるときが来るでしょう。
関数って何?
関数とは、いくつかの作業をひとつにまとめたものです。
プログラム中に何度も同じような処理を行なう場合、それらの処理に名前を付けてひとまとめにしておいて、
必要な時に簡単に呼び出すことができるのです。
と言ってもあまりピンと来ないかも知れませんね。
関数は最初から用意してあるものを使うほか、自分で作成する事もできます。
このプログラムではふたつの関数を作成します。
「/* これから作って使う予定の関数を宣言 */」は、その準備です。
乱数を使おう
初期設定が終わりましたので次は関数の入力です(プログラムリストその2)。
void pceAppInit( void )は最初から用意されている関数です。
この関数はプログラムが開始されるときに一度だけ強制的に呼び出されますので、
初期設定のつづきのようなものですね。
まず、乱数を使う準備をします。
ほとんどのゲームでは乱数が使用されますので、ゲームプログラミングでは必須の作業とも言えます。
少し記述の場所が離れていますが、
「/* 擬似乱数の設定をする為に時間情報を保持 */」と「/* 時間情報を元に擬似乱数の発生パターンをセット */」
の箇所は乱数を使う為の「お約束」と考えてください。
―――――――――――
―――――――――――――――――――――――――――――――――――――
プログラムリストその2
―――――――――――――――――――――――――――――――――――――
/* 初期化処理関数(プログラム開始時に必ず通ります)*/
void pceAppInit( void )
{
/* 擬似乱数の設定をする為に時間情報を保持 */
PCETIME ntime;
/* ローカル変数の宣言 */
int f;
/* 画面描画に関する設定等 */
PBMP_FILEHEADER pbhead;
pceLCDDispStop();
pceLCDSetBuffer( vbuff );
/* 仮想画面を初期化 */
memset( vbuff, 0, 128*88 );
/* 保持済みの情報を元に描画データを取得 */
memcpy( &pbmp1.header, ASGR_01, sizeof( PBMP_FILEHEADER ) );
pbmp1.buf = ASGR_01 + sizeof( PBMP_FILEHEADER );
pbmp1.mask = ASGR_01 + sizeof( PBMP_FILEHEADER )+( pbmp1. header. h*pbmp1. header.w )/4;
/* アプリケーションの呼び出し間隔 */
pceAppSetProcPeriod( 32 );
/* 時間情報を元に擬似乱数の発生パターンをセット */
pceTimeGet( &ntime );
srand( ntime.s100 );
/* グローバル変数にそれぞれ適した値を代入 */
draw = 1;
gMode = 0;
myscore = 0;
for ( f=0 ; f<=19 ; f++ ) {
hoshix[f] = ( rand()%128 );
hoshiy[f] = ( rand()%87 );
}
/* 文字の色を白に、文字の背景を透明に設定 */
pceFontSetTxColor(0);
pceFontSetBkColor(-1);
/* トリガーリピートをオフに設定 */
pcePadSetTrigMode( PP_MODE_SINGLE );
/* 画面描画開始 */
pceLCDDispStart();
}
―――――――――――――――――――――――――――――――――――――
―――――――――――
画像表示の準備は大変?
「/* 画面描画に関する設定等 */」「/* 仮想画面を初期化 */」「/* 保持済みの情報を元に描画データを取得 */」と、
画像表示に関する記述が続いています。
一見するとすごく難しそうですが、これもある程度「お約束」なので、あまり深く考えなくてもかまいません。
参考にチュートリアルを見てみましょう。
多少の記述の違いはありますが、ほとんど同じ事をしているのがわかりますね。
ここで注目する点は「/* 保持済みの情報を元に描画データを取得 */」の箇所で、
初期設定で参照した「ASGR_01」というファイル名が使用されていることです。
これで初期設定から続いていた画像表示の準備はほとんど終わりました。
記述が長くて大変でしたが、画像を使用するプログラムでは必ず同じような準備をしなくてはなりませんので、
内容が理解できなくても、とりあえず『こういうものなんだ』と憶えてしまいましょう。
ゲームのスピードを設定しよう
様々な要素が関係しているので一概には言えませんが、
pceAppSetProcPeriod( 32 )のカッコ内の数字でゲームのスピードを変更することができます。
小さければ早く、大きければ遅くなりますが、16未満の数値にしてもほとんどの場合それ以上早くはなりません。
これは説明するより実験するほうが分かりやすいので、後でいろんな数値に書き直して遊んでみてくださいね。
変数に数値を代入するには?
次に、先に宣言したグローバル変数に数値を代入します。
例として、draw = 1という記述は『変数「draw」に「1」を代入します』という意味です。
が、続いてfor ( f=0 ; f<=19 ; f++ ) {という記述が出てきました。
これは一体何なのでしょう?
実はこれは「for文」と言って、プログラミングをするなら必ず使い方を憶えなければならないくらい、重要な文なのです。
for文は繰り返す
for文は回数を数えながら同じ処理を繰り返す時に利用します。
回数を数える為にはあらかじめ変数を宣言しておかなくてはなりません。
解説はおこないませんでしたが、
さりげなくvoid pceAppInit( void )関数のはじめに「f」という名前のローカル変数を宣言していますね。
これはfor文を使う為の準備だったのです。
for文のしくみは以下の通りです(図2)。
[1]で変数に数値を代入し、[2]で条件(表2)を調べて、条件が成立していれば[4]の処理を1回行ない、
[3]の処理にしたがって変数の値を変更します。
そして[2]の条件が成立しなくなるまで、ひたすら処理を繰り返します。
少し難しいですか?
それではここで記述されているfor文(図3)を参考に、どのような処理を行なっているのか考えましょう。
まず変数「f」に0を代入します。
続いて変数「f」の値が19以下であるならhoshix[f] = ( rand()%128 )とhoshiy[f] = ( rand()%87 )の
ふたつの処理を実行し、更に変数「f」の値に1を加えます(つまり、この時点で変数「f」には1が代入されます)。
そして変数「f」の値が19以上になるまで(合計20回)
hoshix[f] = ( rand()%128 )とhoshiy[f] = ( rand()%87 )の処理を繰り返し実行します。
つまりこのfor文は、図4と同じ事を行なっているわけですが、これでは記述が大変です。
今回は繰り返しが20回で済んでいるのでまだ良いのですが、
同じ事を100回も繰り返すとなるとfor文がいかに便利なものか理解できます。
また、先に宣言した配列変数が、for文ととても相性が良い事も理解できますね。
―――――――――――
―――――――――――――――――――――――――――――――――――――
図2
for( [1] ; [2] ; [3] ){ [4] }
―――――――――――――――――――――――――――――――――――――
図3
for ( f=0 ; f<=19 ; f++ ) {
hoshix[f] = ( rand()%128 );
hoshiy[f] = ( rand()%87 );
}
―――――――――――――――――――――――――――――――――――――
図4
hoshix[0] = ( rand()%128 );
hoshiy[0] = ( rand()%87 );
hoshix[1] = ( rand()%128 );
hoshiy[1] = ( rand()%87 );
hoshix[2] = ( rand()%128 );
hoshiy[2] = ( rand()%87 );
:
:
ずーっと続いて‥‥
:
:
hoshix[19] = ( rand()%128 );
hoshiy[19] = ( rand()%87 );
―――――――――――――――――――――――――――――――――――――
―――――――――――
―――――――――――――――――――――――――――――――――――――
表2 比較演算子の例
―――――――――――――――――――――――――――――――――――――
記号と意味
―――――――――――――――――――――――――――――――――――――
a == b aとbが等しい
a < b aがbより小さい
a <= b aがb以下
a > b aがbより大きい
a >= b aがb以上
a != b aとbが等しくない
―――――――――――――――――――――――――――――――――――――
―――――――――――
簡単な演算子
説明が前後してしまいましたが、
f++とは『変数fに1を加える』という意味で、この++はインクリメント演算子と呼びます。
逆にf--と記述するなら『変数fから1を引く』という意味になり、こちらの--はデクリメント演算子と呼びます。
これらの演算子はこれからも頻繁に使用することになりますので、ぜひ理解しておきましょう。
乱数が発生
rand()%128とは『0〜127の間の数値をランダムに決める』という意味です。
ですからrand()%87では0〜86の間の数値がランダムに決まります。
既に乱数を発生させる準備が終わっていれば、このように好きな乱数を簡単に作る事が出来るのです。
APIリファレンスを読もう
続いてpceFontSetTxColor(0)などの記述がありますが、
これはプログラムリストの説明文とAPIリファレンスを照らし合わせて、
どのような処理を行なっているのか自分で考えてみましょう(先頭がpceで始まる関数はAPIリファレンスに説明がのっています)。
少しプログラムの事が理解できてくると、それにともなってAPIリファレンスの内容も理解できると思います。
ここまで入力できたら、次はプログラムのメイン処理関数です(プログラムリストその3)。
―――――――――――
―――――――――――――――――――――――――――――――――――――
プログラムリストその3
―――――――――――――――――――――――――――――――――――――
/* アプリケーションのメイン処理関数(呼び出し間隔の設定を元に周期的に呼び出されます)*/
void pceAppProc( int cnt )
{
int f;
/* 仮想画面を黒く塗りつぶし */
pceLCDPaint( 3, 0, 0, 128, 88);
/* 変数(gMode)を元に処理を分岐 */
if ( gMode == 0 ) {
/* gModeが0の時はタイトル画面表示 */
/* 仮想画面に文字を書く */
pceFontSetPos( 33, 25);
pceFontPrintf("ASTEROID GAME");
pceFontSetPos( 42, 45);
pceFontPrintf("[A] START");
pceFontSetPos( 42, 55);
pceFontPrintf("[B] EXIT");
pceFontSetPos( 0, 0);
pceFontPrintf("SCORE %06d",myscore);
/* キー入力を判定 */
if ( pcePadGet() & TRG_A ) gMode = 5;
if ( pcePadGet() & TRG_B ) pceAppReqExit(0);
}
else if ( gMode == 5 ) {
/* gModeが5の時はゲームの準備 */
myscore = 0;
mCount = 0;
myshipx = 56;
myshootc = 0;
astmax = 2;
for ( f=0 ; f<=2 ; f++ ) {
myshooty[f] = -40;
}
for ( f=0 ; f<=9 ; f++ ) {
asty[f] = 90;
astc[f] = 0;
}
gMode = 10;
}
else if ( gMode == 10 ) {
/* gModeが10の時はゲーム進行中 */
if ( mCount <= 50 ) {
pceFontSetPos( 52, 35);
pceFontPrintf("READY");
mCount++;
}
else if ( mCount <= 100 ) {
pceFontSetPos( 57, 35);
pceFontPrintf("GO!");
mCount++;
}
pceFontSetPos( 0, 0);
pceFontPrintf("SCORE %06d",myscore);
/* キー入力を判定 */
if ( pcePadGet() & TRG_A ) myshootc = 1;
if ( pcePadGet() & PAD_LF ) myshipx -= 4;
if ( pcePadGet() & PAD_RI ) myshipx += 4;
if ( myshipx < 0 ) myshipx = 0;
if ( myshipx > 112 ) myshipx = 112;
}
else if ( gMode == 15 ) {
/* gModeが15の時は自機が爆発 */
mCount++;
if ( mCount <= 130 ) {
pceLCDPaint( 0, 0, 0, 128, 88);
} else {
gMode = 20;
}
}
else if ( gMode == 20 ) {
/* gModeが20の時はゲームオーバー画面表示 */
if ( mCount <= 150 ) {
pceLCDPaint( ( mCount-120 )/10, 0, 0, 128, 88);
mCount++;
}
/* 仮想画面に文字を書く */
pceFontSetPos( 42, 25);
pceFontPrintf("GAME OVER");
pceFontSetPos( 42, 45);
pceFontPrintf("[A] RETRY");
pceFontSetPos( 42, 55);
pceFontPrintf("[B] EXIT");
pceFontSetPos( 0, 0);
pceFontPrintf("SCORE %06d",myscore);
/* キー入力を判定 */
if ( pcePadGet() & TRG_A ) gMode = 5;
if ( pcePadGet() & TRG_B ) pceAppReqExit(0);
}
moveHoshi();
if ( gMode == 10 ) moveMain();
/* 描画用変数(draw)を元に仮想画面の内容を液晶画面に表示(必要無い時は表示しない)*/
if ( draw == 1 ) {
pceLCDTrans();
draw = 0;
}
}
/* アプリケーションの終了処理関数(アプリケーションを終了する時に呼び出します)*/
void pceAppExit( void ){
/* トリガーリピートを通常に設定 */
pcePadSetTrigMode( PP_MODE_REPEAT );
}
―――――――――――――――――――――――――――――――――――――
―――――――――――
if文は魔法の呪文
「/* 変数(gMode)を元に処理を分岐 */」の箇所まで来ると、if ( gMode == 0 ) {という記述が出てきました。
これはプログラミングをするなら先に説明したfor文以上に重要なもので、「if文」というものです。
そのぶんfor文よりも扱いが難しいのですが、if文を使いこなせるようになれば、
怖いものはなくなります(というのは少し言いすぎですが)ので、がんばってくださいね。
if文の簡単なしくみは以下の通りです(図5)。
[1]で条件(表2)を調べて、条件が成立していれば[2]の処理を行ないます。
つまり『もしも〜ならば』という意味ですね。このようにif文のしくみはとても簡単なものですが、
[1]の条件をいかに記述するかによって、様々な処理を自由に行なうことができます。
ここでいかに知恵をふるしぼるかが大切であり、if文の扱いが難しく、なおかつ楽しいところなのです。
―――――――――――
―――――――――――――――――――――――――――――――――――――
図5
if ( [1] ) {
[2]
}
または
if ( [1] ) [2]
―――――――――――――――――――――――――――――――――――――
―――――――――――
論理演算子を憶えよう
少し順番が違ってしまいますが、if文を使うのでしたら論理演算子も憶えましょう。
論理演算子とは、if文の中(図5)で使用した[1]の条件を作る際にとても役に立つもので合計4種類あるのですが、
大きく分けると2種類になりますので、ここではふたつだけを理解しましょう。
まずは「&」(AND)演算子です。しくみは以下の通りです(図6)。
[1]と[2]の条件を調べて、両方が成立していれば[3]の処理を行ないます。
次は「|」(OR)演算子です。しくみは以下の通りです(図7。)
[1]と[2]の条件を調べて、どちらか片方でも成立していれば[3]の処理を行ないます。
もちろん両方が成立していても[3]の処理を行ないます。
―――――――――――
―――――――――――――――――――――――――――――――――――――
図6
if ( [1] & [2] ) {
[3]
}
―――――――――――――――――――――――――――――――――――――
図7
if ( [1] | [2] ) {
[3]
}
―――――――――――――――――――――――――――――――――――――
―――――――――――
高度な呪文も憶えよう
if文と組み合わせて使用するものに「else文」と「else if文」がありますので、こちらもあわせて憶えてしまいましょう。
else文とelse if文は前述のif文の条件が成立しなかった場合の処理として扱います。
else文は『そうでなかったら〜』という意味で、
else if文は少し複雑で『そうでなかったら、もしも〜ならば』という意味です。
しくみはは以下の通りです(図8)。
[1]の条件を調べて、条件が成立していれば[2]の処理を行ない、それ以下の処理はまったく行なわれません。
もし[1]の条件が成立していなかった場合、次は[3]の条件を調べて、
条件が成立していれば[4]の処理を行ない、それ以下の処理は行なわれません。
もし[1]の条件も[3]の条件も成立しなかった場合は、自動的に[5]の処理が行なわれます。
だいぶややこしい事になってきましたが、これは「高度な呪文」なので難しいのは仕方ありません。
じっくり取り組んで、がんばって理解してくださいね。
―――――――――――
―――――――――――――――――――――――――――――――――――――
図8
if ( [1] ) {
[2]
}
else if ( [3] ) {
[4]
}
else {
[5]
}
―――――――――――――――――――――――――――――――――――――
―――――――――――
仮想画面って何?
「/* 仮想画面に文字を書く */」の箇所はAPIリファレンスを参考にしてください。
pceFontSetPos()で文字を書く座標を指定し、pceFontPrintf("")で仮想画面に文字を書いています。
ここで大切なのは「仮想画面」というしくみです。
文字や画像はまず仮想画面に書かれます。仮想画面を例えるなら、描きかけの絵のようなものですね。
表示したいものすべてを仮想画面に書き終えてから(つまり、絵が完成したら)P/ECEの画面に表示するわけです。
今はまだ絵が描きかけですので画面には表示していませんが、
後の「/* 描画用変数(draw)を元に仮想画面の内容を液晶画面に表示(必要無い時は表示しない)*/」という箇所で
画面表示を行なっていますので参考にしてみてください。
キー入力を判定しよう
「/* キー入力を判定 */」の箇所で、pcePadGet()という関数がif文の中で使われています。
このpcePadGet()がキー入力を判定する関数なのです。
くわしい使い方はAPIリファレンスを参考にしてください。
代入演算子を使いこなそう
これまでに、変数にある数値を代入する為に「draw = 1」のような記述を行なってきましたね。
この「=」という記号は数多くの代入演算子のひとつなのです。
表3に代入演算子の例をあげておきましたので参考にしてください。
―――――――――――
―――――――――――――――――――――――――――――――――――――
表3 代入演算子の例(記号aは変数)
―――――――――――――――――――――――――――――――――――――
記号と意味
―――――――――――――――――――――――――――――――――――――
a = b aをbとする
a += b aにbを加えた値をaとする
a -= b aからbを引いた値をaとする
a *= b aにbをかけた値をaとする
a /= b aをbで割った値をaとする
―――――――――――――――――――――――――――――――――――――
―――――――――――
あと一息
さて、すでにたくさんの事を学んで来ましたので、
だいぶプログラムのしくみが理解できたのではないでしょうか?
まだプログラムリストはたくさん残っていますが、しくみに関して言えば応用の技術ばかりですので、
ここから先は重要な点のみを解説していきます。わからない点はここまでの解説を読みかえしたり
、APIリファレンスとチュートリアルを参考にしたりしながらがんばってくださいね。
それではプログラムの続きを入力していきましょう。
―――――――――――
―――――――――――――――――――――――――――――――――――――
プログラムリストその4
―――――――――――――――――――――――――――――――――――――
/* ゲーム中で使用する関数を作成 */
/* 星(背景)の移動と描画を行なう関数 */
void moveHoshi( void )
{
int f;
for ( f=0 ; f<=19 ; f++ ) {
hoshiy[f] += 2;
if ( f >= 10 ) hoshiy[f]++;
if ( hoshiy[f] >= 87 ) {
hoshix[f] = ( rand()%128 );
hoshiy[f] -= 87;
}
pceLCDPoint( 0, hoshix[f], hoshiy[f] );
pceLCDPoint( 0, hoshix[f], hoshiy[f]+1 );
}
draw = 1;
}
―――――――――――――――――――――――――――――――――――――
―――――――――――
関数を作ろう
プログラムリストその3の終わりのほうでmoveHoshi()とmoveMain()という関数が呼び出されていましたね。
初期設定を行なったときに「このプログラムではふたつの関数を作成します」と書かれていた事を憶えていますか?
そうです。
moveHoshi()とmoveMain()のふたつの関数は自分で作らなくてはいけないのです。
moveHoshi()は、背景の星をスクロールしながら描画する関数で、
moveMain()はその他すべての画像を制御し、当たり判定などの処理を行なう関数です。
まずは簡単なmoveHoshi()関数を作成しました。
関数の型について説明しますと、
void moveHoshi( void )の先頭が戻り値(必要無い場合はvoidと記述します)で、
次が関数の名前(例外はありますが自由に付けられます)、
最後のカッコ内が引数の型と名前(これも必要無い場合はvoidと記述します)になっています。
今回は戻り値と引数を使いませんでしたので、名前だけのとても単純な型になっています。
内容もそれほど難しいものではありませんが、星を1ピクセルの大きさにしてスクロールさせると見えづらいので、
縦に2ピクセルの長さで表示するように工夫しています。
また、変数「draw」に「1」を代入し、
moveHoshi()関数が呼び出された時(実は毎回呼び出されているのですが)は
仮想画面の内容を液晶画面に表示する準備を行なっている点にも注意してくださいね。
さあ、moveHoshi()関数のプログラムの入力が終わったら、残りはmoveMain()関数だけです。
―――――――――――
―――――――――――――――――――――――――――――――――――――
プログラムリストその5
―――――――――――――――――――――――――――――――――――――
/* 自機と隕石の移動と描画、当たり判定を行なう関数 */
void moveMain( void )
{
int f,ff;
/* 点数のカウントと難易度の変更 */
if ( mCount >= 100 ) {
myscore++;
astmax = ( myscore / 500 ) + 2;
if ( astmax > 10 ) astmax = 10;
/* 隕石の移動と当たり判定 */
for ( f=0 ; f<astmax ; f++ ) {
if ( ( asty[f] >= 90 | astc[f] == 5 ) & rand()%5 == 0 ) {
astx[f] = ( ( rand()%148 )-16 );
asty[f] = -16;
astc[f] = 0;
}
/* 自弾と隕石の当たり判定 */
for ( ff=0 ; ff<=2 ; ff++ ) {
if ( astx[f]-13 <= myshootx[ff] & astx[f]+14 >= myshootx[ff] & asty[f]-16 <= myshooty[ff] & asty[f]+16 >= myshooty[ff] & astc[f] == 0 ) {
astc[f] = 1;
myshooty[ff] = -40;
myscore += 10;
}
}
/* 自機と隕石の当たり判定 */
if ( astx[f]-12 <= myshipx & astx[f]+13 >= myshipx & asty[f] >= 58 & asty[f] <= 80 & astc[f] == 0 ) {
gMode = 15;
}
/* 隕石の移動 */
if ( asty[f] <= 89 ) {
asty[f] += 4;
if ( f%2 == 0 ) {
astx[f]++;
} else {
astx[f]--;
}
}
/* 隕石の描画 */
if ( astc[f] == 0 ) {
pceLCDSetObject(&obj, &pbmp1, astx[f], asty[f], 16, 0, 16, 16, DRW_NOMAL);
pceLCDDrawObject(obj);
}
else if ( astc[f] < 5 ) {
astc[f]++;
pceLCDSetObject(&obj, &pbmp1, astx[f], asty[f], 48, 0, 16, 16, DRW_NOMAL);
pceLCDDrawObject(obj);
}
}
}
/* 得点が上限を超えた時 */
if ( myscore > 999999 ) myscore = 999999;
/* 自機の描画 */
pceLCDSetObject(&obj, &pbmp1, myshipx, 70, 0, 0, 16, 16, DRW_NOMAL);
pceLCDDrawObject(obj);
/* 自弾の描画 */
for ( f=0 ; f<=2 ; f++ ) {
if ( myshootc == 1 & myshooty[f] <= -40 ) {
myshootc = 0;
myshootx[f] = myshipx;
myshooty[f] = 70;
}
if ( myshooty[f] > -40 ) {
myshooty[f] -= 8;
pceLCDSetObject(&obj, &pbmp1, myshootx[f], myshooty[f], 32, 0, 16, 16, DRW_NOMAL);
pceLCDDrawObject(obj);
}
}
}
―――――――――――――――――――――――――――――――――――――
―――――――――――
画像を表示しよう
moveMain()関数では、for文のダブルループやif文の論理演算子を多く活用していますが、
先に書いたようにそれらについての解説はしません。
ただし、当たり判定の箇所は1行の記述が長くなっていますので気をつけてくださいね。
ここでは初めて出てきた関数pceLCDSetObject()とpceLCDDrawObject()について簡単に解説しましょう。
このふたつの関数は常にあわせて使用するものと考えてください。
pceLCDSetObject()は、表示する画像の位置や大きさを決める関数で、設定する事柄が多くて難しそうですね。
これは実際にゲームで遊んでみて、
初めに作成した画像データとpceLCDSetObject()で設定された数値などを見比べながら、
何をどのように表示しているのか考えてみましょう。
しくみが理解できればそれほど難しいものではありませんし、
チュートリアルにも画像データを表示する箇所が記述されていますので参考にしてください。
pceLCDDrawObject()はpceLCDSetObject()で設定された内容を仮想画面に描画する関数です。
こちらはそのままですので難しくありませんが、うっかりこの関数を書き忘れてしまうと、
せっかく用意した画像データが表示されませんので注意しましょう。
「アステロイドゲーム」完成
ようやくプログラムの入力が終わりましたね。
お疲れさまでした。
今までの苦労が水の泡にならないように確実にセーブしましょうね。
それでは、早速プログラムを実行しましょう。
チュートリアルを参考にしながら、
DOSプロンプトを起動して「cd \usr\piece\app\asteroid」(作業用フォルダを指定します)と入力します。
続いて、「pcc33 asteroid.c asgr_01.c」と入力し、更に「run asteroid」と入力します。
入力したプログラムリストに間違いが無くて、この作業が正しく行なわれていれば、
P/ECEでプログラムが実行されます。
ただし、USB接続の調子が悪いときはうまくいかない事がありますので、
その場合はコンピュータを再起動してからもういちど実行してみましょう。
スクロールする星を背景にタイトル画面が表示されましたら成功です!おめでとうございます!
もし画面が暗すぎるときはコントラストを調整して、見やすいようにしてください。
プログラムをP/ECEで持ち歩く場合は実行イメージを作成する必要があります。
この場合は更に「ppack -e asteroid.srf -oasteroid.pex -nアステロイド」と入力してください。
出来あがったasteroid.pexファイルをP/ECEに転送しておけば、
いつでもどこでもP/ECEでアステロイドゲームを遊ぶ事ができるのです。
早速遊んでみよう
宇宙船はパッドの左右で移動し、Aボタンでレーザー砲が発射されますので、
飛来する隕石を避けつつレーザー砲で破壊してください。
レーザー砲は3連射が可能です。隕石は最初ふたつしか飛んできませんが、
得点を重ねるごとにたくさん飛んできて難易度があがっていきます。
隕石が宇宙船に接触するとゲームオーバーです。
もし動作がおかしいと感じた場合、入力したプログラムリストに間違いがあるおそれがありますので、
もういちど確認をしてみてください。
終わりに‥‥
急ぎ足でしたが、これでゲームの制作作業をひととおり経験することができました。
いかがでしたか?
難しいと感じるところもたくさんあったかと思います。
つまずきやすい箇所や重要な箇所はできるだけ説明してきましたが、
残念ながら足りないところもありますし、厳密に言えば間違っているところも一部あります。
それでも、このプログラムは非常に少ない関数でつくられていますし、
何度も読みかえすことで、ほとんどのしくみが理解できると思います。
しくみが理解来たら、今度はあなた自身のオリジナルゲーム作成に挑戦してみましょう。
その際には今回の「アステロイドゲーム」のプログラムリストから、
必要な箇所を抜粋して使用してもかまいません。
シューティングゲームを作るのでしたらきっと役に立つでしょう。それでは、がんばってくださいね。
―――――――――――
おまけ
既にプログラムの内容を理解している方の為に、ソースと画像データ、pexファイルを用意しました。
ダウンロードはこちらをクリックしてください。
プログラムを勉強中の方は、ここに書かれている内容を理解するために
どうかダウンロードの誘惑に負けず、ご自分の力でプログラムを入力してゲームを完成してくださいね。
―――――――――――