なんだか雲行きの怪しい雑記帖

ぐだぐだ日記とメモと,あと不定期更新

HSPとDxLibで2D描画の基礎ら辺とか


適当な前置き


具体的に触れるのは
 ・初期化から描画ループ一式
 ・簡単な図形描画
 ・テクスチャ描画
らへんです。

3Dとか難しい所に触れる気は一切ないのはもとより、2Dでも大して難しい所に触れる気も毛頭ないので、その辺はご注意を。

なお、私が躓いた点も含めてほぼ全部書いていくつもりなので、かなり長くなると予想されます。
が、なぜそのコードを選んだかも含めて書いてあるので、何かご自身で問題があった場合は柔軟に修正をしていけると思います。…多分。


前準備

外部プラグインという形になるので、DxLib本体とヘッダを用意する必要があります。

DLL本体はDXライブラリ置き場の「DXライブラリのダウンロード」→「VisualC# 用パッケージ」からドウゾ。
 (実はDxLibは結構細かい更新が多くあるので、細めにチェックするといいかもしれません)

ヘッダはCodetter:【HSP3】DxライブラリをHSPで使ってみるサンプル(キー入力の基本)(http://codetter.com/?p=577)の上部リンクから取得して下さい。

ダウンロードしたファイルのうち、「DxLib.dll」と「DxLib.hsp」をソースコードと同じフォルダに置いて準備は完了。


初期化と描画ループ


ひとまず最小限のサンプルを示しておきます。

#include "DxLib.hsp"

// ウィンドウモードをON(OFFの場合フルスクリーンになる)
ChangeWindowMode 1

// DxLibの初期化
DxLib_Init
if ( stat == -1 ) : dialog "初期化エラー"

repeat
// 裏画面を描画ターゲットに指定
SetDrawScreen DX_SCREEN_BACK

// 描画色のコードの取得
GetColor 64, 128, 255
bgcolor = stat

// 左上(0,0)->(100,100)に矩形を描画
DrawBox 0, 0, 100, 100, bgcolor, 1

// 裏画面を表画面へ転送
ScreenFlip

// ウィンドウメッセージ等の処理(DxLib側)
ProcessMessage

// ウィンドウメッセージ等の処理(HSP側)
await 0
loop


コメントを書いておいたので、何となくな流れは分かるかと思います。
「ウィンドウモード・フルスクリーンモード」の設定がDxLibの初期化前であることに注意してください。

二つほど問題点がありますので、一つずつ書いていきます。


二つのウィンドウ

さて、実行して頂けると分かると思いますが、ウィンドウが二つでてきたと思います。
片方は真っ白ですが、片方は左上に青い矩形が描画されていると思います。

DxLibは自分でウィンドウを作成し、それに対して描画を行います。
それに対して、HSPも実行すると自動で自分のウィンドウを作成しますよね。
というわけで、合わせて二つになります、プログラム的には合っています

また、HSPのウィンドウは「await」などのウェイトを挟まないと、制御不能なウィンドウになってしまいます。
DxLibが作ったウィンドウにもそれは当てはまりまして、DxLibでの「await」のようなウィンドウの処理をする命令がProcessMessageとなります。

…さて、画面が二つなのはかなりややこしいので一つにしようかと思います。
単純にやろうとするならHSP側のウィンドウを非表示にするという方針ですが、これはとるべきではありません

例えば次のコードを実行してみましょう。

#include "DxLib.hsp"

// HSP側のウィンドウを隠す
gsel 0, -1

// ウィンドウモードをON(OFFの場合フルスクリーンになる)
ChangeWindowMode 1

// DxLibの初期化
DxLib_Init
if ( stat == -1 ) : dialog "初期化エラー"

onexit goto *OnExitApp

repeat
// 裏画面を描画ターゲットに指定
SetDrawScreen DX_SCREEN_BACK

// 描画色のコードの取得
GetColor 64, 128, 255
bgcolor = stat

// 左上(0,0)->(100,100)に矩形を描画
DrawBox 0, 0, 100, 100, bgcolor, 1

// 裏画面を表画面へ転送
ScreenFlip

// ウィンドウメッセージ等の処理(DxLib側)
ProcessMessage

// ウィンドウメッセージ等の処理(HSP側)
await 0
loop
stop

*OnExitApp
dialog "終了!"
end


単純に、最初に示したサンプルにHSPのメインウィンドウを隠し、「onexit」で終了時の処理を追加したものです。
終了させてみようとすると「終了!」とダイアログが出てきて欲しいですが、残念ながらそううまく動いてくれません。

「onexit」はHSP側のウィンドウが閉じられた時に動作します。
しかし、DxLibが管理するウィンドウの閉じられた時の動作をフックできません。


解答として、実際にウィンドウを一つにする際は、以下のコードに修正する必要がありました。

#include "DxLib.hsp"

// ウィンドウモードをON(OFFの場合フルスクリーンになる)
ChangeWindowMode 1

// DxLibで描画するウィンドウをHSPのウィンドウに変更
SetUserWindow hwnd
SetUserWindowMessageProcessDXLibFlag 0

// DxLibの初期化
DxLib_Init
if ( stat == -1 ) : dialog "初期化エラー"

onexit goto *OnExitApp

repeat
// 裏画面を描画ターゲットに指定
SetDrawScreen DX_SCREEN_BACK

// 描画色のコードの取得
GetColor 64, 128, 255
bgcolor = stat

// 左上(0,0)->(100,100)に矩形を描画
DrawBox 0, 0, 100, 100, bgcolor, 1

// 裏画面を表画面へ転送
ScreenFlip

// ウィンドウメッセージ等の処理(DxLib側)
ProcessMessage

// ウィンドウメッセージ等の処理(HSP側)
await 0
loop
stop

*OnExitApp
dialog "終了!"
end


これが以降のひな形となります。


浮動小数点の精度

DirectXは内部の演算速度を稼ぐ為にデフォルトではFPUの演算精度をdoubleからfloatに下げます。

「だから何だ?」という話なんですが、実に細かいところで影響が出てきます。
HSP掲示板で言うとこの話題(「OBAQ」と「OBAQ+HGIMG3」におけるオブジェクトの挙動の再現性)です。

この問題を回避するためには、単純に以下のように修正します。

#include "DxLib.hsp"

// ウィンドウモードをON(OFFの場合フルスクリーンになる)
ChangeWindowMode 1

// DxLibで描画するウィンドウをHSPのウィンドウに変更
SetUserWindow hwnd
SetUserWindowMessageProcessDXLibFlag 0

// FPUの演算精度をそのままに
SetUseFPUPreserveFlag 1

// DxLibの初期化
DxLib_Init
if ( stat == -1 ) : dialog "初期化エラー"
//…以下略



簡単な図形描画


特に説明することないので、コードでドン。

#include "DxLib.hsp"

// ウィンドウモードをON(OFFの場合フルスクリーンになる)
ChangeWindowMode 1

// DxLibで描画するウィンドウをHSPのウィンドウに変更
SetUserWindow hwnd
SetUserWindowMessageProcessDXLibFlag 0

// DxLibの初期化
DxLib_Init
if ( stat == -1 ) : dialog "初期化エラー"

onexit goto *OnExitApp

repeat
// 裏画面を描画ターゲットに指定
SetDrawScreen DX_SCREEN_BACK

// 描画色のコードの取得
hsvcolor cnt/5 \ 191, 255, 255
GetColor ginfo_r, ginfo_g, ginfo_b
spcolor = stat

// 矩形
DrawBox 0, 0, 50, 50, spcolor, 0// 中抜き
DrawBox 50, 50, 100, 100, spcolor, 1

// 線分
DrawLine 100, 0, 200, 100, spcolor

// 円
DrawCircle 25, 125, 25, spcolor, 0
DrawCircle 75, 175, 25, spcolor, 1

// 楕円
DrawOval 50, 225, 50, 25, spcolor, 0
DrawOval 50, 275, 50, 25, spcolor, 1

// 三角形
DrawTriangle 100, 100, 200, 150, 100, 200, spcolor, 0
DrawTriangle 100, 200, 200, 250, 100, 300, spcolor, 1

// 裏画面を表画面へ転送
ScreenFlip
ProcessMessage
await 0
loop
stop

*OnExitApp
dialog "終了!"
end




テクスチャ描画


テクスチャ描画は、主に次の三つの手順で行います。
 ・DxLibにテクスチャのロードをお願いし、テクスチャハンドルを取得する
 ・描画設定諸々を行う
 ・テクスチャハンドルを用いてDxLibに描画をお願いする

たとえば適当なファイルを用意して、次のように書きます。
 (因みに今回使用した適当なファイルはこちら(pngcheck.png)


#include "DxLib.hsp"

// ウィンドウモードをON(OFFの場合フルスクリーンになる)
ChangeWindowMode 1

// DxLibで描画するウィンドウをHSPのウィンドウに変更
SetUserWindow hwnd
SetUserWindowMessageProcessDXLibFlag 0

// DxLibの初期化
DxLib_Init
if ( stat == -1 ) : dialog "初期化エラー"

// テクスチャのロードとテクスチャハンドルの取得
LoadGraph "pngcheck.png"
htex = stat
if ( htex == -1 ) : dialog "テクスチャロードエラー"

onexit goto *OnExitApp

repeat
// 裏画面を描画ターゲットに指定
SetDrawScreen DX_SCREEN_BACK

// 画面をクリア
ClearDrawScreen 0

// テクスチャを描画
// 中心座標Y、X、拡大率、回転角度、テクスチャハンドル、透明色を有効化、反転フラグ
DrawRotaGraph 100, 100, 1.0+0.5*cos( deg2rad(cnt) ), deg2rad(cnt/2), htex, 1, 0

// 裏画面を表画面へ転送
ScreenFlip
ProcessMessage
await 0
loop
stop

*OnExitApp
dialog "終了!"
end


次は描画設定諸々について見て行きましょう。

#include "DxLib.hsp"

// ウィンドウモードをON(OFFの場合フルスクリーンになる)
ChangeWindowMode 1

// DxLibで描画するウィンドウをHSPのウィンドウに変更
SetUserWindow hwnd
SetUserWindowMessageProcessDXLibFlag 0

// DxLibの初期化
DxLib_Init
if ( stat == -1 ) : dialog "初期化エラー"

// テクスチャのロードとテクスチャハンドルの取得
LoadGraph "pngcheck.png"
htex = stat
if ( htex == -1 ) : dialog "テクスチャロードエラー"

onexit goto *OnExitApp

repeat
// 裏画面を描画ターゲットに指定
SetDrawScreen DX_SCREEN_BACK

// 画面をクリア
ClearDrawScreen 0

// 拡縮補完の設定
SetDrawMode DX_DRAWMODE_BILINEAR

// 描画をアルファブレンドに設定、アルファ値もここで設定
SetDrawBlendMode DX_BLENDMODE_ALPHA, 128.0*cos( deg2rad(cnt) )

// 描画輝度の設定
// 赤だけ描画
SetDrawBright 255, 0, 0

// テクスチャを描画
// 中心座標Y、X、拡大率、回転角度、テクスチャハンドル、透明色を有効化、反転フラグ
DrawRotaGraph 100, 100, 1.0+0.5*cos( deg2rad(cnt) ), deg2rad(cnt/2), htex, 1, 0

// 裏画面を表画面へ転送
ScreenFlip
ProcessMessage
await 0
loop
stop

*OnExitApp
dialog "終了!"
end


それぞれコメントつけたので何となく分かると思います。

「SetDrawMode」は拡大縮小の際に用いる補完法の指定ですが、標準が「DX_DRAWMODE_NEAREST」でNN法だそうなので、「DX_DRAWMODE_BILINEAR」くらいには設定したいですね。

「SetDrawBlendMode」は恐らく一番頻繁に使うかと思います。
アルファ値を指定する際に毎回呼ぶ必要があるので…。

ブレンド方法には
 ・通常のアルファブレンドの「DX_BLENDMODE_ALPHA」
 ・加算合成の「DX_BLENDMODE_ADD」
 ・減算合成の「DX_BLENDMODE_SUB」
 ・乗算合成の「DX_BLENDMODE_MULA」
などがあります。状況に応じて使い分けましょう。

「SetDrawBright」も地味に使おうと思えば使いやすい機能です。
描画するテクスチャのそれぞれのチャンネルに対して係数を掛けられるようなもので、テンプレートとして白い画像を一枚用意しておけば「SetDrawBright」で好きな色に変えられます。

#include "DxLib.hsp"

// ウィンドウモードをON(OFFの場合フルスクリーンになる)
ChangeWindowMode 1

// DxLibで描画するウィンドウをHSPのウィンドウに変更
SetUserWindow hwnd
SetUserWindowMessageProcessDXLibFlag 0

// DxLibの初期化
DxLib_Init
if ( stat == -1 ) : dialog "初期化エラー"

// テクスチャのロードとテクスチャハンドルの取得
LoadGraph "template_shape.png"
htex = stat
if ( htex == -1 ) : dialog "テクスチャロードエラー"

onexit goto *OnExitApp

repeat
// 裏画面を描画ターゲットに指定
SetDrawScreen DX_SCREEN_BACK

// 画面をクリア
ClearDrawScreen 0

// そのまま描画
SetDrawBright 255, 255, 255
DrawRotaGraph 100, 100, 1.0, 0, htex, 1, 0

// 赤
SetDrawBright 255, 0, 0
DrawRotaGraph 200, 100, 1.0, 0, htex, 1, 0

// 紫
SetDrawBright 255, 0, 255
DrawRotaGraph 300, 100, 1.0, 0, htex, 1, 0

// カスタム
hsvcolor (cnt/2)\191, 255, 255
SetDrawBright ginfo_r, ginfo_g, ginfo_b
DrawRotaGraph 400, 100, 1.0, 0, htex, 1, 0

// 裏画面を表画面へ転送
ScreenFlip
ProcessMessage
await 0
loop
stop

*OnExitApp
dialog "終了!"
end


今回テンプレートに使用した画像はこちら↓

template_shape.png

描画結果はこんな感じ↓
template_sample.png



メモリ上のテクスチャ


よく考えたら、メモリ上に作成できる裏画面もそこそこ重要なファクターであることに気付いた。
HSPでいうと「buffer」命令に相当しますね。


#include "DxLib.hsp"

// ウィンドウモードをON(OFFの場合フルスクリーンになる)
ChangeWindowMode 1

// DxLibで描画するウィンドウをHSPのウィンドウに変更
SetUserWindow hwnd
SetUserWindowMessageProcessDXLibFlag 0

// DxLibの初期化
DxLib_Init
if ( stat == -1 ) : dialog "初期化エラー"

// メモリ上にテクスチャ生成
MakeScreen 640, 480
htex = stat
if ( htex == -1 ) : dialog "テクスチャ作成エラー"

onexit goto *OnExitApp

repeat
// メモリ上に作成したテクスチャを描画ターゲットに指定
SetDrawScreen htex
ClearDrawScreen 0
GetColor 64, 128, 255
DrawString 0, 100, "sample string", stat


// 裏画面を描画ターゲットに指定
SetDrawScreen DX_SCREEN_BACK
ClearDrawScreen 0

// テクスチャを描画
// 中心座標Y、X、拡大率、回転角度、テクスチャハンドル、透明色を有効化、反転フラグ
DrawRotaGraph 320, 240, 1.0, deg2rad(cnt/2), htex, 1, 0

// 裏画面を表画面へ転送
ScreenFlip
ProcessMessage
await 0
loop
stop

*OnExitApp
dialog "終了!"
end


サラッと使いましたが、文字列描画は「DrawString」です。


割と駆け足で説明してきた訳ですが


2D描画で必要な知識は殆ど埋めたと思います、多分な!
ここまで恐らく何か一つくらいゲーム作れるくらいにはなってるんじゃないんですかねぇ…。

「これ必要だけど説明してないよ!」っていうのがあった場合は、本家リファ(DXライブラリ 関数リファレンスページ)等をご参照して頂きますようお願いします。




















その他細かい話(FAQ形式&不真面目な解答)

君も細かい所気にするねぇ!!!!

取得したリソースは解放するべきでは?

するべきです^^;;;

特にテクスチャハンドルは要らなくなった時点で解放するべきですので、要らないテクスチャハンドルは適宜

DeleteGraph htex

と処理するべきでしょう。

…まぁ個人的には

if ( htex != -1 ) {
DeleteGraph htex
htex = -1
}

のようなガードが入ってる方が安心ですが。
 (DxLibで無効なハンドルは-1が使われているため)

あと、「onexit」ではDxLibを明示的に解放するべきだとも思っている派なので、最後に

DxLib_End

と入れてあげるといいのかもしれません。


画面クリアするのにDrawBoxとかじゃダメなん?

ホントは使わん方がいいよ、っていうか「ClearDrawScreen」が正しいよ。
厳密に言うと深度バッファもクリアしてると思うから、出来るだけ「ClearDrawScreen」行う必要があるんだけど、それは3Dがでてからでいいんじゃないかなぁ。

#include "DxLib.hsp"

// ウィンドウモードをON(OFFの場合フルスクリーンになる)
ChangeWindowMode 1

// DxLibで描画するウィンドウをHSPのウィンドウに変更
SetUserWindow hwnd
SetUserWindowMessageProcessDXLibFlag 0

// background color : 実は指定できる
SetBackgroundColor 255, 0, 0

// DxLibの初期化
DxLib_Init
if ( stat == -1 ) : dialog "初期化エラー"

onexit goto *OnExitApp

repeat
// 裏画面を描画ターゲットに指定
SetDrawScreen DX_SCREEN_BACK
// 実は凄い重要?
ClearDrawScreen 0

// 描画色のコードの取得
GetColor 64, 128, 255
bgcolor = stat

// 左上(0,0)->(100,100)に矩形を描画
DrawBox 0, 0, 100, 100, bgcolor, 1

// 裏画面を表画面へ転送
ScreenFlip
ProcessMessage
await 0
loop
stop

*OnExitApp
dialog "終了!"
end



ウェイトってどこでとってるの?

「ScreenFlip」が勝手にディスプレイのVSyncをとるようにSleepします。
VSyncはディスプレイのリフレッシュレートに依存したSleepですが、大概60Hzなので16.6666...msec待ってくれると思ってほぼ差し支えないと思います、しかも結構な精度で。
 (だからこそ、HSP側のSleep処理はawait 0でメッセージ処理を行うだけで十分となります)

VSync切りたければ「SetWaitVSyncFlag 0」で出来ます。

#include "DxLib.hsp"

// ウィンドウモードをON(OFFの場合フルスクリーンになる)
ChangeWindowMode 1

// DxLibで描画するウィンドウをHSPのウィンドウに変更
SetUserWindow hwnd
SetUserWindowMessageProcessDXLibFlag 0

// VSync設定
// ここをコメントアウトしてみた場合と比較してみよう!
SetWaitVSyncFlag 0

// DxLibの初期化
DxLib_Init
if ( stat == -1 ) : dialog "初期化エラー"

onexit goto *OnExitApp

count = 0
repeat
// 裏画面を描画ターゲットに指定
SetDrawScreen DX_SCREEN_BACK
ClearDrawScreen 0

// 描画色のコードの取得
GetColor 64, 128, 255
bgcolor = stat

// 描画回数を描画
DrawString 0, 100, ""+count, bgcolor
count++

// 裏画面を表画面へ転送
ScreenFlip
ProcessMessage
await 0
loop
stop

*OnExitApp
dialog "終了!"
end

これだと超高速で描画してるのが分かるかなと思います。


途中でフルスクリーン・ウィンドウを切り替えるにはどうすればいいの?

気付いてしまったか…。

DxLibのウィンドウモード・フルスクリーンモードの指定はDxLib_Init以前に行う必要があることは既に述べました。
つまり、アプリケーション実行中にウィンドウ・フルスクリーンを切り替えるには、
 ・一度DxLib_Endを呼び出し、全てのリソースを破棄
 ・ウィンドウ・フルスクリーンのフラグを指定
 ・DxLib_Initにより初期化
 ・必要なリソースを初期化(再取得)
という手順を踏む必要があります。

そもそもDirectXの描画系を使っている場合、基本的にリソースはいつ無効になる(デバイスロスト)かもわからない状況で、いつでも復帰可能にしておく必要があったりします。
要するにいつでもテクスチャなどのリソースは再構築可能にしておく必要があります。

Enterを押す毎にフルスクリーン・ウィンドウ切り替わるサンプル↓

#include "DxLib.hsp"

// ウィンドウモードをON(OFFの場合フルスクリーンになる)
ChangeWindowMode 1

// DxLibで描画するウィンドウをHSPのウィンドウに変更
SetUserWindow hwnd
SetUserWindowMessageProcessDXLibFlag 0

// DxLibの初期化
DxLib_Init
if ( stat == -1 ) : dialog "初期化エラー"

// リソースの初期化
b_fullscr = 0
gosub *OnLoadContent

// 各種こーるばっく
onkey gosub *OnKeyDown
onexit goto *OnExitApp

repeat
// 裏画面を描画ターゲットに指定
SetDrawScreen DX_SCREEN_BACK
ClearDrawScreen 0

// テクスチャを描画
DrawRotaGraph 100, 100, 1.0+0.5*cos( deg2rad(cnt) ), deg2rad(cnt/2), htex, 1, 0

// 裏画面を表画面へ転送
ScreenFlip
ProcessMessage
await 0
loop
stop

*OnLoadContent
LoadGraph "pngcheck.png"
htex = stat
return 1

*OnUnloadContent
if ( htex != -1 ) {
DeleteGraph htex
htex = -1
}
return 1

*OnKeyDown
if ( wparam == 13 ) {// Enter
gosub *OnUnloadContent
DxLib_End
b_fullscr = ( b_fullscr == 0 )// 真偽反転
ChangeWindowMode ( b_fullscr == 0 )
DxLib_Init
gosub *OnLoadContent
}
return

*OnExitApp
dialog "終了!"
end


 (ちなみに私が作ったBallRoad2ndでも上記のようなリソース再取得のためのサブルーチンを使っていました)

デバイスロスト時のコールバック設定は「SetUseGraphBaseDataBackup」や「SetRestoreGraphCallback」などでそこそこ煩雑な処理になるのと、コールバックのためにプラグインを使わないとうまく実現できない為ここでは割愛します。
関連記事
スポンサーサイト



コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://fe0km.blog.fc2.com/tb.php/53-7b50ed6c
この記事にトラックバックする(FC2ブログユーザー)