ゲームプログラミング/画面出力
Windowsの場合
編集Windowsでゲームをつくる際、マイクロソフト社の提供する開発ツールを使ってゲーム開発する場合には、画像レイヤー表現のための描画機能の都合により、実質的に DirectX プログラミングだけに開発ツールが限られます。
なぜなら、PNGやJPEGなどビットマップ以外の画像形式のダブルバッファリングをしようとしても、DirectX 以外の GDI+ や CImage などのWindows標準の画像開発ツールでは不可能だからです[1]。ダブルバッファ用画面を作成するときに必要になるハンドル HBITMAP には透明色がないので、原理的に絶対、GDI+ や CImage では透明化をできないのです。
重要な点としては、作成しようとしているゲームがたとえ3次元ポリゴンを駆使したゲームでなくても、たとえファミコン的な平面的グラフィックのゲームであっても、DirectXのレイヤー表現の機能が必要になってしまうという事です。
なお、DirectX の文法は Visual C++ の文法に則して書かれるので、Visual C++ の知識自体は、無駄にならず、再活用できます。
さて、参考リンク先のサイト(『教えて!goo』の質問掲示板)がいつまでも存続するとは限らないので当wikiでリンク先の説明内容の概要を述べておきます。
まず、ゲーム制作では画面のチラツキを押さえるために、ほとんどのゲームで「ダブルバッファリング」という手法が必要になります。なお、このダブルバッファリングはDirectXまたはDXライブラリを使うと、ほぼ自動的に画像表示の際にダブルバッファリングの設定を行ってくれるので、これらのライブラリを用いる場合なら普段はダブルバッファリングを意識する必要は無いです。
そして、ゲーム開発では、PNGなどの背景の透過のある画像が必要です。なぜなら、もし背景を透過させないと、たとえばマップ上を歩くキャラクター画像を背景上に表示したい場合でも、キャラの周囲の画像ファイル幅の四角い枠ごと表示されてしまうからです。そのせいで、背景の一部が、キャラ周囲の四角い枠で塗りつぶされてしまいます。
つまり、ファミコンのスーパーマリオ1やドラクエ1のようなゲームですら、Windowsでは、キャラクター画像の周囲を透過させないと、そういうシステムのゲームは作成できないのです。
なお、実はDXライブラリによる透明の機能は、PNGなどの透明部分を黒色 (R,G,B)=(0,0,0) に変換しています。そして(0,0,0)のドット部分だけ描画をしないという仕組みで、DXライブラリでは透明を実装しています。なので、DXライブラリ用の画像には、(0,0,0)を色としては使用できません。むりやり使用しても(0,0,0)のドット部分が描画されないので下の画像が見えてしまいます。なのでゲーム用のイラストを描く場合などは色(0,0,0)の使用を避けなければなりません。なお、たとえ色が(20,20,20)くらいでも人間の目では充分に黒色に見えます。(1,1,1)だと保存などの際に色情報が変化した場合に(0,0,0)になって透明化してしまいかねないので、もし黒色を使うなら余裕をもって(20,20,20)くらいにしておきましょう。裏を返すと、実は2Dゲームでは、完全な黒色を再現するのは基本的に無理です。もし仮に透明色を変更したとしても、今度はその色が使えなくなります。
-
テレビ業界などのクロマキー合成の現場。緑をキー色とする場合
-
合成後の画像
なお参考ですが、これはゲームだけなく、実写映像の合成(w:クロマキー合成)なども同じような仕組みです。実写撮影では青色がよく透明色に使われます(w:ブルーバック撮影)。だからよく、テレビの合成シーンの撮影では、青色の背景ボードの前で役者が演技してたりします。テレビ番組の収録映像などで、そういう撮影シーンを見たことある人もいるでしょう。(おそらく、当然ですが、そういう合成シーンの撮影では、役者の服などでは青色を使えなくなるハズです。) ただし、若干の違いとして、クロマキー撮影ではキー色と似たような色まで透過してしまいま。しかしゲームのデジタル画像の場合、似たような色は透過せず、キー色と全く同じ色だけが透過するので、なのでゲームでは(0,0,0)は透過するけど見た目が黒っぽくても(20,20,20)は透過しないわけです。」
実を言うと、上述のようなDXライブラリの透明色の仕組みは、DXライブラリを使わない場合のwindows標準の透明の実装方法と原理的には同じです。つまり、windowsAPIには特定の一色を描画しない機能によって、透明機能を実装する機能があります。そして実はDXライブラリの透明機能は、windowsAPIのその機能を流用しています。
なので原理的にはPNG画像を使わなくてもwindowsAPIだけでも透明の機能は実装できるのですが、まあ、せっかくPNGという規格があるのでそれを活用したほうがラクだし、ゲーム処理速度などの都合でDXライブラリを使うほうが効率的でしょう。
さて参考知識を述べるなら、 DirectX以外の GDI や CImage の提供する関数群(たとえば CImage の Draw 関数など)を使うことで、チラツキはありますが、とりあえず、マップ上にキャラクターチップのPNG画像の背景部を透過させる事だけなら可能です。
しかし、Windows標準の GDI+ や CImage の提供する関数群は、ダブルバッファリングに対応していないのです。一方、Windowsが標準で提供するダブルバッファ用のBitBlt などの関数は ビットマップ用に作成されているので、その際に HBITMAP ハンドルを定義して画像をそのハンドルに代入しなければいけないのですが、そのHBIMTMAPへの代入の際に(元のPNG画像には存在していた)透過情報が失われてしまい背景が塗りつぶされているビットマップ画像に変換されてしまうので、もはやキャラクター周囲が透過しなくなり、せっかくPNG画像を使っていた意味が無くなってしまいます。
このため、GDI+やCImageによる、提供されたAPIをそのまま利用するような標準的な方法では(あくまで「標準的な」との条件付きですが)、標準の方法では絶対にPNG画像のダブルバッファができないのです。
非標準な方法なら、可能です。ビットマップ関連の関数には、画像を1ドットずつ読み取って特定の一色を透過させる機能があるので(Win2000以降なら「TransparentBlt」という関数があります)、そういった関数を利用する方法もあります。ですが、それだと、
- 特定の一色が使用不可能になる欠点がある事。また、もしキャラクターの画像内部にその色が使われていると、キャラクターが透過してしまい、ゲーム中での画像バグになっていまう。
- わざわざ透過専用の処理を書く必要があり、労力がかなり増える。
- PNGなどと違い、画像の国際規格などとしては規格化されてないので、他のアプリでは使い回しが聞かない。
などの欠点があり、大変に不便です。
あるいは、透過機能をもった独自の画像形式などを作って、それを読み書きする関数を使えば、透過自体は可能ですが、しかし労力が多すぎて、実質的な意味がないです。また、独自機能なので、ハードウェアの支援なども受けられず、処理速度が低下する可能性が高くあり、そういう理由からも実質的な意味がないです。
なので、透過画像のダブルバッファ対応のため、Windwosゲームプログラミングでは開発ツールの画像ライブラリが、DirectXまたはその互換ライブラリ(たとえば『DXライブラリ』という日本人の作成した無料ライブラリがある)に、事実上の選択肢は限られてきます。
原理的には OpneGL など別の画像ライブラリでもレイヤー表現は可能かもしれませんが、せっかく DirectX というゲ-ム用の画像ライブラリがあるのだから、 Windowsゲーム制作では DirectX を用いるほうが合理的でしょう。
なお、DirectXは、仕様がコロコロと変わっています。最新の商業ゲームのような作品をより高速で処理するため、それに最適化させたDirectXの開発方針がwindows開発元のマイクロソフトでは採用されています。
このため、dirextXのコードの互換性は、DXライブラリよりもやや劣る可能性があります。 なので、ちょっとした入門的なゲームプログラムの練習では、DirectXよりもDXライブラリを使うほうが安全かもしれません。
なお、実はDXライブラリでPNG画像を使用した場合、DXライブラリの説明書の指示にある下記の支持のように、libpngおよびzlibのライセンス表記が必要です。
- DXライブラリ説明書『DxLib.txt』からの抜粋
・下記の機能を使用した場合は、配布するソフトウエアのドキュメント等に各機能に応じた 著作権表示を含めてください。 JPEG 画像を読みこむ機能を使用した場合 libjpeg Copyright (C) 1991-2013, Thomas G. Lane, Guido Vollbeding. PNG 画像を読みこむ機能を使用した場合 ・下記の機能を使用した場合は、配布するソフトウエアのドキュメント等に各機能に応じた 著作権表示を含めてください。 JPEG 画像を読みこむ機能を使用した場合 libjpeg Copyright (C) 1991-2013, Thomas G. Lane, Guido Vollbeding. PNG 画像を読みこむ機能を使用した場合 libpng Copyright (C) 2004, 2006-2012 Glenn Randers-Pehrson. zlib Copyright (C) 1995-2012 Jean-loup Gailly and Mark Adler. TIFF 画像を読み込む機能を使用した場合 libtiff Copyright (c) 1988-1997 Sam Leffler libtiff Copyright (c) 1991-1997 Silicon Graphics, Inc. Ogg Vorbis 音声データを読み込む機能、又は Ogg Theora 動画データを読み込む機能を使用した場合 TIFF 画像を読み込む機能を使用した場合 libtiff Copyright (c) 1988-1997 Sam Leffler libtiff Copyright (c) 1991-1997 Silicon Graphics, Inc. Ogg Vorbis 音声データを読み込む機能、又は Ogg Theora 動画データを読み込む機能を使用した場合
- ※ 長いので後半部は省略。
このように、自作の説明書にも、ライセンスの項目に
libpng Copyright (C) 2004, 2006-2012 Glenn Randers-Pehrson. zlib Copyright (C) 1995-2012 Jean-loup Gailly and Mark Adler.
のような表記で、libpngおよびzlibを使用したということを書かなければなりません。
さて、ゲーム以外の話題になりますが、実はよく鉄道駅やバス交通機関など公共交通機関にある液晶モニター(デジタルサイネージ)に表示されている動画は、Windowsで作成・表示されている動画だったりします。日本だけでなく世界各地で、そのような傾向です。なので、ときどきWindowsアップデートなどで画面が動画からWindowsメイン画面に切り替わるシーンがみられる場合があります(時々ネット上で、その交通機関の利用客などからSNSなどでそういう画面が報告されたりします)。おそらくWindowsの全画面モードにて、DirextXを用いて動画表示をしていると思われます。DirectXには、こういったゲーム以外の用途もありますので、操作の際など御参考に。
DXライブラリのコード初期設定
編集DXライブラリはvisual stuio プロジェクト作成時のセットアップに手間が掛かります。
なので、セットアップしたら、プログラミングをする前にまず、たとえばフォルダ名「DXセットアップ直後」とでも名づけたフォルダでも作って、 そこにセットアップ直後の作成したプロジェクト一式を保管しておくと良いと思います。
さて、 DXライブラリはプログラミング開始直後は、白紙です。 つまり、あえてコード例を書けば、初期状態ではコードは下記のような空白の画面です。
- コード例
(※空白なのは仕様です。)
なので、コードを書き足していくのです。
まず、DXライブラリ公式サイトには、下記のようなコードがあります[2]。
- 公式サイトのコード例
#include "DxLib.h"
// プログラムは WinMain から始まります
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{
if( DxLib_Init() == -1 ) // DXライブラリ初期化処理
{
return -1 ; // エラーが起きたら直ちに終了
}
DrawPixel( 320 , 240 , GetColor( 255,255,255 ) ) ; // 点を打つ
WaitKey() ; // キー入力待ち
DxLib_End() ; // DXライブラリ使用の終了処理
return 0 ; // ソフトの終了
}
しかし、これだけだと、起動して点をひとつ打って終了するだけなので、ゲームになりません。また、点が見えづらいので、点を1pxずつズラして幾つも追加するなどして確認することになるでしょう。
しかも上記コードだけでは、ループもしません。DXライブラリはそういう仕様なのです。
ループさせるには、後述のコードのようにwhile文を1つだけ加えます。(while文を加えてもフリーズしません。そういう仕様です。つまり、素のwindows API のようなイベントオリブン型のパラダイムとはDXライブラリは異なります。)
ともかく公式サイトのコード例だけではループもせずにゲームにならないので、ともかく、参考サイト新・C言語 ~ゲームプログラミングの館~ DXライブラリ (『1.7章 裏画面処理をして画像を動かす』) のコードのように変えます。
#include "DxLib.h"
int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int){
ChangeWindowMode( TRUE ); // ウィンドウモードに設定
DxLib_Init(); // DXライブラリ初期化処理
SetDrawScreen( DX_SCREEN_BACK ); //描画先を裏画面に設定
int x = 0;
int Handle; // 画像格納用ハンドル
Handle = LoadGraph( "画像/キャラクタ01.png" ); // 画像のロード
while( 1 ){
if( ProcessMessage() != 0 ){ // メッセージ処理
break;//ウィンドウの×ボタンが押されたらループを抜ける
}
ClearDrawScreen(); // 画面を消す
DrawGraph( x, 100, Handle, TRUE ); //画像の描画
x = x + 2; // xを2増やす
ScreenFlip(); //裏画面を表画面に反映
}
DxLib_End(); // DXライブラリ終了処理
return 0;
}
画像ファイル名(「画像/キャラクタ01.png」)などは参考先サイトでの一例にすぎませんので、私たちが実際に作るゲームではファイル名は変わります。
で、実際の各自のゲームではおおむね、下記のような骨格
#include "DxLib.h"
int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int){
ChangeWindowMode( TRUE ); // ウィンドウモードに設定
DxLib_Init(); // DXライブラリ初期化処理
SetDrawScreen( DX_SCREEN_BACK ); //描画先を裏画面に設定
// 初期設定
// ここにゲーム起動時に一度だけ読み込む命令を書いてください
//
while( 1 ){
if( ProcessMessage() != 0 ){ // メッセージ処理
break;//ウィンドウの×ボタンが押されたらループを抜ける
}
ClearDrawScreen(); // 画面を消す
// ループ中で処理したい命令
// ここにゲームのメインループ中の命令を書いてください
//
ScreenFlip(); //裏画面を表画面に反映
}
DxLib_End(); // DXライブラリ終了処理
return 0;
}
のような構成になるでしょう。
上記コードが、DXライブラリでゲームプログラミングする場合の基本例になるでしょう。
上記コードで用意されている関数は、DXライブラリまたはwindows APIが用意している関数ですので、
誰がコード記述しても、このようなプログラミングになるのが普通でしょう。
さて、もしプログラミング中に誤って{
や}
を消したり追加してしまったりして、
元のブロック構造が不明になってしまった場合には、上記の基本例に戻ればいいだけです。
なお、参考サイトの別ページにキー入力のコード例
int Key[256]; // キーが押されているフレーム数を格納する
// キーの入力状態を更新する
int gpUpdateKey() {
char tmpKey[256]; // 現在のキーの入力状態を格納する
GetHitKeyStateAll(tmpKey); // 全てのキーの入力状態を得る
for (int i = 0; i < 256; i++) {
if (tmpKey[i] != 0) { // i番のキーコードに対応するキーが押されていたら
Key[i]++; // 加算
}
else { // 押されていなければ
Key[i] = 0; // 0にする
}
}
return 0;
}
というコードがありますが、これはあくまで参考先サイトの方のゲーム用でのキー処理コードですので、 別にこれが無くてもキー入力の処理は可能です。
実際、DXライブラリに用意されているキー読み取り関数 CheckHitKey(KEY_INPUT_キー名) </cpde>を使用し、たとえばzボタンを読み取るなら、
if文などの条件式で
CheckHitKey(KEY_INPUT_Z) == 1</cpde> を記述すればキー処理が可能です。
もちろん、1秒間に60回ループが回っているので、そのままだとzボタンに対応するコマンドを60連発してしまうので、
何らかの対応は必要です。
実際のコード例のヒナガタは、DXライブラリ公式サイトのコード例と参考サイト『ゲームオウログラミングの館』などのコード例などを合わせて、さらに作成したいゲーム用に必要なヘッダなどをincludeしたりするので、たとえば下記のようになるかもしれない。
#include "DxLib.h"
#include <stdio.h> // セーブ用
#pragma warning(disable:4996) // fopen
#include <math.h> // 切り上げ計算で使用
// ここらへんに初期設定したい変数を宣言できる
// int mapNumber = 1; // マップ番号
/*
// なくても動く
int Key[256]; // キーが押されているフレーム数を格納する
// キーの入力状態を更新する
int gpUpdateKey() {
char tmpKey[256]; // 現在のキーの入力状態を格納する
GetHitKeyStateAll(tmpKey); // 全てのキーの入力状態を得る
for (int i = 0; i < 256; i++) {
if (tmpKey[i] != 0) { // i番のキーコードに対応するキーが押されていたら
Key[i]++; // 加算
}
else { // 押されていなければ
Key[i] = 0; // 0にする
}
}
return 0;
}
*/
// ここらへんで初期設定したい変数を宣言してもいい
// int jumpRyoku1 = 50; // アクションゲームでのジャンプ力
// これは文字数バッファなど。(モード番号ではない。)
#define MAX_LOADSTRING 100
#define MAX_LENGTH 300 // 要素数に注意
HINSTANCE hInst; // 現在のインターフェイス
WCHAR szTitle[MAX_LOADSTRING]; // タイトル バーのテキスト
WCHAR szWindowClass[MAX_LOADSTRING]; // メイン ウィンドウ クラス名
// ここらへんで初期設定したい変数を宣言してもいい
//int offsetY = 100; // ウィンドウのy座標位置の調整用
// プログラムは WinMain から始まります
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
ChangeWindowMode(TRUE); // ウィンドウモードに設定
// SetUseSoftwareRenderModeFlag(TRUE); // ソフトウェアレンダリングのテスト
if (DxLib_Init() == -1) // DXライブラリ初期化処理
{
return -1; // エラーが起きたら直ちに終了
}
SetDrawScreen(DX_SCREEN_BACK);
// ここにアプリ起動時に一度だけ読み込まれる命令を書く
// 実際には WinMain内部でないと実効できない命令などを、ここで行うことになる
// int treex = 5; int treey = 4; // 説明の簡単のため、例では単なる変数宣言にした
// 下記while がゲーム中でのメインループ。この中を1秒間に60回、繰り返すことになる
while (1) {
if (ProcessMessage() != 0) { // メッセージ処理
break;//ウィンドウの×ボタンが押されたらループを抜ける
}
if (CheckHitKey(KEY_INPUT_ESCAPE) == 1) {
break;
}
ClearDrawScreen();
// ここにゲーム中のメインループ中での命令
//if (keyEnableZ == 0 && nyuuryokuMatiZ > 0) {
//nyuuryokuMatiZ = nyuuryokuMatiZ - 1;
// }
ScreenFlip(); //裏画面を表画面に反映
}
WaitKey(); // キー入力待ち
DxLib_End(); // DXライブラリ使用の終了処理
return 0; // ソフトの終了
}
DXライブラリの画像上限個数
編集
DXライブラリの画像読み込み命令 LoadGraph で読み込める画像の合計枚数には、制限個数があります。
ネット検索しても特に出典は見当たりませんが、しかし後述のように実際にプログラミングで LoadGraph の実行を繰り返しするプログラムを書けば簡単に制限個数の減少を確認できますので、疑うなら、ご確認ください。
このセクションの元の文を書いた編集者が実験でいちど試したところ、制限個数は3万数千個の程度でした。
後日、条件を少し変えて試したら、
203682:Graphハンドルの数が限界数( 32768 )に達していて新たなハンドルを作成できません
とVisual Studio の出力欄(エディタ画面右下にある)に出ました。
なお、編集者のプロセス環境では、このときプロセスメモリは約 236MB でした。
画像のサイズは、キャラチップ用に作成した、横 32×縦 64 の画像です。
もしかしたらパソコンのスペックなどによって、制限個数は変わるかもしれません。
DXライブラリ開発者の意図は知りませんが、一般にプログラミングにおいてこのようなハンドル作成命令の制限個数の必要性は、メモリーを保護する役目もあります。
なぜなら、たとえば、もしバグにより、メインループ1周ごとに新規に画像ハンドルを作成するプログラムが組まれてしまった場合、
60FPSなら1秒間に60周するわけですから、60個ぶんの画像ハンドルのメモリが使用されてしまいます。
秒数が経つたびにどんどんと画像ハンドルが新規作成されてしまうので、しまいにはメモリが不足してしまいます。
画像に限らず、メモリの使用量がどんどんと上がっていくバグのことをメモリーリークといいます。
こういった事が分かってくると、DXライブラリはあまり大規模なゲームの開発には向かないことが分かってきます。
一応、画像ハンドルを破棄する命令を書けば可能ですが(関数 DeleteGraph で画像ハンドルを破棄できます)、管理が面倒ですし、破棄のタイミングを間違えればバグの原因にもなります。
- 教訓
上述のようなメモリーリーク対策のため、プログラミング時は原則、けっしてメイン while ループ内では LoadGraph 命令は使用しないのが基本スタイルになります。
つまり、アプリ起動時に一度だけ、それぞれの画像の LoadGraph 命令を読み込むことになります(そういう命令を記述する箇所があるので、そこに書く)。
// プログラムは WinMain から始まります
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
ChangeWindowMode(TRUE); // ウィンドウモードに設定
// SetUseSoftwareRenderModeFlag(TRUE); // ソフトウェアレンダリングのテスト
if (DxLib_Init() == -1) // DXライブラリ初期化処理
{
return -1; // エラーが起きたら直ちに終了
}
SetDrawScreen(DX_SCREEN_BACK);
の次のあたりが、アプリ起動時に1度しか実行されない箇所なので、そういう場所だけでメモリー確保の命令を使うことになります。
この技術はアプリ開発開始の時点では覚えていても、しかし開発の中盤以降になると忘れやすいので(そしてメモリーリークのバグを踏んで思い出す)、教訓にしておきましょう。
- その他
このバグを追試するのは意外と難しく、なぜかリークしているハンドル画像が1個だけだと、うまくリークしない場合があります。
なので、たとえば、下記のようにひとつのハンドルの画像を書き換えるなどする必要があります。
int hitoMigiHandle = LoadGraph("GameData\\charachip\\chara_right.png");
hitoMigiHandle = LoadGraph("GameData\\charachip\\chara_right_m1.png");
hitoMigiHandle = LoadGraph("GameData\\charachip\\chara_right_m2.png");
しかもこのバグの嫌な点として、アプリを終了するのにも時間が掛かります(おそらく、メモリを掃除するのに時間が掛かっている)。
なお、出力メッセージの「Graphハンドル」の文言から分かるように、どうやらwindowsプログラミングの「ハンドル」機能を用いてDXライブラリでは画像のロードを実装しているようです。
ともかく、なんらかの事情で、本来ならリークするはずのコードがリークしない場合があるので、そのためバグの発見が遅れることがあります。
なので、私たちはルール「メインwhileループ内では画像ロードしない」を徹底させることを原則にしましょう。
処理速度の問題
編集
- 半透明と処理速度
画面中、半透明部分が増えると、じつは処理速度が低下します。半透明処理というのは、やや描画に時間が掛かるのです。
Windwosだけでなくプレステ2も同様、半透明よりも完全透明のほうが処理が軽いです[3]。専門用語で「1bit抜き」とか「パンチスルー」と言って、半透明の無い完全透明方式のほうが処理が軽いのです[4]。
また、ボカシ処理は、もしリアルタイム3Dレンダリングで行っているなら、それはプレステでは半透明処理として扱われます[5]。
RPGにかぎらず、このため古いゲームで半透明部分のあるゲームでも、半透明の画像の下側にある画像は静止画像だったりします(静止画像なら、描画を使いまわしで演算省略できるので、高速化しやすい)。
不透明の場合なら単に上書きすればいいだけですが、完全透明の部分のある画像の場合なら下地の画像を転写すればイイだけすが、
しかし半透明の場合だと上書き画像と下地画像の両方を読み取らないといけないので時間が掛かるのです。
だから昔のスーファミ時代のドラクエやファイナルファンタジーでの不透明処理の多さや、ツクール2000あたりが標準的な設定ではウィンドウなどを透過処理をしないのには、それなりに理由があるのです。(実は1990年代前半のPCエンジン作品やメガドライブ作品などの時点で、ゲーム中に半透明ウィンドウを用いている作品はあります(たとえばPCエンジン版のパトレイバー)。)
しかし、「半透明をさけるため」といって、ドットを1点ずつ打つ命令によって大きな半透明画像を作るのは、もっと処理速度が悪いので(CPUへのアクセス回数が多くなるので処理速度がかなり悪い)、「ドット1点ずつ」方式はやめるべきだというのが、ゲームにかぎらない画像プログラミングの常識です。
これはつまり、現代のパソコンのハードウェア基盤には、なんらかのICチップなどで、半透明などを効率的に良い速度で処理できるようなチップが基盤に組み込まれているととになります。そういうハードウェアによる画像描画の支援があるので、なるべくそのようなハードウェア支援を受けやすい命令を用いて描画するのが、画像プログラミングのコツです。
あるいは、もしそういう特別なチップが無いハードウェアの場合でも、OSメーカー企業がとても効率的な描画プログラムをシステム内に搭載していたりするので、あらかじめ容易されている半透明処理などの命令を使ったほうが、そういう効率的プログラムを活用できます。
もし自前で「ドット1点ずつ打つ」プログラムを作る方式だと、ハードウェア支援やOS支援の機能をあまり活用できないのです。よって、半透明の機能についての開発方針としては「処理速度のリスクを意識しながら、演出的にどうしても必要なら半透明の処理郡も使う」ということになるでしょうか。
こういうハードウェア的な事情の歴史的な変化があるので、半透明処理や3D処理などの現代的な描画に関しては、メガドライブやPCエンジンの時代の古い手法は、あいにく21世紀の現代ではやや通じないでしょう。現代的にアレンジして活用していくことになるでしょう。
なお、昔のゲームだと、不透明で隠れている部分は描画を省略するなどして、処理を高速化したりしています。なので、90年代ぐらいの古いゲームだと、そもそも半透明部分がなかったりとか、あるいは半透明部分のサイズが小さかったりするわけです。
昨今のゲームでは、パソコン/ゲーム機のハードウェア性能が上がったので、半透明部分の大きい画像などが動いたりしますが、ああいう事がノートパソコンでも気軽にできるようになったのは2010年くらいからです(ツクールVXやウディタの登場時期のあたり)。それ以前は、パソコンではノートパソコンでは、大きな半透明部分のある動画などは、処理が困難だったわけです(2001年ころなら、半透明&動画などの高度な映像処理あるゲームをプレイしたいなら、ゲーミングPCなどの高値の筐体PCを買う必要があったわけです)。
現在の最新のツクールやウディタの半透明&動画の画像描画ばかりに見慣れると、あたかも昔からそういう表現がノートパソコンやタブレットPCなどで気軽にできたかのように見えますが、しかし実はなかなか半透明の描画は、2001年ころの過去には困難だったわけです。
半導体の性能向上は2010年以降、頭打ちです。このため、あまりにも処理速度を今よりもさらに高く要求するUIは、実装に問題が生じます。
- ※ 下記のような話は、一見現代では不要なように見えるが、実はスマホゲームなどで、やや形を変えて、似たようなノウハウが生き残っている。携帯電話は、通信帯域の制約や、ゲーム専用機ほどにはメモリが使えないので、レトロゲーム的な工夫が必要になることもある。(マンガ『ナナのリタテラシー』2巻のゲーム会社勤務回で、そういうネタがある。)
- なお、川村元気 著『理系に学ぶ』で、対談相手の一人の任天堂の宮本茂は、文脈はやや違いますが、
スマホゲームの流行について、
- 宮本「ゲーム文化全体はどうかというと、専用機がどんどん豪華なものに進化してしまったのかなと。
だから、スマホのおかげでゲームがまた日常に戻ってきたとも感じいて」と述べています[6]。
- 必ずしも宮本は、スマホでファミコンの技術が通じるとは言ってませんが、このあとの文章で
宮本「たとえば「ファミコンの頃のほうがよかったよね」というゲームを今作っても遊んでくれる方はいるし、」
と続けています[7]。
Windows Vista と DirectX のサポートの歴史
Windowsに関しては、半透明や3D描画をするなら、なるべくならDirectXまたはDXライブラリ(またはそれと同等の技術力のある有名ライブラリ)を使ってプログラミングをするのが良いでしょう。なぜなら、DirectXの基本的な機能のひとつで、半透明処理および3D処理があります。
実際、ゲームをしない場合のWindowsのデスクトップ・ウィンドウでも、Windows Vista 以降バージョンのWindowsではウィンドウの上部や端部が半透明になっていますが、あれはDirecrXがVista以降では原則オンになっていて標準作動になっているからです。Vista以降のOSでは、ウィンドウが何枚も重なっている場合の演算処理を、3D演算的にWindows内部で計算しています。
Vista以前である Windows 2000 などのOSはもうサポート切れですので(それどころか Widows 7 すら2021年の現代(本文の執筆時)ではサポート切れ)、今ではもう、低スペックでDirectXの動作しないパソコンのことを気にする必要は無いです(なぜなら上述の理由によりVista以降、DirectXが快適に動作するスペックでないと、そもそもパソコン販売におけるマイクロソフト推奨条件を満たさないはずだからです)。
Windowsだけでなく、mac系OSやLinuxでも、現代ではDirectXと同等の3D描画エンジンや半透明処理機能を搭載しています。
だから、「どうしてもWindowsの原理を勉強したい」や「1990年代のような古典的なゲームの勉強をしたい」などの特別な理由を除けば、現代のゲーム制作では、わざわざDirectXやDXライブラリなどのゲーム用ライブラリの利用を封印してプログラミングしていく必然性は乏しいです。
RPGのマップレイヤー
編集
透過ありマップチップとキャラの描画順序問題
編集
問題点
編集
このページではマップチップのことを便宜上「背景」と言ったりもします。しかしキャラクターチップよりも前側に存在するマップチップもあり、たとえば木々の上部の葉のおいしげっている部分は、その生い茂り(おいしげり)の後ろ側にいるキャラを隠さなければなりません。
つまり、キャラと樹木が、下記マップのようにそれぞれ2マス使用している場合の、描画の問題です。()
(キャラ↓)
頭 葉 (←樹木)
足 幹
キャラが木の後ろに立っているときは、下記のようにキャラも足は隠れて、頭だけ見えるわけです。
頭
葉
幹
一方で、キャラクターが木々よりも前に立っている場合には、けっしてキャラクターは隠れてはいけません。つまり、下記のようにキャラの頭も足も両方とも表示しなければなりません。
葉
頭
足
このため、もしキャラが木よりも前に立っているときは、木々の下側の幹はキャラチップよりも後ろの層に配置し(便宜上「後景」と本ページでは呼びます)、一方で、木々の上側の葉の生い茂りは、キャラよりも前に配置(便宜的に「前景」とする)しなければならないのです。
樹木に限らず、たとえばアーチ上の構造物のアーチの下をくぐる場合なども、その構造物の柱とアーチとキャラとの柱前後関係を、適切に設定しなければならないわけです。面倒です。
これらの問題の解決策として、下記のような幾つかの方法があるでしょう。
レイヤー問題の解決策
編集
ラクな方法
編集
一番ラクな方法は、キャラチップの縦方向の長さを制限した上で、キャラ位置と樹木位置とで場合わけのプログラムを書くことです。
キャラが樹木の手前にいるときに、キャラの頭が樹木の葉の生い茂る部分に届かない長さにすれば、何も悩むことはありません。
ただしこの場合、ドラクエ2以降のような隊列を組んでパーティが複数人表示される方式は、困難になります。ドラクエ1のようにキャラ1しか表示されないなら可能な方式です。また、マップ中の通行人ごとにプログラム判定するのは面倒なので、通行人は全員、立ち止まっていなければなりません。なので、この方式は(実装だけならラクですが)実用性は乏しいです。
妥協の必要性
さて、前後関係の設定まで含めて、制作ツールによっては、この前後表現をたとえば3層程度で表現しないといけないので、ある程度の妥協は必要です。別の制作ツールなら、もしかしたら5層や6層ほど使えるかもしれませんが、どちらにレイヤー枚数には制限があります。
なぜレイヤー枚数に制限があるかというと、もしレイヤーを増やすと処理速度の負担になるからです。だから、2Dゲームのプログラミングにおいては、ある程度の妥協は必要です。
もし妥協しないと、自分でレイヤー節約などのために、たとえば下記のようにキャラ位置によって表示アルゴリズムを切り替えるプログラミングをするなどの面倒な羽目になりかねません。
たとえば、もし妥協しない場合には、たとえば看板などが立っている場合、物理的に正確な表現では、前からキャラが看板に近づいたら看板よりも前にキャラ表示をするために看板は後景レイヤーになり、一方では後ろからキャラが看板に近づいた場合には看板でキャラが隠れるように看板が前景レイヤーになるはずです。
つまり、看板に前から近づくか後ろから近づくかで、前景レイヤーになるか後景レイヤーになるかの切り替えが必要になってしまいます。
しかし、たいした内容でもない「ここがハジメガルドの町です」ぐらいの中身しかない小さい看板に、果たしてそこまで手間をかける必要があるのか、疑問です。
こういう妥協をするため、どの制作ツールでも、後ろからは(看板などに)ピッタリは近づけないようになっています。あるいは、前からピッタリ近づけない代わりに後ろ側からはピッタリ近づける場合もあります(ツールや作品に寄る)。
そもそもゲームに物理的に正確な表現を求めるべきかという問題もあるし、もし物理的・数学的に正確な表現がほしい場合には3DプログラミングでDirect3Dなどに頼るべきかという問題もあるでしょう。
草で足元を隠されている処理も、看板と同様に、上下左右のどの方向から草に入っても歩行中のすべてのシーンで足元がうまく隠されている必要があるので、
なかなか大変でしょう。
草チップをキャラ基準で前景と後景に分離するよりも、草チップを基準にしてキャラチップの足元を後景にして、膝より上を前景に設定する方式のほうが、ラクかもしれません(未確認)。
擬似zバッファ的な方法
編集
y方向の一番上の行ごとに、マップ背景、キャラ&樹木を書いていって、その行が書き終わったら、一個下の次の行を書いていく方式です。
イメージ的にコードを書くと、だいたい下記コードのようなイメージです。(DrawGraphなどDXライブラリの命令などについては解説しません。)
for (y_map = 0; y_map <= 8; ++y_map)
{
for (x_map = 0; x_map <= 10; ++x_map) {
// mapGround は地面レイヤ-
if (mapGround[y_map][x_map] == 0) { // 草原
DrawGraph(mapChipWidthX * x_map, mapChipWidthY * y_map, sougenHandle, false);
}
if (mapGround[y_map][x_map] == 1) { // 岩山 (通行不能)
DrawGraph(mapChipWidthX * x_map, mapChipWidthY * y_map, iwayamaHandle, false);
}
} // for (x_map = 0; x_map <= 10; ++x_map)
for (x_map = 0; x_map <= 10; ++x_map) {
// mapGTree は地面レイヤ-
// if (mapTree[y_map][x_map] == 0) { // 木が無いマス
// 何も書かない
// }
if (mapTree[y_map][x_map] == 1) { // 木があるマス
DrawGraph(mapChipWidthX * x_map, mapChipWidthY * (y_map - 1), treechip1Handle, true); // 透過させるので最後 true
}
// このあと、さらにキャラチップ4方向のif文章をforで同様に記述
} // for (x_map = 0; x_map <= 10; ++x_map)
} // for (y_map = 0; y_map <= 8; ++y_map) // yのほう
本当は、さらにキャラクターチップの描画があるのですが、向き4方向ぶんの処理を書くためにコードが長くなるので、省略しました。
これは、「画像プログラミングでは、あとから描画されたものが、まえに描画してあったものを覆い隠す」という仕組みを利用する方法ですので、手前にいるキャラの頭が自然に後ろの樹木を隠したりできます。同様に、手前に樹木があれば、自然に後ろのキャラを隠せます。
なお、3Dグラフィックのzバッファ法はこの方式に近いです。とはいえ、2Dゲームなので「yバッファ」と言うのが正確かもしれませんが。
ただし、この方式の場合、y方向に移動するときの描画処理が、少し面倒です(あと、処理速度がやや低下する可能性もありますが説明を省略)。キャラが下に移動する場合には、描画の処理を変えないといけません。
なぜそうしないといけないかというと、もしキャラが下に移動する場合、先に移動先である1行下の地面を移動処理をしておかないと、キャラが描画先の地面で上書きされてうのでキャラ表示が消えてしまうからです。(一方、(移動前マス基準ではなく)もし移動先のマスを基準に描画タイミングを指定しても、今度は上方向に移動するときにキャラ画上書きされてしまいます。だから移動前マスを基準にしても、移動後マスを基準にしても、どちらにせよif文での場合によって上書きの上から描画するようにタイミングを1行ぶんだけズラしてコードを書かなければなければなりません。 )
だから、(マップ描画でfor文を使いますが)もしキャラの移動方向が下方向のときだけ、for文での1マス下の行の地面を書くタイミングでキャラを書かないといけないのです。
しかも、さらにややこしい事に、この方式のプログラミングでの注意事項としては、下方向のマス移動中だけはif文により描画タイミングを区別する必要がありますが、しかしマス移動前と移動後の下向き画像は区別しないままで右向き・上向き・左向きと一緒のタイミングで描画する必要があります。もし下移動中と下移動後の描画を同じタイミングで描画してしまうと、移動後にそのキャラ表示が消えてしまいます(なぜなら、そうすると現在の y段 の描画タイミング時に、下向きの画像だけ描画がなくなってしまっているので)。
だから、描画タイミングを区別するシーンは、下向きでさらに移動中のシーンだけです。それ以外の移動の前後のシーンは、たとえキャラ画像の向きが下向きであっても、描画タイミングは他の上向き・横向きのタイミングと一緒にしなければなりません。
ナナメ下移動とかの実装も大変そうですが、入門の範囲を超えるので、もう説明は省略します。
2022年の現時点の編集では、このページを書いてる人自体、斜め移動の原理をまだ理解できていません。一方、キャラが上に移動する場合、特に工夫しないでも、キャラが消えたりしません。
- 欠点
さて、この方式の短所として、もはや「マップチップを描き終えてから、キャラチップを描く」ということが出来なくなります。
「各マスを描くごとに、このマスにキャラがいるかどうかを判定して、キャラが居ればマップチップの描画後にキャラチップを描く」というシステムにせざるをえません。
一般的に2DのRPGゲームでは、画面中のマップチップのマス個数の数十個に比べて、同じ画面中のキャラの人数は敵味方を含めて数人程度と少ないので、このため処理速度的には少し非効率です。もし(2Dゲームでなく)3Dグラフィックならグラフィックボードのハード支援を活用できたりして処理を効率化できるかもしれませんが、しかし2D-RPGのマップチップ描画のプログラムでは、グラフィックボードは3D認識をしないので、ハード支援は受けられず、CPUで1個ずつ描画処理していくことになってしまいます。
3Dポリゴンで2D-RPG再現する方式
編集
初心者レベルでは不要な方式ですが、3Dポリゴン用のハードウェア支援を受けるための手段として、
いっそ3Dポリゴンによって2D的な画像を再現する方式もあります。
任天堂DSだかPSPだのの携帯ゲーム機などで、目的は企業秘密なので不明ですが、このような3Dポリゴンで2D-RPGを再現する方式のゲームがあり、
プレイヤー視点だと2Dゲームですが、実は内部プログラムでは3Dポリゴンやそのテクスチャなどとして実装しているというゲームもあります。
ただし、この方式では、キャラチップが実は斜め45度に奥側に傾いていたりなどのテクニックが必要になったりします(たしか任天堂のゲームでそういうのがあります)。
果たして初心者はそこまでする必要あるかは疑問です。3Dポリゴンを使う方式は、プロの企業の人たちに任せましょう。
知識として、「3Dポリゴンによって2D-RPGを実装する方法もある」とだけ知っておけば、初心者レベルの知識としては十分でしょう。
それよりもプログラミングを実際に経験していくのが良いでしょう。
上記のように2D的なデフォルメを3Dポリゴンで再現しようとすると、どこかの段階で、人間の直感から離れたプログラミングの発想が必要になってしまうでしょう。なので、初心者には敷居が高いでしょう。
既存の製作ツールのマップチップ予備知識
編集
同人ゲームなどでは、キャラチップのゲーム読み込み用素材を作るとき、
けっして、いちいちキャラ前向きの画像で1ファイル、横向きの画像でまた別の1ファイル、みたいなことはしません。
そうではなく、その同じキャラクターなどの前向き画像、横向き画像、後ろ向き画像などをまとめて、1枚の画像ファイルにして管理しているのが一般的です。
こういったキャラチップの各向きがひとまとめになった画像ファイルのことを「タイル」または「タイルセット」などと言います。
チップのタイルは等方的とは限らない
編集
ややこしいことに、製作ツール側の事情なのか、キャラチップのキャラ画像の1体ずつは16×16で正方形なのに、
しかしチップタイルだとタイル内の画像の感覚が等方向的ではなく、タテ18×ヨコ20のように、やや長方形で並んでいる場合もあります。
なので、手本のゲームを調べる場合は、実際のゲーム場面をスクリーンショットするなどして確認しましょう。
ゲーム側での拡大表示
編集
ゲーム制作ツールでは、キャラチップ素材は16×16なのに、しかしプレイされるゲーム側では2倍に拡大して32×32で表示している場合もあります。
これを確かめるには、比較のために手元で32×32の新規キャラチップを作成して自作ゲームエンジンで表示してみましょう。
パソコン上でそのアプリのウィンドウの横に、ゲーム製作ツールの画面を表示してみましょう。
すると、マップチップの大きさがピッタリと一致します。
さらに、念のためドットエディタで確認してみると、たしかにゲーム製作ツールのゲーム側の画面が32×32だったりします。
製作ツールと自作エンジンとでスクリーンショットを撮影してドットエディタで確認してみましょう。
こういうことは後述のことは、サンプルゲームをいくらよく見ても気づきません。ドットエディタでキャラチップ素材などのサイズを実際に確認しましょう。
マップ表示位置とキャラ表示位置が少し違う場合も
編集
さらに、ゲーム表示時には、キャラチップの表示位置をマップチップの表示位置よりもy方向に4ドットずらすという方法もあります(数え方によっては2ドット)。
こうすると、あたかもキャラチップがマップチップ2マスぶんを使用しているように見えるので、プレイヤー視点ではリッチに見えます。
黒色はrgbが0,0,0ではない
編集
キャラチップで黒色に見えるドットでも、画像ツールのスポイトで色確認してみるとRGB値が (20,20,20) だったりする場合もあります。
さらにキャラチップの周囲の黒い輪郭線(ただし RGB値が (20,20,20) だったりする )まで含めると、
もはやチップサイズが 16×16ではなく18×18だったりする場合もあります。
その製作ツールがどうやって表示しているか走りません。
輪郭線の有無でサイズ規格が違う
編集
あるゲーム制作ツールをドットエディタで調べると、輪郭線の抜きではサイズ規格が16×16だが、
しかし輪郭線まで含めるとサイズ規格が18×18だったりと(ゲーム中で2倍表示されるので 36×36 で輪郭線こみで表示)、
つまり輪郭線の有無によってサイズ規格が違っています。
なおチップタイルの各ドットの色情報を見ると、キャラの輪郭線を RGB値が (20,20,20) で描いていたりする場合もありますので、背景色(RGB が 0,0,0 )と区別できます。
それらのゲーム製作ツールでどう輪郭線を表示しているのかは知りません。
(なお、一般にこの20,20,20を読み込まずとも、輪郭線を表示する方法はある。それは、まず輪郭線抜きのチップをもうあと4枚コピ-して色変換して20,20,20に塗りつぶすことでシルエット上の画像にしてから、そのシルエット画像をキャラの後ろに表示することで輪郭線っぽく見せるという技法もあるので。なお、この技法は、3Dゲームなどでも使われ、シルエットなんとか法と言われる(業界でも名前が細かくは定まってないっぽい。本wikiではシルエット法については『ゲームプログラミング/3Dグラフィック/手書きアニメ調の3D#黒コピー拡大背景の方法』で説明。)
半透明のコード例
編集
なお、実際のコード例として、たとえばDXライブラリなら、読み込み画像(読み込みの時点では不透明だとする)を半透明化する描画は
SetDrawBlendMode(DX_BLENDMODE_ALPHA, 128);
DrawGraph(x座標, y座標, 目的画像のハンドル, false);
SetDrawBlendMode(DX_BLENDMODE_NOBLEND, 0);
のようなコードになります。
DXライブラリの組み込み関数である SetDrawBlendMode の第1引数の DX_BLENDMODE_ALPHA は予約語であり、これから透明モードを開始することを宣言しています。
第2引数の 128の部分はアルファ値です。不透明なら255であり、完全透明(見えなくなる)なら0です。
128だと、ちょうどぴったり半透明(0と255の中間の整数なので)です。
SetDrawBlendMode(DX_BLENDMODE_ALPHA, 128);
だけだと、ソフト内で今後に描画する画像の全部が透明になってしまいますので、
透明モードを終了するために SetDrawBlendMode(DX_BLENDMODE_NOBLEND, 0);
によって透明モードを終了します。
重要な点としては、半透明モードに限らず、負担のかかる特殊な描画モードを使い終えたら、すぐにその特殊な描画モードは終了して、通常の描画モードに戻るのがコツです。
なお、教育を兼ねてコード例を書きましたが、もうひとつの簡単な半透明の実装の方法として、あらかじめ画像製作ソフトなどで半透明のPNG画像を作成しておいて、それを読み込ませるという手法もあります。ただしこの場合、ゲーム中で不透明画像と半透明画像の両方を表示したい場合、画像を2枚(不透明画像を1枚、および半透明画像を1枚)を用意しなければならないので、用意する画像が増えます。
その他、微妙に処理速度などがそれぞれの方式で違う場合がありえますので、用途に応じて適切なほうの手法を使いましょう。
- DXライブラリの画像ハンドルは配列化を可能
さて、DXライブラリで画像の読み込みは、
int ハンドル名 = LoadGraph("画像名.bmp");
で読み込めます。
たとえば
int test = LoadGraph("gazou0.bmp");
のようなコードになります。
int で宣言した変数(上記なら変数test)は実際に整数です。
なので、たとえば画像の読み込み結果は、整数配列などにも代入できます。
つまり
int hairetu[5]:
hairetu[0] = LoadGraph("gazou0.bmp");
のようなコードも可能です。(ただし、変数宣言の以外の演算を伴うので、Visual C++なら WinMain 以降で定義する必要がある。)
RPGの場合、キュアラター画像やモンスター画像などを何種類も同種の画像を読み込むので、もし配列でコードを使いまわしできないのならば不便です。ですが上記のように配列の整数型に画像ハンドルを代入できるので安心しましょう。
技術的に上記の整数 int への画像ハンドル代入について考察するなら、整数型変数のメモリに大きな画像データそのものを代入するのは難しいので、
おそらく画像のアドレス変数などをポインタ的に記録していると思われます。
動作がポインタ的に振舞うので、てっきり「配列にできないかも?」と心配しがちですが、しかし平気で配列にできます。安心しましょう。
そもそもDXライブラリに限らず、C言語の通常の使用法でも、
ポインタ値やアドレス値を、単なる整数型変数に代入することは実は可能です。
せっかくなので、ポインタを復習的に理解しておきましょう。
下記コードの前半部分は、wikibooksのC言語の教科書にあった内容です。
後半が、このゲームプログラミングの節独自の検証内容です。
#include <stdio.h>
int main(void) {
int i = 1234,
*p = &i;
printf("整数変数iのアドレスは%p\n", &i);
printf("ポインタ変数pの値は%p\n", p); // 注目
// ここまで教科書的な普通のコード
printf("\n後半部\n");//
int b;int c; int d; int e;
b = &i;
printf("整数型bにアドレスiを代入して%%d 表示すると%d\n", b);//
c = &i;
printf("整数型cにアドレスiを代入して%%p 表示すると%p\n", c);// よく見ると上記の注目と同じ
d = *p;
printf("整数型dに*pを代入して%%d 表示すると%d\n", d); //
e = *p;
printf("整数型dに*pを代入して%%p 表示すると%p\n", e); //
int hairetu[7];
hairetu[0] = *p;
printf("配列0番目は %p\n", hairetu[0]); //
printf("配列0番から最初の数を復元すれば %d\n", hairetu[0]); //
return 0;
}
これを実行すると(windows上でgccで実験)、警告文は出ますがコンパイル可能です。
そして実行すると、表示結果として
整数変数iのアドレスは000000000022FE34
ポインタ変数pの値は000000000022FE34
後半部
整数型bにアドレスiを代入して%d 侮ヲすると2293300
整数型cにアドレスiを代入して%p 侮ヲすると000000000022FE34
整数型dに*pを代入して%d 侮ヲすると1234
整数型dに*pを代入して%p 侮ヲすると00000000000004D2
配列0番目は 00000000000004D2
配列0番から最初の数を復元すれば 1234
- (※ なぜかエスケープシーケンスのあとの漢字が文字化けする。)
結果をよく見ると、単なる整数変数 b は、ポインタ変数pと同じです。また、変数cでは、もとの数値を復元できています。
配列については説明の簡略化のため抜粋しましたが、上記コードのように単なる整数配列にもポインタ関連の内容を代入可能ですし、配列からもとの数も復元できています。
このように、ポインタ変数の値を整数に代入することができますので、同様に整数の配列にもポインタの結果を代入することが可能です。
下記コードを見てください。変数bは宣言時には全くポインタもアドレスも代入していません。ですが最終的に、変数bを仲介して得られた変数cを用いて、もとの変数iの数値を復元できています。
#include <stdio.h>
int main(void) {
int i = 1234;
printf("変数iは%d\n", i);
printf("変数iのアドレスは%p\n", &i);
int b; // 宣言時はまったくポインタもアドレスも無関係
b =&i;
printf("bは%p\n",b );
int *c =b;
printf("cは%d\n",*c );
return 0;
}
変数iは1234
変数iのアドレスは000000000022FE3C
bは000000000022FE3C
cは1234
上記のように、ポインタ型でない整数を仲介しても、読み込んだアドレス元の変数をすること可能です。
配列変数でも同様にもとの変数を復元する処理が可能です。
#include <stdio.h>
int main(void) {
int i = 1234;
printf("変数iは%d\n", i);
printf("変数iのアドレスは%p\n", &i);
int hairetu[7]; // 宣言時はまったくポインタもアドレスも無関係
hairetu[0] =&i;
printf("hairetu[0]は%p\n",hairetu[0] );
int *c =hairetu[0];
printf("cは%d\n",*c );
return 0;
}
変数iは1234
変数iのアドレスは000000000022FE44
hairetu[0]は000000000022FE44
cは1234
WindowsAPIの場合
編集
詳細は「Windows API/画像の操作」を参照
WindowsAPIの場合
編集
下記の問題は、DXライブラリでは無関係です。なので初心者は、なるべくDXライブラリで開発するのが無難です。
DXライブラリでの開発には不要な知識ですが、将来的にもしWindowsAPIでゲーム制作する人が居たときの参考のため、ここに情報を残しておきます。
Windowsによる画面消去
編集
半透明レイヤー合成に限らず、Windowsではプログラマーが画面クリアの命令を書いてなくても、OSがなんらかの理由で強制的にいったん画面クリアする場合があります。
典型的なのが、アプリをウィンドウ右上の最小化ボタンを押してタスクバーに格納すると、いったん画面が消去されてから、WM_PAINTを実行するという再描画が始まります。
モードの管理方式だと、この際、前のモードの描画はすべて消えてしまいます。
たとえば、(ドラクエ1みたいに)戦闘モード中にマップ画面を背景で表示しようと思って、マップモードの画像を残していても、しかしWindowsの場合、なんらかの理由でWindowsが強制的に画面クリアすることがあり、そのあとにはマップモード時に描画された画像は、このままでは消去されてしまい再描画されません。
こういったWindowsによる強制消去の対策としては、戦闘画面モード側にもマップ背景の描画をコード記述するしか、方法はありません。(他の方法があるかどうかは、一般には知られていません。)
結局、それぞれのモードで、それぞれ別個に、同じ背景画面の描画を指定することになります。
なお、関数などを使えば、背景などの描画はコードの使いまわしが出来ます。
たとえば、戦闘画面モードで、マップ画面描画の関数を呼び出せば、追加する必要のあるコードはその呼出し命令だけで済むという事です。
方法1(非推奨)
編集
バグが発生しやすいので非推奨な方法ですが、もし異なる関数どうしで、裏画面(ダブルバッファ)用の画像を受け渡しする場合、下記のような方法で画像の受け渡しが一応は出来ます。しかし、他の画像関連の関数の描画時に原因不明のバグ画像が発生する事が多く、あまりオススメできないです。
参考前に、下記の方法を紹介しておきます。
たとえば、戦闘画面の背景にマップ画面を書く場合、コード例として下記
// マップ描画側の関数
void map2_Draw(HDC hdc,HDC hbackDC) {
// hbackDC上で 画像の作成のコードを個々に書いておく
// ここにマップ描画の関数を書くが、本画面(hdc)に転送 BitBlt しないでおく
// BitBlt(hdc, 0, 0, 700, 500, hbackDC, 0, 0, SRCCOPY); // なのでコレはコメントアウト
}
void battle_Draw(HDC hdc) {
filterFlag = 1; // 背景にフィルターをかぶせるのでフラグのセット
static HDC hbackDC = CreateCompatibleDC(hdc); // 裏画面用のハンドル
map2_Draw(hdc, hbackDC);
BitBlt(hdc, 0, 0, 700, 500, hbackDC, 0, 0, SRCCOPY);
// ここまでマップ画面の記述
// 後略
のようにすれば、一応は、異なる関数どうしで裏画面のデバイスコンテキストの受け渡しが出来ます。
つまり、呼び出しの戦闘関数側で、引数をhdcだけでなく、裏画面用の hbackDC も引数として含めて、
map2_Draw(hdc, hbackDC);
のような2引数以上の関数にして呼び出す必要があります。
また、このため、呼び出されるマップ側にも、引数として
void map2_Draw(HDC hdc,HDC hbackDC) {
のように引数を増やす必要があります。
このため、既存のマップ描画関数では引数がhdcの一つだけなので、そのままでは使いまわし出来ないです。よって、戦闘画面の背景用に書き換える必要があるので、なので既存の map1_Draw(HDC hdc) をコピーペーストして新しく map2_Draw(HDC hdc,HDC hbackDC) 関数を作らなければなりません。
このため、この方式では、あまりコード行数は減らないし、むしろ行数が増えます。
しかも、せっかく作ったコードなのに、実際にテストプレイしてみると、街頭の戦闘シーンだけは画像は正常に表示されるのですが、上述のコード例の場合、なぜか戦闘後のマップ移動画面がバグりました。
このwikiを書いた著者の戦闘画面の描画プログラムが単に欠陥なのか、それともwindowsの仕様上の問題があるのかは当wiki著者には分かりません。
ですが、どちらにしろ、上記の方式では、たとえ異なる関数どうしの間で裏画面の受け渡しが出来たとしても、デバッグやメンテナンスがかなり難しくなると予想されるので、なるべくこの方式は避けるほうが安全でしょう。
ネットでさらっと検索して調べてみても、この問題の対処法は全然見当たりません。ネット上にいるゲームプログラマー志望の皆さん、どうもここまで実験してないようです。
なので、前の節で述べたように、マップ側で本画面に転写するなど、裏画面を作成した関数の側で、本画面にも転写してしまうのが、バグが発生しないで安全です。
windowsAPIでは出来ない事
編集
上述のような問題が発生する背景として、windowsでは下記のような、機能不足の仕様があるという理由があります。
- グローバル変数では画像を保管できない
グローバル変数などで画像を保有する方法は、試みても、うまくいかないです。(標準c言語の文法どおりの仕様には、Windows APIの仕様は従っていないのです。)
たとえば、あらかじめグローバル変数として(コード冒頭の部分の)グローバル領域で
HDC hbackDC;
など、裏画面用の画面変数を宣言しておいて、ためしにローカル関数サイドでグローバル変数のhbitmapやHDCなどに代入コピーしようとしてイコール記号「=」でプログラム中で代入コピーしても、(コンパイルはできるのですが、)なぜかモード変更時にはコピー内容を残せない仕様になっています。(おそらく、メモリのオーバーブローを防ぐための仕様として、Winodowsが自動で勝手に、代入命令の意味をメモリ内容の複製ではなく(コピーではなく)、参照リンクにしていると思われます。)
(なお、HDCは型名である。Windowsではデバイスコンテキストの型名がこう決まっている。 hbackDCは単なる変数名なので、自由に命名できるし、別の変数名でも構わない。)
どうやらWindowsAPIにおけるhbitmapやHDCといった型のグローバル変数宣言は、実態は単に、各ローカル関数ごとに宣言をする手間を減らすために、一括でグローバル領域で宣言できるという仕様でしかないようです。
もし、上述の説明とは別のうまい方法があったとしても、つまり複数のローカル関数どうしでhbitmapやHDCの内容を共有できる方法があったとしても、その方法はけっして、上述のような直感的な方法ではないので(上述のような直感的な(標準C言語のような)方法では、共有不可能です)、メンテナンス上の理由であまり用いないほうが安全でしょう。
このようなWindowsAPIの非直感的な仕様のため、どうしても背景を複数のモードで使いまわしたい場合にはWindowsの場合、やむを得ず、CPUに負担は掛かってしまうのですが、結局は前の節で説明したように、関数などを用いて各モードごとに同じ描画をする方法しか、IT業界ですら、この関数の方法しか世間的にはよく知られていません。
- static でも保存できない
静的変数 static としてグローバル変数として、
static HDC hbackDC;
とグローバル領域で宣言しても、同様の結果です(共有できない)。なお、これとは別件で、ローカル関数側で static HDC と宣言しても、何も表示されなくなりますので、ローカルで static 宣言しても役立ちません。
- ポインタ変数でも保存できない
画像の描画の際、グローバル変数でアドレス変数やポインタ変数を作成しても、通常変数の場合と同様に、異なるモード間では一行に画像を共有できないです。
たとえば、下記のようにポインタなどを使ったコードを書いてもコンパイルは出来るのですが、しかし画像がモード遷移時に消去され、複数のモード間では画像を共有できないです。
例えばグローバル変数としてグローバル領域で
HDC* point_dc;
と宣言してみて、
描画時に
point_dc = &hbackDC;
などと宣言をしてみて、
BitBlt(hdc, 0, 0, 600, 400, *point_dc, 0, 0, SRCCOPY);
としても、単に普通に通常変数で描画した場合と同様のことが起きるだけで(つまり、宣言したモード内だけで描画が出来るだけ)、異なるモード間では何も画像は共有されません。
また、
HBITMAP*
で試しても、同様の結果です。
画像や音声の埋め込み方法あれこれ
編集
ツクールやウディタやその他の各種の日本産のゲーム制作ツールなどのゲームをプレイすると、実行ファイルかせいぜいあと1つのデータファイルしかなく、画像ファイルや音声ファイルなどのアイコンはゲームフォルダの中には見つかりません。
windowsの Visual Studio には、一応、実行ファイルに画像ファイルや音声ファイルなどの埋め込みをする機能がありますので、Visual Studio でゲーム制作する場合はそれを使えば済みます。
なので、この説明でこの章を終わりしてもいいのですが、しかしツクールやウディタが、C言語コンパイラの機能もないのに、どうやって何十枚や何百枚もある画像や、あるいは合計で何曲もある音声ファイルなどを埋め込んでいるのか、それはそれで読者は気になると思います。
なので、ツクールの方法かは分かりませんんが(ツクールの製作元企業の企業秘密なので)、画像などをファイルに埋め込む方法を調べてみて、まとめます。
調べた結果、下記のように、Visual Stduio 以外にも、幾つかの方法があるようです。
- 方法1. 実行ファイルを機械語で編集して、実行ファイルの末尾に画像ファイルなどの機械語(機械語は簡単に読み取れる)をつけたし、使用時にはC言語のfopenなどを使って読み込む。
- 方法2. windowsの実行ファイルexe形式のPEフォーマットという仕様を解析して、正攻法で書く。(しかし難度は激高)
- 方法3. 独自の画像フォーマットや音声フォーマットを作り、それを使用時に変換(エンコード)する方法。(難度は激高。しかも、エンコードの手間が掛かる
などの方式があるようです。
難易度的には、機械語を用いる方法が簡単なようです。しかし、そのような動作はマイクロソフトはそのような使い方を未保証にしていま。しかし、他のPEフォーマット方式や、独自フォーマットはとても難しいというとか、ほとんどOS開発に近いので、あまりオススメできないです。総合的に考えて、機械語が無難かと思います。
RPGのほかアクションゲームなどでも、無料のゲーム制作ツールを作って配布している人達は日本に数人ほどいますが、それらの無料ゲームエンジンはどれも、画像や音声などの埋め込み機能があります。
さすがに、そんなに多くの人達が、音声ファイルの独自フォーマットやら、PEフォーマットの仕様に精通しているとは思えませんので、おそらくは、ほとんどの無料ゲームエンジンの場合、画像などリソースの埋め込みは、機械語の方式だと思います。
ただし、暗号化のつもりなので機械語化などをしても、それでも第三者によって復号されるツールなどは出回っています。
かといって、たとえば画像ファイルを、プレイヤー視点では画像ファイルだとばれないように、ファイル冒頭の機械語にあるファイル形式の情報を書き換えると(たとえばビットマップファイルには冒頭にファイルヘッダを示す文字列「BM」がある)、windowsの画像用API関数では処理できなくなってしまうでしょう(処理時にヘッダファイルを読み取る必要があるので)。また仮に、ヘッダファイルを偽装したまま読み取らせる事がもし出来たとしても今度は、最悪の場合、外部のセキュリティセフトによってウイルス判定されるかもしれません。なぜならファイル偽装は、コンピュータウイルスによくある手口だからです。
あるいは、もし使用の瞬間にだけ画像ファイルを機械語リソース集などから取り出してファイル出力などで作成して読み込み終わったら消す方式にしたりする方式にすると、今度は頻繁に画像を作成・消去をする事になるので、処理速度が低下して、あまり好ましくありません。特に、SSDフラッシュメモリの普及している現代、頻繁にファイルの作成・消去することはSSDを消耗するので製品寿命を損なってしまいます。
実際にハードディスクに書き込まずにメモリ上で仮想のファイルやフォルダを作る方法については、この版のwikibooks著者が知りません。また、そのようなメモリ上の仮想化は、仮に出来たとしても、膨大な画像ファイルや音声ファイルをメモリ上に常駐させる事になるので、だいぶメモリに負担になるだろうし、処理速度なども低下させるのだろうから、あまり好ましくないと思います。
もしかしたら、リソースファイル集の機械語の文字列をC言語プログラムで1文字ずつ、各コンテンツの最後まで読み取って、プログラム側で、各種のメディア読み取り用のAPI関数でエンコードする事で、むりやりに画像や音声などをメモリに読み込ませる事が出来るのかもしれません(未確認)。ですが、そのためには、読み取り元のデータベース集ファイル側に、どこからどこまでが1つの画像コンテンツ単位(または1つの音楽コンテンツ単位)だとかの目次的な情報を、冒頭あたりに記録しておく必要があります。そのためには、各ファイルのサイズ数(つまりバイト文字数)なども正確に目次部分で管理する必要がったりと面倒そうです。目次の仕様も、自身で策定しなければならないでしょう。
なお、偶然に目次の情報の機械語と、画像コンテンツ側の機械語が一致してしまうと、読み取りミスになってしまいますので、そういう現象の発生しないように、充分に長い機械語文字列を使って、目次の位置を表示したりなどの工夫が必要かと思われます。
- 用語
なお、windowsには、「tempフォルダ」がありますが、これはべつにメモリ上の仮想(ハードディスク上にはない「仮想」)のフォルダではないはずです(メモリ上ファイルだとは、聞いた事が無いし、ネット検索しても出て来ません)。単に、ウィンドウズが一時的に自動作成したファイルであるので、ガッツリとハードディスクなどのストレージに書き込まれているはずです。(そもそも、なるべくメモリの負担を減らす必要があります。)
なお、「仮想メモリ」は、上記とは意味が異なります。仮想メモリとは、ハードディスクなどストレージ領域をメモリとして使用する機能です。このため、「仮想メモリ」を使うと、頻繁にハードディスクに書き込まれることになります。
「仮想ディスクドライブ」というメモリ上の開き領域をドライブ化する技術やソフトウェアが存在しますが、それをどうゲームに組み込めるのか、知りません。また、当然ですが、残りのメモリ容量が圧迫されます。なお、「仮想フォルダ」とは、単に「マイコンピュータ」や「ごみ箱」のような、システム内でデータの実在する場所とは別の階層位置に表示されるアイコンやそのリンクの機能のことであり、「仮想ディスクドライブ」とは意味が異なります。
- ^ 『ActiveBasic 透過画像の作り方 -ゲームを作っています。そのときに、背- その他(プログラミング・Web制作) | 教えて!goo』質問日時:2010/02/26 16:09 2021年に2月11日に閲覧して確認
- ^ 『DXライブラリ置き場 使い方説明』 2022年1月12日に確認.
- ^ 『ローポリスーパーテクニック』、P89
- ^ nyny著『ローポリスーパーテクニック』、ソフトバンククリエイティブ、2010年2月16日 初版 第5刷、P89
- ^ 『ローポリスーパーテクニック』、P89
- ^ 川村元気『理系に学ぶ』、ダイヤモンド社、2016年4月21日 第1刷発行、P.87
- ^ 川村元気『理系に学ぶ』、ダイヤモンド社、2016年4月21日 第1刷発行、P.87
- ^ 『DXライブラリ関数はスレッドフリーでしょうか?』 2021年12月24日に確認.
- ^ 『DXライブラリとマルチスレッド』日時:2017/04/19 03:02 2021年12月24日に確認.
- ^ 『DirectX/DirectXとは - WisdomSoft』 著者: 赤坂玲音 公開日: 2012/10/13 2021年12月24日に閲覧.