Я использую 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;
}
Как правило, другим людям будет легче помочь вам, если вы предоставите минимально воспроизводимый пример.
В качестве примечания: я не рекомендую использовать SDL_INIT_EVERYTHING
, потому что это увеличит время загрузки вашей программы. Как правило, вы должны инициализировать только те компоненты, которые вам нужны. Однако для отладки его использование может быть полезно, чтобы убедиться, что ваша проблема не связана с тем, что какой-то компонент не инициализирован.
Как уже было указано кем-то в разделе комментариев, вероятно, в одном из ваших 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_PumpEvents между кадрами должно быть достаточно, чтобы окно оставалось отзывчивым. Но решение, упомянутое в предыдущем абзаце, чище, если вы хотите иметь возможность реагировать на события.
Другой альтернативой может быть использование функции SDL_PollEvent, которая неявно вызывает SDL_PumpEvents
и позволяет проверять наличие новых событий без блокировки.
Хорошо, так что насчет цвета, я понимаю, и было глупо спрашивать об этом. Теперь - размер массива 600 на 800. Итак, я начал делать то, о чем вы мне сказали SDL_AddTimer
, и мне нужно больше подробностей об этом. Я сделал функцию SDL_TimerCallback pushEvent(){...}
и в ней делаю событие и помещаю его внутрь SDL_PushEvent(...);
. Дело в том, что я не знаю, каким должно быть это событие? А насчет SDL_WaitEvent
, я не знаю, куда именно это поставить. Извините, что задаю эти глупые вопросы, но я очень новичок в библиотеке и до сих пор не совсем знаю, как ее использовать.
@CraigsCraig: Прежде чем настраивать таймер, вы должны вызвать SDL_RegisterEvents
, чтобы получить идентификатор события, который вы можете использовать. Сохраните этот идентификатор события в глобальной переменной, чтобы он был доступен как из функции обратного вызова таймера, так и из основной программы. В вашей функции обратного вызова вы должны заполнить структуру SDL_Event
и передать ее SDL_PushEvent
. См. SDL_UserEvent для получения дополнительной информации. В вашей основной программе все, что вам нужно сделать сейчас, это дождаться событий, используя SDL_WaitEvent
в цикле, и визуализировать новый кадр, когда вы получите событие, сохраненное в глобальной переменной.
@CraigsCraig: если вы не хотите использовать глобальную переменную, вы можете передать указатель на переменную, содержащую идентификатор события, в параметр param
в вызове AddTimer
. Таким образом, ваша функция обратного вызова получит этот указатель в качестве одного из своих параметров. Однако использование глобальной переменной проще (но считается плохой практикой программирования).
@CraigsCraig: цикл, в котором вы вызываете SDL_WaitEvent
, должен быть бесконечным циклом. Вы должны выйти из этого цикла только тогда, когда получите событие SDL_QUIT
.
@CraigsCraig: На самом деле документация немного неоднозначна. Кажется, вам не нужно вызывать SDL_RegisterEvents
и можно просто использовать SDL_USEREVENT
в event.type
, если вам нужно только одно событие. Как правило, одного события должно быть достаточно, поскольку определяемое пользователем событие содержит несколько полей для хранения дополнительных данных.
@CraigsCraig: Вот пример цикла событий SDL, который также обрабатывает определенные пользователем события. Однако это код C++, а не C. Если вы проигнорируете GameApp::
(который указывает пространство имен C++), то я считаю, что это допустимый код C.
Массив довольно большой, поэтому, допустим, мне не нужен таймер, поэтому он может печатать быстрее. Теперь о SDL_WaitEvent
. Я не уверен, куда поместить этот бесконечный цикл? Я попытался поместить его в самый внутренний цикл for, но это просто дало мне полностью белое окно. Он не перестает отвечать, но и не может быть закрыт через X в правом верхнем углу. Я также попытался использовать вместо этого SDL_PollEvent
, и он делает то же самое, за исключением того, что в верхнем левом углу рисуется маленький серый квадрат, а остальная часть черная. Кроме того, если у меня нет таймера/задержки, сколько примерно времени потребуется, чтобы нарисовать массив квадратов 60x80?
@CraigsCraig: Если вы не настроили таймер с помощью SDL_AddTimer
, вам не следует использовать SDL_WaitEvent
, так как эта функция будет блокироваться (переводить программу в спящий режим) до тех пор, пока вы не получите новое событие. Вместо этого вам следует использовать SDL_PollEvent
, если вы хотите иметь возможность реагировать на события (например, SDL_QUIT
, когда пользователь закрывает окно, или другой ввод), так как эта функция не будет блокироваться, а немедленно вернется. Если вы не заботитесь о событиях, а просто хотите, чтобы ваше окно оставалось отзывчивым, достаточно вызвать SDL_PumpEvents
один раз для каждого визуализируемого кадра.
@CraigsCraig: Поскольку у вас активен SDL_RENDERER_PRESENTVSYNC
, вы, вероятно, автоматически будете ограничены 60 кадрами в секунду (в зависимости от частоты обновления вашего монитора). Вызов SDL_RenderPresent
будет заблокирован до тех пор, пока не наступит время рендеринга следующего кадра. Тем не менее, вы можете перепроверить это, потому что некоторые драйверы дисплея позволяют вам переопределять настройки VSYNC, и я не уверен, как ваша программа поведет себя в этом случае. Если этого механизма для задержки рендеринга достаточно, то вам может не понадобиться вставлять дополнительную задержку в вашу программу.
@CraigsCraig: «Кроме того, если у меня нет таймера/задержки, сколько примерно времени потребуется, чтобы нарисовать массив квадратов 60x80?» -- Если у вас активен SDL_RENDERER_PRESENTVSYNC
и у вас монитор с частотой обновления 60 Гц (кадров в секунду), и вы рисуете по 1 квадрату за кадр, то вам потребуется 80 секунд, чтобы нарисовать все 4800 (60*80) квадратов. Фактическое время рендеринга квадрата в этом случае не будет иметь значения, потому что только VSYNC будет замедлять работу вашей программы.
Итак, что я сделал сейчас, так это удалил SDL_RENDERER_PRESENTVSYNC
, сохранив SDL_PollEvent()
в начале while(running)
. Я также добавил SDL_PumpEvents();
в начале самого внутреннего for
цикла. И что происходит, так это серый квадрат, нарисованный вверху слева, а все остальное черное. Так что, похоже, он не рисует все 4800 (синих) квадратов, которые ему нужны (по крайней мере, не в первые ~ 10 секунд, которые я ждал). Однако окно было отзывчивым, и когда я нажимаю X, оно закрывается через секунду. Есть идеи, почему он не рисует все квадраты? Текущая функция hastebin.com/piyabivako.yaml
Вместо обновления x_pos
и y_pos
необходимо обновить rect.x
и rect.y
. Я обновил свой ответ соответственно.
В своем ответе я сказал вам, что в вашем break
утверждении отсутствуют switch
утверждения. Похоже, вы до сих пор не исправили это. Это означает, что, например, на case air
будут выполнены все четыре SDL_SetRenderDrawColor
строки. Поскольку последний предназначен для установки цвета камня, вероятно, поэтому цвет всегда серый. Чтобы это исправить, вы должны добавить оператор break
для каждого ярлыка case
.
@CraigsCraig: «Так что, похоже, он не рисует все 4800 (синих) квадратов, которые ему нужны (по крайней мере, не в первые ~ 10 секунд, которые я ждал)». -- Я предполагаю, что он рисует все 4800 квадратов, однако эти 4800 квадратов (1) все серые из-за отсутствия break
в выражении switch
и (2) все записаны в одно и то же место на экране из-за rect
не обновляется.
Даааааааааааа!!! Работает! Большое вам спасибо, если честно, спасибо, что потратили все это время на мои нубские вопросы, спасибо.
@CraigsCraig: Приятно слышать. Кстати, вы намеренно пишете только один прямоугольник на кадр? Обычно вы хотите написать все прямоугольники в одном кадре и вызвать SDL_RenderPresent
только один раз после того, как закончите, вместо того, чтобы вызывать функцию в цикле.
@CraigsCraig: я только что заметил, что вы, кажется, полагаетесь на то, что предыдущий кадр все еще находится в заднем буфере, когда вы рисуете новый кадр. Однако, согласно официальной документации по функции SDL_RenderPresent
, полагаться на это не стоит. Поэтому я добавил к моему ответу новый абзац, посвященный этому вопросу.
МММ ясно. Этот код будет использоваться только на моем собственном компьютере и компьютере друга, но я все же готов исправить это на всякий случай. Мне на самом деле нравится, как он в настоящее время отображается квадрат за квадратом. Итак, что мне сделать, чтобы отобразить предыдущие квадраты в новом кадре, не полагаясь на буфер?
@CraigsCraig: если вы хотите отображать один дополнительный квадрат на кадр и хотите вызывать SDL_RenderClear
перед каждым кадром, как это рекомендуется в документации, вам придется ввести две дополнительные переменные int new_square_x, new_square_y;
, которые отслеживают индексы (не координаты экрана) появится новый квадрат. Затем вы можете визуализировать все квадраты во вложенном цикле, но после визуализации каждого квадрата вы должны проверить, достигли ли вы этих индексов, и выйти из обоих вложенных циклов, если это так.
отсутствует
break;
в переключателе?