This commit is contained in:
2026-01-04 19:42:22 +08:00
parent 888b0c368c
commit 4dadeb807d
9 changed files with 279 additions and 148 deletions

3
.gitignore vendored
View File

@ -9,4 +9,5 @@ build/
star-invaders star-invaders
*.kra *.kra
*.xcf *.xcf
*.png *.png
*.*~

View File

@ -1,8 +1,8 @@
# Simple Makefile using sdl-config for SDL flags # Simple Makefile using sdl-config for SDL flags
CC := gcc CC := clang
SDL_CFLAGS := $(shell sdl2-config --cflags) SDL_CFLAGS := $(shell sdl2-config --cflags)
SDL_LIBS := $(shell sdl2-config --libs) SDL_LIBS := $(shell sdl2-config --libs)
CFLAGS := -Wall -Wextra -std=c99 $(SDL_CFLAGS) CFLAGS := -Wall -Wextra -std=c99 -g $(SDL_CFLAGS)
SRCDIR := src SRCDIR := src
SOURCES := $(wildcard $(SRCDIR)/*.c) SOURCES := $(wildcard $(SRCDIR)/*.c)
BUILD_DIR := build BUILD_DIR := build

BIN
res/bullet.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -1,9 +1,21 @@
#include "bullet.h" #include "bullet.h"
#include "game.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void initBullet(Bullet *bullet, int x, int y) {
bullet->rect.x = x;
bullet->rect.y = y;
bullet->rect.w = 50; // Bullet width
bullet->rect.h = 50; // Bullet height
bullet->active = 0;
}
void updateBullets(Bullet *bullets, int *bulletCount, SDL_Renderer* renderer) { void updateBullets(Bullet *bullets, int *bulletCount, SDL_Renderer* renderer) {
int bulletSpeed = 7; int bulletSpeed = 7;
for (int i = 0; i < *bulletCount; i++) { for (int i = 0; i < MAX_BULLETS; i++) {
if (bullets[i].active) { if (bullets[i].active) {
// Move bullet upward // Move bullet upward
bullets[i].rect.y -= bulletSpeed; bullets[i].rect.y -= bulletSpeed;
@ -11,32 +23,31 @@ void updateBullets(Bullet *bullets, int *bulletCount, SDL_Renderer* renderer) {
// Deactivate if bullet goes off screen // Deactivate if bullet goes off screen
if (bullets[i].rect.y < 0) { if (bullets[i].rect.y < 0) {
bullets[i].active = 0; bullets[i].active = 0;
(*bulletCount)--;
} else { } else {
// Draw bullet // Draw bullet using global textures
SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255); SDL_RenderCopy(renderer, textures.bulletTexture, NULL, &bullets[i].rect);
SDL_RenderFillRect(renderer, &bullets[i].rect);
} }
} }
} }
} }
void fireBullet(Bullet *bullets, int *bulletCount, SDL_Rect* rect) { void fireBullet(Bullet *bullets, int *bulletCount, SDL_Rect* rect) {
if (*bulletCount < MAX_BULLETS) { for (int i = 0; i < MAX_BULLETS; i++) {
bullets[*bulletCount].rect.x = rect->x + rect->w / 2 - 2; if (!bullets[i].active) {
bullets[*bulletCount].rect.y = rect->y - 10; bullets[i].rect.x = rect->x;
bullets[*bulletCount].rect.w = 4; bullets[i].rect.y = rect->y;
bullets[*bulletCount].rect.h = 10; bullets[i].active = 1;
bullets[*bulletCount].active = 1; (*bulletCount)++;
(*bulletCount)++; break;
}
} }
} }
void cleanupUnrenderedBullets(Bullet* bullets, int* bulletCount) { void cleanupBullets(Bullet *bullets, int *bulletCount) {
for (int i = 0; i < *bulletCount; i++){ // Deactivate all bullets and reset count
if(bullets[i].active == 0){ for (int i = 0; i < MAX_BULLETS; i++) {
memmove(&bullets[i], &bullets[i + 1], sizeof(Bullet) * (*bulletCount - i - 1)); bullets[i].active = 0;
(*bulletCount)--;
i--;
}
} }
*bulletCount = 0;
} }

View File

@ -10,8 +10,11 @@ typedef struct {
int active; int active;
} Bullet; } Bullet;
/* Initialize an existing Bullet struct */
void initBullet(Bullet *bullet, int x, int y);
void updateBullets(Bullet *bullets, int *bulletCount, SDL_Renderer *renderer); void updateBullets(Bullet *bullets, int *bulletCount, SDL_Renderer *renderer);
void fireBullet(Bullet *bullets, int *bulletCount, SDL_Rect *rect); void fireBullet(Bullet *bullets, int *bulletCount, SDL_Rect *rect);
void cleanupUnrenderedBullets(Bullet* bullets, int* bulletCount); void cleanupBullets(Bullet *bullets, int *bulletCount);
#endif #endif

25
src/game.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef GAME_H
#define GAME_H
#include <SDL.h>
#define FPS 60
#define FRAME_DELAY (1000 / FPS) // Milliseconds per frame
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
typedef struct {
char *spaceshipPath;
char *backgroundPath;
char *bulletPath;
} AssetPaths;
typedef struct {
SDL_Texture *bulletTexture;
SDL_Texture *playerTexture;
SDL_Texture *backgroundTexture;
} Textures;
extern Textures textures;
#endif /* GAME_H */

View File

@ -1,25 +1,150 @@
#include "bullet.h" #include "bullet.h"
#include "game.h"
#include "player.h" #include "player.h"
#include <SDL.h> #include <SDL.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#define FPS 60 Textures textures = {0}; // global textures storage (initialized to NULL pointers)
#define FRAME_DELAY (1000 / FPS) // Milliseconds per frame
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
void handlePlayer(SDL_Renderer *renderer, Player *player, int speed, const Uint8 *keys) { int loadTextures(SDL_Renderer *renderer, AssetPaths *paths) {
if (keys[SDL_SCANCODE_LEFT] || keys[SDL_SCANCODE_A]) { SDL_Surface *bulletSurface = SDL_LoadBMP(paths->bulletPath);
movePlayerLeft(player, speed); if (!bulletSurface) {
} fprintf(stderr, "Could not load bullet image: %s\n", SDL_GetError());
if (keys[SDL_SCANCODE_RIGHT] || keys[SDL_SCANCODE_D]) { return 0;
movePlayerRight(player, speed, SCREEN_WIDTH);
} }
// Render player flipped vertically textures.bulletTexture = SDL_CreateTextureFromSurface(renderer, bulletSurface);
renderPlayerFlipped(renderer, player, SDL_FLIP_VERTICAL); SDL_FreeSurface(bulletSurface);
if (!textures.bulletTexture) {
fprintf(stderr, "Could not create bullet texture: %s\n", SDL_GetError());
return 0;
}
SDL_Surface *playerSurface = SDL_LoadBMP(paths->spaceshipPath);
if (!playerSurface) {
fprintf(stderr, "Could not load player image: %s\n", SDL_GetError());
SDL_DestroyTexture(textures.bulletTexture);
textures.bulletTexture = NULL;
return 0;
}
textures.playerTexture = SDL_CreateTextureFromSurface(renderer, playerSurface);
SDL_FreeSurface(playerSurface);
if (!textures.playerTexture) {
fprintf(stderr, "Could not create player texture: %s\n", SDL_GetError());
SDL_DestroyTexture(textures.bulletTexture);
textures.bulletTexture = NULL;
return 0;
}
/* Load background (non-fatal) */
SDL_Surface *bgSurface = SDL_LoadBMP(paths->backgroundPath);
if (!bgSurface) {
fprintf(stderr, "Could not load background %s: %s\n", paths->backgroundPath, SDL_GetError());
textures.backgroundTexture = NULL;
} else {
textures.backgroundTexture = SDL_CreateTextureFromSurface(renderer, bgSurface);
SDL_FreeSurface(bgSurface);
if (!textures.backgroundTexture) {
fprintf(stderr, "Could not create background texture: %s\n", SDL_GetError());
textures.backgroundTexture = NULL;
}
}
return 1;
}
/*
* Parse command-line arguments. Returns:
* 0 -> continue normally
* 1 -> printed help/version, should exit success
* -1 -> error in args, should exit failure
*/
static int handleArguments(int argc, char *argv[], char **resLocation) {
for (int args = 1; args < argc; args++) {
if (strcmp(argv[args], "--help") == 0 || strcmp(argv[args], "-h") == 0) {
printf("Star Invaders Help:\n");
printf("Use arrow keys or A/D to move the spaceship left and right.\n");
printf("Press SPACE to fire bullets.\n");
printf("Press ESC or close the window to exit the game.\n");
printf("Use --res-location or -rl to specify the resource location.\n");
return 1;
} else if (strcmp(argv[args], "--version") == 0 || strcmp(argv[args], "-v") == 0) {
printf("Star Invaders Version 1.0.0\n");
return 1;
} else if (strcmp(argv[args], "--res-location") == 0 || strcmp(argv[args], "-rl") == 0) {
if (args + 1 >= argc) {
fprintf(stderr, "Missing value for %s\n", argv[args]);
printf("Use --help or -h for usage information.\n");
return -1;
}
*resLocation = argv[++args];
} else {
printf("Unknown argument: %s\n", argv[args]);
printf("Use --help or -h for usage information.\n");
return -1;
}
}
return 0;
}
AssetPaths *getResources(const char *resLocation) {
AssetPaths *paths = (AssetPaths *)malloc(sizeof(AssetPaths));
if (!paths) {
fprintf(stderr, "Out of memory\n");
return NULL;
}
size_t pathLen;
const char *shipFile = "spaceship.bmp";
const char *bgFile = "background.bmp";
const char *bulletFile = "bullet.bmp";
pathLen = strlen(resLocation) + strlen(shipFile) + 2;
paths->spaceshipPath = (char *)malloc(pathLen);
if (!paths->spaceshipPath) {
free(paths);
fprintf(stderr, "Out of memory\n");
return NULL;
}
if (resLocation[strlen(resLocation) - 1] == '/') {
snprintf(paths->spaceshipPath, pathLen, "%s%s", resLocation, shipFile);
} else {
snprintf(paths->spaceshipPath, pathLen, "%s/%s", resLocation, shipFile);
}
pathLen = strlen(resLocation) + strlen(bgFile) + 2;
paths->backgroundPath = (char *)malloc(pathLen);
if (!paths->backgroundPath) {
free(paths->spaceshipPath);
free(paths);
fprintf(stderr, "Out of memory\n");
return NULL;
}
if (resLocation[strlen(resLocation) - 1] == '/') {
snprintf(paths->backgroundPath, pathLen, "%s%s", resLocation, bgFile);
} else {
snprintf(paths->backgroundPath, pathLen, "%s/%s", resLocation, bgFile);
}
pathLen = strlen(resLocation) + strlen(bulletFile) + 2;
paths->bulletPath = (char *)malloc(pathLen);
if (!paths->bulletPath) {
free(paths->backgroundPath);
free(paths->spaceshipPath);
free(paths);
fprintf(stderr, "Out of memory\n");
return NULL;
}
if (resLocation[strlen(resLocation) - 1] == '/') {
snprintf(paths->bulletPath, pathLen, "%s%s", resLocation, bulletFile);
} else {
snprintf(paths->bulletPath, pathLen, "%s/%s", resLocation, bulletFile);
}
return paths;
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
@ -29,74 +154,31 @@ int main(int argc, char *argv[]) {
} }
char *resLocation = "./res/"; char *resLocation = "./res/";
int argResult = handleArguments(argc, argv, &resLocation);
for (int args = 1; args < argc; args++) { if (argResult == 1) {
if (strcmp(argv[args], "--help") == 0 || SDL_Quit();
strcmp(argv[args], "-h") == 0) { return EXIT_SUCCESS;
printf("Star Invaders Help:\n"); } else if (argResult == -1) {
printf("Use arrow keys or A/D to move the spaceship left and right.\n");
printf("Press SPACE to fire bullets.\n");
printf("Press ESC or close the window to exit the game.\n");
printf("Use --res-location or -rl to specify the resource location.\n");
SDL_Quit();
return EXIT_SUCCESS;
} else if (strcmp(argv[args], "--version") == 0 ||
strcmp(argv[args], "-v") == 0) {
printf("Star Invaders Version 1.0.0\n");
SDL_Quit();
return EXIT_SUCCESS;
} else if (strcmp(argv[args], "--res-location") == 0 ||
strcmp(argv[args], "-rl") == 0) {
resLocation = argv[args + 1];
args++;
} else {
printf("Unknown argument: %s\n", argv[args]);
printf("Use --help or -h for usage information.\n");
SDL_Quit();
return EXIT_FAILURE;
}
}
printf("Using resource location: %s\n", resLocation);
// Build full paths for assets
size_t pathLen;
char *spaceshipPath;
char *backgroundPath;
const char *shipFile = "spaceship.bmp";
const char *bgFile = "background.bmp";
pathLen = strlen(resLocation) + strlen(shipFile) + 2;
spaceshipPath = (char *)malloc(pathLen);
if (!spaceshipPath) {
fprintf(stderr, "Out of memory\n");
SDL_Quit(); SDL_Quit();
return EXIT_FAILURE; return EXIT_FAILURE;
} }
if (resLocation[strlen(resLocation) - 1] == '/') {
snprintf(spaceshipPath, pathLen, "%s%s", resLocation, shipFile);
} else {
snprintf(spaceshipPath, pathLen, "%s/%s", resLocation, shipFile);
}
pathLen = strlen(resLocation) + strlen(bgFile) + 2; AssetPaths *paths = getResources(resLocation);
backgroundPath = (char *)malloc(pathLen); if (!paths) {
if (!backgroundPath) { fprintf(stderr, "Could not load resources\n");
free(spaceshipPath);
fprintf(stderr, "Out of memory\n");
SDL_Quit(); SDL_Quit();
return EXIT_FAILURE; return EXIT_FAILURE;
} }
if (resLocation[strlen(resLocation) - 1] == '/') {
snprintf(backgroundPath, pathLen, "%s%s", resLocation, bgFile);
} else {
snprintf(backgroundPath, pathLen, "%s/%s", resLocation, bgFile);
}
SDL_Window *window = SDL_CreateWindow("Star Invaders", SDL_WINDOWPOS_CENTERED, SDL_Window *window = SDL_CreateWindow("Star Invaders", SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH,
SCREEN_HEIGHT, SDL_WINDOW_SHOWN); SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
if (!window) { if (!window) {
fprintf(stderr, "Could not create window: %s\n", SDL_GetError()); fprintf(stderr, "Could not create window: %s\n", SDL_GetError());
free(paths->backgroundPath);
free(paths->bulletPath);
free(paths->spaceshipPath);
free(paths);
SDL_Quit(); SDL_Quit();
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -106,42 +188,46 @@ int main(int argc, char *argv[]) {
if (!renderer) { if (!renderer) {
fprintf(stderr, "Could not create renderer: %s\n", SDL_GetError()); fprintf(stderr, "Could not create renderer: %s\n", SDL_GetError());
SDL_DestroyWindow(window); SDL_DestroyWindow(window);
free(paths->backgroundPath);
free(paths->bulletPath);
free(paths->spaceshipPath);
free(paths);
SDL_Quit(); SDL_Quit();
return EXIT_FAILURE; return EXIT_FAILURE;
} }
int speed = 5; if (!loadTextures(renderer, paths)) {
fprintf(stderr, "Could not create game textures: %s\n", SDL_GetError());
free(paths->backgroundPath);
free(paths->bulletPath);
free(paths->spaceshipPath);
free(paths);
SDL_Quit();
return EXIT_FAILURE;
}
// Bullets array // Bullets array
Bullet bullets[MAX_BULLETS];
int bulletCount = 0; int bulletCount = 0;
Bullet bullets[MAX_BULLETS];
for (int i = 0; i < MAX_BULLETS; i++) { for (int i = 0; i < MAX_BULLETS; i++) {
bullets[i].active = 0; initBullet(&bullets[i], 0, 0);
} }
// Create player // Create player
Player *player = createPlayer(renderer, spaceshipPath, 400, 500); Player *player = createPlayer(400, 500);
if (!player) { if (!player) {
SDL_DestroyRenderer(renderer); SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window); SDL_DestroyWindow(window);
free(spaceshipPath); free(paths->spaceshipPath);
free(backgroundPath); free(paths->backgroundPath);
free(paths);
if (textures.bulletTexture) SDL_DestroyTexture(textures.bulletTexture);
if (textures.playerTexture) SDL_DestroyTexture(textures.playerTexture);
SDL_Quit(); SDL_Quit();
return EXIT_FAILURE; return EXIT_FAILURE;
} }
// Load background /* background is loaded into textures.backgroundTexture by loadTextures */
SDL_Texture *background = NULL;
SDL_Surface *bgSurface = SDL_LoadBMP(backgroundPath);
if (!bgSurface) {
fprintf(stderr, "Could not load background %s: %s\n", backgroundPath, SDL_GetError());
} else {
background = SDL_CreateTextureFromSurface(renderer, bgSurface);
SDL_FreeSurface(bgSurface);
if (!background) {
fprintf(stderr, "Could not create background texture: %s\n", SDL_GetError());
}
}
// Main loop flag // Main loop flag
int running = 1; int running = 1;
@ -171,16 +257,16 @@ int main(int argc, char *argv[]) {
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer); SDL_RenderClear(renderer);
// Draw background if available // Draw background if available
if (background) { if (textures.backgroundTexture) {
SDL_RenderCopy(renderer, background, NULL, NULL); SDL_RenderCopy(renderer, textures.backgroundTexture, NULL, NULL);
} }
// Update and draw bullets // Update and draw bullets
updateBullets(bullets, &bulletCount, renderer); updateBullets(bullets, &bulletCount, renderer);
cleanupUnrenderedBullets(bullets, &bulletCount); // cleanupUnrenderedBullets(bullets, &bulletCount); // removed
// Handle player movement and rendering // Handle player movement and rendering
handlePlayer(renderer, player, speed, keys); handlePlayer(renderer, player, playerSpeed, keys);
// Present the rendered frame // Present the rendered frame
SDL_RenderPresent(renderer); SDL_RenderPresent(renderer);
@ -193,11 +279,17 @@ int main(int argc, char *argv[]) {
} }
destroyPlayer(player); destroyPlayer(player);
if (background) {
SDL_DestroyTexture(background); // Cleanup bullets (deactivate all)
} cleanupBullets(bullets, &bulletCount);
free(spaceshipPath);
free(backgroundPath); if (textures.bulletTexture) SDL_DestroyTexture(textures.bulletTexture);
if (textures.playerTexture) SDL_DestroyTexture(textures.playerTexture);
if (textures.backgroundTexture) SDL_DestroyTexture(textures.backgroundTexture);
free(paths->spaceshipPath);
free(paths->backgroundPath);
free(paths);
SDL_DestroyRenderer(renderer); SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window); SDL_DestroyWindow(window);
SDL_Quit(); SDL_Quit();

View File

@ -1,27 +1,25 @@
#include "player.h" #include "player.h"
#include "game.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
Player *createPlayer(SDL_Renderer *renderer, const char *imagePath, int x, int playerSpeed = 5;
int y) {
void handlePlayer(SDL_Renderer *renderer, Player *player, int playerSpeed, const Uint8 *keys) {
if (keys[SDL_SCANCODE_LEFT] || keys[SDL_SCANCODE_A]) {
movePlayerLeft(player, playerSpeed);
}
if (keys[SDL_SCANCODE_RIGHT] || keys[SDL_SCANCODE_D]) {
movePlayerRight(player, playerSpeed, SCREEN_WIDTH);
}
// Render player flipped vertically
renderPlayerFlipped(renderer, player, SDL_FLIP_VERTICAL);
}
Player *createPlayer(int x, int y) {
Player *player = (Player *)malloc(sizeof(Player)); Player *player = (Player *)malloc(sizeof(Player));
SDL_Surface *surface = SDL_LoadBMP(imagePath);
if (!surface) {
fprintf(stderr, "Could not load image %s: %s\n", imagePath, SDL_GetError());
free(player);
return NULL;
}
player->texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_FreeSurface(surface);
if (!player->texture) {
fprintf(stderr, "Could not create texture: %s\n", SDL_GetError());
free(player);
return NULL;
}
player->rect.x = x; player->rect.x = x;
player->rect.y = y; player->rect.y = y;
player->rect.w = 50; player->rect.w = 50;
@ -31,30 +29,29 @@ Player *createPlayer(SDL_Renderer *renderer, const char *imagePath, int x,
} }
void renderPlayer(SDL_Renderer *renderer, Player *player) { void renderPlayer(SDL_Renderer *renderer, Player *player) {
SDL_RenderCopy(renderer, player->texture, NULL, &player->rect); SDL_RenderCopy(renderer, textures.playerTexture, NULL, &player->rect);
} }
void renderPlayerFlipped(SDL_Renderer *renderer, Player *player, void renderPlayerFlipped(SDL_Renderer *renderer, Player *player,
SDL_RendererFlip flip) { SDL_RendererFlip flip) {
SDL_RenderCopyEx(renderer, player->texture, NULL, &player->rect, 0, NULL, SDL_RenderCopyEx(renderer, textures.playerTexture, NULL, &player->rect, 0, NULL,
flip); flip);
} }
void movePlayerLeft(Player *player, int speed) { void movePlayerLeft(Player *player, int playerSpeed) {
if (player->rect.x - speed >= 0) { if (player->rect.x - playerSpeed >= 0) {
player->rect.x -= speed; player->rect.x -= playerSpeed;
} }
} }
void movePlayerRight(Player *player, int speed, int screenWidth) { void movePlayerRight(Player *player, int playerSpeed, int screenWidth) {
if (player->rect.x + player->rect.w + speed <= screenWidth) { if (player->rect.x + player->rect.w + playerSpeed <= screenWidth) {
player->rect.x += speed; player->rect.x += playerSpeed;
} }
} }
void destroyPlayer(Player *player) { void destroyPlayer(Player *player) {
if (player) { if (player) {
SDL_DestroyTexture(player->texture);
free(player); free(player);
} }
} }

View File

@ -4,17 +4,19 @@
#include <SDL.h> #include <SDL.h>
typedef struct { typedef struct {
SDL_Texture *texture;
SDL_Rect rect; SDL_Rect rect;
} Player; } Player;
Player *createPlayer(SDL_Renderer *renderer, const char *imagePath, int x, extern int playerSpeed;
int y);
void handlePlayer(SDL_Renderer *renderer, Player *player, int playerSpeed, const Uint8 *keys);
Player *createPlayer(int x, int y);
void renderPlayer(SDL_Renderer *renderer, Player *player); void renderPlayer(SDL_Renderer *renderer, Player *player);
void renderPlayerFlipped(SDL_Renderer *renderer, Player *player, void renderPlayerFlipped(SDL_Renderer *renderer, Player *player,
SDL_RendererFlip flip); SDL_RendererFlip flip);
void movePlayerLeft(Player *player, int speed); void movePlayerLeft(Player *player, int playerSpeed);
void movePlayerRight(Player *player, int speed, int screenWidth); void movePlayerRight(Player *player, int playerSpeed, int screenWidth);
void destroyPlayer(Player *player); void destroyPlayer(Player *player);
#endif #endif