windowsゲームプログラミングにはDirectXが一番?
windowsで動作するゲームを作りたかったらDirectXの技術を使うのが一番です。マイクロソフトの人がそういっているのですから本当なのでしょう。ガリヲが以前作ったテトリスやあの馬鹿げた魚釣りゲームはDirectXの機能を使っています。
ガリヲにとってはまさに猫に小判だったわけですが、実際DirectXの処理能力はWINAPIよりも速いのでゲームに向いているのは間違いないようです。
しかし、いざ使うときは一枚絵を描画するだけでアホほど長い初期処理を書かないといけないので、これから始める人には酷だと思います。ガリヲのようなプログラミング初心者クラスの人にDirectXは本当に必要なのか。最近考え始めました。これを機にWINAPIをもう一度見直してみませんか?
解決できそうなゲームプログラミングでの文字列に関する問題
「全角文字のフォント幅を固定したい」「DirectXの描画範囲に文字列を表示したい」「オリジナルのフォントを使いたい」
「文字列の途中や前後に画像を挿入したい」「文字列の一部だけ色やサイズを変えたい」「画像の上にうまく重ね合わせたい」等の問題は、これから紹介する方法で解決できそうです。しかし、解決策は色々あると思います。これらに当てはまる方はもう少し読み進めてみるのも良いかもしれません。
おまけ
今日の記事の内容とはあまり関係ないですが、ガリヲはこれまでに、文字列を操作できるいくつかの関数を作って公開しています。
「文字列を置き換える」「バイト数ではなく文字数を調べる」「数値が何桁か計算する」等の機能を持つ関数があります。
これらの関数は、何もインクルードせずに使用できる独立した機能です。お暇なら覗いてみてください。
自作ゲームでの文字列表示
ゲームプログラミングで文字列に関する問題はつき物ですが、解決策は結構あると思います。思いつく限り書きます。それ。
WINAPIとDirectXの併用
WINAPIで文字列を描くとDirectXで描画する範囲の下に隠れてしまうようです。
ですが、DirectXの描画範囲を画面いっぱいではなく、画面の一部(下のほうとか)に少し余裕をあけておいてそこWINAPI描画用のスペースとして使う方法は解決策のひとつにあると思います。しかししかし、それでも問題となる場合があるのです。
それだと本当に、空けておいたスペースにしか描けないということです。一部に文字列用のスペースがあるといっても普通のゲームはいろんなところに文字列や数字が描いてあります。一部だけという訳にはいかない場合が多いのです。
文字列を一枚絵として描画
これが出来るのは、ほとんど文字列を使わないゲームのみでしょう。たとへば、「こんにちは」と表示したい場合、「こんにちは」とペイントで絵を描いてそれを描画する・・・と。
WINAPIで作る(DirectXでの描画を諦める)
まず、WINAPIは速度が遅いといわれていました。しかし、今はパソコンのスペックも昔よりかなり良くなっています。本当に速度が問題になることがあるのでしょうか。しかも今回作りたいゲームはローグライクです。速い動きは基本無いのでなおさらこの点は問題無いのではと思います。
文字列の描画は簡単です。関数でポンです。フォントも割と簡単に変えられます。しかし、もしかするとWINAPIでは再現が難しいかもしれない問題があります(これは試してない)。
たとへば「ガリヲはカシの杖を置いた」と書きたい場合。文字列の途中の色を変える方法が無かったかもしれません(たぶん)。あるいは文字列の途中に画像を挿入したい場合。例の場合「ガリヲは」の後にドクロの絵を入れて呪い状態であることを解り やすくしたほうが面白そうです。
実現するにはたぶん、幅が完璧に同じフォントを使用してややこしい計算を経てグラフィックを挿入するとか・・・。方法はあるかもしれませんが難しいと思います。WINAPIはゲーム用のAPIではないので当然です。
一文字ずつ画像として用意する
文字を画像として扱えば画像の上に重ねることが出来ます。しかし、画像を用意するのが結構たいへんです。
一つの方法は、ペイント等で一文字づつ描く方法です。気が遠くなる作業です。数字の0〜9。アルファベットのA〜Z。ここまでで事足りるゲームもありますが、日本語を使いたい場合まだ先があります。
少なくとも、「ひらがな」か「カタカナ」のどちらかは用意したほうがいいでしょう。しかも濁点や半濁点つきの文字もあります(※)。小さい文字もあります。記号も必要なら描きます。漢字は・・・好きにしてください。
画像ファイル一つに一文字づつ描くとファイル数が膨大になってしまうので、一つのファイルに全ての文字を描くと良いでしょう。今日はこのあと、この方法についての駄文がづ続きます。
※ 半濁点とは「ぴ」等に付くまるのことです。昔のファミコンなどは容量が限られていたため、文字の表現に工夫を凝らしてありました。
たとへば初期のドラクエでは、「が」は「か」という文字の次に「"」みたいな記号を続けることで表現していました。
パルテナの鏡やゼルダの伝説等は、ひらがなを使用せず、全てカタカナで表現していましたが、ゲームの面白さは損なっていなかったと思います。
我々のように個人でゲームを作って遊ぶ際にもちょっとした工夫で解決できることはあるのかもしれませんネ。
一文字ずつ画像として用意する方法
この方法について詳しく書きます。
この方法の利点
・DirectXでもWINAPIでも採用可能
・文字列の途中で色を変えたり画像を挟んだりするのが容易(文字も画像だから)
・画像なのでプログラムを書き換えずにフォントを変えられる
この方法の欠点
・文字の画像を用意するのが大変(自分で描くかなんらかの方法で用意する)
・用意していない文字は当然扱えない
・プログラミングが必須
WINAPIで描画されるフォントの幅は平均値なのでそのままでは画像の挿入する位置を計算するのは至難です。幅固定のフォントは英数字ならあるようです。英語圏内の人は恵まれています。
しかし、我々にはプログラミングがあります。一文字づつ手作業で書くより自動的にビットマップに書き込んでくれるプログラムを作ったほうが速そうです。WINAPIで文字の一覧をサイズを固定して書き込んでいくプログラムを作ってみました。
本当に、昨日急いで作ったものなので雑です。実用に耐えうるかもあやしいですがサンプルプログラムとして載せます。
void draw(HDC _hdc)
{
const char fw = 24;//文字横幅
const char fh = 24;//文字縦幅
char t[3];//全角
char c1,c2;//文字検索用(下位ビットと上位ビット)
const char e = (("あ"[1]) > 0) ? 0 : 1;//ビッグエンディアンは0?
RECT rect;//描画範囲
c1 = (-127);//検索開始範囲(-127〜-125)
c2 = (-128);//(-128〜127)
t[2] = '\0';//ヌル文字
rect.top = 0;//描画範囲
rect.bottom = fh;
rect.left = 0;
rect.right = fw;
PatBlt(_hdc,0,0,bw,bh,WHITENESS);//背景色(白)で初期化
for(int i = 0;i < 256;i++)
{
if(i != 0 && (i % 32) == 0)//32文字目で改行
{
rect.top = rect.top + fh;
rect.bottom = rect.bottom + fh;
rect.left = 0;
rect.right = fw;
}
t[e == 0] = c1;//文字検索
t[e == 1] = c2;
DrawText(_hdc,t,(-1),&rect,0);//描く
c2++;//次の文字
rect.left = rect.left + fw;//文字幅分シフト
rect.right = rect.right + fh;
if(i == 255)
{
rect.left = 0;
rect.right = fh;
i = 0;//再度ループ
c2 = 0;
c1++;
if(c1 == (-124))return;//3回ループした
}
}
}
この関数に適切なHDC(デバイスコンテキストハンドル)を指定して実行した画像がこちらです(画像は縮小したものです。あと「・」を一つ消してあります。)
ファイルに保存するプログラムは作っていませんが、問題無いと思います。なぜならwindowsにはクリップボードに画面をコピーする機能があります。画面の出ているときにプリントスクリーンキーを押して手作業でペイントにコピーしてください。
それと当然、このプログラムはゲーム中一切使用しません。開発専用です。
このプログラムの使い方
フォントは変更していませんので、そのままだと上の画像のようにデフォルトのフォントです。フォントの変更についてはあまりにもローグライクプログラミングから離れるので書きません。
このプログラムはリトルエンディアンのパソコンで動作確認しました。ビッグエンディアンで動作するかわかりません(ゴメソ)。
簡易的な機能なので、未使用領域「・」がいっぱいですね。プログラムを書き換えて「・」や使わない記号等を除外しても良いと思います。
この例に限らずWINAPIで描画するときは、仮想のビットマップを用意して使うのが定石です。上の例では、引数に仮想ビットマップと関連付けされたHDCを指定して、仮想ビットマップに全部書き込んだあと最後にメインのHDCに コピーするのが良いらしいです(ちらつき防止)。すみません。あまり詳しくないです。あとで少し説明がんばります。
なぜかあまり語られないBitBlt関数の使い方(WINAPI)
実はスゴイ!?BitBlt関数
BitBlt関数についてググってみてください(手抜いた)。たぶん、あまりゲームに結びついた説明は無いと思います。今日はBitBlt関数のすばらしい使い方を書きます。
画像データは一つにまとめられる
ゲームを作るとき画像ファイルを複数扱う必要はありません。なぜかあまり語られないことですが、画像ファイルは一つにまとめられます。
一つのファイルにたくさんの画像が入っていても、BitBlt関数で参照する範囲を指定すればその範囲だけ描画できるのです。気付いていない人マジでいると思います。
画像から文字を拾って描画する方法
プログラムを見る前に
例はWINAPIでの描画です。WINAPIでの描画についてのプログラムは記事と関係無いので載せませんが、一応簡単に説明だけしておこうと思います(一例)。
_菫を読み込むためのHDCを宣言
画像を読み込むためのHBITMAPを宣言(よくわからないが必要らしい)
2菫のサイズや色数などの情報を格納する為のBITMAPを宣言
LoadImage()で画像を読み込んで返り値を上のHBITMAPにぶち込む
GetObject()にHBITMAPとBITMAPのアドレスを入れると、HBITMAPの情報がBITMAPに入る
SelectObject()でHDCとHBITMAPをくっつける
こんな感じで仮想ビットマップを準備して、それに書き込む。全部書き込んだらメインのHDCにコピーすることでちらつきを無くす。
本題のプログラム
int TextViewAPI(HDC *_hdc,HDC *_bufhdc,char *str,int x,int y,int fw,int fh)
{
int px = x;//表示座標
int py = y;
int gx;//参照する文字グラフィック座標
int gy;
int n = 0;//配列長
const char e = (("あ"[1]) > 0) ? 0 : 1;//ビッグエンディアンは0?
for(int i = 0;i < 30000;i++)
{
if(str[n] == NULL)return 0;//正常終了
if(str[n] >= 0)
{
//英数字
switch(str[n])
{
case ' ':gx = fw * 15,gy = fh * 9;goto DRAW;
case '0':gx = fw * 16,gy = fh * 9;goto DRAW;
case '1':gx = fw * 17,gy = fh * 9;goto DRAW;
case '2':gx = fw * 18,gy = fh * 9;goto DRAW;
case '3':gx = fw * 19,gy = fh * 9;goto DRAW;
case '4':gx = fw * 20,gy = fh * 9;goto DRAW;
case '5':gx = fw * 21,gy = fh * 9;goto DRAW;
case '6':gx = fw * 22,gy = fh * 9;goto DRAW;
case '7':gx = fw * 23,gy = fh * 9;goto DRAW;
case '8':gx = fw * 24,gy = fh * 9;goto DRAW;
case '9':gx = fw * 25,gy = fh * 9;goto DRAW;
case 'A':gx = fw * 1,gy = fh * 10;goto DRAW;
case 'B':gx = fw * 2,gy = fh * 10;goto DRAW;
case 'C':gx = fw * 3,gy = fh * 10;goto DRAW;
case 'D':gx = fw * 4,gy = fh * 10;goto DRAW;
case 'E':gx = fw * 5,gy = fh * 10;goto DRAW;
case 'F':gx = fw * 6,gy = fh * 10;goto DRAW;
case 'G':gx = fw * 7,gy = fh * 10;goto DRAW;
case 'H':gx = fw * 8,gy = fh * 10;goto DRAW;
case 'I':gx = fw * 9,gy = fh * 10;goto DRAW;
case 'J':gx = fw * 10,gy = fh * 10;goto DRAW;
case 'K':gx = fw * 11,gy = fh * 10;goto DRAW;
case 'L':gx = fw * 12,gy = fh * 10;goto DRAW;
case 'M':gx = fw * 13,gy = fh * 10;goto DRAW;
case 'N':gx = fw * 14,gy = fh * 10;goto DRAW;
case 'O':gx = fw * 15,gy = fh * 10;goto DRAW;
case 'P':gx = fw * 16,gy = fh * 10;goto DRAW;
case 'Q':gx = fw * 17,gy = fh * 10;goto DRAW;
case 'R':gx = fw * 18,gy = fh * 10;goto DRAW;
case 'S':gx = fw * 19,gy = fh * 10;goto DRAW;
case 'T':gx = fw * 20,gy = fh * 10;goto DRAW;
case 'U':gx = fw * 21,gy = fh * 10;goto DRAW;
case 'V':gx = fw * 22,gy = fh * 10;goto DRAW;
case 'W':gx = fw * 23,gy = fh * 10;goto DRAW;
case 'X':gx = fw * 24,gy = fh * 10;goto DRAW;
case 'Y':gx = fw * 25,gy = fh * 10;goto DRAW;
case 'Z':gx = fw * 26,gy = fh * 10;goto DRAW;
case '\n':px = x;py = py + fh;n++;continue;//改行
default://認識できない文字
n++;px = px + fw;continue;//何も描画しない
}
}
else//全角
{
if(str[n + e] == ("あ"[1])){gx = fw * 1;gy = fh * 12;n++;goto DRAW;}
if(str[n + e] == ("い"[1])){gx = fw * 3;gy = fh * 12;n++;goto DRAW;}
if(str[n + e] == ("う"[1])){gx = fw * 5;gy = fh * 12;n++;goto DRAW;}
if(str[n + e] == ("え"[1])){gx = fw * 7;gy = fh * 12;n++;goto DRAW;}
if(str[n + e] == ("お"[1])){gx = fw * 9;gy = fh * 12;n++;goto DRAW;}
n++;px = px + fw;continue;//認識できない文字
}
DRAW :
BitBlt(*_hdc,px,py,fw,fh,*_bufhdc,gx,gy,SRCCOPY);//描画
n++;//次の文字を参照
px = px + fw;//改行文字でなければ文字幅分x点を移動
}
return 0;
}
全角は「あいうえお」までしか書いてません。この方法だとすごく大変なので・・・。
これはあくまでもサンプル。こんな感じで、文字も画像として扱うという方法もある。もちろん、こういった方法以外で文字列を扱う方法を知っているのならそれを使うべき。そして、この関数は汎用性が必要無いのでgoto文を使っています。
TextViewAPI(hdc,bufhdc,"ABCあいうえお\n0123",0,0,24,24);
のように使った結果がこちら。
それと、このプログラムはWINAPIを使っていますが、DirectXでも基本同じように使えます。描画する部分が違うだけです。どうです?やるときはやるでしょう?
結局文字列はどう扱うべきか(まとめ)
ガチですごいものをつくりたいならやっぱりDirectXだが?
文字列の問題は画像として扱うという方法で一応に解決したと見る。ならば、WINAPIではなくDirectXを使う価値があると思います。
しかしながら、今回作るのは不思議のダンジョン。さっきも書いたが、あまり速い動きは無いし、完成したときのファイルサイズが小さいという利点があります。さらに、実行する際DirectXランタイムが必要ないので多くの環境で動作すると思われます。
まあ、今あるwindowsパソコンでDirectXランタイムが入っていないパソコンはあまり無いと思うけど、そうでなくてもDirectX特有の不可解な問題を回避できるのは利点でしょう(人による)。
今回はWINAPIを採用する方向に決定!
WINAPIでもゲームは作れる。そう信じて、先祖帰りしましょう。WINAPIのゲームといったらあまりグラフィカルなイメージは無いと思いますが、それでもWINAPIで作るというのはまさに壮大なジョークです。こういうの好きです。
今日はここまで
不思議のダンジョン関係ねー!やっちまったー!文字数はもはや仕事レベルー!どうせだれも見ないのにー!・・・でも今日の記事はかなり中身のあるものができたと思います。
ここまでの更新速度が速いのは、あまりプログラミングしなくても良い部分だからであることは無論。次は4F最低限の描画処理か・・・。あれ!?今日描画までやっちゃったぞ!?
まあ、次はこんなにアホほど書かなくて済むということで良いんですね。ちょっと飛ばしすぎたし、5Fからはガチのローグライクプログラミングが待ってるっぽいので、4Fは力を抜いて通過しましょうか。
そういえば1Fで言ったダンジョンの規則の話。あれは、良い考えだナ。可能な限り知識の介入を省くという理念は割りと美しい。こ んなに良いことを思ってもだれにも伝わらない。このブログは今のままで良いのだろうか。このプロジェクトのおかげで情報の質も間違いなく上がる。
もっと多くの人に見てもらえるブログにしても良いころなのでは無いだろうか。
まてよ?そういったってどうすればいいのかガリヲには解らない。どうすればここの存在を知ってもらえるんだ?
不思議のダンジョンを作りたい人はガリヲだけではないはず。不思議のダンジョンをきっかけにゲームプログラミングの世界に入れるかもしれないのに・・・。
いや、不思議のダンジョンに本当に必要なのはプログラミングじゃない。愛・・・とかそんなきれいごとじゃなくて、グラフィックだ!
グラが描ける人とプログラミングできる人とBGM作れる人が揃え� ��まさに不思議のダンジョンだが、一人でも欠けたら「ちょっと不思議」くらいになってしまう。
おっと、話がずれ始めました。お題無しだと無限に駄文を書き続けてしまう、キグニ状態なので、今日はここらでペンを置きます。次回一服です。ありがとうございました。
カテゴリトップへ
0 件のコメント:
コメントを投稿