...

ゲーム制作練習

by user

on
Category: Documents
14

views

Report

Comments

Transcript

ゲーム制作練習
Game Practice 2011
Game Practice
2011
AOI Daichi
MesLibres
Vantan Game Academy
Game Practice - 1 -
Game Practice 2011
目次
1 ゲーム・プログラムの基本..............................3
4.3 衝突判定関数........................................................27
1.1 ゲーム・ライブラリ...............................................3
4.4 自機と敵の衝突処理.............................................28
1.2 開発ツール..............................................................3
4.5 自弾と敵の衝突処理.............................................30
1.3 開発ツールの起動...................................................3
4.6 自機と敵弾の衝突処理.........................................30
1.4 プログラムの流れ...................................................4
4.7 衝突処理に関する主な関数..................................30
1.5 画面の仕様..............................................................5
5 描画キャンバス..............................................31
1.6 色番号......................................................................5
5.1 描画キャンバスの仕組み......................................31
2 画像の表示.......................................................6
5.2 描画キャンバスを用いた描画の基本...................32
2.1 画像データの読み込み~描画~解放.....................6
5.3 描画キャンバスを用いた爆炎の描画...................33
2.2 背景の描画..............................................................7
5.4 描画キャンバスを用いた文字の描画...................35
2.3 キャラクターの描画(自機)................................9
5.5 描画キャンバスに関する主な関数.......................38
2.4 描画に関する主な関数.........................................14
6 サウンド.........................................................39
3 キャラクターの移動・出現・消去.................15
6.1 XACT によるサウンド..........................................39
3.1 キー入力による自機の移動..................................15
6.2 XACT でのサウンド処理の流れ...........................39
3.2 自機の移動範囲の制限.........................................16
6.3 XACT ツールを用いたサウンドデータ作成........40
3.3 インターバルカウンタによる敵の出現...............17
6.4 効果音....................................................................44
3.4 敵の等速度運動.....................................................19
6.5 BGM......................................................................45
3.5 敵の画面範囲外消去.............................................20
6.6 サウンドに関する主な関数..................................46
3.6 キー入力による自機の弾発射..............................21
7 マップ............................................................47
3.7 インターバルカウンタによる弾発射間隔の調整 22
7.1 背景描画のいろいろな方法..................................47
3.8 インターバルカウンタによる敵の弾発射...........23
7.2 マップチップを用いた背景..................................47
3.9 キャラクターに関する主な関数..........................25
7.3 マップチップ画像データ......................................48
4 衝突処理.........................................................26
7.4 マップチップ配置データ......................................49
4.1 衝突判定................................................................26
7.5 マップ表示データ.................................................50
4.2 実体データ............................................................26
7.6 マップの描画........................................................51
7.7 マップスクロール.................................................54
Game Practice - 2 -
1 ゲーム・プログラムの基本
Game Practice 2011
1 ゲーム・プログラムの基本
この章では、ゲーム・プログラムを作成するための、基本知識を説明しよう。
1.1
ゲーム・ライブラリ
WindowsPC で動作するゲーム・プログラムを作るには、いくつかの選択肢があ
るが、その中のひとつが C/C++言語で、DirectX を用いて作る方法だ。
この授業では、その DirectX を簡単に使えるようにした DXUT というライブラリ
を用い、さらにバンタンゲームアカデミーの授業用に開発された、「ゲーム制作
練習用ゲーム・ライブラリ」を用いる。
このゲームライブラリを用いるには、授業で指定された教材データをダウン
ロードすること。
1.2
開発ツール
授業で使う PC にはすでに開発ツールがインストール・セッティングされている。
自宅の PC で、開発する場合は、下記のソフトウェアが必要になる。
Microsoft Visual Studio 2010
Windows 用プログラムを作成するための開発ツー
ルは何種類かあるが、ここでは、Microsoft 社製の
Visual Studio 2010 を用いる。
この開発ツールには、使える機能によってさらに
何種類かあるが、無料で配布している express
edition というバージョンでも、いくつかの機能が省
略されてはいるが、学習用プログラムを作成するに
は十分である。
Microsoft DirectX SDK
Visual Studio の他に、Microsoft DirectX SDK というプログラムが必要になるが、
これも、無料で配布されている。
バージョンは時々新しいものに変わっているが、その時点で最新のものをイン
ストールすればよい。
1.3
開発ツールの起動
Visual Studio 2010 を開始するときは、教材データの中の、ソリューションファイ
ル "*.sln" のアイコンをダブルクリックする。(* は指定したファイル名)
Game Practice - 3 -
1 ゲーム・プログラムの基本
1.4
Game Practice 2011
プログラムの流れ
プログラムを開始する関数は main ではない
コンソール画面上で実行するプログラムは、main 関数から始まるが、ゲーム・
プログラムは、Windows の複雑なしくみの中でプログラムが進むため、main 関数
は存在しない。
InitializeGame 関数から始まる流れ
プログラムの処理は、InitializeGame 関数から始まり、
初期化処理
InitializeGame
右下図のような流れで進む。
Esc が
押されて
いない
InitializeGame 関数は、プログラム開始時に、一度だ
け実行される関数で、システム全体で必要な初期化処理(画
面や色の設定など)を行う。
Yes
UpdateGame 関数・DrawGame 関数は、主処理を担当す
ゲーム主処理
る関数だ。1 秒間に 60 回実行され、毎回これらの関数の実
行が終わる度に、画面の更新処理が行われる。
FinalizeGame 関数は、プログラムが終了する直前に実
終了処理
FinalizeGame
行される関数で、メモリの解放などの終了処理を行う。
プログラム全体の制御や、画面の更新は、これらの関数を呼び出す親関数が
行っている。親関数は、"WinMain.cpp"や、"DxSystem09.cpp"に記述して
ある。
主処理関数
主処理
主処理を行う関数は左図のように、2 つの関数からなって
いる。
データ更新
UpdateGame
UpdateGame 関数は、描画以外のデータの更新処理を記
述する。
描画
DrawGame
DrawGame 関数は、描画処理を記述する。
主処理終了
正しい場所に処理を書く
UpdateGame, DrawGame の各関数は、それぞれ役割が異なる。処理内容に応
じて、正しい関数内に記述し、わかりやすいソースを作るよう、心がけよう。
Game Practice - 4 -
No
1 ゲーム・プログラムの基本
1.5
Game Practice 2011
画面の仕様
ゲーム制作練習用ゲーム・ライブラリ では、下記のような画面サイズ・カラー
モードを使用している。
640 pixels
● 画面サイズ
640 × 480 pixels
●
カラーモード True-Color モード
(フルカラーモード)
480 pixels
画面
画像データファイルを読み込んで描画する場合、フルカラー画像(1677 万色
モード)を用いること。
1.6
色番号
DrawPixel などの描画関数では、色を下記のような数値で指定する。
描画関数の色番号
0x000000 ~ 0xffffff
0x の後ろに赤の輝度(00~ff)・緑の輝度(00~ff)・青の輝度(00~ff)を順に 2
桁ずつの数値で、計 6 桁記述する。
例
明るい赤
0xff0000
(R=ff G=00 B=00)
暗い赤
0x880000
(R=88 G=00 B=00)
明るい緑
0x00ff00
(R=00 G=ff B=00)
暗い青
0x000088
(R=00 G=00 B=88)
明るい紫
0xff00ff
(R=ff G=00 B=ff)
Game Practice - 5 -
2 画像の表示
Game Practice 2011
2 画像の表示
この章では、ファイルとして用意された画像をスクリーン上に描画する処理を
作成する。
2.1
画像データの読み込み~描画~解放
画像データをスクリーンに表示するには、ファイルからメモリ内のテクスチャ
という領域に読み込み、スプライトという仮想スクリーンに描画する。そして、
プログラム終了時には、データを読み込んだテクスチャを解放する。
読み込みから解放までの流れは、下図のようになる。
画像チップの
設定
SetChip etc.
メモリ
テクスチャ
画像
データ
ファイル
1 度だけ
スプライト
毎フレーム
画像
チップ
LoadImage
ReleaseTexture
DrawBg
DrawChip
画像
チップ
終了時
スクリーン(画面)
解放
テクスチャ
テクスチャは元々、3D でポリゴンの表面に貼り付ける模様などのデータを指す。この教材の
システムでは、3D のテクスチャの仕組みを利用して 2D の画像を扱っているのである。
テクスチャはプログラム終了時に解放する必要がある。
スプライト
スプライトは元々、画面上のキャラクタ(人物・物品等)など小さな絵を高速に表示するた
めの技術的な仕組みである。
ここでは、テクスチャとして用意した画像を、ポリゴンや 3D 表示機能を用いないで表示する
ための DirectX の機能を指している。
Game Practice - 6 -
2 画像の表示
2.2
Game Practice 2011
背景の描画
例題 1
image フォルダ内の、640×480 サイズの画像ファイルを読み込み、画像全体を
スクリーンに描画する。
(グローバル変数)
TEXTURE bgTexture;
// 背景画像
InitializeGame 関数(初期化処理)
if (FAILED(LoadImage(L”image¥¥bg.png”, &bgTexture)))
{
DXUTTRACE(L"背景画像がありません¥n");
return E_FAIL;
}
// 初期モードの設定
gameMode = MODE_STAGE; // 動作確認用に、ステージ本編から開始するように変更する
FinalizeGame 関数(終了処理)
ReleaseTexture(&bgTexture);
DrawGame 関数(描画処理)
case MODE_STAGE:
DrawBg(&bgTexture);
break;
なお、上記ソースの灰色文字部分は、7 月 4 日以降に配布された教材では、あら
かじめ記述してあるので、改めて入力する必要はない。
画像のファイル形式
画像のファイル形式は、PNG 形式(拡張子 png)を推奨する。
BMP 形式(拡張子 bmp, dib)や JPEG 形式(拡張子 jpg, jpeg)等にも対応し
ているが、BMP 形式はファイルサイズが大きい点で望ましくなく、JPEG 形式は非
可逆圧縮方式なので、ドット絵などの保存には向かない。他のファイル形式の場
合、Gimp や Jtrim などの画像ツールで PNG 形式に変換するとよい。
読み込める画像の最大サイズ
読み込める画像の最大サイズは、その PC のビデオカードで扱えるテクスチャの
最大サイズとなる。DirectX SDK に付属の DirectX Caps Viewer などで確認すること
ができる。
Game Practice - 7 -
2 画像の表示
Game Practice 2011
読み込み失敗時の処理
画像ファイル名を間違えたり、画像ファイルを指定したフォルダにコピーし忘
れたりすると、LoadImage 関数はエラー値(S_OK 以外の値)を戻す。このとき
は、エラー処理を行わなければならない。
エラー時の処理は、エラーの理由がわかるようにして、処理を打ち切ることが
多い。この教材では、読み込みエラーのとき、DXUTTRACE 関数を用いてエラー
メッセージを出力し、E_FAIL を戻値として終了させることとする。
FAILED マクロ
FAILED(HRESULT 型の式)と記述すると、失敗のとき真、成功のとき偽の値と
なる。
DXUTTRACE 関数
現在のアプリケーションのデバッガに文字列を送信する関数。Visual Studio では、
出力ウインドウに文字列が出力される。
引数は printf 関数と同様である。
E_FAIL
HRESULT 型の定数で、E_FAIL はエラーが起きたことを表す。エラーが起きず
に、正常に終了した場合は、S_OK を用いる。
終了時にテクスチャを解放する
LoadImage 関数で画像をファイルからテクスチャに読み込むとき、テクスチャ
用のメモリが自動的に確保される。このメモリは、そのテクスチャを使い終わっ
たときに解放しなければならない。これを「テクスチャの解放」という。
プログラム終了時に解放されていないテクスチャがあると、「メモリリーク(
= メモリが漏れている)エラー」となってしまう。
テクスチャを解放する処理は、プログラム終了時に実行される FinalizeGame
関数内で、ReleaseTexture 関数を用いる。
Game Practice - 8 -
2 画像の表示
2.3
Game Practice 2011
キャラクターの描画(自機)
例題 2
image フォルダ内の画像ファイルを読み込み、その一部分をスクリーンに描画
する。
(グローバル変数)
TEXTURE charTexture;
// キャラクター画像
CHARACTER myChar;
// キャラクターデータ
InitializeGame 関数(初期化処理)
// キャラクター画像の読み込み
if (FAILED(LoadImage(L”image¥¥hStgChrTest.png”, &charTexture)))
{
DXUTTRACE(L"キャラクター画像がありません¥n");
return E_FAIL;
}
// キャラクターチップの設定
SetChip(&myChar.chip, 0, 0, 48, 32, 0, 0, &charTexture);
// 自機の設定
SetPos(&myChar, 120.0f, 160.0f);
FinalizeGame 関数(終了処理)
ReleaseTexture(&charTexture);
DrawGame 関数(描画処理)
case MODE_STAGE:
DrawChip(&myChar.chip, (int)myChar.posX, (int)myChar.posY);
Game Practice - 9 -
2 画像の表示
Game Practice 2011
キャラクターチップは、キャラクターデータ(CHARACTER)に含まれるチップ
メンバを用いているが、読み込んだ画像の一部を単に表示するだけなら、下記の
ようにチップデータを用意すればよい。
(グローバル変数)
CHIP myChip;
// チップデータ
InitializeGame 関数(初期化処理)
// チップの設定
SetChip(&mychip, …);
DrawGame 関数(描画処理)
case MODE_STAGE:
DrawChip(&mychip, 120, 160);
※画像の読み込み、解放などの処理は省略してある。
キャラクター画像サンプルのレイアウト
ここで用いているキャラクター画像サンプル "hStgChrTest.png"は、下記
のようなパターン配置となっている。
0
0
32
X-Wing
48
64
X-Wing
96
128 144
X-Wing
160
192
X-Wing
48x32
32
Tie-Fighter Tie-Fighter Tie-Fighter Tie-Fighter Tie-Fighter Tie-Fighter
32x32
64
Tie-Fighter-D
96
Tie-Interceptor
240
アニメーション
翼 : 開→閉(左 3 パターンのみ
使用すること)
Tie シリーズの
アニメーションはすべて
機体 : 回転
Tie-Fighter-D
Tie-Fighter-D
Tie-Fighter-D
Tie-Fighter-D
Tie-Fighter-D
Tie-Interceptor
Tie-Interceptor
Tie-Interceptor
Tie-Interceptor
Tie-Interceptor
48x32
48x32
128 AT-AT
AT-AT
160x112
Game Practice - 10 -
アニメーション
歩行
2 画像の表示
Game Practice 2011
画像チップデータ
キャラクターなどは、テクスチャから、指定した部分だけを切り出して描画す
る。この切り出し方を指定するのが画像チップデータである。
sourcePosition.x
テクスチャ
sourcePosition.y
size.y
size.x
画像チップ
center.y
center.x
画像チップデータは"Image.h"に CHIP 構造体として定義してある。
CHIP 構造体
struct CHIP
{
POINT sourcePosition;
// 画像チップのテクスチャー上の位置
POINT size;
// 画像チップのサイズ(幅・高さ)
POINT center;
// 画像チップの中心位置
TEXTURE* pTexture;
// テクスチャ
};
POINT 構造体
位置や速度など、2 次元ベクトルデータをあらわす POINT 構造体が、
"WinDef.h"で定義されている。
POINT 構造体
struct POINT
{
int x;
int y;
};
Game Practice - 11 -
2 画像の表示
Game Practice 2011
画像の基準点(center)
敵が自機を狙い撃ちしたり、追尾ミサイルで敵を攻撃するときなどは、基準点
と呼ばれる 1 点を目標として移動する処理を行う。なるべく自然な動きに見せるた
めには、キャラクター画像の中心に基準点があるとよい。
そこで、画像チップ毎にあらかじめ基準点(center)を設定し、その基準点の座
標で描画位置を指定するようになっている。
drawPosX
drawPosY
基準点をどこにするか
基準点を画像チップ上のどこにすればよいかは、ゲームの種類やキャラクター
の画像によって変わる。
STG のキャラクター画像は、画像チップの中心に基準位置を指定することが多
い。
ただし、見た目のバランスが偏っている画像では、チップの中心ではなく、見
た目の中心に基準点を指定するほうがよい。
背景などの動かない画像の場合や、テクスチャ全体を描画する場合は、画像
チップの左上端を基準点にすることが多い。
Game Practice - 12 -
2 画像の表示
Game Practice 2011
チップデータの初期化
チップデータの各メンバの値へ直接代入するよりも、関数を用いて代入するほ
うが、プログラムの意味がわかりやすくなる。
チップデータの初期化関数は、全部の値を一度に設定する SetChip 関数と、テ
クスチャ上の位置・サイズ・基準点の位置を個別に設定する SetChipSourcePosX,
SetChipSourcePosY, SetChipSize, SetChipCenter の各関数がある。
透明色
キャラクターの周囲など、画像で透明にしておきたい部分がある場合、次のど
ちらかの方法をとる。
方法 1
Gimp などの、透明度を表すアルファ値を扱える画像ツールで、透明にしたい部
分の色を削除しておく。これを PNG 形式で保存する。半透明の画像を作成するこ
ともできる。
方法 2
透明にしたい部分に、透明色(R,G,B)=(255,0,255)を塗っておくと、その部
分は描画されない。
透明色は、テクスチャごとに SetTransparentColor 関数で変更できる。
キャラクター周囲の色を
透明色に指定すると
透明色の部分は
描画されない
Game Practice - 13 -
2 画像の表示
2.4
Game Practice 2011
描画に関する主な関数
以下の関数仕様は、簡略表記のものである。詳細な仕様は、別途記載する。
LoadImage
画像データをファイルからテクスチャに読み込む
LoadImage( L”ファイル名”, &テクスチャ )
ReleaseTexture テクスチャを解放する
ReleaseTexture( &テクスチャ )
SetChip
画像チップを設定する
SetChip( &画像チップデータ, 元画像 X 位置, 元画像 Y 位置, 幅, 高さ,
基準点 X 位置, 基準点 Y 位置, &テクスチャ )
SetTransparentColor
透明色を設定する
SetTransparentColor( 色番号 )
DrawBg
背景を描画する
DrawBg( &テクスチャ )
DrawChip
画像チップを描画する
DrawChip( &画像チップ, 描画 X 位置, 描画 Y 位置 )
Game Practice - 14 -
3 キャラクターの移動・出現・消去
Game Practice 2011
3 キャラクターの移動・出現・消去
この章では、自機・敵・弾といったキャラクターの基本的な処理について説明
する。
3.1
キー入力による自機の移動
自機の移動は、IsKeyPressed 関数を用いて、矢印キーが押された方向に位置
を変化させる。
例題 3
矢印キーを入力すると、その方向に自機が移動する。
MoveMyChar 関数
if (IsKeyPressed(VK_RIGHT))
myChar.posX += 2.0f;
if (IsKeyPressed(VK_LEFT))
myChar.posX -= 2.0f;
if (IsKeyPressed(VK_DOWN))
myChar.posY += 2.0f;
if (IsKeyPressed(VK_UP))
myChar.posY -= 2.0f;
上の記述では、左右キーを同時に押した場合、どちらにも移動しない。どちら
か片方に移動させる場合には、if ~ else if 文を用いるとよい。
IsKeyPressed 関数
IsKeyPressed 関数は、指定したキーの押下状態を取得する。指定したキーが
押されていた場合 true、押されていない場合 false となる。
キーの指定の仕方は仮想キーコードで行う。主なキーを下に示す。
アルファベットキー・数字キー(フルキー)
'A' 'B' 'C'
[A][B][C]
'1' '2' '3'
[1][2][3](フルキー)
その他のキー
VK_LEFT VK_RIGHT VK_UP VK_DOWN
[←][→][↑][↓]
VK_SPACE VK_SHIFT VK_CONTROL
[space][shift][ctrl]
VK_F1 VK_F12
[F1][F12]
VK_NUMPAD0
[0](テンキー)
使用例
if (IsKeyPressed('A'))
......
※IsKeyPressed 関数は、DXUTIsKeyDown 関数を#define マクロで名前を付け替
えたもの。WEB でこの関数について調べる場合は、DXUTIsKeyDown で調べるとよい。
Game Practice - 15 -
3 キャラクターの移動・出現・消去
3.2
Game Practice 2011
自機の移動範囲の制限
自機が画面からはみ出さないよう、スクリーンの左右上下端で移動制限を行う。
例題 4
自機が画面端から外に出ないように、移動制限を加える。
MoveMyChar 関数
// 左端の移動制限
if (myChar.posX < (screenArea.left + myChar.chip.center.x))
{
myChar.posX = screenArea.left + myChar.chip.center.x;
}
// 右端の移動制限
if (myChar.posX > (screenArea.right + myChar.chip.center.x – myChar.chip.size.x))
{
myChar.posX = screenArea.right + myChar.chip.center.x - myChar.chip.size.x;
}
(上端・下端も同様なので省略)
screenArea.left
myChar.posX
スクリーン
center.x
screenArea.right
myChar.posX
スクリーン
center.x
size.x
Game Practice - 16 -
3 キャラクターの移動・出現・消去
3.3
Game Practice 2011
インターバルカウンタによる敵の出現
インターバルカウンタを用いて、一定時間ごとに敵を出現させる。
なお、下記ソースの灰色文字部分は、7 月 4 日以降に配布された教材では、あら
かじめ記述してあるので、改めて入力する必要はない。
例題 5
一定時間(1 秒)ごとに、敵が画面右端付近に出現する。
(記号定数)"GameMain.h"
#define MAX_ENEMY 10
(グローバル変数)
// 敵データ
CHARACTER enemy[MAX_ENEMY];
// 敵出現インターバルカウンタ
int enemyLaunchIntervalCounter;
// カウンタ
int enemyLaunchIntervalTime;
// 出現間隔(インターバル)時間
InitializeGame 関数
// 敵の初期化
for (int i=0; i<MAX_ENEMY; i++)
{
enemy[i].flag = false;
// 生存フラグのリセット
}
enemyLaunchIntervalTime = 60;
// 1 秒ごとに出現
enemyLaunchIntervalCounter = enemyLaunchIntervalCounter;
UpdateGame 関数
case MODE_STAGE:
...
if (enemyLaunchIntervalCounter <= 0)
{
int enemyType = 0;
// 敵のタイプ
if (SUCCEEDED(LaunchEnemy(enemyType)))
{
enemyLaunchIntervalCounter = enemyLaunchIntervalTime;
}
}
else
{
enemyLaunchIntervalCounter--;
}
Game Practice - 17 -
3 キャラクターの移動・出現・消去
Game Practice 2011
DrawGame 関数
case MODE_STAGE:
// 敵の描画
for (int i=0; i<MAX_ENEMY; i++)
{
if(enemy[i].flag)
{
DrawChip(&enemy[i].chip, (int)enemy[i].posX, (int)enemy[i].posY);
}
}
LaunchEnemy 関数
// 空き(未出撃)敵データのインデックス取得
int index = GetFreeCharacterIndex(enemy, MAX_ENEMY);
if (index == -1)
{
return E_FAIL;
}
switch (enemyType)
{
case 0:
// 画像チップの設定
SetChip(&enemy[index].chip, 0, 32, 32, 32, 16, 16, &charTexture);
// 初期位置・初速度の設定
SetPos(&enemy[index], 600.0f,
(float)(rand() % 12 * 32));
// 動作確認用の位置
break;
}
// 生存フラグのセット
enemy[index].flag = true;
Game Practice - 18 -
3 キャラクターの移動・出現・消去
3.4
Game Practice 2011
敵の等速度運動
等速度運動(等速直線運動)させるには、速度を LaunchEnemy 関数などで設
定し、MoveEnemy 関数内で MoveStraight 関数を呼び出せばよい。
例題 6
敵を画面左方向に等速度で移動させる。
LaunchEnemy 関数
switch (enemyType)
{
case 0:
.....
// 初期位置・初速度の設定
SetPos(&enemy[index], 600.0f,
SetVel(&enemy[index], -2.0f,
240.0f);
0.0f);
// 動作確認用の位置
// 左移動
break;
}
MoveEnemy 関数
for (int i=0; i<MAX_ENEMY; i++)
{
if (enemy[i].flag)
{
MoveStraight(&enemy[i]);
if(!IsInArea(&enemy[i], &screenArea))
{
enemy[i].flag = false;
}
}
}
Game Practice - 19 -
3 キャラクターの移動・出現・消去
3.5
Game Practice 2011
敵の画面範囲外消去
IsInArea 関数を用いて画面範囲外に出ているかどうかを調べる。出ていた場
合は生存フラグをリセットする。
なお、この処理は 7 月 4 日以降に配布した教材では、すでに記述されている。
例題 7
画面範囲外から出た敵を消去する。
MoveEnemy 関数
for (int i=0; i<MAX_ENEMY; i++)
{
if (enemy[i].flag)
{
MoveStraight(&enemy[i]);
if(!IsInArea(&enemy[i], &screenArea))
{
enemy[i].flag = false;
}
}
}
キャラクターがスクリーン(画面)の端から完全に外に出てしまうと、画像が
画面中央に描画されてしまう。これは、DrawChip 関数が呼び出している
DrawOnSprite 関数の画面外描画チェック機能のためである。
画像描画は時間がかかる処理なので、不必要なときは画像描画を行わないこと
が重要だ。画像チップが完全にスクリーン外にある場合は、描画を行わないよう
にする。
Game Practice - 20 -
3 キャラクターの移動・出現・消去
3.6
Game Practice 2011
キー入力による自機の弾発射
弾の画像や弾データをまず用意する。そして、ShootMyBullet 関数内に、
キーを押したら自機が弾を撃つ処理を記述する。
例題 8
Z キーを押すと、自機が弾を撃つ。
(記号定数)"GameMain.h"
#define MAX_BULLET 100
(グローバル変数)
TEXTURE bulletTexture;
CHARACTER myBullet[MAX_MYBULLET];
InitializeGame 関数
// 弾画像の読み込み
if (FAILED(LoadImage(L"image¥¥bullet.png", &bulletTexture)))
{
return E_FAIL;
}
// 自弾の初期化
for (int i=0; i<MAX_MYBULLET; i++)
{
SetChip(&myBullet[i].chip, 0, 0, 8, 2, 4, 1, &bulletTexture);
myBullet[i].flag = false;
}
FinalizeGame 関数
ReleaseTexture(&bulletTexture);
ここまでが弾の画像と弾データの準備だ。
ShootMyBullet 関数
if (IsKeyPressed('Z'))
{
ShootCharacter(&myChar, myBullet, MAX_MYBULLET,
0, 0, 3.0, 0.0, 0);
}
ShootCharacter 関数を用いると、指定したキャラクターを基準に発射位置
を決め、指定した速度で弾を撃つ。
Game Practice - 21 -
3 キャラクターの移動・出現・消去
Game Practice 2011
MoveMyBullet 関数
for (int i=0; i<MAX_MYBULLET; i++)
{
if (myBullet[i].flag)
{
MoveStraight(&myBullet[i]);
if(!IsInArea(&myBullet[i], &screenArea))
{
myBullet[i].flag = false;
}
}
}
DrawGame 関数
for (int i=0; i<MAX_MYBULLET; i++)
{
if(myBullet[i].flag)
{
DrawChip(&myBullet[i].chip,
(int)myBullet[i].posX, (int)myBullet[i].posY);
}
}
3.7
インターバルカウンタによる弾発射間隔の調整
前項のプログラムでは、キーを押すと、毎フレーム弾が発射されてしまうので、
インターバルカウンタを用いて、数フレームおきに発射されるように改良する。
例題 9
インターバルカウンタを用いて、数フレームおきに弾が発射されるように改良
する。
(グローバル)
// 弾発射インターバルカウンタ
int bulletShotIntervalCounter;
// カウンタ
int bulletShotIntervalTime;
// 発射間隔時間
InitializeGame 関数
// 自弾発射間隔の設定
bulletShotIntervalTime = 20;
bulletShotIntervalCounter = bulletShotIntervalTime;
Game Practice - 22 -
3 キャラクターの移動・出現・消去
Game Practice 2011
SootMyBullet 関数
if(bulletShotIntervalCounter <= 0)
{
if (IsKeyPressed('Z'))
{
HRESULT hr;
hr = ShootCharacter(&myChar, myBullet, MAX_MYBULLET,
0, 0, 3.0, 0.0, 0);
if (SUCCEEDED(hr))
// 発射成功時はカウンタをリセット
{
bulletShotIntervalCounter = bulletShotIntervalTime;
}
}
}
else
{
bulletShotIntervalCounter--;
}
3.8
インターバルカウンタによる敵の弾発射
敵は自機と異なり、勝手に弾を発射する。ここでは、インターバルタイマを用
いて、一定時間ごとに撃つ処理を追加する。
例題 10
インターバルカウンタを用いて、数フレームおきに敵が弾が発射する。
(記号定数)"GameMain.h"
#define MAX_ENEMYBULLET 100
(グローバル)
CHARACTER enemyBullet[MAX_ENEMYBULLET];
// 敵弾データ
int enemyShotIntervalTime;
// 敵弾発射間隔時間
InitializeGame 関数
// 敵弾の設定
for (int i=0; i<MAX_MYBULLET; i++)
{
SetChip(&enemyBullet[i].chip, 0, 2, 8, 8, 4, 1, &bulletTexture);
enemyBullet[i].flag = false;
}
// 敵弾発射時間の設定
enemyShotIntervalTime = 60;
Game Practice - 23 -
3 キャラクターの移動・出現・消去
Game Practice 2011
LaunchEnemy 関数
switch (enemyType)
{
case 0:
......
// 弾発射間隔の設定
enemy[index].shotIntervalCounter
= rand() % enemyShotIntervalTime;
// = 0 ~ 59 frame
break;
}
ShootEnemyBullet 関数
for (int i=0; i<MAX_ENEMY; i++)
{
if (enemy[i].flag)
{
if (enemy[i].shotIntervalCounter <= 0)
{
// 発射
HRESULT hr;
hr = ShootCharacter(&enemy[i], enemyBullet,
MAX_ENEMYBULLET, 0, 0, -3.0f, 0.0f, 0);
if (SUCCEEDED(hr))
// 発射成功時はカウンタをリセット
{
enemy[i].shotIntervalCounter
= rand() % enemyShotIntervalTime;
}
}
else
{
enemy[i].shotIntervalCounter--;
}
}
}
この例題では、乱数を使って求めた値で発射間隔を設定している。
MoveEnemyBullet 関数
for (int i=0; i<MAX_ENEMYBULLET; i++)
{
if (enemyBullet[i].flag)
{
MoveStraight(&enemyBullet[i]);
Game Practice - 24 -
3 キャラクターの移動・出現・消去
Game Practice 2011
if(!IsInArea(&enemyBullet[i], &screenArea))
{
enemyBullet[i].flag = false;
}
}
}
DrawGame 関数
for (int i=0; i<MAX_ENEMYBULLET; i++)
{
if(enemyBullet[i].flag)
{
DrawChip(&enemyBullet[i].chip,
(int)enemyBullet[i].posX, (int)enemyBullet[i].posY);
}
}
3.9
キャラクターに関する主な関数
SetPos
キャラクターの位置を設定する
SetPos( &キャラクターデータ, X 位置, Y 位置 )
SetVel
キャラクターの速度を設定する
SetVel( &キャラクターデータ, 速度 X 成分, 速度 Y 成分 )
MoveStraight
キャラクターを等速度運動させる
MoveStraight( &キャラクターデータ )
IsInArea
キャラクターが指定領域内にあるかを調べる
IsInArea( &キャラクターデータ, &領域データ )
ShootCharacter 親キャラクターが子キャラクターを発射する
ShootCharacter( &親キャラクターデータ, 子キャラクターデータ配列, 配列長,
発射 X 位置オフセット, 発射 Y 位置オフセット,
速度 X 成分, 速度 Y 成分, 弾タイプ )
GetFreeCharacterIndex 空きキャラクターのインデックスを取得する
GetFreeCharacterIndex( キャラクターデータ配列, 配列長 )
Game Practice - 25 -
4 衝突処理
Game Practice 2011
4 衝突処理
前章までのプログラムで、背景画を表示し、自機・敵・自弾・敵弾をそれぞれ
表示、移動させることができた。だが、キャラクターどうしがぶつかっても、す
り抜けてしまうだけだ。
この章では、ゲームに欠かせない衝突処理について説明する。
4.1
衝突判定
ゲームで、2 つの物体が衝突しているかどうかを判定する方法には様々なものが
ある。いくつかあげてみよう。
基準点どうしの距離で判定
ce
tan
dis
円に近い形の物体どうしの衝突判定によく用いる方法。
「中心の距離 ≦ 半径の和」となったときに衝突してい
るとする。
r2
r1
ドットの重なりで判定
画像を 1 ドットずつ全て調べ、判定する方法。処理速度が遅く
なるが、絵に合わせて判定するため、正確に判定できる。
判定用に、1 ドットを 1 ビットであらわした、マスク画像を用
意する場合もある。
衝突判定用長方形の重なりで判定
2D ゲームでは、最も一般的に行われている方法。この教材
では、この方法で判定する。
4.2
実体データ
衝突判定を行うための、長方形の範囲を表すのが実体デー
タだ。
(left, top)
実体
実体データは、衝突判定用長方形の左上隅と右下隅が、画
像チップの基準点から、どれだけ離れたところにあるのかを
示している。
この教材では、自機・敵・弾といったキャラクター
(CHARACTER)の実体データは、長方形の左上隅と右下隅の
座標を表す RECT 型で、body メンバとして宣言されている。
Game Practice - 26 -
基準点
(right, bottom)
4 衝突処理
Game Practice 2011
RECT 構造体
長方形の左上隅と右下隅の座標を表す。"WinDef.h"で下記のように定義され
ている。
RECT 構造体
struct RECT
{
int
left;
int
top;
int
right;
int
bottom;
};
実体データの大きさと難易度
実体データを小さくすれば、そのキャラクターは衝突しにくくなり、大きくす
れば衝突しやすくなる。実体データの大きさはゲームの難易度に影響する。
当りにくい
当りやすい
実体
4.3
実体
衝突判定関数
キャラクターどうしの衝突判定は、実体どうしが重なっているかどうかで判定
する。
衝突していない
(重なっていない)
基準点
実体
衝突している
(重なっている)
キャラクターの実体どうしの重なりは、IntersectsChar 関数を用いて判定す
る。
Game Practice - 27 -
4 衝突処理
4.4
Game Practice 2011
自機と敵の衝突処理
自機と敵の衝突処理を行うとき、まず、衝突したときに自機が消えるようにし
なければならない。データ更新・描画の書く処理を行うかどうかを、生存フラグ
によって決めるようにする。
例題 11
自機の生存フラグによって、データ更新・描画処理を行うかどうかを決めるよ
うにする。
InitializeGame 関数
// 自機の設定
......
myChar.flag = true;
MoveMyChar 関数
if (!myChar.flag)
{
return;
}
if (IsKeyPressed(VK_RIGHT))
myChar.posX += 2.0f;
......
ShootMyBullet 関数
if (!myChar.flag)
{
return;
}
if(bulletShotIntervalCounter <= 0)
......
DrawGame 関数
case MODE_STAGE:
if (myChar.flag)
{
DrawChip(&myChar.chip, (int)myChar.posX, (int)myChar.posY);
}
この処理の動作を確認できたら、InitializeGame 関数で行っている自機の
設定を変更しよう。生存フラグに false を代入するように変更して、自機が表示
されなくなり、弾も撃てなくなることを確認すること。
Game Practice - 28 -
4 衝突処理
Game Practice 2011
自機の生存フラグが正しく機能していることを確認したら、自機と敵に実体を
設定し、衝突処理を作成しよう。
1 つの自機に対して、敵は複数存在する。そこで、for 文による繰り返しを用い
て、自機とすべての敵との衝突判定を行う。
例題 12
自機と敵が衝突したら、双方を消去する。
InitializeGame 関数
// 自機の設定
SetRect(&myChar.body, 0, 0, 48, 32);
LaunchEnemy 関数
switch (enemyType)
{
case 0:
......
// 実体の設定
SetRect(&enemy[index].body, 0, 0, 32, 32);
IntersectsEnemyAndMyShip 関数
if (!myChar.flag)
{
return;
}
for (int i=0; i<MAX_ENEMY; i++)
{
if (enemy[i].flag)
{
if (IntersectsChar(&myChar, &enemy[i]))
{
myChar.flag = false;
enemy[i].flag = false;
break;
}
}
}
Game Practice - 29 -
4 衝突処理
4.5
Game Practice 2011
自弾と敵の衝突処理
複数の自弾に対して、複数の敵が存在する。この場合、2 重の for 文による繰り
返しを用いて、すべての自弾とすべての敵との衝突判定を行う。
例題 13
自弾と敵が衝突したら、双方を消去する。
IntersectsMyBulletAndEnemy 関数
for (int iE=0; iE<MAX_ENEMY; iE++)
{
if (enemy[iE].flag)
{
for (int iB = 0; iB<MAX_MYBULLET; iB++)
{
if(myBullet[iB].flag)
{
if (IntersectsChar(&enemy[iE], &myBullet[iB]))
{
enemy[iE].flag = false;
myBullet[iB].flag = false;
break;
}
}
}
}
}
この例題のように、敵が 1 発の自弾で消滅する場合、衝突が起こったら、もう他
の弾と衝突判定する必要はない。そこで、break 文で自弾のループを終了させてい
る。
4.6
自機と敵弾の衝突処理
自機と敵弾の衝突処理は、自機と敵の衝突処理を参考に、自分で作成してみよう。
4.7
衝突処理に関する主な関数
SetRect
実体などの長方形領域を設定する
SetRect( &長方形領域, 左位置, 上位置, 右位置, 下位置 )
IntersectsChar
キャラクターどうしの衝突判定を行う
IntersectsChar( &キャラクターデータ A, &キャラクターデータ B )
Game Practice - 30 -
5 描画キャンバス
Game Practice 2011
5 描画キャンバス
5.1
描画キャンバスの仕組み
描画キャンバス機能を用いると、1 ピクセル単位で、画面に直線や円などの図形
を描画することができる。
描画キャンバスは、メモリ上に用意した仮想スクリーンに描画し、その仮想ス
クリーンをスプライトに転送する仕組みになっている。
メモリ
描画キャンバス
DrawBox
DrawEllipse
DrawLine
FlushCanvas
テクスチャ
スプライト
スクリーン(画面)
Game Practice - 31 -
5 描画キャンバス
5.2
Game Practice 2011
描画キャンバスを用いた描画の基本
例題 14
自機の位置を中心とした、十字線を描く。
DrawGame 関数
// キャンバスの消去
ClearCanvas();
// 図形の描画
DrawHLine(0, 639, (int)myChar.posY, 0xffffff);
DrawVLine((int)myChar.posX, 0, 479, 0xffffff);
// キャンバスの反映
FlushCanvas();
ClearCanvas 関数でキャンバスの消去を行い、DrawHLine などの描画関数
で図形を描いた後、FlushCanvas 関数で、スプライト上に反映する。
Game Practice - 32 -
5 描画キャンバス
5.3
Game Practice 2011
描画キャンバスを用いた爆炎の描画
自機や敵が弾に当たったとき、爆発する炎があると臨場感が沸く。爆炎の画像
を用意して描画する方法が一般的だが、ここでは、描画キャンバスの機能を使っ
て、爆炎を円で描いてみよう。
例題 15
敵が爆発したとき、一定時間爆炎が表示される。
(グローバル変数)
// 爆炎
FIRE fire[MAX_ENEMY];
// 敵と同じ数用意する
int fireOffIntervalTime;
// 爆炎が消えるまでの時間
InitializeGame 関数
// 爆炎の初期化
for (int i=0; i<MAX_ENEMY; i++)
{
fire[i].flag = false;
}
fireOffIntervalTime = 120;
IntersectsMyBulletAndEnemy 関数
for (int iE=0; iE<MAX_ENEMY; iE++)
{
......
if (IntersectsChar(&enemy[iE], &myBullet[iB]))
{
enemy[iE].flag = false;
myBullet[iB].flag = false;
// 爆炎の設定
fire[iE].posX = enemy[iE].posX;
fire[iE].posY = enemy[iE].posY;
fire[iE].r = 32;
fire[iE].color = 0x00ffff00;
fire[iE].flag = true;
fire[iE].offCounter = fireOffIntervalTime;
break;
}
......
}
Game Practice - 33 -
5 描画キャンバス
Game Practice 2011
UpdateGame 関数
// 爆発の炎
for (int i=0; i<MAX_ENEMY; i++)
{
if (fire[i].flag)
{
if (fire[i].offCounter <= 0)
{
fire[i].flag = false;
}
else
{
fire[i].offCounter--;
}
}
}
DrawGame 関数
ClearCanvas();
// 爆炎の描画
for (int i=0; i<MAX_ENEMY; i++)
{
if (fire[i].flag)
{
DrawSolidCircle((int)fire[i].posX, (int)fire[i].posY,
fire[i].r, fire[i].color);
}
}
FlushCanvas();
前の例題で作成した十字線描画の記述は省いてある。
Game Practice - 34 -
5 描画キャンバス
5.4
Game Practice 2011
描画キャンバスを用いた文字の描画
描画キャンバスには、半角英数文字(ANK 文字)を描画する機能がある。これ
を用いてスコアを表示したり、何かメッセージを表示したりすることができる。
なお、この項の説明は、2011 年 7 月 29 日更新の教材を用いた場合に対応してい
る。
例題 16
自機が敵と衝突して消去されたとき、画面に「GAME OVER」と表示する。
(グローバル変数)
// ANK フォント
ANKFONT ankFont;
InitializeGame 関数
// ANK フォントの初期化
if (FAILED(CreateAnkFont(&ankFont,
L"image¥¥fontMonospaceBold.png", 12, 20)))
{
return E_FAIL;
}
FinalizeGame 関数
// ANK フォントの解放
ReleaseAnkFont(&ankFont);
DrawGame 関数
if (!myChar.flag)
{
DrawAnk(266, 150, "GAMEOVER", 0xffbb00);
}
文字描画の手順
まず、CreateAnkFont 関数で、用意してある文字画像ファイルを読み込み、
幅と高さを設定する。
(posX, posY)
描画には DrawAnk 関数を用いる。文字列の
ほかに、位置と色を指定する。描画位置は、
書き始める左上の位置を指定する。
GAME OVER
終了処理で、ReleaseAnkFont 関数を用いて解放する。
Game Practice - 35 -
5 描画キャンバス
Game Practice 2011
例題 17
スコアを表示する。敵を 1 機倒すごとに、100 点加算する。
(グローバル変数)
int score;
// スコア
InitializeGame 関数
// スコアの初期化
score = 0;
IntersectsMyBulletAndEnemy 関数
for (int iE=0; iE<MAX_ENEMY; iE++)
{
......
if (IntersectsChar(&enemy[iE], &myBullet[iB]))
{
enemy[iE].flag = false;
myBullet[iB].flag = false;
// スコアの加算
score += 100;
......
DrawGame 関数
// スコアの描画
char bfr[20];
sprintf_s(bfr, sizeof(bfr), "SCORE : %06d", score);
DrawAnk(8, 8, bfr, 0xffffff);
数値の描画
DrawAnk 関数には printf 関数のような書式指定出力機能はない。puts 関数
と同様に、ただ文字列を描画するだけだ。
そこで、sprintf_s 関数を用いて数値を文字列に変換し、その文字列を
DrawAnk 関数で描画する方法をとる。なお、sprintf_s 関数の書式指定文字列
は printf 関数とまったく同じ書式である。
sprintf_s 関数
文字列に文字列に書式付きデータを書き込む関数。C 標準関数の sprintf 関数のセ
キュリティ強化版。
sprintf_s( 文字列用配列, 配列サイズ, 書式指定文字列, 変数 1... )
Game Practice - 36 -
5 描画キャンバス
Game Practice 2011
文字画像データの自作
教材には、「Lucida Console」「Monospace Bold」「Courier New」の 3 種類の
フォントの、幅 12 × 高さ 20 pixels の文字画像が用意されているが、自分で文字画
像を用意することもできる。
この場合、文字コード 32 から 127 の文字を、等しい幅で横一列に並べて描いた
画像を用意すればよい。文字の幅と高さは文字に合わせて自分で決め、その値を
CreateAnkFont 関数で指定する。
拡大図
なお、教材では、1 度に 1 種類のフォントしか使用することはできない。
Game Practice - 37 -
5 描画キャンバス
5.5
Game Practice 2011
描画キャンバスに関する主な関数
ClearCanvas
ClearCanvas(
FillCanvas
背景を色番号 0 で塗りつぶす(クリアする)
)
背景を 1 色で塗りつぶす
FillCanvas( 色番号 )
DrawPixel
点を描画する
DrawPixel( X 座標, Y 座標, 色番号 )
DrawHLine
水平線分を描画する
DrawHLine( 開始 X 座標, 終了 X 座標, Y 座標, 色番号 )
DrawVLine
垂直線分を描画する
DrawVLine( X 座標, 開始 Y 座標, 終了 Y 座標, 色番号 )
DrawLine
線分を描画する
DrawLine( 開始 X 座標, 開始 Y 座標, 終了 X 座標, 終了 Y 座標, 色番号 )
DrawBox
長方形の枠を描画する
DrawBox( 左位置, 上位置, 幅, 高さ, 色番号 )
DrawSolidBox
塗りつぶした長方形を描画する
DrawSolidBox( 左位置, 上位置, 幅, 高さ, 色番号 )
DrawCircle
円周を描画する
DrawCircle( 中心 X 座標, 中心 Y 座標, 半径, 色番号 )
DrawSolidCircle 塗りつぶした円を描画する
DrawSolidCircle( 中心 X 座標, 中心 Y 座標, 半径, 色番号 )
DrawEllipse
楕円周を描画する
DrawEllipse( 中心 X 座標, 中心 Y 座標, 水平方向半径, 垂直方向半径, 色番号 )
DrawSolidEllipse 塗りつぶした楕円を描画する
DrawSolidEllipse( 中心 X 座標, 中心 Y 座標, 水平方向半径, 垂直方向半径, 色番号 )
DrawAnk
半角文字列を描画する
DrawAnk( 左位置, 上位置, 文字列, 色番号 )
Game Practice - 38 -
ABCabc
123!"#
6 サウンド
Game Practice 2011
6 サウンド
6.1
XACT によるサウンド
XACT(イグザクトまたはザクト)とは、Microsoft が DirectX の一部として用意
した、オーディオ・プログラミングライブラリおよびオーディオ・エンジンで、
Windows と Xbox 両方のプラットフォームで使用可能である。
XACT は元々 Xbox の開発のために作られたが、後に Windows でも動作するよう
に修正された。
6.2
XACT でのサウンド処理の流れ
ウェーブバンク
まず、用意した音データをまとめて、ウェーブバンクを作成する。
ウェーブバンクを作成するとき、1 つの音データをすべてメモリーに読み込ん出
から再生する「インメモリー」方式なのか、音データの必要な部分だけ読み込み
ながら再生する「ストリーミング」方式なのかを選ぶ。
ウェーブバンクは複数作成することができる。一般的には、インメモリーの
ウェーブバンクとストリーミングのウェーブバンクの 2 つを作成する。
Game Practice - 39 -
6 サウンド
Game Practice 2011
サウンドバンク
XACT の「サウンド」とは、ウェーブバンクの音データを素材とし、ボリューム
やピッチの調整を行って「演奏データ」としてまとめたものを指す。
サウンドの再生の仕方を「キュー」というデータにまとめる。プログラムから
はキューを指定して再生開始の合図を送ると、キューに関連付けられたサウンド
が再生される。
6.3
XACT ツールを用いたサウンドデータ作成
サウンドデータは Microsoft XNA GameStudio 4.0 付属のツール Microsoft CrossPlatform Audio Creation Tool 3 (XACT3)を用いて作成する。
音データの用意
使用できる音データの形式は WAV・AIFF・XMA であるが、ここでは WAV
データを用いることにする。
他の形式の場合、変換ツール(えこでこツール等)を用いて WAV 形式に変換す
ること。
用意した音データは、作成しているゲームプロジェクト内に "sound" などの
名前でフォルダを作り、そこへ入れておく。
XACT ツールの起動
[スタートメニュー]→[すべてのプログラム]→[Microsoft XNA GameStudio
4.0]→[tools]→[Microsoft Cross-Platform Audio Creation Tool 3(XACT3)]を選択し
XACT ツールを起動する。
Game Practice - 40 -
6 サウンド
Game Practice 2011
① 新規プロジェクトの作成
[File]→[New Project]を選択し、音データを用意するときに作成した"sound"
フォルダ内にプロジェクトを作成する。ここでは、"GameSound.xap"という名
前で保存している。
② ウェーブバンクの作成
[Wave Banks]→[New Wave Banks] を選択し、ウェーブバンクを作成する。左にあ
るツリーに、ウェーブバンクのアイコンが出来ているので、名前をつける。
ここでは、インメモリーデータ用"SeWaveBank"と、ストリーミングデータ用
"BgmWaveBank"の2つのウェーブバンクを作成している。
複数のウインドウが表示された場合、[Window]→[Tile Horizontally]を選ぶと、ウ
インドウが縦に並び、編集しやすいだろう。(並べた状態は次ページ参照)
Game Practice - 41 -
6 サウンド
Game Practice 2011
③ ウェーブバンクに音データを登録
"SeWaveBank"と"BgmWaveBank"それぞれのウインドウ上で[右クリック]し、
[Insert Wave File(s)]を選択する。そして、ゲームで使用する WAV データを選んで
[開く]ボタンを押して登録する。
左下にある[General]-[Type]は、そのウェーブバンクのデータをインメモリーデー
タとするか、ストリーミングデータとするかを決めるものである。
初期値は[In Memory]になっているので、"BgmWaveBank"のウインドウを選び、
[Streaming]のボタンを押して変更する。
④ サウンドバンクの作成
[Sound Banks]→[New Sound Banks] を選択し、サウンドバンクを作成する。左にあ
るツリーに、サウンドバンクができているので、名前をつける。ここでは、
"GameSoundBank"という名前をつけている。
⑤ サウンドとキューの作成
ウェーブバンクに登録した音データを、そのまま再生する場合について説明す
る。ウェーブバンクのウインドウにある音データを、サウンドバンクのウインド
ウの左上の欄(Sound Name とある欄)にドラッグ&ドロップする。そのサウンド
データを左下の欄(Cue Name とある欄)にドラッグ&ドロップする。
Game Practice - 42 -
6 サウンド
Game Practice 2011
⑥ ループの指定
BGM など、曲が最後まで行ったら、最初に戻してもう一度演奏するループにす
ることが多い。
ループの指定は、サウンドバンクのウインドウの左上の欄でデータを選び、
XACT ツール左下にある[Looping]を指定する。
回数を決めて繰り返す場合は、[Loop Count]に回数を指定する。
無限に繰り返す場合は、[Infinit]にチェックを入れる。
⑦ ビルド
[File]→[Build...]を選ぶ。
[Build Project]のウインドウでは、そのまま
[Finish]を押す。
これで、プログラムで使用する各ファイル
が"Win"フォルダ内に出来上がった。
⑧ プロジェクトの保存
[File]→[Save Project]を選択し、プロジェクトを保存する。
Game Practice - 43 -
6 サウンド
6.4
Game Practice 2011
効果音
効果音の場合、データをすべてメモリに読み込んでから再生する「インメモ
リー」の方法で音を鳴らすのが一般的だ。
例題 18
自機が弾を撃つとき、効果音を鳴らす。
InitializeGame 関数
// サウンドの初期化
if (FAILED(InitializeXACT(L"sound¥¥Win¥¥SeWaveBank.xwb",
L"sound¥¥Win¥¥BgmWaveBank.xwb",
L"sound¥¥Win¥¥GameSoundBank.xsb")))
{
return E_FAIL;
}
// キューの設定
SetSoundCue(0, "se_pyuun");
FinalizeGame 関数
// サウンドの終了
FinalizeXACT();
UpdateGame 関数
// サウンド
ContinueXACT();
ShootMyBullet 関数
if (IsKeyPressed('Z'))
{
hr = ShootCharacter(&myChar, myBullet, MAX_MYBULLET,
0, 0, 3.0, 0.0, 0);
if (SUCCEEDED(hr))
// 発射成功時はカウンタをリセット
{
bulletShotIntervalCounter = bulletShotIntervalTime;
}
PlaySoundCue(0);
}
Game Practice - 44 -
6 サウンド
Game Practice 2011
インメモリーまたはストリーミングどちらかのウェーブバンクファイル
("*.xwb")がない場合は、InitializeXACT 関数の引数には NULL を指定する。
効果音などループしていない短い音データは、再生処理だけを行って、停止処
理はしなくてもよい。
キューの番号
XACT では、再生・停止するキューは名前で指定するが、この教材では、番号で
指定するようにしている。名前と番号の関連付けは SetSoundCue 関数で行う。
6.5
BGM
BGM の場合は、音を再生しながらデータを順次メモリに読み込んでいく「スト
リーミング」の方法で音を鳴らすことが多い。
例題 19
ゲームプログラム開始時から BGM を演奏する。プログラム終了時に停止する。
InitializeGame 関数
// サウンドの初期化
if (FAILED(InitializeXACT(L"sound¥¥Win¥¥SeWaveBank.xwb",
L"sound¥¥Win¥¥BgmWaveBank.xwb",
L"sound¥¥Win¥¥GameSoundBank.xsb")))
{
return E_FAIL;
}
// キューの設定
SetSoundCue(0, "se_pyuun");
SetSoundCue(4, "lucky_star");
// BGM 演奏開始
PlaySoundCue(4);
FinalizeGame 関数
// サウンドの終了
StopSoundCue(4);
FinalizeXACT();
BGM など、ループしていたり、長かったりする音データの場合、
StopSoundCue 関数で音の再生を停止する。
2011 年 8 月 9 日更新版(バージョン 2.0)以降の教材ではなく、7 月 22 日更新版
(バージョン 1.0)を使用している場合、SetSoundCue 関数ではなく、
SetStreamingSoundCue 関数を用いること。
Game Practice - 45 -
6 サウンド
6.6
Game Practice 2011
サウンドに関する主な関数
InitializeXACT
サウンドシステムを初期化する
InitializeXACT( L"インメモリーウェーブバンクファイル名",
L"ストリーミングウェーブバンクファイル名", L"サウンドバンクファイル名" )
FinalizeXACT
サウンドシステムを終了する
FinalizeXACT( )
SetSoundCue
サウンドキューを設定する
SetSoundCue( キュー番号, "キュー名")
PlaySoundCue
サウンドを再生する
PlaySoundCue( キュー番号 )
StopSoundCue
サウンドを停止する
StopSoundCue( キュー番号 )
ContinueXACT
サウンド演奏を続ける
ContinueXACT( )
Game Practice - 46 -
7 マップ
Game Practice 2011
7 マップ
7.1
背景描画のいろいろな方法
ゲーム画面(スクリーン)に、背景画を描画する方法には主に 2 通りある。

一枚の絵を描画する

マップチップを並べる
また、背景画がスクロールするかしないかで、描画処理も大きく異なってくる。
つまり、次の 4 通りの方法があるというわけだ。
A) 一枚の絵でスクロールしない
B) 一枚の絵でスクロールする
C) マップチップでスクロールしない
D) マップチップでスクロールする
代表的な方法は、(A)と(D)だが、(A)は「2.2 背景の描画」(p.7)で取り上げたので、
ここでは、(D)の場合について説明する。
7.2
マップチップを用いた背景
マップチップを用いた背景の場合、マップチッ
プ画像と、マップチップ配置データを用意し、そ
れらを読み込んだ後、マップ表示データを記述し、
マップ描画関数で描く。
マップ描画の手順
1. 必要なデータを用意する
2. 必要なデータを読み込む(初期化する)
3. マップを描く
4. 終了時にデータを解放する
マップチップ画像
画面(表示部分)
マップ全体
Game Practice - 47 -
7 マップ
Game Practice 2011
スクロールさせるには、マップのどの部分を表示させるのかというマップ表示
データ(→ p.50参照)の値を ScrollMap 関数を用いて変更する(→ p.54参照)。
7.3
マップチップ画像データ
まずは、マップチップ画像データを作成しよう。
この教材では、マップチップ(マップを構成する小さな画像)は、次の形式の
ものを使用する。作成は、Gimp や JTrim、PictBear などの、グラフィックエディタ
を用いて行う。
!

ファイル形式 : PNG / BMP(無圧縮)

カラーモード : True-Color(1677 万色モード)

1 チップのサイズ : 32 × 32 pixels

最大チップ数 : 16 × 16 chips
マップエディタ Platinum 使用上の注意
マップチップ配置データ(→次項)を作成するマップエディタ Platinum は、PNG 形式に対応
していない。ゲームで PNG 形式の画像を使いたい場合でも、Gimp や JTrim などを使って、同じ
画像を BMP 形式で保存したものも用意しなければならない。
Game Practice - 48 -
7 マップ
7.4
Game Practice 2011
マップチップ配置データ
マップチップを並べるための配置データは、マップエディタ Platinum で作成す
る。
新規作成時の設定
横スクロールでスクリーン全体にマップを描画する場合、Platinum で新規作成時
に行う設定は下記のようにする。

マップサイズ : 横幅 20 以上 × 高さ 15 parts

パーツ(チップ)サイズ : 32 × 32 pixels

データサイズ : 8 bit
保存(書き出し)時のファイル形式
保存(書き出し)時のファイル形式は下記のようにする。

ファイル形式 : FMF
マップチップ画像
0 1 2 3 4 5 6 7 8 9
16 17 18 19 20 21 22 23 24 25
0
0
0
0
0
0
0
0
マップチップ配置データ
0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 1 2 2 2 2 2 2 1
0 0 1 1 1 1 2 17 2 17 2 17 1
0 0 0 0 1 2 2 2 2 2 2 2 2
0 0 0 0 1 2 2 2 2 2 2 2 2
0 0 1 1 1 1 2 17 2 17 2 17 1
0 0 0 0 0 1 2 2 2 2 2 2 1
0 0 0 0 0 0 0 0 0 0 0 0 0
描画されたマップ
Game Practice - 49 -
0
0
1
1
1
1
0
0
0
0
1
0
0
1
0
0
0
0
1
0
0
1
0
0
7 マップ
7.5
Game Practice 2011
マップ表示データ
マップ全体のサイズはスクリーンよりも大きいのが普通だ。
例えば、横スクロールの場合では、高さはスクリーンと同じでも、横幅がスク
リーンよりも長い。マップのどの部分を表示するのか、スクリーンのどこに表示
するのかを指定するのがマップ表示データである。
マップ全体
スクリーン
このデータは、ファイルから読み込むのではなく、初期化関数内で、必要な値
を設定する。
マップ表示データとして、縦横の表示チップ数の値が必要になるので、スク
リーンサイズとマップチップサイズから計算しておく。スクリーン全体にマップ
を描画する場合、表示チップ数は下記のような値となる。
縦横の表示チップ数
!

横表示チップ数 = スクリーン横サイズ ÷ チップ横サイズ = 640 ÷ 32 = 20

縦表示チップ数 = スクリーン縦サイズ ÷ チップ縦サイズ = 480 ÷ 32 = 15
スクリーンの一部にマップを描画するときの注意
現在のバージョンのマップモジュールの各関数は、スクリーン全体にマップを描画すること
を前提に作られている。スクリーンの一部分にだけ描画する場合、スクロールさせると、マッ
プが表示指定範囲を超えて描画されてしまう。
スクリーンの一部分にだけマップを描画する場合、描画キャンバス(→p.31)の機能を用いる
などして、表示指定範囲外のところを DrawSolidBox 関数などで塗りつぶし、マスクする
(覆い隠す)必要がある。
スクリーン全体にマップを
描画する場合
0
マップ(=スクリーン全体)
480
スクリーンの一部にマップを
描画する場合
マップ
32
スクリーン
416
10 チップ
15 チップ
20 チップ
12 チップ
Game Practice - 50 -
ここをマスクする
7 マップ
7.6
Game Practice 2011
マップの描画
必要なデータが用意できたら、それらのデータを読み込んだり、初期化したり
し、マップを描画する。
例題 20
マップチップ画像データ・マップチップ配置データを読み込み、マップを描画
する。
(グローバル変数)
// テクスチャ
TEXTURE mapChipTexture; // マップチップ画像
// マップ
MAP map;
// マップ情報データ
MAPVIEW mapView;
// マップ表示データ
InitializeGame 関数
// マップチップ画像の読み込み
if (FAILED(LoadImage(L"image¥¥mapchip.png", &mapChipTexture)))
{
return E_FAIL;
}
// マップデータの読み込み
if (FAILED(LoadMap(L"data¥¥stage00.fmf", &map)))
{
return E_FAIL;
}
// マップ表示データ
SetMapView(&mapView, 20, 15, 0.0f, 0.0f, 0, 0); // スクリーン全体
FinalizeGame 関数
// テクスチャの解放
ReleaseTexture(&mapChipTexture);
// マップの解放
ReleaseMap(&map);
DrawGame 関数
case MODE_STAGE:
DrawBg(&bgImage);
DrawMap(&map, &mapView, &mapChipImage);
Game Practice - 51 -
7 マップ
Game Practice 2011
マップ情報データ
マップエディタ Platinum で作成し、FMF ファイルで保存したマップチップ配置
データは、LoadMap 関数を用いてファイルから読み込む。この時、配置データを
読み込むためのマップデータ(MAP 型変数)を用意する。
マップ情報データ(MAP 構造体) "map.h"
struct MAP
{
size_t chipSizeX;
// マップチップ横サイズ
size_t chipSizeY;
// マップチップ縦サイズ
size_t srcChipCountX;
// チップ画像の横チップ数
size_t countX;
// マップ横チップ数
size_t countY;
// マップ縦チップ数
unsigned char* pChipNo;
// マップチップ配置データのアドレス
};
マップデータ(MAP 型変数)の、チップサイズやチップ数といったメンバは、
FMF ファイル内に記録されていて、LoadMap 関数で読み込むときに自動的に設定
される。
マップ情報データ
chipSizeX
chipSizeY
srcChipCountX
countY
countX
Game Practice - 52 -
7 マップ
Game Practice 2011
マップ表示データ
マップ表示データはファイルから読み込むのではなく、MAPVIEW 型変数を用意
し、InitializeGame などの初期化関数内で、必要な値を設定する。
マップ表示データ(MAPVIEW 構造体)
"map.h"
struct MapView
{
size_t countX;
// 横表示チップ数
size_t countY;
// 縦表示チップ数
float scopePosX;
// マップ表示範囲左位置(マップ内での座標)
float scopePosY;
// アップ表示範囲上位置(マップ内での座標)
int drawPosX;
// マップ描画左位置
int drawPosY;
// マップ描画上位置
};
マップ表示データ
0
(scopePosX, scopePosY)
countY
countX
(drawPosX, drawPosY)
スクリーン
マップ
表示範囲
横スクロールで、マップを右方向へ進んでいく場合、最初にマップの左端を表
示することになるため、マップ表示範囲左位置は 0.0f に初期化しておく。
Game Practice - 53 -
7 マップ
7.7
Game Practice 2011
マップスクロール
スクロールとは、マップの表示位置を徐々に変えていくことで、背景が移動し
ているように見せる処理のことである。
マップ表示部分の変更
画面(表示部分)
scopePosX = 0.0
マップ全体
scopePosX
例題 21
マップをスクロールさせる。
UpdateGame 関数
case MODE_STAGE:
......
// マップスクロール
if (myChar.flag)
{
ScrollMap(&mapView, &map, 3.0f, 0.0f);
}
マップをスクロールさせる
描画に用いた DrawMap 関数は縦・横スクロールに対応しているので、スクロー
ルさせるには ScrollMap 関数を使って、マップ表示データの表示範囲指定メン
バ(scopePosX, scopePosY)を変更するだけでよい。
一定の速度でスクロールさせるには、毎フレーム実行される UpdateGame 関数
内で ScrollMap 関数を呼び出す。
Game Practice - 54 -
7 マップ
Game Practice 2011
マップ終端でのスクロール停止
ScrollMap 関数を用いてマップをスクロールさせる場合、表示部分がマップの
終端(右端)まで到達したら、それ以上スクロールしない。このとき、
ScrollMap 関数の戻り値が終端に到達したことを示す値となるので、これを用い
てキャラクターを出すなどの処理を行うことができる。
例題 22
マップスクロールが停止したら、マップを書き換える。
UpdateGame 関数
case MODE_STAGE:
......
// マップスクロール
if (myChar.flag)
{
if (ScrollMap(&mapView, &map, 3.0f, 0.0f)
& SCRL_END_RIGHT)
{
for (int iy=1; iy<14; iy++)
{
for (int ix=map.countX-mapView.countX+1;
ix<map.countX-1; ix++)
{
SetMapChipNo(&map, ix, iy, 12);
}
}
}
}
SetMapChipNo 関数
指定位置のマップチップ番号を書き換える関数。一度書き換えると、再び
LoadMap 関数で読み込まないと、元には戻せないので注意。
Game Practice - 55 -
Fly UP