diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9435041 --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +# Compiler and flags +CPPC = g++ +CPPC_FLAGS = -std=c++23 -s -O3 -Wall -Wextra -lSDL3 -lSDL3_image +# Debug flags: +#CPPC_FLAGS = $(CPPC_FLAGS) -ggdb + + +SRC_PATH := src +OBJ_PATH := build/obj +BIN_PATH := build/bin + + +SRC_FILES := $(shell find $(SRC_PATH) -name '*.cpp') +# Generate corresponding object file paths by replacing src/ with build/obj/ +OBJ_FILES := $(patsubst $(SRC_PATH)/%.cpp,$(OBJ_PATH)/%.o,$(SRC_FILES)) + + + +all: make-build-dir $(BIN_PATH)/naval_swarm + + +make-build-dir: + mkdir -p $(OBJ_PATH) + mkdir -p $(BIN_PATH) + + +$(BIN_PATH)/naval_swarm: $(OBJ_FILES) + $(CPPC) $(CPPC_FLAGS) $^ -o $@ + + +$(OBJ_PATH)/%.o: $(SRC_PATH)/%.cpp + $(CPPC) $(CPPC_FLAGS) -c $< -o $@ + + +clean: + rm -fr build + +.PHONY: all clean make-build-dir diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..c266001 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,154 @@ +#include +#include + +constexpr int SCREEN_WIDTH = 800; +constexpr int SCREEN_HEIGHT = 600; +constexpr int WORLD_WIDTH = 1600; +constexpr int WORLD_HEIGHT = 1200; +constexpr int TARGET_FPS = 60; +constexpr Uint64 TARGET_FRAME_TIME_NS = 1'000'000'000 / TARGET_FPS; + +struct Animation { + SDL_Rect frames[4]; + int currentFrame = 0; + Uint32 frameDuration = 100; + Uint32 lastFrameTime = 0; +}; + +struct Entity { + SDL_FRect position; + SDL_Texture* texture; + Animation animation; + float speed = 250.0f; +}; + +struct Camera { + SDL_FRect view = {0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT}; + bool followPlayer = true; + float speed = 500.0f; + float smoothness = 0.1f; +}; + +int main() { + SDL_Init(SDL_INIT_VIDEO); + SDL_Window* window = SDL_CreateWindow("Naval Swarm", SCREEN_WIDTH, SCREEN_HEIGHT, 0); + SDL_Renderer* renderer = SDL_CreateRenderer(window, "gpu,vulcan"); + + // Load textures + SDL_Texture* spriteSheet = IMG_LoadTexture(renderer, "assets/spritesheet.png"); + SDL_Texture* bgTexture = IMG_LoadTexture(renderer, "assets/background.png"); + + // Initialize player + Entity player; + player.texture = spriteSheet; + player.position = {WORLD_WIDTH/2.0f, WORLD_HEIGHT/2.0f, 64.0f, 64.0f}; + for(int i = 0; i < 4; i++) { + player.animation.frames[i] = {i * 64, 0, 64, 64}; + } + + // Initialize camera + Camera camera; + bool running = true; + Uint64 lastFrameTime = SDL_GetTicksNS(); + float deltaTime = 0.0f; + + while (running) { + const Uint64 frameStart = SDL_GetTicksNS(); + + // Event handling + SDL_Event event; + while (SDL_PollEvent(&event)) { + if (event.type == SDL_EVENT_QUIT) running = false; + } + + // Input handling + const bool* keystate = SDL_GetKeyboardState(NULL); + + // Toggle camera mode with C key + static Uint32 lastToggle = 0; + if (keystate[SDL_SCANCODE_C] && (SDL_GetTicks() - lastToggle > 200)) { + camera.followPlayer = !camera.followPlayer; + lastToggle = SDL_GetTicks(); + } + + // Player movement + float moveX = keystate[SDL_SCANCODE_RIGHT] - keystate[SDL_SCANCODE_LEFT]; + float moveY = keystate[SDL_SCANCODE_DOWN] - keystate[SDL_SCANCODE_UP]; + player.position.x += moveX * player.speed * deltaTime; + player.position.y += moveY * player.speed * deltaTime; + + // Camera movement + if (camera.followPlayer) { + // Smooth follow + float targetX = player.position.x + player.position.w/2 - camera.view.w/2; + float targetY = player.position.y + player.position.h/2 - camera.view.h/2; + camera.view.x += (targetX - camera.view.x) * camera.smoothness; + camera.view.y += (targetY - camera.view.y) * camera.smoothness; + } else { + // Manual camera control with IJKL + float camMoveX = keystate[SDL_SCANCODE_L] - keystate[SDL_SCANCODE_J]; + float camMoveY = keystate[SDL_SCANCODE_K] - keystate[SDL_SCANCODE_I]; + camera.view.x += camMoveX * camera.speed * deltaTime; + camera.view.y += camMoveY * camera.speed * deltaTime; + } + + // World bounds + player.position.x = SDL_clamp(player.position.x, 0.0f, WORLD_WIDTH - player.position.w); + player.position.y = SDL_clamp(player.position.y, 0.0f, WORLD_HEIGHT - player.position.h); + camera.view.x = SDL_clamp(camera.view.x, 0.0f, WORLD_WIDTH - camera.view.w); + camera.view.y = SDL_clamp(camera.view.y, 0.0f, WORLD_HEIGHT - camera.view.h); + + // Animation update + if (moveX != 0 || moveY != 0) { + Uint32 now = SDL_GetTicks(); + if (now - player.animation.lastFrameTime > player.animation.frameDuration) { + player.animation.currentFrame = (player.animation.currentFrame + 1) % 4; + player.animation.lastFrameTime = now; + } + } else { + player.animation.currentFrame = 0; + } + + // Rendering + SDL_SetRenderDrawColor(renderer, 30, 30, 45, 255); + SDL_RenderClear(renderer); + + // Draw background + if (bgTexture) { + SDL_FRect bgDest = { -camera.view.x, -camera.view.y, WORLD_WIDTH, WORLD_HEIGHT }; + SDL_RenderTexture(renderer, bgTexture, NULL, &bgDest); + } + + // Draw player + SDL_FRect srcFRect; + SDL_RectToFRect(&player.animation.frames[player.animation.currentFrame], &srcFRect); + SDL_FRect destRect = { + player.position.x - camera.view.x, + player.position.y - camera.view.y, + player.position.w, + player.position.h + }; + SDL_RenderTextureRotated(renderer, player.texture, &srcFRect, &destRect, 0.0, nullptr, SDL_FLIP_NONE); + + SDL_RenderPresent(renderer); + + // Frame timing + const Uint64 frameTime = SDL_GetTicksNS() - frameStart; + if (frameTime < TARGET_FRAME_TIME_NS) { + const Uint64 sleepTime = TARGET_FRAME_TIME_NS - frameTime; + SDL_DelayNS(sleepTime - 2'000'000); + while (SDL_GetTicksNS() - frameStart < TARGET_FRAME_TIME_NS) {} + } + + deltaTime = (SDL_GetTicksNS() - lastFrameTime) / 1e9f; + lastFrameTime = SDL_GetTicksNS(); + } + + // Cleanup + SDL_DestroyTexture(spriteSheet); + SDL_DestroyTexture(bgTexture); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return 0; +}