Странная проблема живописи Вильди; Примерно через 30 секунд окно больше не будет обновляться - PullRequest
1 голос
/ 14 января 2020

Я всегда был довольно знаком с C. Я решил использовать C с Visual Studio 2017, чтобы сделать небольшую windows игру для промежуточного проекта в моем классе AP по информатике в этом году. Конечно, он использует winapi, но я использую библиотеку wingdi для рендеринга простой 2d игры; использование wingdi, возможно, не лучший метод, но в целях экономии времени и простоты я решил сделать это, поскольку у меня действительно есть всего пара дней, чтобы завершить sh this.

Игра загружает среды в что вы можете перемещаться по персонажу вокруг него, и когда я закончил тестировать простую физику двумерных столкновений, которую я реализовал для движения персонажа и стен, я понял, что когда я оставлял приложение работающим в режиме отладки, казалось, что оно перестало рисовать зеленые границы стен для среды Demo, вскоре после этого маленький прямоугольник для хитбокса персонажа исчез, а когда я изменил размер окна, вся верхняя панель исчезла и была заменена на коричневый цвет фона, для которого я установил hbrBackground окна .

Я прочитал пост о ком-то, у кого также были проблемы с рисованием wingdi, и люди предположили, что это утечка памяти, но мое приложение значительно меньше, и я действительно не вижу ничего, что могло бы быть памятью. утечка в моем с Ода, насколько мне известно ...

Я установил таймер с USER_TIMER_MINIMUM, чтобы вызывать InvalidateRect, чтобы экран можно было постоянно обновлять, поскольку персонаж должен иметь возможность перемещаться.

В WM_PAINT я был уверен, что начать с BeginPaint и закрыть с EndPaint. Вот мой код, который на самом деле имеет дело с wingdi:

source. c:

int DEMO_ROOM[] = 
{
    0, 0, 500, 10,
    0, 0, 10, 500,
    490, 0, 500, 500,
    0, 490, 500, 500
};



LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC hdc;

    switch (msg)
    {
    case WM_CREATE:
        RunTrack1();
        TITLE_IMAGE = LoadBitmapA(PROGRAM, MAKEINTRESOURCE(IDB_PNG1));

        char_pos.X = 250;
        char_pos.Y = 250;

        SetTimer(hwnd, 1, USER_TIMER_MINIMUM, NULL);

        break;
    case WM_TIMER:

        InvalidateRect(hwnd, NULL, TRUE);

        break;

    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);

        LoadRoom(hdc, hwnd, &DEMO_ROOM, sizeof(DEMO_ROOM));
        LoadChar(hdc, hwnd, char_pos.X, char_pos.Y);

        EndPaint(hwnd, &ps);
        break;
    case WM_KEYDOWN:
        switch (wParam)
        {
        case 0x57: //W
            MoveChar
            (
                &char_pos,
                char_pos.X,
                char_pos.Y - 5,
                &DEMO_ROOM,
                sizeof(DEMO_ROOM)
            );
            break;
        case 0x41: //A
            MoveChar
            (
                &char_pos,
                char_pos.X - 5,
                char_pos.Y,
                &DEMO_ROOM,
                sizeof(DEMO_ROOM)
            );
            break;
        case 0x53: //S
            MoveChar
            (
                &char_pos,
                char_pos.X,
                char_pos.Y + 5,
                &DEMO_ROOM,
                sizeof(DEMO_ROOM)
            );
            break;
        case 0x44: //D
            MoveChar
            (
                &char_pos,
                char_pos.X + 5,
                char_pos.Y,
                &DEMO_ROOM,
                sizeof(DEMO_ROOM)
            );
            break;
        default:
            break;
        }
        break;
    case WM_KEYUP:

        break;
    case WM_GETMINMAXINFO:
        ((LPMINMAXINFO)lParam)->ptMinTrackSize.x = GetSystemMetrics(SM_CXSCREEN) / 2;
        ((LPMINMAXINFO)lParam)->ptMinTrackSize.y = GetSystemMetrics(SM_CYSCREEN) / 2;
        break;
    case WM_DESTROY:
        //KillTimer(hwnd, 1);
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

GameLibrary.h:

GameCoordinate GameSpaceToWindowSpace(HWND hwnd, int X, int Y, int MAX)
{
    RECT w;
    GameCoordinate pos;
    if (GetWindowRect(hwnd, &w))
    {
        int width = (w.right - w.left) - 20;
        int height = (w.bottom - w.top) - 43;
        float XRatio = (float)X / (float)MAX;
        float YRatio = (float)Y / (float)MAX;
        float XWindowPos = XRatio * (float)width;
        float YWindowPos = YRatio * (float)height;
        pos.X = (int)XWindowPos;
        pos.Y = (int)YWindowPos;
    }
    return pos;
}

void LoadRoom(HDC hdc, HWND hwnd, int * ROOM_DATA, unsigned int R_SIZE)
{
    for (unsigned int i = 0; i < (R_SIZE / 16); i++)
    {
        int left = ROOM_DATA[i * 4];
        int top = ROOM_DATA[(i * 4) + 1];
        int right = ROOM_DATA[(i * 4) + 2];
        int bottom = ROOM_DATA[(i * 4) + 3];
        GameCoordinate C1 = GameSpaceToWindowSpace(hwnd, left, top, 500);
        GameCoordinate C2 = GameSpaceToWindowSpace(hwnd, right, bottom, 500);
        RECT obj;
        obj.left = C1.X;
        obj.top = C1.Y;
        obj.right = C2.X;
        obj.bottom = C2.Y;
        FillRect(hdc, &obj, CreateSolidBrush(RGB(22, 110, 18)));
    }
}

void LoadChar(HDC hdc, HWND hwnd, int x, int y)
{
    GameCoordinate windowPos = GameSpaceToWindowSpace(hwnd, x, y, 500);
    Rectangle(hdc, windowPos.X - 1, windowPos.Y - 1, windowPos.X + 1, windowPos.Y + 1);
}

Это не было действительно необходимо для чтобы я включил функцию GameSpaceToWindowSpace в код, но я решил, просто чтобы избежать путаницы с моей маленькой пользовательской системой координат.

Этот код пока что в значительной степени моя игра, за исключением двух функций столкновения, моего WinMain и пара глобальных переменных.

Вот изображение того, как должно выглядеть окно, маленькая точка с синим кружком вокруг нее представляет координату персонажа, а зеленые стены вокруг окна должны быть пограничными стенами, чтобы помочь мне проверить столкновения:

enter image description here

Вот что происходит с окном примерно через 30 секунд:

enter image description here

Иногда прямоугольник символа также исчезает, и это последнее изображение представляет собой представление, показывающее, что по какой-то причине оно больше не будет рисовать, или refre sh окно; как у меня было окно развернуто, а затем свернуло его, чтобы показать, что прямоугольники границы не изменили свой размер и положение с окном:

enter image description here

To В заключение, я определенно новичок в wingdi, но с моим знакомством с winapi я решил, что лучше всего будет создать простую графику для простой 2D-игры за короткий промежуток времени. Однако, когда мое приложение выполняется в течение немного продолжительного времени, процесс рисования кажется неудачным, и он больше не может обновлять и обновлять sh содержимое окна. Я действительно не уверен, что проблема здесь, и при этом я не знаю, как я мог бы должным образом решить это. Я не думаю, что это утечка памяти из-за небольшого размера моей программы в настоящее время, однако я также в значительной степени нуб из-за wingdi, так что я могу быть очень неправ, и это вполне может быть утечка памяти, но в любом случае я не знаю, как go решить эту проблему, и буду признателен за более опытное понимание проблемы.

Спасибо всем за ваше время и опыт.

Ответы [ 2 ]

4 голосов
/ 14 января 2020

У вас заканчиваются ресурсы GDI. LoadRoom вызывается для каждого цикла WM_PAINT. Внутри этой функции это код, вызывающий сбой:

FillRect(hdc, &obj, CreateSolidBrush(RGB(22, 110, 18)));

Обратите внимание, что CreateSolidBru sh возвращает ресурс, которым вы должны управлять:

Когда вам больше не нужен объект HBRUSH, вызовите функцию DeleteObject , чтобы удалить его.

Вы никогда этого не сделаете, и в конечном итоге исчерпаете ресурсы GDI. Простое исправление будет следующим:

HBRUSH hbr = CreateSolidBrush(RGB(22, 110, 18));
FillRect(hdc, &obj, hbr);
DeleteObject(hbr);

Создание bru sh на каждом цикле рисования все еще расточительно, но утечки больше нет. Если вы хотите быть консервативным, вы можете выделить bru sh при инициализации ресурсов рендеринга и использовать его повторно.

Решением проблемы на C ++ будет использование RAII техника. Полезное приложение можно найти в Microsoft Windows Библиотеки реализации (WIL) . Предварительно созданный тип для решения этой проблемы: unique_hbru sh.


Простой способ диагностики подобных проблем - использование диспетчера задач. Вы можете переключиться на вкладку Сведения и включить столбец Объекты GDI . Если вы видите постоянное увеличение этого числа, у вас почти наверняка есть утечка ресурсов.

1 голос
/ 14 января 2020

В дополнение к утечке памяти, упомянутой в комментариях, вам также необходимо обратить внимание на проблему мерцания и проблему координат после максимизации окна.

Что такое мерцание?

Каждый раз, когда вы получаете сообщение WM_PAINT, вы знаете, что у вас есть хороший холст fre sh для рисования. Однако, если дважды нарисовать окно (один раз с помощью WM_ERASEBKGND, еще раз с помощью WM_PAINT), окно будет сильно мерцать.

Beacuse WM_ERASEBKGND сообщение отправляется окну, когда его фон необходимо стереть.

Как нам избежать стирания фона окна?

Существует два метода.

  • Установить фон окна bru sh на NULL. (Установите элемент hbrBackground структуры WNDCLASS в ноль при регистрации класса окна).

  • Возврат ненулевого значения в обработчик сообщений WM_ERASEBKGND.

    Код:

    case WM_ERASEBKGND:
        return 1;
    

Конечно, общий метод полного устранения мерцания windows заключается в использовании метода, называемого двойной буферизацией. Основная идея c состоит в том, чтобы нарисовать содержимое окна в буфер вне экрана, а затем перенести этот буфер на экран одним падением sw oop (используя BitBlt ). Это довольно хороший способ уменьшить мерцание, но часто используется слишком часто, особенно программистами, которые не совсем понимают, как эффективно работать с чертежами.

Вы можете обратиться к этой ссылке для получения более подробной информации. .

Из-за проблемы с координатами:

Вот пример, который вы можете использовать:

Как я могу справиться с изменением размеров детей windows когда родительский размер windows изменяется?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...