Pokusiłem się o przykład:
nagłówki
Ot, SDL, jakiś zgrabny kontener no i oczywiście dostęp do funkcji trygonometrycznych (konkretnie przydał nam się sin
i cos
)
#include <vector>
#include <cmath>
#include "SDL.h"
#include "SDL_image.h"
#undef main
main
auto main() {
return finalize(entry(initialize(window_config {}))), 0;
}
Czyli tworzona jest konfiguracja okna, coś jest później nią inicjalizowane, gdzieśtam to trafia i na końcu jest sprzątanie.
initialize
Na podstawie window_config
tworzone jest okno i renderer. Ponieważ nie chciało mi się bawić w liczenie delty czasu, dorzuciłem vsynca, żebym nie musiał zmyślać wartości prędkości.
auto initialize(window_config const &cfg) {
SDL_Init(SDL_INIT_VIDEO);
auto window = SDL_CreateWindow(cfg.title, cfg.rect.x, cfg.rect.y, cfg.rect.w, cfg.rect.h, cfg.flags);
auto renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC);
return app_context {
window, renderer, cfg
};
}
finalize
Czyścimy po sobie, nic wielkiego.
auto finalize(app_context &&context) {
SDL_DestroyRenderer(context.renderer_ptr);
SDL_DestroyWindow(context.window_ptr);
SDL_Quit();
}
entry
czyli nasz prawdziwy main
auto entry(app_context &&context) {
application app { context };
while(!context.stop) {
context.events = handle_events();
context.stop = app.update(context);
render(context, [&] {
app.draw(context);
});
}
return std::move(context);
}
Tworzymy jakąś aplikację dając jej app_context
(o tym zaraz) i dopóki całość ma mielić:
- Zaciągamy zdarzenia (
handle_events()
) (nazwa może nie do końca trafiona, ale już nie chce mi się tego zmieniać. zaakceptuj to)
- Aktualizujemy stan apki
- rysujemy co apka chce żeby rysować
app_context
struct app_context {
SDL_Window * const window_ptr;
SDL_Renderer * const renderer_ptr;
window_config cfg;
bool stop;
events_container events;
};
Pojawiło się kilka magicznych rzeczy do wyjaśnienia:
handle_events()
, render(context, f)
no i oczywiście application
handle_events()
Wyciąga zdarzenia z wewnętrznej kolejki SDLa, żebyśmy nie musieli się z nią później już bawić.
auto handle_events() {
events_container result;
SDL_Event event;
while(SDL_PollEvent(&event)) {
result.push_back(event);
}
return result;
}
render
template<typename F>
void render(app_context &context, F f) {
SDL_RenderClear(context.renderer_ptr);
f();
SDL_RenderPresent(context.renderer_ptr);
}
Zostaje więc...
application
W perfekcyjnym świecie chciałbyś wydzielić logikę car
do jakiejś ładnej struktury, która sama zajmowałaby się swoimi rzeczami - ale ponieważ jest to szybki przykład na forum to liczę, że sam się tego podejmiesz.
struct application {
SDL_Texture * const car_image;
float car_x = 0, car_y = 0;
float car_angle = 50.f;
float car_speed = 0.f;
const float car_angle_speed = 4.f;
const float car_acceleration = 0.4f;
const float car_backward_acceleration = car_acceleration / 1.1f;
application(application const &) = delete;
auto operator=(application const &) = delete;
application(app_context const &context):
car_image(IMG_LoadTexture(context.renderer_ptr, "car.png")) {}
~application() {
SDL_DestroyTexture(car_image);
}
auto update(app_context const &context) {
auto stop = consume_events(context.events, [](auto _) {});
const Uint8 *keystate = SDL_GetKeyboardState(NULL);
if(keystate[SDL_SCANCODE_LEFT]) {
car_angle -= car_angle_speed;
}
if(keystate[SDL_SCANCODE_RIGHT]) {
car_angle += car_angle_speed;
}
if(keystate[SDL_SCANCODE_UP]) {
car_speed += car_acceleration;
}
if(keystate[SDL_SCANCODE_DOWN]) {
car_speed -= car_backward_acceleration;
}
const double pi_divided_by_180 = 0.0174532925;
car_x += float(car_speed*cos((double) car_angle*pi_divided_by_180));
car_y += float(car_speed*sin((double) car_angle*pi_divided_by_180));
return stop;
}
auto draw(app_context const &context) {
draw_texture(context.renderer_ptr, car_image, SDL_Point { (int)car_x, (int)car_y }, car_angle);
}
};
Kolejne nowe rzeczy:
consume_events
, draw_texture
consume_events
Ogarnia SDL_QUIT
, więc gdybyśmy chcieli męczyć jakieś zdarzenia, to możemy zająć się tylko tymi, na których nam zależy.
Początkowo tego używałem do odczytywania klawiszy, ale dla płynności ruchu przerzuciłem się na SDL_GetKeyboardState
template<typename F>
auto consume_events(events_container const &events, F f) {
for(auto event: events) {
if(event.type == SDL_QUIT) {
return true;
}
f(event);
}
return false;
}
draw_texture
Ta mała dziecinka dba o to, żebyśmy nie musieli dłubać przy wyciąganiu danych z tekstury, jakiś sourcach, destinationach i innych okropnych rzeczach
auto draw_texture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_Point position, float angle) {
int w, h, x = position.x, y = position.y;
SDL_QueryTexture(texture, nullptr, nullptr, &w, &h);
SDL_Rect dest = { x, y, w, h };
SDL_RenderCopyEx(renderer, texture, nullptr, &dest, angle, nullptr, SDL_FLIP_NONE);
}
Tak wygląda używane w projekcie autko:
A to przykład jazdy (nagrywane przy 20fpsach na potrzeby gifa, ofc normalnie całość jest płynna)
Gdybyś chciał pojeździć, w załączniku masz builda. Przy okazji z niego możesz się dowiedzieć, że aby ktoś mógł odpalić naszą apkę, to trzeba dorzucić wszystkie niezbędne dlle
. (+ dorzuciłem tam main.cpp
, żebyś nie musiał sklejać w.w. snippetów)
Release.zip