124 SpaceRoxWindow(
const std::string &path,
int wx,
int wy,
bool full,
bool enable_vsync)
129 srand(
static_cast<unsigned>(time(
nullptr)));
132 if (gamepad !=
nullptr) {
133 SDL_CloseGamepad(gamepad);
139 initializeResources();
140 if (!resources_initialized || pixel ==
nullptr || starSprite ==
nullptr) {
146 Uint32 currentTime = SDL_GetTicks();
147 Uint32 frameTime = currentTime - lastFrameTime;
148 if (frameTime < targetFrameTime) {
149 SDL_Delay(targetFrameTime - frameTime);
150 currentTime = SDL_GetTicks();
151 frameTime = currentTime - lastFrameTime;
154 if (lastFrameTime > 0) {
155 float deltaTime = frameTime / 1000.0f;
156 globalTime += deltaTime;
158 lastFrameTime = currentTime;
164 const bool *keys = SDL_GetKeyboardState(
nullptr);
177 if ((keys[SDL_SCANCODE_SPACE] || joyFire) && canFire()) {
178 fireProjectile(ship.x, ship.y, ship.angle);
181 updateFireTimer(keys);
185 checkAsteroidCollisions();
186 checkShipAsteroidCollision();
187 checkProjectileAsteroidCollisions();
188 checkAndSpawnAsteroids();
189 handleDeathSequence();
203 case SDL_EVENT_KEY_DOWN:
204 if (e.key.key == SDLK_ESCAPE) {
211 if (e.key.key == SDLK_LEFT)
213 if (e.key.key == SDLK_RIGHT)
215 if (e.key.key == SDLK_UP)
219 case SDL_EVENT_KEY_UP:
221 if (e.key.key == SDLK_LEFT)
223 if (e.key.key == SDLK_RIGHT)
225 if (e.key.key == SDLK_UP)
229 case SDL_EVENT_GAMEPAD_ADDED:
230 openGamepad(e.gdevice.which);
234 case SDL_EVENT_GAMEPAD_REMOVED:
235 if (gamepad !=
nullptr && e.gdevice.which == gamepadId) {
236 SDL_CloseGamepad(gamepad);
239 joyLeft = joyRight = joyThrust = joyFire =
false;
245 case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
248 case SDL_EVENT_GAMEPAD_BUTTON_UP:
257 if (button == SDL_GAMEPAD_BUTTON_SOUTH || button == SDL_GAMEPAD_BUTTON_START) {
262 if (button == SDL_GAMEPAD_BUTTON_SOUTH) {
264 }
else if (button == SDL_GAMEPAD_BUTTON_EAST || button == SDL_GAMEPAD_BUTTON_BACK) {
266 }
else if (button == SDL_GAMEPAD_BUTTON_START) {
276 if (button == SDL_GAMEPAD_BUTTON_SOUTH) {
283 if (gamepad ==
nullptr) {
284 joyLeft = joyRight = joyThrust = joyFire =
false;
289 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_START)) {
295 joyLeft = joyRight = joyThrust = joyFire =
false;
299 Sint16 rawLX = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX);
300 Sint16 rawLY = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY);
302 if (abs(rawLX) < JOY_RECENTER_ZONE && abs(rawLY) < JOY_RECENTER_ZONE) {
303 if (joyCenterStable < JOY_RECENTER_FRAMES) {
306 if (joyCenterStable >= JOY_RECENTER_FRAMES) {
314 int lx =
static_cast<int>(rawLX) -
static_cast<int>(joyRestLX);
315 int ly =
static_cast<int>(rawLY) -
static_cast<int>(joyRestLY);
316 joyLeft = (lx < -JOY_ROTATE_ZONE);
317 joyRight = (lx > JOY_ROTATE_ZONE);
318 joyThrust = (ly < -JOY_THRUST_ZONE);
319 Sint16 rt = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER);
320 joyFire = (rt > JOY_THRUST_ZONE) || SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH);
324 std::string current_path;
325 bool resources_initialized =
false;
332 Uint32 lastFrameTime = 0;
333 static constexpr Uint32 targetFrameTime = 1000 / 60;
334 float globalTime = 0.0f;
341 bool starsInit =
false;
344 bool keyLeft =
false, keyRight =
false, keyThrust =
false;
345 bool keyFire =
false;
346 bool joyLeft =
false, joyRight =
false, joyThrust =
false;
347 bool joyFire =
false;
348 bool wasExploding =
false;
350 SDL_Gamepad *gamepad =
nullptr;
351 SDL_JoystickID gamepadId = 0;
352 static constexpr Sint16 JOY_ROTATE_ZONE = 16000;
353 static constexpr Sint16 JOY_THRUST_ZONE = 12000;
354 static constexpr Sint16 JOY_RECENTER_ZONE = 8000;
355 static constexpr int JOY_RECENTER_FRAMES = 12;
356 Sint16 joyRestLX = 0, joyRestLY = 0;
357 int joyCenterStable = 0;
359 Uint32 countdownTimer = 0;
360 Uint32 countdownDuration = 1000;
361 Uint32 countdownStartMs = 0;
362 int countdownNumber = 3;
363 Uint32 launchTimer = 0;
364 Uint32 launchDuration = 1000;
365 Uint32 launchStartMs = 0;
366 float shipLaunchY = 0;
368 int lastFontSize = 0;
370 float sx()
const {
return static_cast<float>(w) /
GAME_W; }
371 float sy()
const {
return static_cast<float>(h) /
GAME_H; }
373 int toScreenX(
float gx)
const {
return static_cast<int>(gx * sx()); }
374 int toScreenY(
float gy)
const {
return static_cast<int>(gy * sy()); }
375 int toScreenW(
float gw)
const {
return std::max(1,
static_cast<int>(gw * sx())); }
376 int toScreenH(
float gh)
const {
return std::max(1,
static_cast<int>(gh * sy())); }
378 void refreshWindowSize() {
380 SDL_GetWindowSizeInPixels(
window.get(), &w, &h);
384 bool openGamepad(SDL_JoystickID
id) {
385 if (gamepad !=
nullptr && gamepadId ==
id) {
388 if (gamepad !=
nullptr) {
389 SDL_CloseGamepad(gamepad);
393 gamepad = SDL_OpenGamepad(
id);
394 if (gamepad ==
nullptr) {
401 void tryOpenFirstGamepad() {
402 if (gamepad !=
nullptr) {
406 SDL_JoystickID *ids = SDL_GetGamepads(&count);
407 if (ids ==
nullptr || count <= 0) {
408 if (ids !=
nullptr) {
417 void initializeResources() {
418 if (resources_initialized) {
422 tryOpenFirstGamepad();
424 std::array<std::uint8_t, 4 * 4 * 4> starPixels{};
425 for (
int y = 0; y < 4; y++) {
426 for (
int x = 0; x < 4; x++) {
427 float dx = (x - 1.5f) / 1.5f;
428 float dy = (y - 1.5f) / 1.5f;
429 float dist = sqrtf(dx * dx + dy * dy);
430 std::uint8_t alpha = (dist < 1.0f) ?
static_cast<std::uint8_t
>(255.0f * (1.0f - dist)) : 0;
431 const int idx = (y * 4 + x) * 4;
432 starPixels[idx + 0] = 255;
433 starPixels[idx + 1] = 255;
434 starPixels[idx + 2] = 255;
435 starPixels[idx + 3] = alpha;
442 current_path +
"/data/sprite_vert.spv",
443 current_path +
"/data/sprite_frag.spv");
444 starSprite->updateTexture(starPixels.data(), 4, 4, 4 * 4);
446 const std::array<std::uint8_t, 2 * 2 * 4> whitePixels{
468 current_path +
"/data/sprite_vert.spv",
469 current_path +
"/data/solid_frag.spv");
470 pixel->updateTexture(whitePixels.data(), 2, 2, 2 * 4);
473 resources_initialized =
true;
476 void setColor(
float r,
float g,
float b,
float a = 1.0f) {
477 pixel->setShaderParams(r, g, b, a);
480 void drawPixel(
float gx,
float gy) {
481 int psz = std::max(1,
static_cast<int>(2.0f * std::min(sx(), sy())));
482 pixel->drawSpriteRect(toScreenX(gx), toScreenY(gy), psz, psz);
485 void drawRect(
float gx,
float gy,
float gw,
float gh) {
486 pixel->drawSpriteRect(toScreenX(gx), toScreenY(gy), toScreenW(gw), toScreenH(gh));
489 void drawLine(
float x0f,
float y0f,
float x1f,
float y1f) {
490 int x0 = toScreenX(x0f), y0 = toScreenY(y0f);
491 int x1 = toScreenX(x1f), y1 = toScreenY(y1f);
492 int psz = std::max(1,
static_cast<int>(1.5f * std::min(sx(), sy())));
494 pixel->drawSpriteRect(px, py, psz, psz);
498 void drawPoint(
int screenX,
int screenY,
int size = 2) {
499 pixel->drawSpriteRect(screenX, screenY, size, size);
502 void updateFontSize() {
503 int fontSize =
static_cast<int>(20.0f * (
static_cast<float>(h) / 480.0f));
504 fontSize = std::max(14, std::min(128, fontSize));
505 if (fontSize != lastFontSize) {
506 lastFontSize = fontSize;
507 setFont(current_path +
"/data/font.ttf", fontSize);
520 countdownStartMs = SDL_GetTicks();
522 wasExploding =
false;
532 for (
int i = 0; i <
STAR_COUNT; ++i, ++starIndex) {
533 stars[starIndex].x =
static_cast<float>(rand() %
GAME_W);
534 stars[starIndex].y =
static_cast<float>(rand() %
GAME_H);
535 stars[starIndex].speed = 0.15f + (rand() % 40) / 100.0f;
536 stars[starIndex].size = 0.8f + (rand() % 12) / 20.0f;
537 stars[starIndex].brightness = 0.25f + (rand() % 45) / 100.0f;
538 stars[starIndex].layer = 0;
539 assignStarColor(stars[starIndex]);
540 stars[starIndex].twinklePhase = (rand() % 628) / 100.0f;
541 stars[starIndex].twinkleSpeed = 0.4f + (rand() % 150) / 100.0f;
544 for (
int i = 0; i <
STAR_COUNT; ++i, ++starIndex) {
545 stars[starIndex].x =
static_cast<float>(rand() %
GAME_W);
546 stars[starIndex].y =
static_cast<float>(rand() %
GAME_H);
547 stars[starIndex].speed = 0.4f + (rand() % 80) / 100.0f;
548 stars[starIndex].size = 1.2f + (rand() % 18) / 20.0f;
549 stars[starIndex].brightness = 0.45f + (rand() % 55) / 100.0f;
550 stars[starIndex].layer = 1;
551 assignStarColor(stars[starIndex]);
552 stars[starIndex].twinklePhase = (rand() % 628) / 100.0f;
553 stars[starIndex].twinkleSpeed = 0.8f + (rand() % 250) / 100.0f;
556 for (
int i = 0; i <
STAR_COUNT; ++i, ++starIndex) {
557 stars[starIndex].x =
static_cast<float>(rand() %
GAME_W);
558 stars[starIndex].y =
static_cast<float>(rand() %
GAME_H);
559 stars[starIndex].speed = 0.8f + (rand() % 180) / 100.0f;
560 stars[starIndex].size = 1.8f + (rand() % 25) / 20.0f;
561 stars[starIndex].brightness = 0.65f + (rand() % 35) / 100.0f;
562 stars[starIndex].layer = 2;
563 assignStarColor(stars[starIndex]);
564 stars[starIndex].twinklePhase = (rand() % 628) / 100.0f;
565 stars[starIndex].twinkleSpeed = 1.2f + (rand() % 350) / 100.0f;
570 void assignStarColor(Star &s) {
571 float roll = (rand() % 1000) / 1000.0f;
574 s.
r = 0.85f + (rand() % 15) / 100.0f;
575 s.
g = 0.90f + (rand() % 10) / 100.0f;
577 }
else if (roll < 0.55f) {
579 s.
r = 0.5f + (rand() % 20) / 100.0f;
580 s.
g = 0.75f + (rand() % 15) / 100.0f;
584 }
else if (roll < 0.70f) {
587 s.
g = 0.95f + (rand() % 5) / 100.0f;
588 s.
b = 0.6f + (rand() % 30) / 100.0f;
590 }
else if (roll < 0.82f) {
593 s.
g = 0.6f + (rand() % 20) / 100.0f;
594 s.
b = 0.2f + (rand() % 20) / 100.0f;
596 }
else if (roll < 0.90f) {
599 s.
g = 0.3f + (rand() % 20) / 100.0f;
600 s.
b = 0.2f + (rand() % 15) / 100.0f;
602 }
else if (roll < 0.96f) {
626 ship.fire_cooldown = 0;
627 ship.burst_count = 0;
628 ship.exploding =
false;
629 ship.explosion_timer = 0;
631 ship.continuous_fire_timer = 0;
632 ship.overheated =
false;
633 ship.overheat_cooldown = 0;
634 for (
auto &p : particles)
638 void initProjectiles() {
639 for (
auto &p : projectiles)
643 void initAsteroids() {
644 for (
auto &a : asteroids)
646 for (
int i = 0; i < 4; ++i) {
649 x =
static_cast<float>(rand() %
GAME_W);
650 y =
static_cast<float>(rand() %
GAME_H);
651 }
while (hypotf(x - ship.x, y - ship.y) < 100);
652 float vx = ((rand() % 100) - 50) / 50.0f;
653 float vy = ((rand() % 100) - 50) / 50.0f;
654 float radius = 20.0f + (rand() % 20);
655 spawnAsteroid(x, y, vx, vy, radius);
662 if (keyLeft || joyLeft)
664 if (keyRight || joyRight)
666 if (keyThrust || joyThrust) {
668 ship.vx += sinf(ship.angle) * thrust;
669 ship.vy += -cosf(ship.angle) * thrust;
670 float spd = sqrtf(ship.vx * ship.vx + ship.vy * ship.vy);
673 ship.vx = (ship.vx / spd) * maxSpd;
674 ship.vy = (ship.vy / spd) * maxSpd;
690 if (ship.fire_cooldown > 0)
691 ship.fire_cooldown--;
694 void updateFireTimer(
const bool *keys) {
695 bool firing = keys[SDL_SCANCODE_SPACE] || joyFire;
696 if (firing && !ship.overheated) {
697 ship.continuous_fire_timer++;
698 ship.overheat_cooldown = 0;
699 if (ship.continuous_fire_timer >= 180) {
700 ship.overheated =
true;
701 ship.overheat_cooldown = 0;
702 ship.continuous_fire_timer = 0;
703 ship.burst_count = 0;
705 }
else if (firing && ship.overheated) {
706 ship.overheat_cooldown = 0;
708 if (ship.overheated) {
709 ship.overheat_cooldown++;
710 if (ship.overheat_cooldown >= 180) {
711 ship.overheated =
false;
712 ship.overheat_cooldown = 0;
713 ship.continuous_fire_timer = 0;
716 if (ship.continuous_fire_timer > 0)
717 ship.continuous_fire_timer--;
725 if (ship.fire_cooldown <= 0) {
732 ship.burst_count = 0;
739 void fireProjectile(
float x,
float y,
float angle) {
740 for (
auto &p : projectiles) {
742 float c = cosf(angle), s = sinf(angle);
743 float lx = 0, ly = -12;
744 p.x = x + (lx * c - ly * s);
745 p.y = y + (lx * s + ly * c);
755 void updateProjectiles() {
756 for (
auto &p : projectiles) {
761 if (p.x < 0 || p.x >=
GAME_W || p.y < 0 || p.y >=
GAME_H) {
771 void spawnAsteroid(
float x,
float y,
float vx,
float vy,
float radius) {
772 for (
auto &a : asteroids) {
780 a.rotation_angle = 0;
781 a.rotation_speed = ((rand() % 100) - 50) / 1000.0f;
784 float vr = radius * (0.7f + (rand() % 30) / 100.0f);
785 a.vertices[j][0] = cosf(ang) * vr;
786 a.vertices[j][1] = sinf(ang) * vr;
793 void updateAsteroids() {
794 for (
auto &a : asteroids) {
799 a.rotation_angle += a.rotation_speed;
811 void splitAsteroid(
int idx) {
812 float x = asteroids[idx].x;
813 float y = asteroids[idx].y;
814 float r = asteroids[idx].radius / 2.0f;
816 createAsteroidExplosion(x, y);
818 asteroids[idx].active =
false;
821 int splits = 2 + (rand() % 2);
822 for (
int i = 0; i < splits; ++i) {
823 float ang = (rand() % 628) / 100.0f;
824 float spd = 0.5f + (rand() % 15) / 10.0f;
825 float nvx = cosf(ang) * spd + asteroids[idx].vx * 0.3f;
826 float nvy = sinf(ang) * spd + asteroids[idx].vy * 0.3f;
827 float off = r * 0.5f;
828 spawnAsteroid(x + cosf(ang) * off, y + sinf(ang) * off, nvx, nvy, r);
834 createAsteroidExplosion(x, y);
835 asteroids[idx].active =
false;
838 void checkAndSpawnAsteroids() {
840 for (
auto &a : asteroids)
843 int minAsteroids = 3;
844 if (
active < minAsteroids) {
845 for (
int s = 0; s < minAsteroids -
active; ++s) {
850 x = (rand() % 2) ? -30.0f :
GAME_W + 30.0f;
851 y =
static_cast<float>(rand() %
GAME_H);
853 x =
static_cast<float>(rand() %
GAME_W);
854 y = (rand() % 2) ? -30.0f :
GAME_H + 30.0f;
857 }
while (hypotf(x - ship.x, y - ship.y) < 100.0f && attempts < 10);
858 float vx = ((rand() % 100) - 50) / 50.0f;
859 float vy = ((rand() % 100) - 50) / 50.0f;
861 int roll = rand() % 100;
863 radius = 30.0f + (rand() % 15);
865 radius = 20.0f + (rand() % 10);
867 radius = 12.0f + (rand() % 8);
868 spawnAsteroid(x, y, vx, vy, radius);
873 void checkAsteroidCollisions() {
880 float dx = asteroids[i].x - asteroids[j].x;
881 float dy = asteroids[i].y - asteroids[j].y;
882 float dist = sqrtf(dx * dx + dy * dy);
883 float minDist = asteroids[i].radius + asteroids[j].radius;
884 if (dist < minDist && dist > 0) {
885 float nx = dx / dist, ny = dy / dist;
886 float overlap = (minDist - dist) * 0.5f;
887 asteroids[i].x += nx * overlap;
888 asteroids[i].y += ny * overlap;
889 asteroids[j].x -= nx * overlap;
890 asteroids[j].y -= ny * overlap;
891 float rvx = asteroids[i].vx - asteroids[j].vx;
892 float rvy = asteroids[i].vy - asteroids[j].vy;
893 float van = rvx * nx + rvy * ny;
896 float restitution = 0.8f;
897 float impulse = -(1 + restitution) * van * 0.5f;
898 asteroids[i].vx += impulse * nx;
899 asteroids[i].vy += impulse * ny;
900 asteroids[j].vx -= impulse * nx;
901 asteroids[j].vy -= impulse * ny;
907 void checkShipAsteroidCollision() {
913 float dx = ship.x - asteroids[i].x;
914 float dy = ship.y - asteroids[i].y;
915 float dist = sqrtf(dx * dx + dy * dy);
916 float shipRadius = 8.0f;
917 if (dist < asteroids[i].radius + shipRadius) {
918 startShipExplosion();
919 createAsteroidExplosion(ship.x + dx * 0.5f, ship.y + dy * 0.5f);
921 float nx = -dx / dist, ny = -dy / dist;
922 asteroids[i].vx += nx * 2.0f;
923 asteroids[i].vy += ny * 2.0f;
930 void checkProjectileAsteroidCollisions() {
931 for (
auto &p : projectiles) {
937 float dx = p.x - asteroids[j].x;
938 float dy = p.y - asteroids[j].y;
939 if (sqrtf(dx * dx + dy * dy) < asteroids[j].radius) {
941 if (asteroids[j].radius >= 20.0f)
954 void startShipExplosion() {
957 ship.exploding =
true;
960 ship.overheated =
false;
961 ship.overheat_cooldown = 0;
962 ship.continuous_fire_timer = 0;
963 ship.burst_count = 0;
964 for (
auto &p : particles) {
965 p.x = ship.x + ((rand() % 20) - 10);
966 p.y = ship.y + ((rand() % 20) - 10);
967 float ang = (rand() % 628) / 100.0f;
968 float spd = 0.5f + (rand() % 20) / 10.0f;
969 p.vx = cosf(ang) * spd;
970 p.vy = sinf(ang) * spd;
971 p.lifetime = 30 + (rand() % 60);
976 void createAsteroidExplosion(
float x,
float y) {
977 int toCreate = 15 + (rand() % 10);
979 for (
auto &p : particles) {
980 if (created >= toCreate)
983 p.x = x + ((rand() % 16) - 8);
984 p.y = y + ((rand() % 16) - 8);
985 float ang = (rand() % 628) / 100.0f;
986 float spd = 1.0f + (rand() % 20) / 10.0f;
987 p.vx = cosf(ang) * spd;
988 p.vy = sinf(ang) * spd;
989 p.lifetime = 15 + (rand() % 25);
996 void updateExplosion() {
997 for (
auto &p : particles) {
1005 if (p.lifetime <= 0) {
1018 if (ship.exploding) {
1019 ship.explosion_timer--;
1020 if (ship.explosion_timer <= 0)
1021 ship.exploding =
false;
1025 void handleDeathSequence() {
1026 if (wasExploding && !ship.exploding) {
1027 if (ship.lives > 0) {
1032 wasExploding =
false;
1033 }
else if (ship.exploding) {
1034 wasExploding =
true;
1038 void triggerRespawn() {
1044 ship.exploding =
false;
1045 ship.explosion_timer = 0;
1046 keyLeft = keyRight = keyThrust = keyFire =
false;
1047 joyLeft = joyRight = joyThrust = joyFire =
false;
1050 countdownNumber = 3;
1051 countdownStartMs = SDL_GetTicks();
1055 void updateCountdown() {
1056 const Uint32 now = SDL_GetTicks();
1057 const Uint32 elapsed = now - countdownStartMs;
1059 countdownTimer = elapsed % countdownDuration;
1060 countdownNumber = 3 -
static_cast<int>(elapsed / countdownDuration);
1062 if (elapsed >= (countdownDuration * 4U)) {
1065 launchStartMs = now;
1066 shipLaunchY =
static_cast<float>(
GAME_H);
1070 void updateLaunch() {
1071 const Uint32 now = SDL_GetTicks();
1072 launchTimer = std::min<Uint32>(now - launchStartMs, launchDuration);
1073 if (launchTimer < launchDuration / 2U) {
1074 shipLaunchY =
static_cast<float>(
GAME_H) -
1075 (
static_cast<float>(launchTimer) * (
static_cast<float>(
GAME_H) - ship.y) /
1076 static_cast<float>(launchDuration / 2U));
1078 shipLaunchY = ship.y;
1080 if (launchTimer >= launchDuration) {
1082 if (gamepad !=
nullptr) {
1083 joyRestLX = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX);
1084 joyRestLY = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY);
1089 void drawStars(
float speedMul = 1.0f) {
1091 for (
auto &s : stars) {
1092 s.
y += s.
speed * speedMul;
1094 s.
x =
static_cast<float>(rand() %
GAME_W);
1098 s.
speed = 0.15f + (rand() % 40) / 100.0f;
1099 s.
size = 0.8f + (rand() % 12) / 20.0f;
1100 s.
brightness = 0.25f + (rand() % 45) / 100.0f;
1101 }
else if (s.
layer == 1) {
1102 s.
speed = 0.4f + (rand() % 80) / 100.0f;
1103 s.
size = 1.2f + (rand() % 18) / 20.0f;
1104 s.
brightness = 0.45f + (rand() % 55) / 100.0f;
1106 s.
speed = 0.8f + (rand() % 180) / 100.0f;
1107 s.
size = 1.8f + (rand() % 25) / 20.0f;
1108 s.
brightness = 0.65f + (rand() % 35) / 100.0f;
1116 float finalBrightness = s.
brightness * twinkle;
1118 starSprite->setShaderParams(
1119 s.
r * finalBrightness,
1120 s.
g * finalBrightness,
1121 s.
b * finalBrightness,
1124 int screenSize =
static_cast<int>(s.
size * std::min(sx(), sy()));
1125 screenSize = std::max(2, screenSize);
1126 starSprite->drawSpriteRect(toScreenX(s.
x), toScreenY(s.
y), screenSize, screenSize);
1130 void drawShipAt(
float px,
float py) {
1131 if (ship.exploding) {
1135 float c = cosf(ship.angle), s = sinf(ship.angle);
1136 auto rot = [&](
float lx,
float ly,
float &ox,
float &oy) {
1137 ox = px + (lx * c - ly * s);
1138 oy = py + (lx * s + ly * c);
1141 float nose_x, nose_y, lw_x, lw_y, rw_x, rw_y;
1142 float le_x, le_y, re_x, re_y, ce_x, ce_y;
1143 float ld_x, ld_y, rd_x, rd_y;
1144 float cl_x, cl_y, cr_x, cr_y, cc_x, cc_y;
1146 rot(0, -15, nose_x, nose_y);
1147 rot(-12, 8, lw_x, lw_y);
1148 rot(12, 8, rw_x, rw_y);
1149 rot(-8, 12, le_x, le_y);
1150 rot(8, 12, re_x, re_y);
1151 rot(0, 10, ce_x, ce_y);
1152 rot(-6, 2, ld_x, ld_y);
1153 rot(6, 2, rd_x, rd_y);
1154 rot(-3, -8, cl_x, cl_y);
1155 rot(3, -8, cr_x, cr_y);
1156 rot(0, -5, cc_x, cc_y);
1158 if (ship.overheated) {
1159 setColor(1.0f, 0.0f, 0.0f, 1.0f);
1160 }
else if (ship.continuous_fire_timer > 120) {
1161 int heat = ship.continuous_fire_timer - 120;
1162 setColor(1.0f, std::max(0.0f, 1.0f - heat / 64.0f), 0.0f, 1.0f);
1164 setColor(1.0f, 1.0f, 1.0f, 1.0f);
1167 drawLine(nose_x, nose_y, lw_x, lw_y);
1168 drawLine(nose_x, nose_y, rw_x, rw_y);
1169 drawLine(lw_x, lw_y, rw_x, rw_y);
1171 drawLine(lw_x, lw_y, le_x, le_y);
1172 drawLine(rw_x, rw_y, re_x, re_y);
1173 drawLine(le_x, le_y, ce_x, ce_y);
1174 drawLine(re_x, re_y, ce_x, ce_y);
1176 if (ship.overheated)
1177 setColor(0.78f, 0.0f, 0.0f, 1.0f);
1178 else if (ship.continuous_fire_timer > 120)
1179 setColor(0.78f, std::max(0.0f, 0.78f - (ship.continuous_fire_timer - 120) / 80.0f), 0.0f, 1.0f);
1181 setColor(0.78f, 0.78f, 0.78f, 1.0f);
1182 drawLine(nose_x, nose_y, ld_x, ld_y);
1183 drawLine(nose_x, nose_y, rd_x, rd_y);
1184 drawLine(ld_x, ld_y, rd_x, rd_y);
1185 drawLine(ld_x, ld_y, lw_x, lw_y);
1186 drawLine(rd_x, rd_y, rw_x, rw_y);
1188 if (ship.overheated)
1189 setColor(0.0f, 0.5f, 0.5f, 1.0f);
1191 setColor(0.0f, 1.0f, 1.0f, 1.0f);
1192 drawLine(cl_x, cl_y, cr_x, cr_y);
1193 drawLine(cl_x, cl_y, cc_x, cc_y);
1194 drawLine(cr_x, cr_y, cc_x, cc_y);
1196 setColor(0.59f, 0.59f, 0.59f, 1.0f);
1197 drawLine(cc_x, cc_y, ce_x, ce_y);
1199 if (keyThrust || joyThrust) {
1200 float f1x, f1y, f2x, f2y, f3x, f3y;
1201 rot(-6, 18, f1x, f1y);
1202 rot(0, 22, f2x, f2y);
1203 rot(6, 18, f3x, f3y);
1204 setColor(1.0f, 0.39f, 0.0f, 1.0f);
1205 drawLine(le_x, le_y, f1x, f1y);
1206 drawLine(ce_x, ce_y, f2x, f2y);
1207 drawLine(re_x, re_y, f3x, f3y);
1209 rot(0, 20, ifx, ify);
1210 setColor(1.0f, 1.0f, 0.0f, 1.0f);
1211 drawLine(ce_x, ce_y, ifx, ify);
1215 void drawProjectiles() {
1216 setColor(1.0f, 0.0f, 0.0f, 1.0f);
1217 for (
auto &p : projectiles) {
1220 float endX = p.x - p.vx * 0.5f;
1221 float endY = p.y - p.vy * 0.5f;
1222 drawLine(p.x, p.y, endX, endY);
1226 void drawAsteroids() {
1228 if (!asteroids[i].
active)
1230 auto &a = asteroids[i];
1231 float cosR = cosf(a.rotation_angle);
1232 float sinR = sinf(a.rotation_angle);
1236 rv[j][0] = a.vertices[j][0] * cosR - a.vertices[j][1] * sinR;
1237 rv[j][1] = a.vertices[j][0] * sinR + a.vertices[j][1] * cosR;
1240 setColor(1.0f, 1.0f, 1.0f, 1.0f);
1243 drawLine(a.x + rv[j][0], a.y + rv[j][1],
1244 a.x + rv[next][0], a.y + rv[next][1]);
1249 iv[j][0] = rv[j][0] * 0.6f;
1250 iv[j][1] = rv[j][1] * 0.6f;
1252 setColor(0.59f, 0.59f, 0.59f, 1.0f);
1255 drawLine(a.x + iv[j][0], a.y + iv[j][1],
1256 a.x + iv[next][0], a.y + iv[next][1]);
1259 setColor(0.39f, 0.39f, 0.39f, 1.0f);
1261 drawLine(a.x + rv[j][0], a.y + rv[j][1],
1262 a.x + iv[j][0], a.y + iv[j][1]);
1265 setColor(0.71f, 0.71f, 0.71f, 1.0f);
1268 drawLine(a.x + rv[j][0], a.y + rv[j][1],
1269 a.x + iv[next][0], a.y + iv[next][1]);
1274 void drawExplosion() {
1275 for (
auto &p : particles) {
1278 float lp =
static_cast<float>(p.lifetime) / 50.0f;
1280 setColor(1.0f, 1.0f, 1.0f, 1.0f);
1282 setColor(1.0f, 1.0f, 0.0f, 1.0f);
1284 setColor(1.0f, 0.5f, 0.0f, 1.0f);
1286 setColor(1.0f, 0.0f, 0.0f, 1.0f);
1287 drawRect(p.x, p.y, 2, 2);
1292 std::string buf = std::format(
"Score: {}", ship.score);
1293 printText(buf.c_str(), toScreenX(10), toScreenY(10), {255, 255, 255, 255});
1294 buf = std::format(
"Lives: {}", ship.lives);
1295 printText(buf.c_str(), toScreenX(10), toScreenY(30), {255, 255, 255, 255});
1298 void drawCountdown() {
1300 if (countdownNumber > 0) {
1301 std::string buf = std::format(
"{}", countdownNumber);
1306 int centerX = w / 2;
1307 int centerY = h / 2;
1308 int textX = centerX - textW / 2;
1309 int textY = centerY - textH / 2;
1310 printText(buf.c_str(), textX, textY, {255, 255, 0, 255});
1312 if (((countdownTimer / 167U) % 2U) != 0U) {
1313 setColor(1.0f, 1.0f, 0.0f, 0.5f);
1314 int padding = std::max(15,
static_cast<int>(15 * std::min(sx(), sy())));
1315 int rectX = textX - padding;
1316 int rectY = textY - padding;
1317 int rectW = textW + padding * 2;
1318 int rectH = textH + padding * 2;
1319 int thickness = std::max(3,
static_cast<int>(3 * std::min(sx(), sy())));
1321 pixel->drawSpriteRect(rectX, rectY, rectW, thickness);
1323 pixel->drawSpriteRect(rectX, rectY + rectH - thickness, rectW, thickness);
1325 pixel->drawSpriteRect(rectX, rectY, thickness, rectH);
1327 pixel->drawSpriteRect(rectX + rectW - thickness, rectY, thickness, rectH);
1334 printText(
"LAUNCH!", w / 2 - textW / 2, h / 2 - textH / 2, {0, 255, 0, 255});
1336 printText(
"LAUNCH!", toScreenX(270), toScreenY(160), {0, 255, 0, 255});
1342 printText(
"PREPARE FOR MISSION", w / 2 - textW / 2, h / 2 + textH * 2, {255, 255, 255, 255});
1344 printText(
"PREPARE FOR MISSION", toScreenX(220), toScreenY(195), {255, 255, 255, 255});
1347 std::string lbuf = std::format(
"Lives: {}", ship.lives);
1348 std::string sbuf = std::format(
"Score: {}", ship.score);
1350 printText(lbuf.c_str(), w / 2 - textW / 2, h / 2 + textH * 3 + 10, {255, 255, 255, 255});
1352 printText(sbuf.c_str(), w / 2 - textW / 2, h / 2 + textH * 4 + 15, {255, 255, 255, 255});
1355 printText(lbuf.c_str(), toScreenX(260), toScreenY(215), {255, 255, 255, 255});
1356 printText(sbuf.c_str(), toScreenX(260), toScreenY(235), {255, 255, 255, 255});
1362 if (launchTimer > launchDuration / 4)
1365 float tempY = ship.y;
1366 ship.y = shipLaunchY;
1367 drawShipAt(ship.x, shipLaunchY);
1370 if (launchTimer < launchDuration / 2) {
1371 setColor(1.0f, 0.39f, 0.0f, 1.0f);
1372 for (
int i = 0; i < 12; ++i) {
1373 float tx = ship.x + (rand() % 8) - 4;
1374 float ty = shipLaunchY + 15 + i * 2;
1379 if (launchTimer >= launchDuration / 2) {
1382 printText(
"MISSION START!", w / 2 - textW / 2, h / 2 - textH / 2, {0, 255, 255, 255});
1384 printText(
"MISSION START!", toScreenX(240), toScreenY(170), {0, 255, 255, 255});
1391 drawShipAt(ship.x, ship.y);
1398 void drawGameOver() {
1400 printText(
"GAME OVER", toScreenX(255), toScreenY(130), {255, 0, 0, 255});
1402 std::string buf = std::format(
"Final Score: {}", ship.score);
1403 printText(buf.c_str(), toScreenX(235), toScreenY(165), {255, 255, 255, 255});
1404 printText(
"Press SPACE to begin", toScreenX(215), toScreenY(195), {255, 255, 0, 255});