ポンクソフト

ドットイートゲームの作成(チュートリアル) - C/C++言語とDXライブラリでゲーム作成入門

前ページ C/C++言語とDXライブラリでゲーム作成入門 TOP 次ページ

目次

  1. C/C++言語とDXライブラリでゲーム作成入門
  2. シューティングゲームの作成(チュートリアル)
  3. ドットイートゲームの作成(チュートリアル)
  4. 15パズルの作成
  5. 神経衰弱の作成
  6. オセロの作成
  7. オブジェクト指向を活用したシューティングゲーム

このページの内容

今回の目的

ドットイートタイプゲームの作成を通じて、マップ・アニメーション・サウンドなどの作成方法を学びます。

ソースファイル

今回の講座のソースを全て含んだプロジェクトファイル(Visual C++ 2010)を以下に置いておきます。
doteat.zip

自機を移動する

まず、自機が上下左右に動くだけのプログラムを作成します。

自機キャラクタの作成

「doteat」というプロジェクトを作成し、ペイントソフトなどで自機画像を32×32ピクセルのPNG形式で作り、「player1.png」という名前でプロジェクトフォルダに保存してください。

自機画像の例
doteat_img/player1.png

プログラムの入力・実行

「doteat」プロジェクトに「doteat1.cpp」という名前の新規ソースファイルを追加し以下のプログラムを入力してください。
#include "DxLib.h"

int player;
int px = 1, py = 1;

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
  ChangeWindowMode(TRUE);
  DxLib_Init();
  SetDrawScreen(DX_SCREEN_BACK);

  player = LoadGraph("player1.png");

  while (!ProcessMessage()) {
    ClearDrawScreen();

    if (CheckHitKey(KEY_INPUT_LEFT)) px--;
    if (CheckHitKey(KEY_INPUT_RIGHT)) px++;
    if (CheckHitKey(KEY_INPUT_UP)) py--;
    if (CheckHitKey(KEY_INPUT_DOWN)) py++;

    DrawGraph(px * 32, py * 32, player, FALSE);
    ScreenFlip();
    WaitTimer(80);
  }

  DxLib_End();
  return 0;
}
入力が完了したらメニューの「デバッグ」→「デバッグなしで開始」を選び、プログラムを実行します。自機が画面上に表示され、カーソルキーで上下左右に移動することができれば正常です。

解説

int player;
playerは自機画像が入るグラフィックハンドルです。
int px = 1, py = 1;
自機の最初の座標を(1, 1)にしています。今回はのゲームでは1ピクセル毎ではなく32ピクセル毎に自機が動き、実際の座標はこの変数の値を32倍したものになります。つまり実際の座標は(32, 32)です。
    if (CheckHitKey(KEY_INPUT_LEFT)) px--;
    if (CheckHitKey(KEY_INPUT_RIGHT)) px++;
    if (CheckHitKey(KEY_INPUT_UP)) py--;
    if (CheckHitKey(KEY_INPUT_DOWN)) py++;
カーソルキーによって上下左右に移動する部分です。
    WaitTimer(80);
32ピクセルずつ自機が動くのでは速すぎるので少し時間待ちをします。WaitTimerは引数で指定した時間止まる関数です。単位はミリ秒なので、ここで80ミリ秒待ちます。

マップの表示

次に迷路のマップを表示するプログラムを作成します。マップを構成するパーツを「チップ」と呼びます。チップの種類は「通路」「壁」「エサ」を作ります。

チップ画像の作成

横96×縦32ピクセルの画像を作り、左から32ピクセルずつ通路・壁・エサの画像を描き、「chip.png」という名前でプロジェクトフォルダに保存してください。

チップ画像の例
doteat_img/chip.png

プログラムの入力・実行

「doteat」プロジェクトに「doteat2.cpp」ファイルを追加し、doteat1.cppの内容をコピーし、doteat1.cppはプロジェクトから除外しておきます。
doteat2.cppを以下のように変更します。
#include "DxLib.h"

int player, chip[3];
int px = 1, py = 1;

int map[15][20] = {
  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
  {1,0,0,0,0,0,2,2,2,2,2,2,2,1,0,0,0,0,0,1},
  {1,0,0,0,0,0,2,2,2,2,2,2,2,1,0,0,0,0,0,1},
  {1,0,0,1,1,0,0,1,1,1,0,0,0,1,1,0,0,0,0,1},
  {1,2,2,0,1,0,2,2,2,1,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,1,0,2,2,2,1,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,0,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,1,1,1,1,1,1,1,1,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,0,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,1,2,2,2,0,1,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,1,2,2,2,0,1,0,0,2,2,1},
  {1,0,0,0,1,1,0,0,0,1,1,1,0,0,1,1,0,2,2,1},
  {1,0,0,0,0,1,2,2,2,2,2,2,2,0,0,0,0,0,0,1},
  {1,0,0,0,0,1,2,2,2,2,2,2,2,0,0,0,0,0,0,1},
  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
};

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
  ChangeWindowMode(TRUE);
  DxLib_Init();
  SetDrawScreen(DX_SCREEN_BACK);

  player = LoadGraph("player1.png");
  LoadDivGraph("chip.png", 3, 3, 1, 32, 32, chip);

  while (!ProcessMessage()) {
    ClearDrawScreen();

    if (CheckHitKey(KEY_INPUT_LEFT)) px--;
    if (CheckHitKey(KEY_INPUT_RIGHT)) px++;
    if (CheckHitKey(KEY_INPUT_UP)) py--;
    if (CheckHitKey(KEY_INPUT_DOWN)) py++;

    for (int y = 0; y < 15; y++) {
      for (int x = 0; x < 20; x++) {
        DrawGraph(x * 32, y * 32, chip[map[y][x]], FALSE);
      }
    }
    DrawGraph(px * 32, py * 32, player, FALSE);
    ScreenFlip();
    WaitTimer(80);
  }

  DxLib_End();
  return 0;
}
入力が完了したらメニューの「デバッグ」→「デバッグなしで開始」を選び、プログラムを実行します。以下のようなマップが表示されればOKです。当たり判定はまだないので、自機を動かしても壁をすり抜けます。
doteat_img/map.gif

解説

int map[15][20] = {
  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
  {1,0,0,0,0,0,2,2,2,2,2,2,2,1,0,0,0,0,0,1},
  {1,0,0,0,0,0,2,2,2,2,2,2,2,1,0,0,0,0,0,1},
  {1,0,0,1,1,0,0,1,1,1,0,0,0,1,1,0,0,0,0,1},
  {1,2,2,0,1,0,2,2,2,1,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,1,0,2,2,2,1,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,0,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,1,1,1,1,1,1,1,1,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,0,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,1,2,2,2,0,1,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,1,2,2,2,0,1,0,0,2,2,1},
  {1,0,0,0,1,1,0,0,0,1,1,1,0,0,1,1,0,2,2,1},
  {1,0,0,0,0,1,2,2,2,2,2,2,2,0,0,0,0,0,0,1},
  {1,0,0,0,0,1,2,2,2,2,2,2,2,0,0,0,0,0,0,1},
  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
};
マップを2次元配列で作成します。要素の値として0が通路、1が壁、2がエサに該当します。
  LoadDivGraph("chip.png", 3, 3, 1, 32, 32, chip);
画像を分割して配列にグラフィックハンドルを読み込む関数です。画像chip.pngを、分割総数3、横分割数3、縦分割数1、ひとつの分割画像のサイズ32×32で配列chipに読み込みます。
chip[0]に通路、chip[1]に壁、chip[2]にエサのグラフィックハンドルが読み込まれます。
    for (int y = 0; y < 15; y++) {
      for (int x = 0; x < 20; x++) {
        DrawGraph(x * 32, y * 32, chip[map[y][x]], FALSE);
      }
    }
マップを実際に描画している部分です。縦方向に15回、横方向に20回ループしています。

当たり判定・エサを食べる

続いて壁との当たり判定と、エサを食べたときにスコアを加算する処理を加えます。

プログラムの入力・実行

「doteat」プロジェクトに「doteat3.cpp」ファイルを追加し、doteat2.cppの内容をコピーし、doteat2.cppはプロジェクトから除外しておきます。
doteat3.cppを以下のように変更します。
#include "DxLib.h"

int player, chip[3];
int px = 1, py = 1;
int score = 0;

int map[15][20] = {
  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
  {1,0,0,0,0,0,2,2,2,2,2,2,2,1,0,0,0,0,0,1},
  {1,0,0,0,0,0,2,2,2,2,2,2,2,1,0,0,0,0,0,1},
  {1,0,0,1,1,0,0,1,1,1,0,0,0,1,1,0,0,0,0,1},
  {1,2,2,0,1,0,2,2,2,1,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,1,0,2,2,2,1,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,0,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,1,1,1,1,1,1,1,1,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,0,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,1,2,2,2,0,1,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,1,2,2,2,0,1,0,0,2,2,1},
  {1,0,0,0,1,1,0,0,0,1,1,1,0,0,1,1,0,2,2,1},
  {1,0,0,0,0,1,2,2,2,2,2,2,2,0,0,0,0,0,0,1},
  {1,0,0,0,0,1,2,2,2,2,2,2,2,0,0,0,0,0,0,1},
  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
};

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
  ChangeWindowMode(TRUE);
  DxLib_Init();
  SetDrawScreen(DX_SCREEN_BACK);

  player = LoadGraph("player1.png");
  LoadDivGraph("chip.png", 3, 3, 1, 32, 32, chip);

  while (!ProcessMessage()) {
    ClearDrawScreen();

    int kx = px, ky = py;
    if (CheckHitKey(KEY_INPUT_LEFT)) kx--;
    if (CheckHitKey(KEY_INPUT_RIGHT)) kx++;
    if (CheckHitKey(KEY_INPUT_UP)) ky--;
    if (CheckHitKey(KEY_INPUT_DOWN)) ky++;
    if (map[ky][kx] != 1) {
      px = kx;
      py = ky;
    }
    if (map[py][px] == 2) {
      map[py][px] = 0;
      score += 10;
    }

    for (int y = 0; y < 15; y++) {
      for (int x = 0; x < 20; x++) {
        DrawGraph(x * 32, y * 32, chip[map[y][x]], FALSE);
      }
    }
    DrawGraph(px * 32, py * 32, player, FALSE);
    DrawFormatString(0, 0, GetColor(255, 255, 255), "SCORE : %d", score);
    ScreenFlip();
    WaitTimer(80);
  }

  DxLib_End();
  return 0;
}
入力が完了したらメニューの「デバッグ」→「デバッグなしで開始」を選び、プログラムを実行します。壁との当たり判定とエサを食べる処理が追加されていればOKです。

解説

int score = 0;
スコアの入る変数です。
    int kx = px, ky = py;
自機が移動した場所に壁(1)があった場合、元の位置に戻さなければならないため、元の座標を保存しておく必要があります。今回は仮の座標(kx, ky)を作り、それに対して移動の操作を行うことによって、元の座標に戻れるようにしています。
    if (map[ky][kx] != 1) {
      px = kx; py = ky;
    }
仮の座標で移動した先が壁でないときに本当の座標(px, py)に仮の座標を反映します。
    if (map[py][px] == 2) {
      map[py][px] = 0;
      score += 10;
    }
自機が移動した先がエサ(2)だった場合は通路(0)にすることによって「エサを食べた」こととし、スコアに10点加算します。

敵を追加

続いて自機を追いかけてくる敵を追加します。

敵キャラクタの作成

ペイントソフトなどで敵画像を32×32ピクセルのPNG形式で作り、「enemy.png」という名前でプロジェクトフォルダに保存してください。

敵画像の例
doteat_img/enemy.png

プログラムの入力・実行

「doteat」プロジェクトに「doteat4.cpp」ファイルを追加し、doteat3.cppの内容をコピーし、doteat3.cppはプロジェクトから除外しておきます。
doteat4.cppを以下のように変更します。
#include "DxLib.h"

int player, enemy, chip[3];
int px = 1, py = 1;
int ex = 18, ey = 13;
int score = 0;

int map[15][20] = {
  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
  {1,0,0,0,0,0,2,2,2,2,2,2,2,1,0,0,0,0,0,1},
  {1,0,0,0,0,0,2,2,2,2,2,2,2,1,0,0,0,0,0,1},
  {1,0,0,1,1,0,0,1,1,1,0,0,0,1,1,0,0,0,0,1},
  {1,2,2,0,1,0,2,2,2,1,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,1,0,2,2,2,1,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,0,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,1,1,1,1,1,1,1,1,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,0,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,1,2,2,2,0,1,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,1,2,2,2,0,1,0,0,2,2,1},
  {1,0,0,0,1,1,0,0,0,1,1,1,0,0,1,1,0,2,2,1},
  {1,0,0,0,0,1,2,2,2,2,2,2,2,0,0,0,0,0,0,1},
  {1,0,0,0,0,1,2,2,2,2,2,2,2,0,0,0,0,0,0,1},
  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
};

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
  ChangeWindowMode(TRUE);
  DxLib_Init();
  SetDrawScreen(DX_SCREEN_BACK);

  player = LoadGraph("player1.png");
  enemy = LoadGraph("enemy.png");
  LoadDivGraph("chip.png", 3, 3, 1, 32, 32, chip);

  while (!ProcessMessage()) {
    ClearDrawScreen();

    int kx = px, ky = py;
    if (CheckHitKey(KEY_INPUT_LEFT)) kx--;
    if (CheckHitKey(KEY_INPUT_RIGHT)) kx++;
    if (CheckHitKey(KEY_INPUT_UP)) ky--;
    if (CheckHitKey(KEY_INPUT_DOWN)) ky++;
    if (map[ky][kx] != 1) {
      px = kx; py = ky;
    }
    if (map[py][px] == 2) {
      map[py][px] = 0;
      score += 10;
    }

    kx = ex; ky = ey;
    if (rand() % 3 == 0) {
      if (kx > px) kx--;
      if (kx < px) kx++;
      if (ky > py) ky--;
      if (ky < py) ky++;
    } else {
      kx += rand() % 3 - 1;
      ky += rand() % 3 - 1;
    }
    if (map[ky][kx] != 1) {
      ex = kx; ey = ky;
    }

    for (int y = 0; y < 15; y++) {
      for (int x = 0; x < 20; x++) {
        DrawGraph(x * 32, y * 32, chip[map[y][x]], FALSE);
      }
    }
    DrawGraph(px * 32, py * 32, player, FALSE);
    DrawGraph(ex * 32, ey * 32, enemy, FALSE);
    DrawFormatString(0, 0, GetColor(255, 255, 255), "SCORE : %d", score);
    ScreenFlip();
    WaitTimer(80);
  }

  DxLib_End();
  return 0;
}
入力が完了したらメニューの「デバッグ」→「デバッグなしで開始」を選び、プログラムを実行します。追いかけてくる敵が追加されていればOKです。

解説

int ex = 18, ey = 13;
敵の座標です。自機と同じく実際の座標は32を掛けた値となります。初期位置を画面右下にしています。
    kx = ex; ky = ey;
ここから、敵の移動部分です。まず自機と同じく壁にぶつかったときに元に戻せるように、仮の座標(kx, ky)に対して移動の操作を行います。
if (rand() % 3 == 0) {
このif文の条件が真の場合(1/3の確率)自機を追いかけ、偽の場合(2/3の確率)ランダムに動くようなロジックにしています。
      if (kx > px) kx--;
      if (kx < px) kx++;
      if (ky > py) ky--;
      if (ky < py) ky++;
自機を追いかける場合は、自機と敵との座標を比較し、近づくように動きます。
      kx += rand() % 3 - 1;
      ky += rand() % 3 - 1;
ランダムに動かしている部分です。現在の座標に-1, 0, 1のいずれかが足されます。

ゲームオーバー・ゲームクリア

続いてゲームオーバーとゲームクリアの処理を追加します。

プログラムの入力・実行

「doteat」プロジェクトに「doteat5.cpp」ファイルを追加し、doteat4.cppの内容をコピーし、doteat4.cppはプロジェクトから除外しておきます。
doteat5.cppを以下のように変更します。
#include "DxLib.h"

int player, enemy, chip[3];
int px = 1, py = 1;
int ex = 18, ey = 13;
int score = 0;
int esa_num = 0;
enum {PLAY, OVER, CLEAR} status = PLAY;

int map[15][20] = {
  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
  {1,0,0,0,0,0,2,2,2,2,2,2,2,1,0,0,0,0,0,1},
  {1,0,0,0,0,0,2,2,2,2,2,2,2,1,0,0,0,0,0,1},
  {1,0,0,1,1,0,0,1,1,1,0,0,0,1,1,0,0,0,0,1},
  {1,2,2,0,1,0,2,2,2,1,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,1,0,2,2,2,1,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,0,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,1,1,1,1,1,1,1,1,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,0,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,1,2,2,2,0,1,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,1,2,2,2,0,1,0,0,2,2,1},
  {1,0,0,0,1,1,0,0,0,1,1,1,0,0,1,1,0,2,2,1},
  {1,0,0,0,0,1,2,2,2,2,2,2,2,0,0,0,0,0,0,1},
  {1,0,0,0,0,1,2,2,2,2,2,2,2,0,0,0,0,0,0,1},
  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
};

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
  ChangeWindowMode(TRUE);
  DxLib_Init();
  SetDrawScreen(DX_SCREEN_BACK);

  player = LoadGraph("player1.png");
  enemy = LoadGraph("enemy.png");
  LoadDivGraph("chip.png", 3, 3, 1, 32, 32, chip);
  for (int y = 0; y < 15; y++) {
    for (int x = 0; x < 20; x++) {
      if (map[y][x] == 2) esa_num++;
    }
  }

  while (!ProcessMessage() && status == PLAY) {
    ClearDrawScreen();

    int kx = px, ky = py;
    if (CheckHitKey(KEY_INPUT_LEFT)) kx--;
    if (CheckHitKey(KEY_INPUT_RIGHT)) kx++;
    if (CheckHitKey(KEY_INPUT_UP)) ky--;
    if (CheckHitKey(KEY_INPUT_DOWN)) ky++;
    if (map[ky][kx] != 1) {
      px = kx; py = ky;
    }
    if (map[py][px] == 2) {
      map[py][px] = 0;
      score += 10;
      esa_num--;
      if (esa_num <= 0) status = CLEAR;
    }

    kx = ex; ky = ey;
    if (rand() % 3 == 0) {
      if (kx > px) kx--;
      if (kx < px) kx++;
      if (ky > py) ky--;
      if (ky < py) ky++;
    } else {
      kx += rand() % 3 - 1;
      ky += rand() % 3 - 1;
    }
    if (map[ky][kx] != 1) {
      ex = kx; ey = ky;
    }

    if (ex == px && ey == py) status = OVER;

    for (int y = 0; y < 15; y++) {
      for (int x = 0; x < 20; x++) {
        DrawGraph(x * 32, y * 32, chip[map[y][x]], FALSE);
      }
    }
    DrawGraph(px * 32, py * 32, player, FALSE);
    DrawGraph(ex * 32, ey * 32, enemy, FALSE);
    DrawFormatString(0, 0, GetColor(255, 255, 255), "SCORE : %d", score);
    if (status == OVER) DrawString(280, 230, "GAME OVER", GetColor(255, 255, 255));
    if (status == CLEAR) DrawString(280, 230, "GAME CLEAR", GetColor(255, 255, 255));
    ScreenFlip();
    WaitTimer(80);
    if (status != PLAY) WaitTimer(1000);
  }

  DxLib_End();
  return 0;
}
入力が完了したらメニューの「デバッグ」→「デバッグなしで開始」を選び、プログラムを実行します。敵にぶつかったらゲームオーバー、エサを全て食べたらゲームクリアになればOKです。

解説

int esa_num = 0;
ゲームクリアを判定するためにエサの数を保持する変数です。map配列からカウントするので最初は0にしておきます。
enum {PLAY, OVER, CLEAR} status = PLAY;
現在の状態が入る変数です。PLAYが通常、OVERがゲームオーバー、CLEARがゲームクリアとなります。
  for (int y = 0; y < 15; y++) {
    for (int x = 0; x < 20; x++) {
      if (map[y][x] == 2) esa_num++;
    }
  }
map配列からエサの総数をカウントしてesa_numにセットしています。
  while (!ProcessMessage() && status == PLAY) {
状態変数がゲームオーバー・ゲームクリアになったときに終了する部分を追加しています。
    if (map[py][px] == 2) {
      ・・・
      esa_num--;
      if (esa_num <= 0) status = CLEAR;
    }
エサを食べたときにエサの総数を減らす処理をし、エサが無くなったときに状態変数をゲームクリアにしています。
    if (ex == px && ey == py) status = OVER;
敵と自機がぶつかったときに状態変数をゲームオーバーにしています。
    if (status == OVER) DrawString(280, 230, "GAME OVER", GetColor(255, 255, 255));
    if (status == CLEAR) DrawString(280, 230, "GAME CLEAR", GetColor(255, 255, 255));
ゲームオーバー・ゲームクリアのときにメッセージを画面中央に表示しています。
    if (status != PLAY) WaitTimer(1000);
ゲームオーバー・ゲームクリアのときに即座に終わると分かりにくいのでここで1秒待ちます。

自機のアニメーション

続いて自機にアニメーションを付けてみます。

アニメーション付き自機キャラクタの作成

横128×縦64ピクセル画像を作り、縦はアニメーションのパターン、横は左から順に32ピクセルずつ右向き・下向き・左向き・上向きのキャラクタを描き、「player2.png」という名前でプロジェクトフォルダに保存してください。

自機画像の例
doteat_img/player2.png

プログラムの入力・実行

「doteat」プロジェクトに「doteat6.cpp」ファイルを追加し、doteat5.cppの内容をコピーし、doteat5.cppはプロジェクトから除外しておきます。
doteat6.cppを以下のように変更します。
#include "DxLib.h"

int player[8], enemy, chip[3];
int px = 1, py = 1;
int pd = 0, pa = 0;
int ex = 18, ey = 13;
int score = 0;
int esa_num = 0;
enum {PLAY, OVER, CLEAR} status = PLAY;

int map[15][20] = {
  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
  {1,0,0,0,0,0,2,2,2,2,2,2,2,1,0,0,0,0,0,1},
  {1,0,0,0,0,0,2,2,2,2,2,2,2,1,0,0,0,0,0,1},
  {1,0,0,1,1,0,0,1,1,1,0,0,0,1,1,0,0,0,0,1},
  {1,2,2,0,1,0,2,2,2,1,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,1,0,2,2,2,1,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,0,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,1,1,1,1,1,1,1,1,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,0,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,1,2,2,2,0,1,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,1,2,2,2,0,1,0,0,2,2,1},
  {1,0,0,0,1,1,0,0,0,1,1,1,0,0,1,1,0,2,2,1},
  {1,0,0,0,0,1,2,2,2,2,2,2,2,0,0,0,0,0,0,1},
  {1,0,0,0,0,1,2,2,2,2,2,2,2,0,0,0,0,0,0,1},
  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
};

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
  ChangeWindowMode(TRUE);
  DxLib_Init();
  SetDrawScreen(DX_SCREEN_BACK);

  LoadDivGraph("player2.png", 8, 4, 2, 32, 32, player);
  enemy = LoadGraph("enemy.png");
  LoadDivGraph("chip.png", 3, 3, 1, 32, 32, chip);
  for (int y = 0; y < 15; y++) {
    for (int x = 0; x < 20; x++) {
      if (map[y][x] == 2) esa_num++;
    }
  }

  while (!ProcessMessage() && status == PLAY) {
    ClearDrawScreen();

    int kx = px, ky = py;
    if (CheckHitKey(KEY_INPUT_LEFT)) {kx--; pd = 2;}
    if (CheckHitKey(KEY_INPUT_RIGHT)) {kx++; pd = 0;}
    if (CheckHitKey(KEY_INPUT_UP)) {ky--; pd = 3;}
    if (CheckHitKey(KEY_INPUT_DOWN)) {ky++; pd = 1;}
    if (map[ky][kx] != 1) {
      px = kx; py = ky;
    }
    if (map[py][px] == 2) {
      map[py][px] = 0;
      score += 10;
      esa_num--;
      if (esa_num <= 0) status = CLEAR;
    }

    kx = ex; ky = ey;
    if (rand() % 3 == 0) {
      if (kx > px) kx--;
      if (kx < px) kx++;
      if (ky > py) ky--;
      if (ky < py) ky++;
    } else {
      kx += rand() % 3 - 1;
      ky += rand() % 3 - 1;
    }
    if (map[ky][kx] != 1) {
      ex = kx; ey = ky;
    }

    if (ex == px && ey == py) status = OVER;

    for (int y = 0; y < 15; y++) {
      for (int x = 0; x < 20; x++) {
        DrawGraph(x * 32, y * 32, chip[map[y][x]], FALSE);
      }
    }
    DrawGraph(px * 32, py * 32, player[pa * 4 + pd], FALSE);
    pa = 1 - pa;
    DrawGraph(ex * 32, ey * 32, enemy, FALSE);
    DrawFormatString(0, 0, GetColor(255, 255, 255), "SCORE : %d", score);
    if (status == OVER) DrawString(280, 230, "GAME OVER", GetColor(255, 255, 255));
    if (status == CLEAR) DrawString(280, 230, "GAME CLEAR", GetColor(255, 255, 255));
    ScreenFlip();
    WaitTimer(80);
    if (status != PLAY) WaitTimer(1000);
  }

  DxLib_End();
  return 0;
}
入力が完了したらメニューの「デバッグ」→「デバッグなしで開始」を選び、プログラムを実行します。自機がアニメーションしていればOKです。

解説

int pd = 0, pa = 0;
pdは自機の方向を表す変数です。0が右、1が下、2が左、3が上となります。paはアニメーションのパターンを表す変数です。0が上の画像、1が下の画像になります。
  LoadDivGraph("player2.png", 8, 4, 2, 32, 32, player);
画像player2.pngを横分割数4、縦分割数2で配列playerに読み込みます。画像に対する配列の要素は以下のようになります。
player[0]player[1]player[2]player[3]
player[4]player[5]player[6]player[7]
    if (CheckHitKey(KEY_INPUT_LEFT)) {kx--; pd = 2;}
    if (CheckHitKey(KEY_INPUT_RIGHT)) {kx++; pd = 0;}
    if (CheckHitKey(KEY_INPUT_UP)) {ky--; pd = 3;}
    if (CheckHitKey(KEY_INPUT_DOWN)) {ky++; pd = 1;}
上下左右に移動したとき、その方向をpdに格納しておきます。
    DrawGraph(px * 32, py * 32, player[pa * 4 + pd], FALSE);
移動した方向pdとアニメーションのパターンpaからplayer配列の適切な画像を描画しています。
    pa = 1 - pa;
1フレームごとにアニメーションのパターンを0と1で切り替えています。

効果音とBGM

最後に、エサを食べたときの効果音とBGMを付けてみます。

効果音とBGMを用意する

エサを食べたときの効果音を「eat.mp3」、BGMを「bgm.mp3」のファイル名で用意して、プロジェクトのフォルダにコピーしてください。この講座で使われているサンプルでは魔王魂様の素材を使用しています。

プログラムの入力・実行

「doteat」プロジェクトに「doteat7.cpp」ファイルを追加し、doteat6.cppの内容をコピーし、doteat6.cppはプロジェクトから除外しておきます。
doteat7.cppを以下のように変更します。
#include "DxLib.h"

int player[8], enemy, chip[3];
int px = 1, py = 1;
int pd = 0, pa = 0;
int ex = 18, ey = 13;
int score = 0;
int esa_num = 0;
enum {PLAY, OVER, CLEAR} status = PLAY;
int eat;

int map[15][20] = {
  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
  {1,0,0,0,0,0,2,2,2,2,2,2,2,1,0,0,0,0,0,1},
  {1,0,0,0,0,0,2,2,2,2,2,2,2,1,0,0,0,0,0,1},
  {1,0,0,1,1,0,0,1,1,1,0,0,0,1,1,0,0,0,0,1},
  {1,2,2,0,1,0,2,2,2,1,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,1,0,2,2,2,1,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,0,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,1,1,1,1,1,1,1,1,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,0,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,1,2,2,2,0,1,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,1,2,2,2,0,1,0,0,2,2,1},
  {1,0,0,0,1,1,0,0,0,1,1,1,0,0,1,1,0,2,2,1},
  {1,0,0,0,0,1,2,2,2,2,2,2,2,0,0,0,0,0,0,1},
  {1,0,0,0,0,1,2,2,2,2,2,2,2,0,0,0,0,0,0,1},
  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
};

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
  ChangeWindowMode(TRUE);
  DxLib_Init();
  SetDrawScreen(DX_SCREEN_BACK);

  LoadDivGraph("player2.png", 8, 4, 2, 32, 32, player);
  enemy = LoadGraph("enemy.png");
  LoadDivGraph("chip.png", 3, 3, 1, 32, 32, chip);
  for (int y = 0; y < 15; y++) {
    for (int x = 0; x < 20; x++) {
      if (map[y][x] == 2) esa_num++;
    }
  }
  eat = LoadSoundMem("eat.mp3");
  PlaySoundFile("bgm.mp3", DX_PLAYTYPE_LOOP);

  while (!ProcessMessage() && status == PLAY) {
    ClearDrawScreen();

    int kx = px, ky = py;
    if (CheckHitKey(KEY_INPUT_LEFT)) {kx--; pd = 2;}
    if (CheckHitKey(KEY_INPUT_RIGHT)) {kx++; pd = 0;}
    if (CheckHitKey(KEY_INPUT_UP)) {ky--; pd = 3;}
    if (CheckHitKey(KEY_INPUT_DOWN)) {ky++; pd = 1;}
    if (map[ky][kx] != 1) {
      px = kx; py = ky;
    }
    if (map[py][px] == 2) {
      map[py][px] = 0;
      score += 10;
      esa_num--;
      PlaySoundMem(eat, DX_PLAYTYPE_BACK, TRUE);
      if (esa_num <= 0) status = CLEAR;
    }

    kx = ex; ky = ey;
    if (rand() % 3 == 0) {
      if (kx > px) kx--;
      if (kx < px) kx++;
      if (ky > py) ky--;
      if (ky < py) ky++;
    } else {
      kx += rand() % 3 - 1;
      ky += rand() % 3 - 1;
    }
    if (map[ky][kx] != 1) {
      ex = kx; ey = ky;
    }

    if (ex == px && ey == py) status = OVER;

    for (int y = 0; y < 15; y++) {
      for (int x = 0; x < 20; x++) {
        DrawGraph(x * 32, y * 32, chip[map[y][x]], FALSE);
      }
    }
    DrawGraph(px * 32, py * 32, player[pa * 4 + pd], FALSE);
    pa = 1 - pa;
    DrawGraph(ex * 32, ey * 32, enemy, FALSE);
    DrawFormatString(0, 0, GetColor(255, 255, 255), "SCORE : %d", score);
    if (status == OVER) DrawString(280, 230, "GAME OVER", GetColor(255, 255, 255));
    if (status == CLEAR) DrawString(280, 230, "GAME CLEAR", GetColor(255, 255, 255));
    ScreenFlip();
    WaitTimer(80);
    if (status != PLAY) WaitTimer(1000);
  }

  DxLib_End();
  return 0;
}
入力が完了したらメニューの「デバッグ」→「デバッグなしで開始」を選び、プログラムを実行します。エサを食べたときの効果音とBGMを確認してください。

解説

int eat;
エサを食べたときの効果音のサウンドハンドルが入る変数です。
  eat = LoadSoundMem("eat.mp3");
効果音をメモリに読み込みサウンドハンドルを取得します。効果音は頻繁に鳴らずのでメモリ上に置きます。
  PlaySoundFile("bgm.mp3", DX_PLAYTYPE_LOOP);
BGMをファイルから読み込み演奏します。DX_PLAYTYPE_LOOPフラグはループ演奏です。
  PlaySoundMem(eat, DX_PLAYTYPE_BACK, TRUE);
エサを食べたときの効果音をメモリに保存された音声データから再生します。DX_PLAYTYPE_BACKはバックグラウンド演奏で、即座に処理が戻ってきます。

前ページ C/C++言語とDXライブラリでゲーム作成入門 TOP 次ページ
このエントリーをはてなブックマークに追加 そっか0