前回やった、草原マップ、主人公キャラの描画に引き続き、今回はキー操作に反応し、キャラが自由に歩き回れるようにするプログラムを作成していきます。
今回作成するプログラムのサンプルファイルを置いておきますので、参考にしたい方はどうぞ☆ Fantasy2.zip(プロジェクトに必要なファイルがすべて入っています)
今回も、First Seed Materialで公開されているRPG用素材を利用させていただきます。


前回作成したプログラムがある方は、MainWnd.sub のプログラムを変更するだけで実現できます。このページのプログラムリストは、前回からの変更点を太字で示してあるので、それを参考に改良してみて下さい。

新規にプログラムを作成する方は、まずはプロジェクトを作成しておきましょう。新規作成→プロジェクトで、"Fantasy" などという名前のプロジェクトを作成し、MainWnd.sbpを開いて、以下の示すプログラムを入力していって下さい。

' ----------------------------------------------------------------------------
' イベント プロシージャ
' ----------------------------------------------------------------------------
' このファイルには、ウィンドウ [MainWnd] に関するイベントをコーディングします。
' ウィンドウ ハンドル: hMainWnd

' メモ - 以下の領域を、変数、構造体、定数、関数を宣言するための、
' グローバル領域として利用することができます。
' ----------------------------------ここから----------------------------------

'画面の大きさ X*Y
Const MAX_MAPX=25
Const MAX_MAPY=20

'マップチップ画像
Const FileName_Road="road.bmp"
Const MAPCHIP_PIXEL=16 'マップチップのピクセル数
Dim hRoadTipsDC As DWord
Dim hRoadTipsBmp As DWord

'キャラクタチップ画像
Const FileName_Char="char.bmp"
Const CHAR_PIXEL_X=24 'キャラクタチップの幅(単位はピクセル)
Const CHAR_PIXEL_Y=32 'キャラクタチップの高さ(単位はピクセル)
Dim hCharTipsDC As DWord
Dim hCharTipsBmp As DWord

'マップ画像
Dim hMapDC As DWord
Dim hMapBmp As DWord

'画面表示用の画像
Dim hMemDC As DWord
Dim hMemBmp As DWord

'描画位置
Dim CharPos As POINTAPI
CharPos.x=12 '初期位置X
CharPos.y=10 '初期位置Y

'歩行スピード
Const WalkingSpeed = 120 'ミリ秒


歩行時のウェイトタイムを追加しています。だいたい、120ミリ秒が自然な感じだと思いますが、ここは任意の間隔で結構です。60とかにすると、ダッシュっぽくなったりします(^^;

Sub DrawMap()
   Dim x As Long, y As Long

   'マップを描画
   For y=0 To MAX_MAPY-1
       For x=0 To MAX_MAPX-1
           BitBlt(hMapDC,x*MAPCHIP_PIXEL,y*MAPCHIP_PIXEL,MAPCHIP_PIXEL,MAPCHIP_PIXEL,
               hRoadTipsDC,0,128,SRCCOPY)
       Next
   Next
End Sub



草原マップ描画のプロシージャに変更点はありません。

Sub DrawCharacter(CharTipX As Long, CharTipY As Long,
       MiddleOffsetX As Double, MiddleOffsetY As Double)
   Dim hMaskDC As DWord
   Dim hMaskBmp As DWord
   Dim hTempDC As DWord
   Dim hTempBmp As DWord
   Dim x As Long, y As Long

   'マップデータをコピー
   BitBlt(hMemDC,0,0,MAX_MAPX*MAPCHIP_PIXEL,MAX_MAPY*MAPCHIP_PIXEL,hMapDC,0,0,SRCCOPY)

   'マスク用画像を作成
   hMaskDC=CreateCompatibleDC(hMemDC)
   hMaskBmp=CreateBitmap(CHAR_PIXEL_X,CHAR_PIXEL_Y,1,1,0)
   SelectObject(hMaskDC,hMaskBmp)
   BitBlt(hMaskDC,0,0,CHAR_PIXEL_X,CHAR_PIXEL_Y, _
       hCharTipsDC, CharTipX*CHAR_PIXEL_X, CharTipY*CHAR_PIXEL_Y, SRCCOPY)

   'スプライト用のキャラクタ画像を作成
   hTempDC=CreateCompatibleDC(hMemDC)
   hTempBmp=CreateCompatibleBitmap(hMemDC,CHAR_PIXEL_X,CHAR_PIXEL_Y)
   SelectObject(hTempDC,hTempBmp)
   BitBlt(hTempDC,0,0,CHAR_PIXEL_X,CHAR_PIXEL_Y,hMaskDC,0,0,NOTSRCCOPY)
   BitBlt(hTempDC,0,0,CHAR_PIXEL_X,CHAR_PIXEL_Y, _
       hCharTipsDC, CharTipX*CHAR_PIXEL_X, CharTipY*CHAR_PIXEL_Y,SRCAND)

   x=CharPos.x*MAPCHIP_PIXEL-((CHAR_PIXEL_X-MAPCHIP_PIXEL)/2)+MAPCHIP_PIXEL*MiddleOffsetX
   y=CharPos.y*MAPCHIP_PIXEL-((CHAR_PIXEL_Y-MAPCHIP_PIXEL)/2)+MAPCHIP_PIXEL*MiddleOffsetY

   'マスク画像をAND転送
   BitBlt(hMemDC, x, y, CHAR_PIXEL_X, CHAR_PIXEL_Y, _
       hMaskDC, 0, 0, SRCAND)

   'キャラクタ画像をOR転送
   BitBlt(hMemDC, x, y, CHAR_PIXEL_X, CHAR_PIXEL_Y, _
       hTempDC,0,0,SRCPAINT)

   'マスク用画像をメモリから解放
   DeleteDC(hMaskDC)
   DeleteObject(hMaskBmp)

   'キャラクタの一時画像をメモリから解放
   DeleteDC(hTempDC)
   DeleteObject(hTempBmp)
End Sub



歩行時の描画に、3つのタイプの画像を必要とし、この関数は少し複雑になります。CharTipX、CharTipY変数を通して、マップ上のキャラクタの位置が指定され、その位置に描画を行います。MiddleOffsetX、MiddleOffsetY変数などを利用することで、滑らかな歩行が実現できます。
スプライト処理については、前回と同様です。

Function MainOperation(dwDummy As DWord)
   While 1
       If GetAsyncKeyState(VK_UP) and &H8000 Then
           '上へ移動
           CharPos.y=CharPos.y-1

           '歩きの途中を描画(足を交互に動かす)
           If CharPos.y mod 2 Then
               DrawCharacter(0, 0, 0, 0.5)
           Else
               DrawCharacter(2, 0, 0, 0.5)
           End If

           '再描画とウェイト処理
           InvalidateRect(hMainWnd,ByVal 0,0)
           Sleep(WalkingSpeed)

           '移動が完了したときのキャラクタを描画
           DrawCharacter(1, 0, 0, 0)

           '再描画とウェイト処理
           InvalidateRect(hMainWnd,ByVal 0,0)
           Sleep(WalkingSpeed)
       ElseIf GetAsyncKeyState(VK_DOWN) and &H8000 Then
           '下へ移動
           CharPos.y=CharPos.y+1

           '歩きの途中を描画(足を交互に動かす)
           If CharPos.y mod 2 Then
               DrawCharacter(0, 2, 0, -0.5)
           Else
               DrawCharacter(2, 2, 0, -0.5)
           End If

           '再描画とウェイト処理
           InvalidateRect(hMainWnd,ByVal 0,0)
           Sleep(WalkingSpeed)

           '移動が完了したときのキャラクタを描画
           DrawCharacter(1, 2, 0, 0)

           '再描画とウェイト処理
           InvalidateRect(hMainWnd,ByVal 0,0)
           Sleep(WalkingSpeed)
       ElseIf GetAsyncKeyState(VK_LEFT) and &H8000 Then
           '左へ移動
           CharPos.x=CharPos.x-1

           '歩きの途中を描画(足を交互に動かす)
           If CharPos.x mod 2 Then
               DrawCharacter(0, 3, 0.5, 0)
           Else
               DrawCharacter(2, 3, 0.5, 0)
           End If

           '再描画とウェイト処理
           InvalidateRect(hMainWnd,ByVal 0,0)
           Sleep(WalkingSpeed)

           '移動が完了したときのキャラクタを描画
           DrawCharacter(1, 3, 0, 0)

           '再描画とウェイト処理
           InvalidateRect(hMainWnd,ByVal 0,0)
           Sleep(WalkingSpeed)
       ElseIf GetAsyncKeyState(VK_RIGHT) and &H8000 Then
           '右へ移動
           CharPos.x=CharPos.x+1

           '歩きの途中を描画(足を交互に動かす)
           If CharPos.x mod 2 Then
               DrawCharacter(0, 1, -0.5, 0)
           Else
               DrawCharacter(2, 1, -0.5, 0)
           End If

           '再描画とウェイト処理
           InvalidateRect(hMainWnd,ByVal 0,0)
           Sleep(WalkingSpeed)

           '移動が完了したときのキャラクタを描画
           DrawCharacter(1, 1, 0, 0)

           '再描画とウェイト処理
           InvalidateRect(hMainWnd,ByVal 0,0)
           Sleep(WalkingSpeed)
       End If
       Sleep(1)
   Wend
End Function

' ----------------------------------ここまで----------------------------------



このMainOperation関数は、ユーザーからのキー操作を受け付ける機能を持ちます。GetAsyncKeyState関数を利用して矢印キーが押されるのを察知し、キャラクタの移動描画を行います。

こんなGetAsyncKeyState関数を使わなくても、WM_KEYDOWNとかでもいいでしょ?

それが、実験してみるとわかりますが、駄目なんですよ。キーの長押しなどを行うとわかりますが、カクカク動作になってとてもRPGのマップ移動の動作は再現できません。このMainOperation関数では、1ミリ秒ごとにキーの状態を調べているので、非常にスムーズな対応ができます。ゲームなどの入力プログラムにおいては重要な点になるで、覚えておくと良いかもしれません。

Sub MainWnd_Destroy()
   DeleteDC(hRoadTipsDC)
   DeleteObject(hRoadTipsBmp)
   DeleteDC(hCharTipsDC)
   DeleteObject(hCharTipsBmp)
   DeleteDC(hMapDC)
   DeleteObject(hMapBmp)
   DeleteDC(hMemDC)
   DeleteObject(hMemBmp)

   Fantasy_DestroyObjects()
   PostQuitMessage(0)
End Sub



Destroyイベントに変更点はありません。

Sub MainWnd_Create(ByRef CreateStruct As CREATESTRUCT)
   Dim hDC As DWord

   'マップチップ画像の読み込み
   hRoadTipsBmp=LoadImage(GetModuleHandle(0),
       FileName_Road,IMAGE_BITMAP,0,0,LR_LOADFROMFILE or LR_DEFAULTSIZE)
   If hRoadTipsBmp=0 Then
       MessageBox(hMainWnd,Ex"\qroad.bmp\qの読み込みに失敗","Error",MB_OK or MB_ICONEXCLAMATION)
       PostQuitMessage(0)
   End If

   'キャラクタチップ画像の読み込み
   hCharTipsBmp=LoadImage(GetModuleHandle(0),
       FileName_Char,IMAGE_BITMAP,0,0,LR_LOADFROMFILE or LR_DEFAULTSIZE)
   If hCharTipsBmp=0 Then
       MessageBox(hMainWnd,Ex"\qchar.bmp\qの読み込みに失敗","Error",MB_OK or MB_ICONEXCLAMATION)
       PostQuitMessage(0)
   End If

   hDC=GetDC(hMainWnd)

   '各画像のデバイスコンテキストを作成
   hRoadTipsDC=CreateCompatibleDC(hDC)
   hCharTipsDC=CreateCompatibleDC(hDC)
   hMapDC=CreateCompatibleDC(hDC)
   hMemDC=CreateCompatibleDC(hDC)

   '各デバイスコンテキストにビットマップを選択
   SelectObject(hRoadTipsDC,hRoadTipsBmp)
   SelectObject(hCharTipsDC,hCharTipsBmp)
   hMapBmp=CreateCompatibleBitmap(hDC,MAX_MAPX*MAPCHIP_PIXEL,MAX_MAPY*MAPCHIP_PIXEL)
   SelectObject(hMapDC,hMapBmp)
   hMemBmp=CreateCompatibleBitmap(hDC,MAX_MAPX*MAPCHIP_PIXEL,MAX_MAPY*MAPCHIP_PIXEL)
   SelectObject(hMemDC,hMemBmp)

   '透明色を設定
   SetBkColor(hCharTipsDC,RGB(0,117,117))

   ReleaseDC(hMainWnd,hDC)

   'hMapDCにマップ描画
   DrawMap()

   '初期描画
   DrawCharacter(1,2,0,0)

   Dim dwDummy As DWord
   CreateThread(ByVal 0,0,AddressOf(MainOperation),0,0,VarPtr(dwDummy))
End Sub



MainOperation関数を新規スレッドとして実行します。これで、通常のウィンドウメッセージとは別にユーザーからのキー操作を受け付けることが可能になります。

Sub MainWnd_Paint(hDC As Long)
   'ウィンドウにhMemDCの内容を描画
   BitBlt(hDC,0,0,MAX_MAPX*MAPCHIP_PIXEL,MAX_MAPY*MAPCHIP_PIXEL,hMemDC,0,0,SRCCOPY)
End Sub



Paintイベントに変更点はありません。

これで作業は完了です。コンパイル、実行をしてみましょう。どうですか?主人公キャラは自由に動けるようになったでしょうか?以外にスムーズに動くので、作った私もビックリしてますが…。