Попытка реализовать простенькую игру на WinAPI и GDI+ (Проблема низкий FPS)
09.09.2021, 19:14. Показов 3133. Ответов 1
Всем доброго времени суток.
Я новичок и пытаюсь постичь основы Win Api. Я новичок в принципе. ООП на C++ почти не юзал и большинство моих проектов - это один файл. Короче я очередной говнокодер и сейчас я это продемонстрирую.
Идея следующая: написать простенькую игру (типа увернись как бои в undertele), но чтобы действие как бы происходило непосредственно на рабочей столе(не в окне).
| C++ | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
| INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, PSTR, INT iCmdShow) //для проекта под Windows
//INT APIENTRY main(HINSTANCE hInstance, HINSTANCE, PSTR, INT iCmdShow) //для консольного проекта
{
HWND hWnd;
MSG msg = {0};
WNDCLASS wndClass;
// Определяем разрешение экрана
scrWidth = GetSystemMetrics(SM_CXSCREEN);
scrHeight = GetSystemMetrics(SM_CYSCREEN);
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
// Initialize GDI+.
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
wndClass.style = 0;// CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc = WndProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = hInstance;
wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.hbrBackground = 0;// (HBRUSH)GetStockObject(WHITE_BRUSH);
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = TEXT("GettingStarted");
globalhInstance = hInstance;
RegisterClass(&wndClass);
hWnd = CreateWindowEx(
WS_EX_TOPMOST,
TEXT("GettingStarted"), // window class name
TEXT("Getting Started"), // window caption
WS_POPUP, // window style
0, // initial x position
0, // initial y position
scrWidth, // initial x size
scrHeight, // initial y size
NULL, // parent window handle
NULL, // window menu handle
hInstance, // program instance handle
NULL); // creation parameters
while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
GdiplusShutdown(gdiplusToken);
return msg.wParam;
} |
|
Тут всё вроде ок. Окно создаётся пустое и прозрачное.
Далее я рванул отрисовывать гифку. Это оказалась ещё та задача. Пока я не стал особо заморачиваться: не вытаскиваю ни количество кадров, ни длительности между ними. Я заранее определил что именно в той гифке, что я использую, 60 кадров и я их меняю каждый кадр.
Добавляем отрисовку кадра в главный цикл:
| C++ | 1
2
3
4
5
6
7
8
9
10
11
12
| while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else update_screen(hWnd);
}
} |
|
И функция отрисовки:
| C++ | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| VOID update_screen(HWND hWnd) {
HDC wndw = GetDC(hWnd);
HDC memDC = CreateCompatibleDC(wndw);
HBITMAP memBM = CreateCompatibleBitmap(wndw, scrWidth, scrHeight);
SelectObject(memDC, memBM);
Graphics graphics(memDC);
//GIF------------------
Image image(L"gif1.gif");
frame++;
if (frame > 59) frame = 0;
GUID pageGuid = FrameDimensionTime;
image.SelectActiveFrame(&pageGuid, frame);
graphics.DrawImage(&image, 0, 0);
//GIF------------
//FPS-----------------
FontFamily fontFamily(L"Times New Roman");
Font font(&fontFamily, 24, FontStyleRegular, UnitPixel);
PointF pointF(30.0f, 10.0f);
SolidBrush solidBrush(Color(255, 0, 0, 255));
std::wstring str = std::to_wstring(FPS);
const wchar_t* wstrFPS = str.c_str();
size_t newLen = 6 + wcslen(wstrFPS) + 1;
wchar_t* cmd = (wchar_t*)malloc(newLen * sizeof(wchar_t));
wcscpy_s(cmd, newLen, L"FPS = ");
wcscat_s(cmd, newLen, wstrFPS);
graphics.DrawString(cmd, -1, &font, pointF, &solidBrush);
//FPS-----------------
BitBlt(wndw, 0, 0, scrWidth, scrHeight, memDC, 0, 0, SRCCOPY);
DeleteDC(memDC);
DeleteObject(memBM);
mtime = GetTickCount();
FPS = 1000 / (mtime - last_screeen_tick);
last_screeen_tick = GetTickCount();
} |
|
В принципе гика замаячила, но как вы наверняка уже догадались, кадры накладываются друг на друга. И вот первая проблема. Если я хочу, чтобы игра как бы происходила не в окне, заливка не подходит. Значит в начале нам нужно делать скриншот и использовать его как фон. Но я так и не понял как вытащить статичную картинку из контекста (DC). В итоге нашёл на форуме код, который сохраняет скриншот в файл. Я закинул его в обработчик сообщений на WM_CREATE. Но к моему стыду так и не смок переменную bitmap вытащить из локальных. В итоге решил временно забить
| C++ | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
{
HDC scrdc, memdc;
HBITMAP membit;
// Получаем HDC рабочего стола
// Параметр HWND для рабочего стола всегда равен нулю.
scrdc = GetDC(0);
// Создаем новый DC, идентичный десктоповскому и битмап размером с экран.
memdc = CreateCompatibleDC(scrdc);
membit = CreateCompatibleBitmap(scrdc, scrWidth, scrHeight);
SelectObject(memdc, membit);
// Улыбаемся... Снято!
BitBlt(memdc, 0, 0, scrWidth, scrHeight, scrdc, 0, 0, SRCCOPY);
HBITMAP hBitmap;
hBitmap = (HBITMAP)SelectObject(memdc, membit);
Gdiplus::Bitmap bitmap(hBitmap, NULL);
bitmap.Save(L"screen.png", &png);
DeleteDC(scrdc);
DeleteDC(memdc);
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
} // WndProc |
|
Отсюда первый вопрос, как получить скриншот в виде экземпляра класса Image, или в другом виде, но с возможностью отрисовать иным способом?
В функции отрисовки добавился вывод скрина в начале и ещё меня чёрт дёрнул сделать что-то типа ограничения maxFPS, которое я приравнял к 120.
| C++ | 1
2
3
4
5
6
7
8
9
10
| while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else if (GetTickCount() - last_screeen_tick >= 1000 / maxFPS) update_screen(hWnd);
} |
|
Но даже без него FPS не поднимается выше 30 и это при условии, что я обрисовываю только фон, одну GIF-ку (считай спрайт анимированный) и сам FPS. А что будет если я буду просчитывать персонажа и врагов, атаки, коллизию и тд.? 2 FPS?
Я хотел отказаться от погрузки картинок из памяти каждый кадр, и остановиться на подгрузке лишь раз(при старте) однако компилятор не позволил мне обьявить экземпляры не в глобалке, ни в WinMain, ни в WM_CREATE.
Вопрос номер два и самый важный: как повысить FPS?
Еще я хотел подгружать данные из ресурсов. Но Image не берёт их просто так. Форум и я нашёл вот такую замечательную функцию:
| C++ | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| Bitmap* BitmapFromResource(HINSTANCE hInstance, LPCTSTR szResName, LPCTSTR szResType)
{
HRSRC hrsrc = FindResource(hInstance, szResName, szResType);
if (!hrsrc) return 0;
// "ненастоящий" HGLOBAL - см. описание LoadResource
HGLOBAL hgTemp = LoadResource(hInstance, hrsrc);
DWORD sz = SizeofResource(hInstance, hrsrc);
void* ptrRes = LockResource(hgTemp);
HGLOBAL hgRes = GlobalAlloc(GMEM_MOVEABLE, sz);
if (!hgRes) return 0;
void* ptrMem = GlobalLock(hgRes);
// Копируем растровые данные
CopyMemory(ptrMem, ptrRes, sz);
GlobalUnlock(hgRes);
IStream* pStream;
// TRUE означает освободить память при последнем Release
HRESULT hr = CreateStreamOnHGlobal(hgRes, TRUE, &pStream);
if (FAILED(hr))
{
GlobalFree(hgRes);
return 0;
}
// Используем загрузку из IStream
Bitmap* image = Bitmap::FromStream(pStream);
pStream->Release();
return image;
} |
|
А Bitmap уже можно передать в Image, но это естественно не подходит анимированным GIF.
Вопрос 3: Как вытащить GIF-ку из ресурсов и передать её в Image?(Наименьший приоритет)
Также я тестил прогу на своем ПК и соседа. Думал, может просто что-то изменится к лучшему. Чудес не бывает. Стало хуже. Если у меня скрин делается всего экрана как и задумано, то на ПК соседа скрин получается урезанным, словно не верно определяются scrWidth и scrHeight.
Код целиком:
| C++ | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
| #include <iostream>
#include <windows.h>
#include <gdiplus.h>
#include <string>
#include "resource.h"
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")
int scrWidth;
int scrHeight;
int maxFPS = 120;
float FPS;
DWORD last_screeen_tick, mtime;
int frame = 0;
HINSTANCE globalhInstance;
static const GUID png =
{ 0x557cf406, 0x1a04, 0x11d3, { 0x9a, 0x73, 0x00, 0x00, 0xf8, 0x1e, 0xf3, 0x2e } };
Bitmap* BitmapFromResource(HINSTANCE hInstance, LPCTSTR szResName, LPCTSTR szResType)
{
HRSRC hrsrc = FindResource(hInstance, szResName, szResType);
if (!hrsrc) return 0;
// "ненастоящий" HGLOBAL - см. описание LoadResource
HGLOBAL hgTemp = LoadResource(hInstance, hrsrc);
DWORD sz = SizeofResource(hInstance, hrsrc);
void* ptrRes = LockResource(hgTemp);
HGLOBAL hgRes = GlobalAlloc(GMEM_MOVEABLE, sz);
if (!hgRes) return 0;
void* ptrMem = GlobalLock(hgRes);
// Копируем растровые данные
CopyMemory(ptrMem, ptrRes, sz);
GlobalUnlock(hgRes);
IStream* pStream;
// TRUE означает освободить память при последнем Release
HRESULT hr = CreateStreamOnHGlobal(hgRes, TRUE, &pStream);
if (FAILED(hr))
{
GlobalFree(hgRes);
return 0;
}
// Используем загрузку из IStream
Bitmap* image = Bitmap::FromStream(pStream);
pStream->Release();
return image;
}
VOID update_screen(HWND hWnd) {
HDC wndw = GetDC(hWnd);
HDC memDC = CreateCompatibleDC(wndw);
HBITMAP memBM = CreateCompatibleBitmap(wndw, scrWidth, scrHeight);
SelectObject(memDC, memBM);
Graphics graphics(memDC);
//background----------
Image bimage(L"screen.png");
graphics.DrawImage(&bimage, 0, 0);
//background----------
//GIF----------
Image image(L"gif1.gif");
frame++;
if (frame > 59) frame = 0;
GUID pageGuid = FrameDimensionTime;
image.SelectActiveFrame(&pageGuid, frame);
//graphics.Clear(Color(0, 0, 0, 0));
graphics.DrawImage(&image, 0, 0);//, image.GetWidth(), image.GetHeight());
//GIF----------
//FPS-----------------
FontFamily fontFamily(L"Times New Roman");
Font font(&fontFamily, 24, FontStyleRegular, UnitPixel);
PointF pointF(30.0f, 10.0f);
SolidBrush solidBrush(Color(255, 0, 0, 255));
std::wstring str = std::to_wstring(FPS);
const wchar_t* wstrFPS = str.c_str();
size_t newLen = 6 + wcslen(wstrFPS) + 1;
wchar_t* cmd = (wchar_t*)malloc(newLen * sizeof(wchar_t));
wcscpy_s(cmd, newLen, L"FPS = ");
wcscat_s(cmd, newLen, wstrFPS);
graphics.DrawString(cmd, -1, &font, pointF, &solidBrush);
//FPS-----------------
BitBlt(wndw, 0, 0, scrWidth, scrHeight, memDC, 0, 0, SRCCOPY);
DeleteDC(memDC);
DeleteObject(memBM);
mtime = GetTickCount();
FPS = 1000 / (mtime - last_screeen_tick);
last_screeen_tick = GetTickCount();
}
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, PSTR, INT iCmdShow)
//INT APIENTRY main(HINSTANCE hInstance, HINSTANCE, PSTR, INT iCmdShow)
{
HWND hWnd;
MSG msg = {0};
WNDCLASS wndClass;
// Определяем разрешение экрана
scrWidth = GetSystemMetrics(SM_CXSCREEN);
scrHeight = GetSystemMetrics(SM_CYSCREEN);
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
// Initialize GDI+.
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
wndClass.style = 0;// CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc = WndProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = hInstance;
wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.hbrBackground = 0;// (HBRUSH)GetStockObject(WHITE_BRUSH);
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = TEXT("GettingStarted");
globalhInstance = hInstance;
RegisterClass(&wndClass);
hWnd = CreateWindowEx(
WS_EX_TOPMOST,
TEXT("GettingStarted"), // window class name
TEXT("Getting Started"), // window caption
WS_POPUP, // window style
0, // initial x position
0, // initial y position
scrWidth, // initial x size
scrHeight, // initial y size
NULL, // parent window handle
NULL, // window menu handle
hInstance, // program instance handle
NULL); // creation parameters
while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else if (GetTickCount() - last_screeen_tick >= 1000 / maxFPS) update_screen(hWnd);
}
GdiplusShutdown(gdiplusToken);
return msg.wParam;
} // WinMain
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
{
HDC scrdc, memdc;
HBITMAP membit;
// Получаем HDC рабочего стола
// Параметр HWND для рабочего стола всегда равен нулю.
scrdc = GetDC(0);
// Создаем новый DC, идентичный десктоповскому и битмап размером с экран.
memdc = CreateCompatibleDC(scrdc);
membit = CreateCompatibleBitmap(scrdc, scrWidth, scrHeight);
SelectObject(memdc, membit);
// Улыбаемся... Снято!
BitBlt(memdc, 0, 0, scrWidth, scrHeight, scrdc, 0, 0, SRCCOPY);
HBITMAP hBitmap;
hBitmap = (HBITMAP)SelectObject(memdc, membit);
Gdiplus::Bitmap bitmap(hBitmap, NULL);
bitmap.Save(L"screen.png", &png);
DeleteDC(scrdc);
DeleteDC(memdc);
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
} // WndProc |
|
И вопросы по приоритету:
1. Как можно повысить FPS?
2. Как получить скриншот в виде экземпляра класса Image, или в другом виде, но с возможностью отрисовать иным способом?
3. С чем может быть связана проблема с урезанным скрином и как это можно решить?
4. Есть ли более простой и грамотный способ проиграть анимированную GIF-ку?
5. Как вытащить GIF-ку из ресурсов и передать её в Image?
Буду очень благодарен за помощь))
0
|