Не удалось нарисовать массив квадратов в SDL2 на C

Я использую SDL2 для C, чтобы рисовать разноцветные квадраты на основе массива (рельеф [что-то] [что-то]). Но когда я запускаю его, происходит то, что красный квадрат рисуется вверху слева, а затем окно перестает отвечать.

По сути, я использую пару циклов for для прохождения массива и, основываясь на значении данного элемента, каждый раз рисую квадрат разного цвета в немного другой позиции. На самом деле я не уверен, что то, что я рисую, автоматически очищается, когда я рисую следующее, поскольку я использую ту же структуру прямоугольника.

int terrain_print()
{
    if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
    {
        printf("Error initializing SDL: %s\n", SDL_GetError());
        return 0;
    }
    SDL_Window* wind = SDL_CreateWindow("Terrain Generator",
        SDL_WINDOWPOS_CENTERED,
        SDL_WINDOWPOS_CENTERED,
        WIDTH, HEIGHT, 0);
    if (!wind)
    {
        printf("Error creating window: %s\n", SDL_GetError());
        SDL_Quit();
        return 0;
    }
    Uint32 render_flags = SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC;
    SDL_Renderer* rend = SDL_CreateRenderer(wind, -1, render_flags);
    if (!rend)
    {
        printf("Error creating renderer: %s\n", SDL_GetError());
        SDL_DestroyWindow(wind);
        SDL_Quit();
        return 0;
    }
    bool running = true;
    float x_pos = 0, y_pos = 0;
    SDL_Rect rect = { (int)x_pos, (int)y_pos, PIXELSIZE, PIXELSIZE };
    SDL_Event event;
    while (running)
    {
        while (SDL_PollEvent(&event))
        {
            switch (event.type)
            {
                case SDL_QUIT:
                    running = false;
                    break;
            }
        }
        for (int i = ARRAYH - 1; i >= 0; i--)
        {
            for (int j = 0; j < ARRAYW; j++)
            {
                switch (terrain[i][j])
                {
                    case air:
                        SDL_SetRenderDrawColor(rend, 75, 163, 255, 255);
                    case grass:
                        SDL_SetRenderDrawColor(rend, 0, 181, 16, 255);
                    case dirt:
                        SDL_SetRenderDrawColor(rend, 74, 26, 0, 255);
                    case stone:
                        SDL_SetRenderDrawColor(rend, 85, 85, 85, 255);
                }
                SDL_SetRenderDrawColor(rend, 255, 0, 0, 255);
                SDL_RenderFillRect(rend, &rect);
                SDL_RenderPresent(rend);
                SDL_Delay(1000 / FPS);
                x_pos += PIXELSIZE;
            }
            y_pos += PIXELSIZE;
        }
    }
    SDL_DestroyRenderer(rend);
    SDL_DestroyWindow(wind);
    SDL_Quit();
    return 0;
}

отсутствует break; в переключателе?

pmg 11.12.2020 17:06

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

Andreas Wenzel 11.12.2020 17:13

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

Andreas Wenzel 14.12.2020 01:27
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
277
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как уже было указано кем-то в разделе комментариев, вероятно, в одном из ваших break утверждений отсутствуют switch утверждения. Однако, даже если бы у вас были эти операторы break, содержимое всего оператора switch было бы переопределено, изменив цвет обратно на красный в следующей строке:

SDL_SetRenderDrawColor(rend, 255, 0, 0, 255);

Кроме того, вместо обновления x_pos и y_pos необходимо обновить rect.x и rect.y. В противном случае вы всегда будете перезаписывать один и тот же прямоугольник.

Еще одна проблема может заключаться в том, что согласно официальной документации на SDL_RenderPresent рекомендуется вызывать SDL_RenderClear перед началом отрисовки каждого нового кадра. В документации указано, что вы не должны полагаться на содержимое предыдущего кадра, все еще существующее в резервном буфере, когда вы начинаете рисовать следующий кадр. Тем не менее, вы, кажется, полагаетесь на это. Даже если ваша программа работает на вашем компьютере без вызова SDL_RenderClear, она может перестать работать, когда вы измените конфигурацию или запустите программу на другой платформе. Я предполагаю, что это предупреждение было помещено в документацию по уважительной причине. Поэтому вы можете изменить свой алгоритм, чтобы отрисовывать все в одном кадре, или, если вы хотите сохранить внешний вид нового прямоугольника, добавляемого в одном кадре, визуализировать все прямоугольники из предыдущих кадров в новом кадре. В качестве альтернативы вы можете использовать SDL_SetRenderTarget для рендеринга в SDL_Texture, а не прямо на экран, чтобы вы могли быть уверены, что содержимое никогда не потеряется.


а потом окно перестает реагировать.

Это неудивительно. Вы вызываете SDL_RenderPresent и SDL_Delay один раз для каждого элемента terrain, то есть один раз за каждую итерацию самого внутреннего цикла. Между рендерингом каждого кадра есть пауза в 1000 / FPS миллисекунд. В зависимости от размера массива terrain (который вы не показываете в опубликованном коде), общее время рендеринга всех этих кадров может составлять несколько секунд. В это время вы не обрабатываете события. Пока вы не обрабатываете события, ваше окно не будет отвечать. Вы снова начинаете обрабатывать события во внешнем цикле только после того, как весь массив terrain будет отрендерен.

Вместо SDL_Delay я рекомендую использовать SDL_AddTimer . Таким образом, функция обратного вызова будет вызываться, когда придет время рендерить следующий кадр. Между тем, вы можете обрабатывать события, и ваше окно будет реагировать. Я рекомендую, чтобы ваша функция обратного вызова не делала ничего, кроме вызова SDL_PushEvent с событием, определяемым приложением (зарегистрированным с помощью SDL_RegisterEvents ), в то время как ваш основной поток ожидает этих и других возможных событий с помощью SDL_WaitEvent.

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

Другой альтернативой может быть использование функции SDL_PollEvent, которая неявно вызывает SDL_PumpEvents и позволяет проверять наличие новых событий без блокировки.

Хорошо, так что насчет цвета, я понимаю, и было глупо спрашивать об этом. Теперь - размер массива 600 на 800. Итак, я начал делать то, о чем вы мне сказали SDL_AddTimer, и мне нужно больше подробностей об этом. Я сделал функцию SDL_TimerCallback pushEvent(){...} и в ней делаю событие и помещаю его внутрь SDL_PushEvent(...); . Дело в том, что я не знаю, каким должно быть это событие? А насчет SDL_WaitEvent , я не знаю, куда именно это поставить. Извините, что задаю эти глупые вопросы, но я очень новичок в библиотеке и до сих пор не совсем знаю, как ее использовать.

CraigsCraig 12.12.2020 08:38

@CraigsCraig: Прежде чем настраивать таймер, вы должны вызвать SDL_RegisterEvents, чтобы получить идентификатор события, который вы можете использовать. Сохраните этот идентификатор события в глобальной переменной, чтобы он был доступен как из функции обратного вызова таймера, так и из основной программы. В вашей функции обратного вызова вы должны заполнить структуру SDL_Event и передать ее SDL_PushEvent. См. SDL_UserEvent для получения дополнительной информации. В вашей основной программе все, что вам нужно сделать сейчас, это дождаться событий, используя SDL_WaitEvent в цикле, и визуализировать новый кадр, когда вы получите событие, сохраненное в глобальной переменной.

Andreas Wenzel 12.12.2020 15:16

@CraigsCraig: если вы не хотите использовать глобальную переменную, вы можете передать указатель на переменную, содержащую идентификатор события, в параметр param в вызове AddTimer. Таким образом, ваша функция обратного вызова получит этот указатель в качестве одного из своих параметров. Однако использование глобальной переменной проще (но считается плохой практикой программирования).

Andreas Wenzel 12.12.2020 15:24

@CraigsCraig: цикл, в котором вы вызываете SDL_WaitEvent, должен быть бесконечным циклом. Вы должны выйти из этого цикла только тогда, когда получите событие SDL_QUIT.

Andreas Wenzel 12.12.2020 15:32

@CraigsCraig: На самом деле документация немного неоднозначна. Кажется, вам не нужно вызывать SDL_RegisterEvents и можно просто использовать SDL_USEREVENT в event.type, если вам нужно только одно событие. Как правило, одного события должно быть достаточно, поскольку определяемое пользователем событие содержит несколько полей для хранения дополнительных данных.

Andreas Wenzel 12.12.2020 15:42

@CraigsCraig: Вот пример цикла событий SDL, который также обрабатывает определенные пользователем события. Однако это код C++, а не C. Если вы проигнорируете GameApp:: (который указывает пространство имен C++), то я считаю, что это допустимый код C.

Andreas Wenzel 12.12.2020 16:19

Массив довольно большой, поэтому, допустим, мне не нужен таймер, поэтому он может печатать быстрее. Теперь о SDL_WaitEvent. Я не уверен, куда поместить этот бесконечный цикл? Я попытался поместить его в самый внутренний цикл for, но это просто дало мне полностью белое окно. Он не перестает отвечать, но и не может быть закрыт через X в правом верхнем углу. Я также попытался использовать вместо этого SDL_PollEvent, и он делает то же самое, за исключением того, что в верхнем левом углу рисуется маленький серый квадрат, а остальная часть черная. Кроме того, если у меня нет таймера/задержки, сколько примерно времени потребуется, чтобы нарисовать массив квадратов 60x80?

CraigsCraig 13.12.2020 10:39

@CraigsCraig: Если вы не настроили таймер с помощью SDL_AddTimer, вам не следует использовать SDL_WaitEvent, так как эта функция будет блокироваться (переводить программу в спящий режим) до тех пор, пока вы не получите новое событие. Вместо этого вам следует использовать SDL_PollEvent, если вы хотите иметь возможность реагировать на события (например, SDL_QUIT, когда пользователь закрывает окно, или другой ввод), так как эта функция не будет блокироваться, а немедленно вернется. Если вы не заботитесь о событиях, а просто хотите, чтобы ваше окно оставалось отзывчивым, достаточно вызвать SDL_PumpEvents один раз для каждого визуализируемого кадра.

Andreas Wenzel 13.12.2020 18:14

@CraigsCraig: Поскольку у вас активен SDL_RENDERER_PRESENTVSYNC, вы, вероятно, автоматически будете ограничены 60 кадрами в секунду (в зависимости от частоты обновления вашего монитора). Вызов SDL_RenderPresent будет заблокирован до тех пор, пока не наступит время рендеринга следующего кадра. Тем не менее, вы можете перепроверить это, потому что некоторые драйверы дисплея позволяют вам переопределять настройки VSYNC, и я не уверен, как ваша программа поведет себя в этом случае. Если этого механизма для задержки рендеринга достаточно, то вам может не понадобиться вставлять дополнительную задержку в вашу программу.

Andreas Wenzel 13.12.2020 18:20

@CraigsCraig: «Кроме того, если у меня нет таймера/задержки, сколько примерно времени потребуется, чтобы нарисовать массив квадратов 60x80?» -- Если у вас активен SDL_RENDERER_PRESENTVSYNC и у вас монитор с частотой обновления 60 Гц (кадров в секунду), и вы рисуете по 1 квадрату за кадр, то вам потребуется 80 секунд, чтобы нарисовать все 4800 (60*80) квадратов. Фактическое время рендеринга квадрата в этом случае не будет иметь значения, потому что только VSYNC будет замедлять работу вашей программы.

Andreas Wenzel 13.12.2020 18:29

Итак, что я сделал сейчас, так это удалил SDL_RENDERER_PRESENTVSYNC, сохранив SDL_PollEvent() в начале while(running). Я также добавил SDL_PumpEvents(); в начале самого внутреннего for цикла. И что происходит, так это серый квадрат, нарисованный вверху слева, а все остальное черное. Так что, похоже, он не рисует все 4800 (синих) квадратов, которые ему нужны (по крайней мере, не в первые ~ 10 секунд, которые я ждал). Однако окно было отзывчивым, и когда я нажимаю X, оно закрывается через секунду. Есть идеи, почему он не рисует все квадраты? Текущая функция hastebin.com/piyabivako.yaml

CraigsCraig 13.12.2020 19:02

Вместо обновления x_pos и y_pos необходимо обновить rect.x и rect.y. Я обновил свой ответ соответственно.

Andreas Wenzel 13.12.2020 19:17

В своем ответе я сказал вам, что в вашем break утверждении отсутствуют switch утверждения. Похоже, вы до сих пор не исправили это. Это означает, что, например, на case air будут выполнены все четыре SDL_SetRenderDrawColor строки. Поскольку последний предназначен для установки цвета камня, вероятно, поэтому цвет всегда серый. Чтобы это исправить, вы должны добавить оператор break для каждого ярлыка case.

Andreas Wenzel 13.12.2020 20:13

@CraigsCraig: «Так что, похоже, он не рисует все 4800 (синих) квадратов, которые ему нужны (по крайней мере, не в первые ~ 10 секунд, которые я ждал)». -- Я предполагаю, что он рисует все 4800 квадратов, однако эти 4800 квадратов (1) все серые из-за отсутствия break в выражении switch и (2) все записаны в одно и то же место на экране из-за rect не обновляется.

Andreas Wenzel 13.12.2020 20:46

Даааааааааааа!!! Работает! Большое вам спасибо, если честно, спасибо, что потратили все это время на мои нубские вопросы, спасибо.

CraigsCraig 13.12.2020 22:17

@CraigsCraig: Приятно слышать. Кстати, вы намеренно пишете только один прямоугольник на кадр? Обычно вы хотите написать все прямоугольники в одном кадре и вызвать SDL_RenderPresent только один раз после того, как закончите, вместо того, чтобы вызывать функцию в цикле.

Andreas Wenzel 13.12.2020 23:07

@CraigsCraig: я только что заметил, что вы, кажется, полагаетесь на то, что предыдущий кадр все еще находится в заднем буфере, когда вы рисуете новый кадр. Однако, согласно официальной документации по функции SDL_RenderPresent, полагаться на это не стоит. Поэтому я добавил к моему ответу новый абзац, посвященный этому вопросу.

Andreas Wenzel 13.12.2020 23:29

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

CraigsCraig 14.12.2020 07:16

@CraigsCraig: если вы хотите отображать один дополнительный квадрат на кадр и хотите вызывать SDL_RenderClear перед каждым кадром, как это рекомендуется в документации, вам придется ввести две дополнительные переменные int new_square_x, new_square_y;, которые отслеживают индексы (не координаты экрана) появится новый квадрат. Затем вы можете визуализировать все квадраты во вложенном цикле, но после визуализации каждого квадрата вы должны проверить, достигли ли вы этих индексов, и выйти из обоих вложенных циклов, если это так.

Andreas Wenzel 14.12.2020 15:20

Другие вопросы по теме