yaneuraoGameScript2000で行こう!

初心者歓迎!プロのゲームプログラマーによる、×××ツクールでは物足りない人のためのゲーム制作入門

written by やねうらお


1.いよいよ、本格的なゲームを作っていきます。

この連載も、すでに5回目になります。わたくしこと、やねうらおは、1ヶ月に1本という驚異的なペースでゲームを作りつづけています。「ゲームプログラムってお金になるんでしょうか?」とよく尋ねられますが、ゲームプログラマというのは企画者から言われた通りにキャラクターやら何やらを動かすのが仕事で、ゲームが売れれば、お金がたくさん入ってくることもありますが、ゲームが売れなければちっとも入って来なかったりします。

そして、やねうらおが関わっている美少女ゲーム業界(=エロゲー業界)は、売れるゲームと売れないゲームとの境界を決めるのは、グラフィッカー、特に原画マンのネームバリューだとか、メーカーブランドとか、そういうところが大きいです。要するにプログラムを2倍頑張っても、半分しかお金がもらえないことだってあるという非常に理不尽な業界なのです。

おまけに、すでにメーカーは乱立し、ブランド名・タイトル数は雑誌編集者やショップ関係者ですら把握できない数になりつつあります。要するに、マーケットは、すでに飽和、あるいは、枯渇していると言っても過言ではありません。このなかを生き抜くのは並大抵のことではないのです。

「でも自分で、自分だけのゲームを作ってみたい!」と思われる方も多いです。プロにならなくてもいいから、遊びとしてプログラムがどんなものか知っておきたい!という方のために、今回から、いよいよ本格的なゲームの製作を行なっていきたいと思います。

まずは、ygs2kの最新版を用意してください。(右クリックしてファイルに保存してください。使い方は以前の記事を参考にしてください。過去の記事については、すべて私のホームページにて閲覧出来ます。もし、この講座を初めてご覧になる方は、過去の記事から読まれることをお勧めいたします)


2.今回は、ジャンプアクションゲーム

前回までで、みなさんは、キャラクター表示、キー入力、交差判定、関数までは理解出来ているはずですので、すでに簡単なゲームならば作れるはずなのです。そんなわけで、今回からは、実際に遊べるサンプルプログラムを作っていくことにしましょう。では、いきなりプログラムです。YGS2000本体の存在するフォルダにscriptという名前のフォルダを作り、以下のプログラムをテキストエディタ(メモ帳でも可)で入力し、gamestart.cというファイル名で保存、そしてYGS2000本体を実行すれば良いわけですね。

なお、ここで使っているビットマップファイルは、これです。(右クリックしてファイルに保存して、scriptフォルダに入れてください)

void main(){
    int x,y;                                //  マイキャラの座標を保持するための変数
    int bx,by;                              //  敵キャラの座標を保持するための変数
    初期化処理();

    x = 100; y= 400;                        //  マイキャラの座標初期化
    bx = 640; by = 400;                     //  敵キャラの座標初期化
    loop {
        ClearSecondary();                   //  画面クリア
        マイキャラ表示(x,y);
        ブロック表示(bx,by);

        //  マイキャラの移動
        KeyInput();                         //  キー入力
        if (IsPressLeftKey()) x = x - 8;    //  左が押されていればx座標を-8せよ
        if (IsPressRightKey()) x = x + 8;   //  右が押されていればx座標を+8せよ
        if (x<0) x = 0;                     //  画面左端からは出れない
        if (x>640-48) x=640-48;             //  画面右端からは出れない

        //  敵キャラの移動
        bx = bx - 8;                        //  8ドットずつ左へ
        if (bx<-48) bx = 640;               //  画面左に消えていればまた右端から出現

        halt;                               //  画面表示
    }
}

void 初期化処理(){
    SetFPS(30);                             //  フレームレートを30に
    LoadBitmap("script/doli.bmp",0,1);
}

void マイキャラ表示(int x,int y){
    BltRect(0,x,y,0,0,48,48);               //  0番のプレーンの(0,0)から48×48を表示
}

void ブロック表示(int x,int y){
    BltRect(0,x,y,0,48,48,48);              //  0番のプレーンの(0,48)から48×48を表示
}

いままで出てきたことしかやってませんので、難しくはないはずです。関数名として、わかりやすいように日本語を使っていますが、そのへんは、真似しないほうがいいかも知れません(笑)

ともかく、実行すると、このような画面になったはずです。

キャラクターは、カンガルーの比古平(ひこべえ)と言います。うちの会社のマスコットキャラです^^;

それはともかく、矢印キー、ジョイパッドの左右(テンキーの4,6でも可)で動かせますんで、主人公を動かしてみます。ブロックとの接触判定はまだありませんので、ぶつかっても何ともなりません。飽きたら、窓の右上の×印で窓を閉じます。

もう、察しの良い読者の方々なら、何をやりたいかわかりましたね。まずは、接触判定を追加することです。接触していれば、ゲームオーバーということにしましょう。

void main(){
  int x,y;                // マイキャラの座標を保持するための変数
  int bx,by;               // 敵キャラの座標を保持するための変数
  初期化処理();

  x = 100; y= 400;            // マイキャラの座標初期化
  bx = 640; by = 400;           // 敵キャラの座標初期化
  loop {
    ClearSecondary();          // 画面クリア
    マイキャラ表示(x,y);
    ブロック表示(bx,by);

    // マイキャラの移動
    KeyInput();             // キー入力
    if (IsPressLeftKey()) x = x - 8;  // 左が押されていればx座標を-8せよ
    if (IsPressRightKey()) x = x + 8;  // 右が押されていればx座標を+8せよ
    if (x<0) x = 0;           // 画面左端からは出れない
    if (x>640-48) x=640-48;       // 画面右端からは出れない

    // 敵キャラの移動
    bx = bx - 8;            // 8ドットずつ左へ
    if (bx<-48) bx = 640;        // 画面左に消えていればまた右端から出現

    halt;                // 画面表示

    if (接触判定(x,y,bx,by)) break;
  }

  ゲームオーバー表示();
}

void 初期化処理(){
  SetFPS(30);               // フレームレートを30に
  LoadBitmap("script/doli.bmp",0,1);
}

void マイキャラ表示(int x,int y){
  BltRect(0,x,y,0,0,48,48);        // 0番のプレーンの(0,0)から48×48を表示
}

void ブロック表示(int x,int y){
  BltRect(0,x,y,0,48,48,48);       // 0番のプレーンの(0,48)から48×48を表示
}

int 接触判定(int x1,int y1,int x3,int y3){
  int x2,x4,y2,y4;
  x2 = x1 + 48; y2 = y1 + 48;   // 主人公の矩形領域 :(x1,y1)-(x2,y2)
  x4 = x3 + 48; y4 = y2 + 48;   // ブロックの矩形領域:(x3,y3)-(x4,y4)

  if( (x1 < x4) && (x3 < x2) && (y1 < y4) && (y3 < y2) ) {
    // 当たり!
    return 1;
  }
  // 当たっていない!
  return 0;
}

void ゲームオーバー表示(){
  TextLayerOn(0,50,200);
  TextSize(0,60);
  TextOut(0,"Game Over");
  loop
    halt;
}

接触判定は、前々回にやったので、難しくはないでしょう。上のプログラム、赤字部分が追加分です。

ところで、これを実行すると、右からブロックが現れて、逃げ惑う猶予もなく、接触する瞬間にゲームオーバーになります。接触判定については、もう少しめり込むぐらいの位置関係になったときにゲームオーバーになったほうが良さそうです。接触判定を、以下のようにいじりましょう。

int 接触判定(int x1,int y1,int x3,int y3){
  int x2,x4,y2,y4;
  x2 = x1 + 32; y2 = y1 + 32;   // 主人公の矩形領域 :(x1,y1)-(x2,y2)
  x4 = x3 + 32; y4 = y2 + 32;   // ブロックの矩形領域:(x3,y3)-(x4,y4)
  x1 = x1 + 16; y1 = y1 + 16;
  x3 = x3 + 16; y3 = y3 + 16;


  if( (x1 < x4) && (x3 < x2) && (y1 < y4) && (y3 < y2) ) {
    // 当たり!
    return 1;
  }
  // 当たっていない!
  return 0;
}

主人公とブロックは48×48ドットで構成されているのですが、そのひとまわり(ふたまわり?)小さい16×16ドットの矩形対矩形の接触判定とすることによって、めり込んだときに接触したとみなすことが可能になります。(下図)

このように、見かけと、実際の判定は異なるもので構わないし、ユーザーが違和感を覚えない範囲で異なるほうが普通なのです。

ところで、このブロックを主人公が回避するには、どうすれば良いでしょうか?攻撃して潰すことが出来るというのも面白そうですが、今回は、ジャンプアクションにしてみましょう。

ジャンプという動作も、何も厳密に物理法則に則っている必要などありません。ふわーっと、上がって、ふわーっと降りてきてもいいのです。そうは言っても、それだとそれらしく見えにくいので、ここでは、重力加速度が働く場合をやってみましょう。スペースキーを押した瞬間、y軸方向の初速度をマイナス10としましょう(スクリーン上では、y軸は、増加方向が下であることに注意!)そして、1フレームごとに重力加速度が加わってきます。何も物理定数通りの9.8とか現実に即した数字でなくて構いません。どうせ、ゲームの中は、嘘の世界なんですから!だから、ここでは1にしましょう。1フレームごとに、速度に1ずつ足すわけです。また、y座標が基準位置より上にあれば、足が地面についていないと考えられるので、そのときは、さらなるジャンプはできないようにしておきましょう。(ジャンプ処理を受け付けないようにしましょう) あと、y座標が、基準位置より下にあるならば速度は0にリセットするような処理も必要でしょう。

文章で書くとややこしそうですが、プログラムとしては、以下の赤い部分を追加するだけのことです。

void main(){
  int x,y;                // マイキャラの座標を保持するための変数
  int vx,vy;               // マイキャラの速度(ジャンプ中)
  int bx,by;               // 敵キャラの座標を保持するための変数
  初期化処理();

  x = 100; y= 400;            // マイキャラの座標初期化
  vx = 0; vy = 0;            // マイキャラの速度は0
  bx = 640; by = 400;           // 敵キャラの座標初期化
  loop {
    ClearSecondary();          // 画面クリア
    マイキャラ表示(x,y);
    ブロック表示(bx,by);

    // マイキャラの移動
    KeyInput();             // キー入力
    if (IsPressLeftKey()) x = x - 8;  // 左が押されていればx座標を-8せよ
    if (IsPressRightKey()) x = x + 8;  // 右が押されていればx座標を+8せよ
    if (x<0) x = 0;           // 画面左端からは出れない
    if (x>640-48) x=640-48;       // 画面右端からは出れない
    if (IsPushSpaceKey() && (vy==0)) { // ジャンプ処理
      vy = -10;  // 初速度
    }
    y = y + vy;             // フレーム毎に速度を加算
    vy = vy + 1;            // フレーム毎に加速度を速度に加算
    if (y >= 400) vy = 0;        // y座標が基準位置より下にあれば、
                      // 地面に衝突したものとして速度を0に


    // 敵キャラの移動
    bx = bx - 8;            // 8ドットずつ左へ
    if (bx<-48) bx = 640;        // 画面左に消えていればまた右端から出現

    halt;                // 画面表示

    if (接触判定(x,y,bx,by)) break;
  }

  ゲームオーバー表示();
}

どうですか?無事にジャンプできるようになりましたでしょうか?

ジャンプ中に左右に移動できてしまうと、簡単すぎるよ!とかそういう話も聞こえてくるかも知れません。あるいは、ブロックが一つずつだなんて、つまらないよ!という話もあるかも知れません。得点が表示されていないだなんて、ゲームじゃないよ!だとか、効果音が鳴らないなんて旧石器時代のゲームなの?だとか、言われそうです(笑) しかし、待ってください。よしんば、やねうらおが聖徳太子の生まれ変わりだったとしても、一度にすべては出来ません(笑)

ジャンプ中に左右に移動できなくすることも、ブロックを複数出現させることも、得点を表示することも、効果音を鳴らすことも、いわば肉付けです。骨格さえきちんと出来ていれば、さほど難しいことではないのです。それでは、果たして、いまのままで、骨格は十分だと言えるのでしょうか?

これは、やねうらおに言わせれば、断じてNo!です。いまのままでは、ガタガタの腰砕けです。プログラムは、建築物と同じで、それほど高く積み上げないならば、ある程度、基礎建築がいいかげんでも何とか形になるものですが、基礎がしっかり出来ていないものをある程度の規模にしようとすると、ぐらぐらと崩れてしまうのです。

もったいぶらずにはっきり言いましょう。マイキャラおよび敵キャラの移動部分が、独立していないこと。まずは、それが気になるのです。


3.使いまわしの利くプログラムを目指して

では、独立しているとはどういうことを意味するかというと、関数として分離されていることです。関数として分離されているとどんなメリットがあるのかというと、別の部分で使いまわしが利くということです。たとえば、先ほどの接触判定という関数は、主人公とブロックの交差判定を調べるためだけのものではなく、48ドット×48ドットで構成されているキャラクター同士の接触判定ならば、何であっても調べることが出来ます。

同様に、関数として分離しておけば、他のジャンプ型アクションゲームに流用できる可能性が生まれてくるのです。もっとも、こんな小さな規模のプログラムならば、使いまわしが出来たとしてもたかだか知れているでしょうが、場合によってはプログラムの90%以上が再利用できることだってあるのです。

「わかったわかった。関数化すりゃーいいんだろ〜」

ところが、これが、そう簡単な話ではないのです。実際に、M君と、S君の二人に関数化をしてみるようにお願いしてみました。そのプログラムを通して、この問題の奥に秘められたプログラムの本質について考えてみましょう。

まず、M君は、関数として分離するために、以下のようにしました。

void main(){
  int x,y;                // マイキャラの座標を保持するための変数
  int vx,vy;               // マイキャラの速度(ジャンプ中)
  int bx,by;               // 敵キャラの座標を保持するための変数
  初期化処理();

  x = 100; y= 400;            // マイキャラの座標初期化
  vx = 0; vy = 0;            // マイキャラの速度は0
  bx = 640; by = 400;           // 敵キャラの座標初期化
  loop {
    ClearSecondary();          // 画面クリア
    マイキャラ表示(x,y);
    ブロック表示(bx,by);

    マイキャラ移動(x,y,vx,vy);
    ブロック移動(bx,by);


    halt;                // 画面表示
    if (接触判定(x,y,bx,by)) break;
  }

  ゲームオーバー表示();
}

void マイキャラ移動(int x,int y,int vx,int vy){
  // マイキャラの移動
  KeyInput();             // キー入力
  if (IsPressLeftKey()) x = x - 8;  // 左が押されていればx座標を-8せよ
  if (IsPressRightKey()) x = x + 8;  // 右が押されていればx座標を+8せよ
  if (x<0) x = 0;           // 画面左端からは出れない
  if (x>640-48) x=640-48;       // 画面右端からは出れない
  if (IsPushSpaceKey() && (vy==0)) { // ジャンプ処理
    vy = -10;  // 初速度
  }
  y = y + vy;             // フレーム毎に速度を加算
  vy = vy + 1;            // フレーム毎に加速度を速度に加算
  if (y >= 400) vy = 0;        // y座標が基準位置より下にあれば、
                    // 地面に衝突したものとして速度を0に
}

void ブロック移動(int bx,int by){
  // 敵キャラの移動
  bx = bx - 8;            // 8ドットずつ左へ
  if (bx<-48) bx = 640;        // 画面左に消えていればまた右端から出現
}

マイキャラ移動と、ブロックの移動を関数として分離し、それを呼び出すようにしてあるようです。しかし残念なことに、これは、実行しても正しく動きません。ブロックがどうも表示されないし、主人公も移動しないのです。M君は、首をかしげてしまいました。

しばらくM君は悩んでいたのですが、関数のなかで変数を変更しても呼び出し元の変数の値には影響を及ぼさないという事実を思い出し、そのまま考え込んでしまいました。結局、マイキャラ移動関数のなかでいくらx,y,vx,vyを変更しても、それが呼び出し元のx,y,vx,vyの変更につながらないのでは、何をやっていることにもならないというわけです。

私が思うに、M君の関数化の発想は正しかったし、評価できるところがあると思うのですが、やや正解には届かないようです。

S君はどうでしょうか?S君は、以下のようなプログラムを書きました。(全文掲載)

// ゲーム内で使う変数
  int x,y;                // マイキャラの座標を保持するための変数
  int vx,vy;               // マイキャラの速度(ジャンプ中)
  int bx,by;               // 敵キャラの座標を保持するための変数

void main(){
  初期化処理();
  loop {
    ClearSecondary();          // 画面クリア
    マイキャラ表示();
    ブロック表示();

    マイキャラ移動();
    ブロック移動();

    halt;                // 画面表示
    if (接触判定()) break;
  }

  ゲームオーバー表示();
}

void 初期化処理(){
  x = 100; y= 400;            // マイキャラの座標初期化
  vx = 0; vy = 0;            // マイキャラの速度は0
  bx = 640; by = 400;           // 敵キャラの座標初期化

  SetFPS(30);               // フレームレートを30に
  LoadBitmap("script/doli.bmp",0,1);
}

void マイキャラ表示(){
  BltRect(0,x,y,0,0,48,48);        // 0番のプレーンの(0,0)から48×48を表示
}

void ブロック表示(){
  BltRect(0,bx,by,0,48,48,48);        // 0番のプレーンの(0,48)から48×48を表示
}

int 接触判定(){
  int x1,y1,x2,y2,x3,y3,x4,y4;
  x1 = x; y1 = y;
  x3 = bx; y3 = by;
  x2 = x1 + 32; y2 = y1 + 32;   // 主人公の矩形領域 :(x1,y1)-(x2,y2)
  x4 = x3 + 32; y4 = y2 + 32;   // ブロックの矩形領域:(x3,y3)-(x4,y4)
  x1 = x1 + 16; y1 = y1 + 16;
  x3 = x3 + 16; y3 = y3 + 16;

  if( (x1 < x4) && (x3 < x2) && (y1 < y4) && (y3 < y2) ) {
    // 当たり!
    return 1;
  }
  // 当たっていない!
  return 0;
}

void ゲームオーバー表示(){
  TextLayerOn(0,50,200);
  TextSize(0,60);
  TextOut(0,"Game Over");
  loop
    halt;
}

void マイキャラ移動(){
  // マイキャラの移動
  KeyInput();             // キー入力
  if (IsPressLeftKey()) x = x - 8;  // 左が押されていればx座標を-8せよ
  if (IsPressRightKey()) x = x + 8;  // 右が押されていればx座標を+8せよ
  if (x<0) x = 0;           // 画面左端からは出れない
  if (x>640-48) x=640-48;       // 画面右端からは出れない
  if (IsPushSpaceKey() && (vy==0)) { // ジャンプ処理
    vy = -10;  // 初速度
  }
  y = y + vy;             // フレーム毎に速度を加算
  vy = vy + 1;            // フレーム毎に加速度を速度に加算
  if (y >= 400) vy = 0;        // y座標が基準位置より下にあれば、
                    // 地面に衝突したものとして速度を0に
}

void ブロック移動(){
  // 敵キャラの移動
  bx = bx - 8;            // 8ドットずつ左へ
  if (bx<-48) bx = 640;        // 画面左に消えていればまた右端から出現
}

なるほど、変数を関数の外におけば(これをグローバル変数と呼びます)、どこからでもアクセスできる、というわけです。確かにプログラムも動きます。複数の関数からアクセスしたい変数は、このように関数の“外に出す”必要がある場合もあるかも知れません。しかし、今回に限って言えば、これは明らかな改悪だと言えると思います。たとえば、接触判定関数は、このようなグローバル変数にアクセスしているので一般性を失っており、変数(x,y)と(bx,by)との位置関係においてしか接触判定を調べることが出来ません。別の言葉で言えば、他のキャラクター同士の判定を行なわせることは不可能となっているということです。また、ブロック表示関数は、(bx,by)の座標にしか表示できません。これも別の言葉で言い換えれば、この変数で指し示される座標以外に表示することは不可能というわけです。

このように、グローバル変数との癒着がある関数は、どうも流用しにくいのです。その後、M君とS君は協力し合って、以下のようなプログラムにこぎつけました。(全文掲載)

void main(){
  int x,y;                // マイキャラの座標を保持するための変数
  int vx,vy;               // マイキャラの速度(ジャンプ中)
  int bx,by;               // 敵キャラの座標を保持するための変数
  初期化処理();

  x = 100; y= 400;            // マイキャラの座標初期化
  vx = 0; vy = 0;            // マイキャラの速度は0
  bx = 640; by = 400;           // 敵キャラの座標初期化
  loop {
    ClearSecondary();          // 画面クリア
    マイキャラ表示(x,y);
    ブロック表示(bx,by);

    マイキャラ移動(&x,&y,&vx,&vy);
    ブロック移動(&bx,&by);

    halt;                // 画面表示
    if (接触判定(x,y,bx,by)) break;
  }

  ゲームオーバー表示();
}

void 初期化処理(){
  SetFPS(30);               // フレームレートを30に
  LoadBitmap("script/doli.bmp",0,1);
}

void マイキャラ表示(int x,int y){
  BltRect(0,x,y,0,0,48,48);        // 0番のプレーンの(0,0)から48×48を表示
}

void ブロック表示(int x,int y){
  BltRect(0,x,y,0,48,48,48);       // 0番のプレーンの(0,48)から48×48を表示
}

int 接触判定(int x1,int y1,int x3,int y3){
  int x2,x4,y2,y4;
  x2 = x1 + 32; y2 = y1 + 32;   // 主人公の矩形領域 :(x1,y1)-(x2,y2)
  x4 = x3 + 32; y4 = y2 + 32;   // ブロックの矩形領域:(x3,y3)-(x4,y4)
  x1 = x1 + 16; y1 = y1 + 16;
  x3 = x3 + 16; y3 = y3 + 16;

  if( (x1 < x4) && (x3 < x2) && (y1 < y4) && (y3 < y2) ) {
    // 当たり!
    return 1;
  }
  // 当たっていない!
  return 0;
}

void ゲームオーバー表示(){
  TextLayerOn(0,50,200);
  TextSize(0,60);
  TextOut(0,"Game Over");
  loop
    halt;
}

void マイキャラ移動(int* x,int* y,int* vx,int* vy){
  // マイキャラの移動
  KeyInput();             // キー入力
  if (IsPressLeftKey()) *x = *x - 8; // 左が押されていればx座標を-8せよ
  if (IsPressRightKey()) *x = *x + 8; // 右が押されていればx座標を+8せよ
  if (*x<0) *x = 0;          // 画面左端からは出れない
  if (*x>640-48) *x=640-48;      // 画面右端からは出れない
  if (IsPushSpaceKey() && (*vy==0)) { // ジャンプ処理
    *vy = -10; // 初速度
  }
  *y = *y + *vy;           // フレーム毎に速度を加算
  *vy = *vy + 1;           // フレーム毎に加速度を速度に加算
  if (*y >= 400) *vy = 0;       // y座標が基準位置より下にあれば、
                    // 地面に衝突したものとして速度を0に
}

void ブロック移動(int* bx,int* by){
  // 敵キャラの移動
  *bx = *bx - 8;           // 8ドットずつ左へ
  if (*bx<-48) *bx = 640;       // 画面左に消えていればまた右端から出現
}

先のプログラムと何がどう変わっているか見比べてください。関数の呼び出し側に、&があること、関数の定義部分で intのあと、* (アスタリスク)があること、そして、実際に変数を使うときには、変数の前に * があることの3点ですね。これは、ポインタというもので、C言語の入門書なんか見れば載っているので興味のある方は、そちらを見ていただくことにして、ともかく、こうすれば、関数内から、呼び出し側の変数の値をいじくれると、そういうわけです。これによって、グローバル変数にアクセスすることなく、関数を書くことが出来たので、めでたく関数の独立に成功したと言えます。だから、そうですね。今日を、関数の独立記念日と定めましょう。(嘘です)


4.ゲームらしく拡張する

では、実際に肉付けしていきましょう。M君とS君が協力して1時間ほどで肉づけしてくれたので、これ(右クリックしてファイルに保存して、ファイルを解凍後、scriptフォルダにコピーして、実行してください)を見てください。

背景は、WAFFLEから12月15日発売になる『HAPPYほたる荘』の画像から拝借させていただきました。特に必然性はないんですけどね(笑) そういや、この真中に立ってる二人のヒロインの女の子、実は、私と苗字が同じなんですけど^^; 勝手に人の名前、使わんといてや〜>企画者(笑)

ともかく、プログラムの勉強は、他人のプログラムを読むことから始まります。M君とS君が書いたソースプログラムを見て、知らない命令があれば、そこをマニュアルで調べていくうちに自然と習得出来ます。他人のソースを見るというのは、まわりくどいようで、案外近道なんです。

効果音やBGが入っただけで、ずいぶんゲームらしくなりましたね。プログラム的には、まだまだですが、初心者同然のM君とS君がほんの2時間ほどで作ったプログラムが、これだけ動くっていうことに勇気づけられるかも知れません。一応、プロの観点として、いくつか彼らのプログラムの問題点を指摘しておきます。余力のある人は、チャレンジしてみてください。



《問題提起》


1.出現するブロックは、乱数によって発生させてあるが、最悪の場合、クリアできないような配置になってしまい得る。これを改善するには、どのような方法が考えられるだろうか?

2.ブロック表示→ブロック移動→(ブロックと主人公の)接触判定という手順で判定を行なうと、移動後の(表示上されていない)座標との接触判定を行なうことになり、見た目は接触していないのに接触したと判定されることは無いだろうか?もしそうだとしたら、順番を入れ替えて、ブロック移動→ブロック表示→接触判定という順番にすれば良さそうな気がする。しかし、このアイデアは失敗する。なぜなら、ブロックを移動させたのち、そのブロックが画面端に到達して無効になることがあり、無効になってしまったブロックに対して表示処理を行なうことは、不正だからだ。これを改善するには、どのような方法が考えられるだろうか?

3.落下してきたブロックは、地面に叩きつけられた瞬間に突然消失してしまう。これをもっと滑らかに消す手段は無いだろうか?

4.ジャンプ出来る高さは固定だが、これをスペースキーを押している長さで調整できるようには出来ないだろうか?そのときも、主人公には放物線を描かせ、軌跡が不自然に見えることのないようにするには、どうすればいいだろうか?

5.主人公の歩き、ジャンプ、走りなどに応じてモーションを変化させたりアニメーションさせるにはどうすればいいだろうか?


それほど難しいことではありません。いままでやってきたことの組み合わせで済みます。プログラムを並びかえたりして、試行錯誤することが大切なのです。ぜひ、ご自分の目で確かめてみられることをお勧めします。


5.おまけ

今回はどうでした?そろそろゲームプログラムにも慣れてきましたか?次回は、ノベルや、パズルゲーム等を作りながら、さらに実践的テクニックを公開しようかと思っています。楽しみにしていてください。そして、今回は、おまけを2つ用意しました。これらを活用して、楽しいプログラミングの時間をお過ごしください。それでは!

おまけ1.yanePack/yanePackEx

複数のファイルをひとまとめにするyanePack、そして、ひとまとめにするときに圧縮も行なう、yanePackEx。圧縮されているファイルも、ゲームスクリプトのなかから見れば、普通のファイルのように扱えるので非常に便利です。実際、yanePackは、『盗撮マニア』,『Message』,『夜這いマニア』,yanePackExは、WAFFLE製作の『Revolution』,『ほたる荘』(いずれもWAFFLE販売)で使いました。ということは、これさえあれば、ゲームのCGやら何やらが見れちゃう!ような見れないような…(詳しいことはここでは言えません(笑))

ファイルを配布するときに便利なので、是非活用してみてください。


おまけ2.インポートライブラリ

yaneuraoGameScript2000本体には、それほど多くの関数は用意してありません。むしろ、簡単なゲームを作るための必要最低限の機能しか無いと言っても過言ではないかも知れません。しかし、それを補うためのインポートライブラリ集を用意しています。これを使えば、さらに高度なことまで出来ますし、私のほかにも自作インポートライブラリを公開されている方がおられるので、無限の可能性を秘めていると言えるかも知れません。