まずはワールドマップ上を主人公が歩くプログラムを組んでみたのですが、
ワールドマップをスクロールする時に若干引っかかるような違和感を感じます。
ゲームプログラミングのサイトなどでは「ちらつき対策」としてダブルバッファリングが紹介されていましたが
私の感じている「引っ掛かり」のような感覚について記載のあるサイトは見つけられませんでした。
ワールドマップ用の非表示画面(デバイスコンテキスト?)が大きすぎるのか、bmpファイルが良くないのか
または単にPCスペックの問題なのか皆目見当がつきません。
今のプログラムを活かしつつ、より滑らかに画面スクロールが出来るようにするにはどうすれば良いでしょうか?
#include <Windows.h>
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
DWORD WINAPI Main(LPVOID param);
class commonstate{
public:
BOOL MainThreadActive;
int ActiveScene;
HWND hMainWindow; // 画面表示ウィンドウ
HDC hMainDC;
int MainClientSize[2]; // クライアントサイズ
HDC hOriginalDC; // hMainWindow に表示させるための元画面(非表示)
HBITMAP hOriginalBitmap;
int OriginalSize[2]; // 元画面のサイズ
HDC hMapDC; // ワールドマップ全体
HBITMAP hMapBitmap;
int MapDrawpoint[2]; // hOriginalDC にStretchBltでコピーする左上の座標
HDC hCharacterDC; // キャラクターの上下左右×2パターン
HDC hCharacterMaskDC; // キャラクターのマスク
HBITMAP hCharacterBitmap;
HBITMAP hCharacterMaskBitmap;
int Character[5][2]; // 表示すべきキャラクターの座標(5キャラまで対応可能)
int CharacterDrawpoint[5][2]; // キャラクターを表示する hOriginalDC の座標
HANDLE hMainThread;
DWORD MainThreadID;
};
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, int nCmdShow){
commonstate common; // プログラム内の共有情報
WNDCLASS wndclass;
MSG msg;
RECT rect;
common.MainThreadActive = TRUE;
common.ActiveScene = 0; // シーン0: 準備中、ロード中
common.OriginalSize[0] = 300; // 元画像サイズを 300×225に設定
common.OriginalSize[1] = 225; // 元画像サイズを 300×225に設定
common.MainClientSize[0] = common.OriginalSize[0] * 2; // Main クライアントサイズを Original の2倍に設定
common.MainClientSize[1] = common.OriginalSize[1] * 2; // Main クライアントサイズを Original の2倍に設定
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = TEXT("ClassName01");
if(!RegisterClass(&wndclass)) return 0; // ウィンドウクラスを登録
common.hMainWindow = CreateWindow( // ウィンドウ生成
TEXT("ClassName01"),
TEXT("Window Title"),
WS_SYSMENU | WS_MINIMIZEBOX | WS_SIZEBOX ,
CW_USEDEFAULT, CW_USEDEFAULT, // 表示位置は Windows に委ねる(オート)
common.MainClientSize[0] + 8, // 非クライアント領域のピクセル数が分からないので、暫定で「+8」
common.MainClientSize[1] + 27, // 非クライアント領域のピクセル数が分からないので、暫定で「+27」
NULL, NULL, hInstance, NULL
);
if(common.hMainWindow == NULL) return 0;
SendMessage(common.hMainWindow, WM_NULL, (WPARAM)101, (LPARAM)(&common)); // コールバック関数に common のアドレスを渡す
int OutofclientareaSize[2]; // 非クライアント領域のサイズ
GetClientRect(common.hMainWindow, &rect); // クライアントサイズ取得
OutofclientareaSize[0] = common.MainClientSize[0] + 8 - rect.right; // 非クライアント領域計算
OutofclientareaSize[1] = common.MainClientSize[1] + 27 - rect.bottom; // 非クライアント領域計算
GetWindowRect(common.hMainWindow, &rect); // ウィンドウ表示位置取得
MoveWindow(common.hMainWindow,
rect.left, rect.top, // 表示位置は変更せず
common.MainClientSize[0] + OutofclientareaSize[0], common.MainClientSize[1] + OutofclientareaSize[1], // Windowサイズを再計算
TRUE);
common.hMainDC = GetDC(common.hMainWindow);
// OriginalWindow 作成(Invisible)
common.hOriginalDC = CreateCompatibleDC(common.hMainDC); // メモリデバイスコンテキストのハンドル取得(幅1、高さ1)
common.hOriginalBitmap = CreateCompatibleBitmap(common.hMainDC, common.OriginalSize[0], common.OriginalSize[1]); // hBitmap の幅、高さを設定
SelectObject(common.hOriginalDC, common.hOriginalBitmap); // メモリデバイスコンテキストに hBitmap の幅、高さを取得(拡張)
// OriginalWindow に、白地&「なうろ~でぃんぐ」描画
Rectangle(common.hOriginalDC, -1, -1, common.OriginalSize[0]+1, common.OriginalSize[1]+1); // OriginalWindow の描画領域より1ピクセル外側に黒線の枠
TextOut(common.hOriginalDC, common.OriginalSize[0]/10, common.OriginalSize[1]/3, TEXT("なうろ~でぃんぐ"), lstrlen(TEXT("なうろ~でぃんぐ")));
// MainWindow 表示
ShowWindow(common.hMainWindow , SW_SHOW);
InvalidateRect(common.hMainWindow, NULL, FALSE);
UpdateWindow(common.hMainWindow);
// Main スレッド起動
common.hMainThread = CreateThread(NULL, 0, Main, (LPVOID)(&common), 0, &common.MainThreadID);
if(!common.hMainThread){
MessageBox(common.hMainWindow, TEXT("MainThread 起動失敗"), TEXT("エラー"), MB_OK);
return -1;
}
while(GetMessage(&msg, NULL, 0, 0)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp){
static commonstate *common;
// Mainスレッドの動作制御用(後で書き換える)マップ移動とかに応用する予定
if(msg == WM_KEYDOWN){
if(wp == VK_SPACE){
int suspend = ResumeThread(common->hMainThread) - 1;
while(suspend < 0) suspend = SuspendThread(common->hMainThread) + 1;
while(suspend > 0) suspend = ResumeThread(common->hMainThread) - 1;
}
}
switch(msg){
case WM_NULL:
if(wp == (WPARAM)101){
common = (commonstate *)lp; // commonstate変数のアドレスをコールバック関数に渡す
}
return 0;
case WM_PAINT:
HDC hPaintDC;
PAINTSTRUCT ps;
hPaintDC = BeginPaint(hwnd, &ps);
StretchBlt(
hPaintDC, 0 , 0 , common->MainClientSize[0], common->MainClientSize[1],
common->hOriginalDC, 0 , 0 , common->OriginalSize[0], common->OriginalSize[1], SRCCOPY
);
EndPaint(hwnd, &ps);
return 0;
case WM_SIZE:
RECT rect;
GetClientRect(common->hMainWindow, &rect);
common->MainClientSize[0] = rect.right;
common->MainClientSize[1] = rect.bottom;
return 0;
case WM_DESTROY:
common->MainThreadActive = FALSE;
SendMessage(common->hMainWindow, (UINT)WM_KEYDOWN, (WPARAM)VK_SPACE, NULL); // Main スレッドの動作を再開し、終了させる。
Sleep(100);
PostQuitMessage(0);
return 0;
}
// シーンごとに制御を変える必要あり
// とりあえずはマップ画面である事を前提に記述
switch(msg){
case WM_KEYDOWN:
switch(wp){
case VK_UP:
case VK_DOWN:
case VK_LEFT:
case VK_RIGHT:
if(wp == VK_UP) common->Character[0][0] = 16*2; // 上向き
if(wp == VK_DOWN) common->Character[0][0] = 16*0; // 下向き
if(wp == VK_LEFT) common->Character[0][0] = 16*4; // 左向き
if(wp == VK_RIGHT) common->Character[0][0] = 16*6; // 右向き
int i1;
int DefTime; // 基準からの経過時間
SYSTEMTIME BaseTime, NowTime;
GetLocalTime(&BaseTime);
for(i1=0; i1<16; i1++){ // 1マス分が16x16ピクセル(キャラクター画像も16x16)
if(i1==8) common->Character[0][0] += 16; // 半分移動した時点で、2パターン目に変更
while(1){
GetLocalTime(&NowTime);
DefTime = (1000*60*60)*(NowTime.wHour - BaseTime.wHour) + (1000*60)*(NowTime.wMinute - BaseTime.wMinute) + (1000)*(NowTime.wSecond - BaseTime.wSecond) + (NowTime.wMilliseconds - BaseTime.wMilliseconds);
if( (NowTime.wHour - BaseTime.wHour) >= 48 ){
MessageBox(NULL, TEXT("48時間以上経過(エラー)"), TEXT("でばっく用"), MB_OK);
DefTime = (1000*60*60) * 48;
}
if(DefTime >= (i1+1)*500/16) break;
Sleep((i1+1)*500/16 - DefTime);
}
switch(wp){ // 画面スクロール
case VK_UP: common->MapDrawpoint[1]--; break;
case VK_DOWN: common->MapDrawpoint[1]++; break;
case VK_LEFT: common->MapDrawpoint[0]--; break;
case VK_RIGHT: common->MapDrawpoint[0]++; break;
}
BitBlt(common->hOriginalDC, 0, 0, common->OriginalSize[0], common->OriginalSize[1], common->hMapDC, common->MapDrawpoint[0], common->MapDrawpoint[1], SRCCOPY);
BitBlt(common->hOriginalDC, common->CharacterDrawpoint[0][0], common->CharacterDrawpoint[0][1], 16, 16, common->hCharacterMaskDC, common->Character[0][0], common->Character[0][1], SRCERASE);
BitBlt(common->hOriginalDC, common->CharacterDrawpoint[0][0], common->CharacterDrawpoint[0][1], 16, 16, common->hCharacterDC, common->Character[0][0], common->Character[0][1], SRCERASE);
InvalidateRect(common->hMainWindow, NULL, FALSE);
UpdateWindow(common->hMainWindow);
}
}
}
return DefWindowProc(hwnd, msg, wp, lp);
}
DWORD WINAPI Main(LPVOID param){
commonstate *common = (commonstate *)param;
// ファイルの読込み(ワールドマップ)
common->hMapBitmap = (HBITMAP)LoadImage(NULL, TEXT("ワールドマップ(256色).bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE|LR_CREATEDIBSECTION);
if(common->hMapBitmap==NULL){
MessageBox(NULL, TEXT("読込みエラー"), TEXT("でばっく用"), MB_OK);
return -1;
}
common->hMapDC = CreateCompatibleDC(common->hMainDC);
SelectObject(common->hMapDC, common->hMapBitmap);
// 表示位置設定 & 描画
common->MapDrawpoint[0] = 16*160-6; // hOriginalDC にStretchBltでコピーする左上の座標
common->MapDrawpoint[1] = 16*206; // hOriginalDC にStretchBltでコピーする左上の座標
BitBlt(common->hOriginalDC, 0, 0, common->OriginalSize[0], common->OriginalSize[1], common->hMapDC, common->MapDrawpoint[0], common->MapDrawpoint[1], SRCCOPY);
InvalidateRect(common->hMainWindow, NULL, FALSE);
UpdateWindow(common->hMainWindow);
// bmpファイルの読込み(Character)
common->hCharacterBitmap = (HBITMAP)LoadImage(NULL, TEXT("キャラ画像whiteback(24bit).bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE|LR_CREATEDIBSECTION);
if(common->hCharacterBitmap==NULL){
MessageBox(NULL, TEXT("読込みエラー"), TEXT("でばっく用"), MB_OK);
return -1;
}
common->hCharacterDC = CreateCompatibleDC(common->hMainDC);
SelectObject(common->hCharacterDC, common->hCharacterBitmap);
// bmpファイルの読込み(Characterマスク)
common->hCharacterMaskBitmap = (HBITMAP)LoadImage(NULL, TEXT("キャラ画像マスク(モノクロbmp).bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE|LR_CREATEDIBSECTION);
if(common->hCharacterMaskBitmap==NULL){
MessageBox(NULL, TEXT("読込みエラー"), TEXT("でばっく用"), MB_OK);
return -1;
}
common->hCharacterMaskDC = CreateCompatibleDC(common->hMainDC);
SelectObject(common->hCharacterMaskDC, common->hCharacterMaskBitmap);
// 表示位置設定 & 描画
common->Character[0][0] = 0; // 前向き
common->Character[0][1] = 0; // 前向き
common->CharacterDrawpoint[0][0] = 6 + 16*9; // hOriginalDC の中央付近
common->CharacterDrawpoint[0][1] = 16*7; // hOriginalDC の中央付近
BitBlt(common->hOriginalDC, common->CharacterDrawpoint[0][0], common->CharacterDrawpoint[0][1], 16, 16, common->hCharacterMaskDC, common->Character[0][0], common->Character[0][1], SRCERASE);
BitBlt(common->hOriginalDC, common->CharacterDrawpoint[0][0], common->CharacterDrawpoint[0][1], 16, 16, common->hCharacterDC, common->Character[0][0], common->Character[0][1], SRCERASE);
InvalidateRect(common->hMainWindow, NULL, FALSE);
UpdateWindow(common->hMainWindow);
// コールバック関数の動作をマップ画面用に設定
common->ActiveScene = 1; // マップシーン
while(common->MainThreadActive){
SuspendThread(common->hMainThread);
}
DeleteDC(common->hMapDC);
DeleteDC(common->hCharacterDC);
DeleteDC(common->hCharacterMaskDC);
DeleteObject(common->hMapBitmap);
DeleteObject(common->hCharacterBitmap);
DeleteObject(common->hCharacterMaskBitmap);
return 0;
}