DXライブラリ×VB.NET 導入メモ
DXライブラリ on VB.NETの備忘録です。VB.NETのフォーム上にDXライブラリの画面を表示するところまでです。C#を使用している場合は作成するプロジェクト、コードを脳内補正してください(DXライブラリ由来の関数は行末の”;”の有無くらいしか違いがないと思います)。
環境
- Windows 10 64bit 1903
- VisualStudio 2019
DXライブラリのダウンロード
DXライブラリのホームページ を開き、ダウンロードページから「VisualC# 用パッケージ」をダウンロードします。ダウンロードが終わったら展開しておきましょう。
[Visual Studio]プロジェクトの作成、参照設定
(DXライブラリのReadmeに書かれてある内容そのままです。)
Visual Studioを開き、新規プロジェクトを作成します。テンプレートは「Windows フォーム アプリケーション(.NET Framework)Visual Basic」を指定。プロジェクト名を適当に入力してプロジェクトを作成しましょう。
プロジェクトが作成できたらとりあえずDebugかReleaseでビルドをします。作成したプロジェクトのフォルダ内にDebugフォルダもしくはReleaseフォルダが作成されるので、そのフォルダの中に、先程DLしたDXライブラリ内の
DxLib.dll DxLib_x64.dll DxLibDotNet.dll を入れます。
DXライブラリにはUnicode版が存在します。使用するメリット等についてはDXライブラリのReadmeを参照してください。他にもC++版では使えるがC#版では使えない、という関数の解説などもされているので、必ず目を通しておいてください。
Visual Studioに戻り、「プロジェクト」→「参照の追加」を選択し、参照マネージャーを表示します。 左のタブで参照を選択、右下の参照ボタンを押し、先程コピーしたdllのうちDxLibDotNet.dllを選択してOKを押し、参照に追加してください。
追加が終わったら、コード内に
Imports DxLibDLL
を記述。これでDXライブラリを使用する準備が整いました。
(DxLibDLL.DXもImportするとコーディングが少し楽になるかもしれません)
コーディング
普通にコーディングするとWindowsフォームではなくDXライブラリ専用のウィンドウが表示され、そのウィンドウ内に各処理の結果が表示されるので、Windowsフォーム内(今回はPictureBox内)に表示していきます。
フォームにPictureBoxを追加(NameはデフォルトであるPixtureBox1)し、フォームのLoadイベントに以下のように記述します。
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
DX.SetMovieColorA8R8G8B8Flag(DX.TRUE) 'アルファチャンネルありの32bitカラーになるようにする
DX.SetUserWindow(PictureBox1.Handle) '描画先をPictureBox1にする
If DX.DxLib_Init() = -1 Then 'DXライブラリの初期化
Exit Sub
End If
End Sub
この状態でデバッグを行うとPictureBox内が黒く表示されると思います。これがDXライブラリの画面です。
あとはDXライブラリのリファレンスとにらめっこしながら書いていくとよいでしょう。基本となるであろう文字列の描画のコードを書いておきます。
DX.ClearDrawScreen() '画面をクリア
DX.DrawBox(0, 0, 320, 180, DX.GetColor(100, 100, 100), DX.TRUE) '全体を塗りつぶす
DX.DrawString(0, 0, "Hello DXlibrary!", DX.GetColor(255, 255, 255)) '文字を描画
DX.ScreenFlip() '表画面に表示
文字列や図形、画像等は一旦裏画面で逐次描画され、Screenflip()で表画面に一気に表示します。詳しくは こちら をご覧ください。
ループ処理について
DXライブラリはゲームを作るためのライブラリです。ゲームではマップ内をキャラクターが移動したり攻撃したり、といった動きがありますが、これを表示するためにアプリケーションの起動中は常にループを回している状態になっており、プレイヤーの操作によってループ内の処理内容が変わり、アニメーションされるようになっています。
DXライブラリでは、
While Not DX.ProcessMessage() = -1
処理内容
End While
のように書いてループを回します。ProcessMessage()は必ず書く必要があります。他にはClearDrawScreen()もループする場合は必ず実行すると思うので条件式内に書いてもよいでしょう。
Windowsアプリケーションを作成する場合はループ処理をすることは基本的に無いと思います…が、私はタイマーアプリを作りたく、そのためにはループ処理が必要に。
普通にループ処理をすると処理が固まってしまうので、非同期処理やタイマー関数を利用する必要があります。しかし、非同期処理(別スレッドを作成する、Asyncを使う)、タイマー関数、どちらを使用してもインターバルが通常のループ処理より落ち込んでしまいました(60fps→約40fps)。40fpsともなると表示が結構かくかくしてしまうので、なんとか60fpsで処理されるように変更するためにtimeSetEventを利用したタイマーを使ってみたところ、処理自体はうまくいくようになりました(BackgroundWorkerを使用しても同様の結果となりました。後述する問題点も同様です。)。
Public Delegate Sub TimerProcDelegate(ByVal uID As Integer,
ByVal uMsg As Integer,
ByVal dwUser As Integer,
ByVal dw1 As Integer,
ByVal dw2 As Integer)
Public Declare Function timeBeginPeriod Lib "winmm.dll" (
ByVal uPeriod As Integer) As Integer
Public Declare Function timeEndPeriod Lib "winmm.dll" (
ByVal uPeriod As Integer) As Integer
Public Declare Function timeSetEvent Lib "winmm.dll" (
ByVal uDelay As Integer,
ByVal uResolution As Integer,
ByVal lpFunction As TimerProcDelegate,
ByVal dwUser As Integer,
ByVal uFlags As Integer) As Integer
Public Declare Function timeKillEvent Lib "winmm.dll" (
ByVal uID As Integer) As Integer
Public gIntTimerID As Integer '高精度タイマID
Private proc As TimerProcDelegate
' <<<< タイマーの起動 >>>>
Public Sub TimerStart(ByVal mSec As Integer)
Call timeBeginPeriod(1)
proc = AddressOf TimerProc
gIntTimerID = timeSetEvent(mSec, 1, proc, 0, 0)
System.Threading.Thread.Sleep(100)
Console.WriteLine("タイマースタート")
gIntTimerID = timeSetEvent(mSec, 1, proc, 0, 1)
End Sub
' <<<< タイマーの停止 >>>>
Public Sub TimerStop()
Call timeKillEvent(gIntTimerID)
Call timeEndPeriod(1)
System.Threading.Thread.Sleep(100)
Console.WriteLine("タイマーストップ")
End Sub
' <<<< 高精度タイマーで繰り返す >>>>
Public Sub TimerProc(ByVal uTimerID As Integer, ByVal uMsg As Integer, ByVal dwUser As Integer, ByVal dw1 As Integer, ByVal dw2 As Integer)
If DX.ProcessMessage() = -1 Then
DX.DxLib_End()
Call TimerStop()
End If
処理内容
End Sub
Private Sub _Start()
Call TimerStart(16)
End Sub
Private Sub _Stop()
Call TimerStop()
End Sub
timeSetEventは使うのを控えたほうが良いのですが…
とにかくこれで60fps以上を達成できたのですが、問題が2つ発生しました。
指定したインターバル通りにループしない
稼働中にエラーが発生する
1つ目は、例えばインターバルを16msに設定しても1秒間に90回ほどループが回ってしまいました。一応インターバルを変更するとループ回数も変化するようなので、無理やり目的のfpsを出すことはできるでしょう…
2つ目は必ず、というわけではないですが、ループ中にAccess Violationエラーが発生して落ちてしまうことがありました。nvwgf2um.dll関連のエラーであるというところまではわかりましたが、対処法は分かっておりません。Access Violationエラーはキャッチできなかった気がする(というかこのエラーは放置するとPCに悪影響を及ぼしかねないためさっさと落ちてくれたほうが安全)ので、流石に改善しないままこれを使うわけにはいきません。
上記方法以外で60fps表示を達成したいところですが、一体どうすれば…とりあえずTimerを使用する方向で。
まぁVB.NETオンリーで簡易なものを作ることになるでしょうが