От переводчика: продолжение серии туториалов Twinklebear, в оригинале доступных тут. Предыдущий урок можно найти здесь.
Сегодня мы начнем рефакторить код с прошлого урока, дописав несколько очень полезных функций, а также разберем то, как изображения позиционируются и масштабируются в SDL_Window.
Как обычно, мы начнем программу со включения SDL. Также в данном уроке мы будем использовать класс string, поэтому подключим и его.
#include "SDL.h"
#include <string>
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
SDL_Window *window = nullptr;
SDL_Renderer *renderer = nullptr;
Также мы объявим пару константных значений для ширины и высоты экрана, вместе с глобальными объявлениями окна и рендерера, чтобы они были доступны из наших функций. Снова мы инициализируем указатели как nullptr для безопасности. Если вы не используете C++11, инициализируйте их как NULL.
Примечание: вы должны стараться избегать использования не константных глобальных значений, или глобальных значений в общем, то есть вы никогда не должны объявлять глобальный SDL_Window или SDL_Renderer. Однако, для этого простого урока мы все же воспользуемся ими. Мы рассмотрим применение глобальных объектов через несколько уроков.
Помните, как мы в первом уроке загружали текстуру? Не так плохо загружать в main’е одну текстуру, но представьте, если бы их было пару сотен? Нам пришлось бы печатать один и тот же кусок кода каждый раз! Намного лучше объявить функцию для загрузки текстур по имени файла:
SDL_Texture* LoadImage(std::string file){
SDL_Surface *loadedImage = nullptr;
SDL_Texture *texture = nullptr;
loadedImage = SDL_LoadBMP(file.c_str());
if (loadedImage != nullptr){
texture = SDL_CreateTextureFromSurface(renderer, loadedImage);
SDL_FreeSurface(loadedImage);
}
else
std::cout << SDL_GetError() << std::endl;
return texture;
}
Эта функция должна выглядеть очень знакомо, ведь этот тот же самый код, что мы писали в первом уроке, но теперь он объединен в хорошую функцию. С ее помощью мы можем передать имя файла как строчку и получить указатель на загруженную текстуру. Заметьте, что указатель будет nullptr, если загрузка не удастся.
Далее мы хотим написать функцию, чтобы упростить рисование. Также эта функция позволит указывать позицию изображения на экране. Она будет принимать x и y координаты, указатели на текстуру и рендерер и рисовать текстуру в указанной точке.
void ApplySurface(int x, int y, SDL_Texture *tex, SDL_Renderer *rend){
SDL_Rect pos;
pos.x = x;
pos.y = y;
SDL_QueryTexture(tex, NULL, NULL, &pos.w, &pos.h);
SDL_RenderCopy(rend, tex, NULL, &pos);
}
Чтобы указать, где будет рисоваться текстура, нам необходимо создать SDL_Rect, адрес которого мы передадим SDL_RenderCopy в качестве параметра целевого прямоугольника.
Для того, чтобы создать наш прямоугольник, мы возьмем x и y, которые были переданы, и присвоим соответствующим значениям прямоугольника их значения. Однако, нам также необходимо указать ширину и высоту, ведь SDL 2.0 позволяет нам масштабировать текстуры. Попробуйте поиграться со значениями ширины и высоты и посмотрите, что получится!
Сейчас же мы просто передадим ширину и высоту текстуры, чтобы нарисовать ее в масштабе 1:1. Эти значения мы можем получить, используя функцию SDL_QueryTexture. Она принимает указатель на текстуру, следующие два параметра укажем как NULL (эти параметры отвечают за формат и уровень доступа, которые нам не нужны). И наконец, мы должны передать адреса переменных, которые функция заполнит шириной и высотой текстуры.
Теперь, когда мы получили инициализированный SDL_Rect, его можно передать в SDL_RenderCopy, чтобы текстура была нарисована в нужной нам точке с оригинальными высотой и шириной. Оставшийся NULL параметр, переданный этой функции отвечает за вырезание фрагмента исходной текстуры. Его использование мы разберем позднее.
Давайте же увидим наши функции в действии. Для начала, запустим SDL и создадим окно и рендерер, как в прошлый раз. Но у нас появилось тут кое-что новенькое, а именно SDL_WINDOWPOS_CENTERED. Эта опция позволяет при создании окна отцентровать его по определенной оси, в данном случае мы используем отцентруем окно как по x, так и по y.
int main(int argc, char** argv){
if (SDL_Init(SDL_INIT_EVERYTHING) == -1){
std::cout << SDL_GetError() << std::endl;
return 1;
}
window = SDL_CreateWindow("Lesson 2", SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
if (window == nullptr){
std::cout << SDL_GetError() << std::endl;
return 2;
}
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED
| SDL_RENDERER_PRESENTVSYNC);
if (renderer == nullptr){
std::cout << SDL_GetError() << std::endl;
return 3;
}
Давайте же загрузим наши картинки. Для этого урока мы нарисуем фоновое изображение, замощенное плиткой, и второе изображение по центру поверх фона.
Вот наш фон:
А это будет поверх фона:
Давайте загрузим их с помощью LoadImage функции, которую мы только что написали.
SDL_Texture *background = nullptr, *image = nullptr;
background = LoadImage("Lesson2res/background.bmp");
image = LoadImage("Lesson2res/image.bmp");
if (background == nullptr || image == nullptr)
return 4;
Заметьте, что вам необходимо поменять пути к изображениям таким образом, чтобы они соответствовали путям на вашей системе.
Перед тем, как мы нарисуем картинки, нам необходимо знать, где мы хотим их расположить. Для начала, необходимо понять, как работает система координат SDL, так она она немного отличается от стандартной 2D Декартовой системы координат. Система координат SDL выглядит так:
Точка (0,0) расположена в левом верхнем углу экрана. Значения по Y увеличиваются при движении вниз экрана, а по X — вправо. Еще следует заметить, что точка, которую мы передаем при рисовании изображения, указывает на левый верхний угол изображения, в отличие от центра, как в некоторых других библиотеках.
Перед тем, как мы будем рисовать: запомните, что то, что мы рисуем, будет располагаться под последующими изображениями. Таким образом, картинка, нарисованная в первую очередь будет под всеми, а в последнюю — над всеми.
Наверное, вы уже заметили, что наше фоновое изображение имеет разрешение 320х240, а значит мы должны нарисовать его четыре раза, чтобы замостить весь экран.
Перед тем, как что-либо рисовать, мы очистим экран, далее мы будем позиционировать элементы. Для того, чтобы получить ширину и высоту текстуры, мы снова воспользуемся функцией QueryTexture. Конечно, можно использовать для рисования фона цикл, но в данном случае воспользуемся тупым методом и просто напишем один и тот же текст 4 раза.
SDL_RenderClear(renderer);
int bW, bH;
SDL_QueryTexture(background, NULL, NULL, &bW, &bH);
ApplySurface(0, 0, background, renderer);
ApplySurface(bW, 0, background, renderer);
ApplySurface(0, bH, background, renderer);
ApplySurface(bW, bH, background, renderer);
Теперь мы нарисуем изображение, которое будет располагаться поверх фона и по центру экрана. Рассчитать центральную точку предельно просто, ведь точка, которую мы передаем в функцию ApplySurface — это координата левого верхнего угла изображения. Поэтому нам необходимо добавить смещение, чтобы центр изображения совпал с центром экрана.
int iW, iH;
SDL_QueryTexture(image, NULL, NULL, &iW, &iH);
int x = SCREEN_WIDTH / 2 - iW / 2;
int y = SCREEN_HEIGHT / 2 - iH / 2;
ApplySurface(x, y, image, renderer);
Для того, чтобы увидеть нарисованное, нам необходимо показать рендерер, а затем подождать несколько секунд, чтобы успеть увидеть результат.
SDL_RenderPresent(renderer);
SDL_Delay(2000);
И наконец, мы завершим программу освобождением занятой памяти, выйдем из SDL и вернем 0.
SDL_DestroyTexture(background);
SDL_DestroyTexture(image);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
Когда вы скомпилируете и запустите программу, то окно должно выглядеть примерно так:
Конец урока
Вот и подошел к своему завершению сегодняшний урок. Всем до встречи в уроке 3: внешние библиотеки SDL.