前回の解説で主人公キャラが自由に動け回れるようになったので、今回は任意のマップを描けるようにしてみます。

マップ図にマップチップを割り当てるという、非常にシンプルな細工をするわけですが、マップチップの種類が非常に多いため、数字攻めになってしまうかもしれませんが、根気良くがんばりましょう。

今回作成するプログラムのサンプルファイルを置いておきますので、参考にしたい方はどうぞ☆ Fantasy3.zip(プロジェクトに必要なファイルがすべて入っています)




今回も、ステップ17「RPGのマップ移動機能を作る②」で掲載したプログラムの変更点を太字で表しているので、参考にして下さい。
また、以下のようなマップの配置図(25*20)をCSV形式で用意しておく必要があります。プロジェクトが保存されているディレクトリに "map.csv" というファイル名で保存しておいて下さい。

map.csvのサンプル

240,276,277,277,278,390,391,392,276,277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,278
240,306,307,307,308,420,421,422,306,307,307,307,307,307,307,307,307,307,307,307,307,307,307,307,308
240,306,307,307,308,420,421,422,306,307,307,307,307,307,307,307,307,307,307,307,307,307,307,307,308
240,306,307,307,308,420,421,422,306,307,307,307,307,307,307,307,307,307,307,307,307,307,307,307,308
240,306,307,307,308,420,421,422,306,307,307,307,307,307,307,307,307,307,307,307,307,307,307,307,308
240,306,307,307,308,420,421,422,306,307,307,307,307,307,307,307,307,307,307,307,307,307,307,307,308
240,336,337,337,338,420,421,422,336,337,337,337,337,337,337,337,337,337,337,337,337,337,337,337,338
240,240,240,240,240,420,421,421,391,391,391,391,391,391,391,391,391,391,391,391,391,391,391,391,392
240,240,240,240,240,420,421,421,421,421,421,421,421,421,421,421,421,421,421,421,421,421,421,421,422
240,240,240,240,240,450,451,451,451,451,451,451,451,451,451,451,451,451,451,451,451,451,451,451,452
240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240
240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240
103,103,103,240,240,240,241,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103
133,133,133,102,103,103,104,133,133,133,133,133,133,133,133,133,133,133,133,133,133,133,133,133,133
193,193,193,132,133,133,134,193,193,193,193,193,193,193,193,193,193,193,193,193,193,193,193,193,193
223,223,223,192,193,193,194,223,223,223,223,223,223,223,223,223,223,223,223,223,223,223,223,223,223
253,253,253,222,223,223,224,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253
283,283,283,252,253,253,254,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283
120,120,120,282,283,283,284,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120
120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120,120



マップチップの番号は、road.bmpを開いて、左上から横方向に0,1,2,3,… 29という具合になります(1マス 16*16)。2行目は、30,31,32,33,… 59になります。

以下から、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 = 100 'ミリ秒

'マップ配置図の二次元配列データ
Dim MapArray[MAX_MAPY-1,MAX_MAPX-1] As Long

↑先頭部分では、マップ配置図用の配列変数の定義を追加しています。

Sub DrawMap()
Dim x As Long, y As Long
Dim num As Long
Dim tempX As Long, tempY As Long
Dim i As Long

'マップチップの配置を記載したファイルをオープン
Open "map.csv" For Input As #1

'マップチップを読み込む
For y=0 To MAX_MAPY-1
For x=0 To MAX_MAPX-1
Input #1,num
MapArray[y,x]=num
Next
Next

'ファイルのクローズ
Close #1

'マップを描画
For y=0 To MAX_MAPY-1
For x=0 To MAX_MAPX-1
'チップの位置(縦、横に何番目か)をtempX、tempYに格納
tempX=MapArray[y,x] mod 30
tempY=MapArray[y,x] \ 30

BitBlt(hMapDC, _
x*MAPCHIP_PIXEL, y*MAPCHIP_PIXEL, MAPCHIP_PIXEL, MAPCHIP_PIXEL, _
hRoadTipsDC, tempX*MAPCHIP_PIXEL, tempY*MAPCHIP_PIXEL, SRCCOPY)
Next
Next
End Sub

↑以前までは草原用のマップチップを羅列するだけでしたが、今回は、map.csvの読み込みと、チップ位置の計算などが追加されています。

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


↑DrawCharacter関数に変更点はありません。

Function IsClosedChip(ChipNum As Long) As Long
If ChipNum=102 or _
ChipNum=103 or _
ChipNum=104 or _
ChipNum=120 or _
ChipNum=132 or _
ChipNum=133 or _
ChipNum=134 or _
ChipNum=192 or _
ChipNum=193 or _
ChipNum=194 or _
ChipNum=222 or _
ChipNum=223 or _
ChipNum=224 or _
ChipNum=252 or _
ChipNum=253 or _
ChipNum=254 or _
ChipNum=282 or _
ChipNum=283 or _
ChipNum=284 Then
IsClosedChip=1
Else
IsClosedChip=0
End If
End Function


↑IsClosedChip関数を新たに追加します。この関数では、主人公キャラクタが移動できないチップを、チップ番号から判別します。移動可能なときは 1 を、不可能なときは 0 を返します。
例えば、草原や草むらなどは移動可能、崖や川などは移動不可といった具合になります。

Function MainOperation(dwDummy As DWord)
Dim TempPos As Long

While 1
If GetAsyncKeyState(VK_UP) and &H8000 Then
'上へ移動
TempPos=CharPos.y-1
If TempPos>=0 and IsClosedChip(MapArray[TempPos,CharPos.x])=0 Then
CharPos.y=TempPos

'歩きの途中を描画(足を交互に動かす)
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)
End If

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

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

'歩きの途中を描画(足を交互に動かす)
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)
End If

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

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

'歩きの途中を描画(足を交互に動かす)
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)
End If

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

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

'歩きの途中を描画(足を交互に動かす)
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)
End If

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

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

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


↑MainOperation関数では、移動可能かどうかを判別するための処理が追加されています。

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


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

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イベントに変更点はありません。

プログラムをコンパイルし、実行してみましょう。キャラクタは、水の中(?)に進むことはできませんが、草むらや道のようなところは歩くことができますね!
RPGのマップ移動機能についての解説は今回で終了となりますが、このプログラムを応用し、マップ間の移動や吹き出しの表示、村人や仲間を出現させてみるとより本格的になるでしょう。