Initial commit basically lol
This commit is contained in:
parent
e29921d9c9
commit
6d77491228
5 changed files with 316 additions and 2 deletions
14
Makefile
Normal file
14
Makefile
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
makeCC = gcc
|
||||||
|
CFLAGS = -Wall -Wextra -std=c11 $(shell sdl2-config --cflags)
|
||||||
|
LDFLAGS = $(shell sdl2-config --libs) -lSDL2_image -lSDL2_ttf -lSDL2_mixer
|
||||||
|
|
||||||
|
TARGET = game
|
||||||
|
SRC = src/main.c
|
||||||
|
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
$(TARGET): $(SRC)
|
||||||
|
$(CC) $(CFLAGS) $(SRC) -o $(TARGET) $(LDFLAGS)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(TARGET)
|
||||||
67
README.md
67
README.md
|
|
@ -1,3 +1,66 @@
|
||||||
# sdk-experiment
|
# C/SDL2 Game Framework
|
||||||
|
|
||||||
just testing out ollama cloud for vibecoding..
|
A lightweight, professional game template built with C and SDL2, designed for macOS. This project implements a robust game loop architecture with a cinematic introductory sequence and precise frame-rate management.
|
||||||
|
|
||||||
|
## 🚀 Features
|
||||||
|
|
||||||
|
- **Fixed Time-Step Loop**: Locked at 60 FPS using `SDL_Delay` and execution time calculation to ensure consistent gameplay across different hardware.
|
||||||
|
- **Cinematic Intro System**:
|
||||||
|
- Typewriter-style text animation driven by millisecond timers (`SDL_GetTicks`).
|
||||||
|
- Dynamic centering algorithm for text rendering regardless of string length.
|
||||||
|
- "Hold-to-Quit" mechanism utilizing `delta_time` for mathematical precision.
|
||||||
|
- **State Management**: Centralized `Game` struct acting as the single source of truth for the application state.
|
||||||
|
- **macOS Optimized**: Configured to work seamlessly with Homebrew-installed SDL2 libraries.
|
||||||
|
|
||||||
|
## 🛠️ Technical Stack
|
||||||
|
|
||||||
|
- **Language**: C (C11)
|
||||||
|
- **Libraries**:
|
||||||
|
- `SDL2`: Core windowing, input, and rendering.
|
||||||
|
- `SDL2_ttf`: TrueType font rendering.
|
||||||
|
- `SDL2_image` & `SDL2_mixer`: Linked and ready for asset expansion.
|
||||||
|
|
||||||
|
## 📋 Prerequisites
|
||||||
|
|
||||||
|
You must have Homebrew installed on your macOS system. Install the required SDL2 dependencies using:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install sdl2 sdl2_ttf sdl2_image sdl2_mixer
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔨 Building and Running
|
||||||
|
|
||||||
|
The project includes a `Makefile` to automate the compilation process.
|
||||||
|
|
||||||
|
1. **Compile the project**:
|
||||||
|
```bash
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Run the game**:
|
||||||
|
```bash
|
||||||
|
./game
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Clean build files**:
|
||||||
|
```bash
|
||||||
|
make clean
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📂 Project Structure
|
||||||
|
|
||||||
|
- `src/main.c`: Core game logic, including the game loop, input handling, update, and render functions.
|
||||||
|
- `Makefile`: Build configuration using `sdl2-config` for path resolution.
|
||||||
|
- `assets/`: Directory for game assets (e.g., `font.ttf`).
|
||||||
|
|
||||||
|
## 🎮 Controls
|
||||||
|
|
||||||
|
- **Hold ESC**: Exit the game (requires a 1-second hold during the intro).
|
||||||
|
- **Arrow Keys**: Movement logic is implemented (currently disabled during the intro sequence).
|
||||||
|
|
||||||
|
## 🗺️ Roadmap
|
||||||
|
|
||||||
|
- [ ] Implement transition from Intro Sequence to Active Gameplay.
|
||||||
|
- [ ] Integrate sprite rendering for the player character.
|
||||||
|
- [ ] Expand game world with interactive objects.
|
||||||
|
- [ ] Add background music and sound effects using `SDL2_mixer`.
|
||||||
BIN
assets/font.ttf
Normal file
BIN
assets/font.ttf
Normal file
Binary file not shown.
26
prompt.md
Normal file
26
prompt.md
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
Project Summary: C/SDL2 Cinematic Game Template
|
||||||
|
# 🛠 Technical Stack
|
||||||
|
Language: C (C11 standard)
|
||||||
|
Libraries: SDL2, SDL2_ttf
|
||||||
|
OS: macOS (Homebrew environment)
|
||||||
|
Build System: Makefile using sdl2-config for cross-platform header/library paths.
|
||||||
|
# 🚀 Current Implementation
|
||||||
|
1. Core Architecture
|
||||||
|
Game Loop: Implemented a professional Input $\rightarrow$ Update $\rightarrow$ Render cycle.
|
||||||
|
FPS Locking: Locked at 60 FPS using a dynamic frame-capper (SDL_Delay based on frame start/end timestamps) to ensure CPU efficiency and consistent timing.
|
||||||
|
Delta Time: Implemented time-based calculations instead of frame-based ones to ensure gameplay speed is identical regardless of hardware performance.
|
||||||
|
2. Cinematic Text System
|
||||||
|
Typewriter Effect: A custom-built system that reveals a message ("Hello, human.") letter-by-letter.
|
||||||
|
Timing: Uses SDL_GetTicks() to trigger a new character reveal every 150ms.
|
||||||
|
Rendering: Uses SDL_ttf for high-quality text. The text is dynamically centered on the screen by calculating the surface dimensions relative to the window size.
|
||||||
|
Memory Safety: Implemented strict null-termination (\0) on the text buffer to prevent memory leakage/artifacts.
|
||||||
|
3. Game Mechanics & State
|
||||||
|
Game State Struct: A centralized Game struct that manages the loop status, timers, font pointers, and player data.
|
||||||
|
Hold-to-Quit: A safety exit mechanism where the user must hold the Escape key for exactly 1000ms (1 second) to terminate the program.
|
||||||
|
Player Object: A Player struct exists with position and speed data (currently invisible but fully functional in the background).
|
||||||
|
# 📂 Project Structure
|
||||||
|
Makefile: Handles compilation and linking of SDL2 libraries.
|
||||||
|
src/main.c: The entire game logic, fully documented with header and inline comments.
|
||||||
|
assets/font.ttf: External TrueType font used for rendering.
|
||||||
|
# 🎯 Current Status
|
||||||
|
The game currently launches a black window, reveals a centered message with a cinematic typewriter animation, and allows the user to exit via a timed Escape-key hold. The codebase is cleaned, documented, and optimized for macOS.
|
||||||
211
src/main.c
Normal file
211
src/main.c
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <SDL_ttf.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Global Constants
|
||||||
|
* These define the window dimensions and are used for centering graphics.
|
||||||
|
*/
|
||||||
|
const int SCREEN_WIDTH = 800;
|
||||||
|
const int SCREEN_HEIGHT = 600;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Player Structure
|
||||||
|
* Even if invisible, we keep the player state to allow for future gameplay logic.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
float x, y;
|
||||||
|
float speed;
|
||||||
|
int size;
|
||||||
|
SDL_Color color;
|
||||||
|
} Player;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Game State Structure
|
||||||
|
* This is the 'Brain' of the game, containing everything needed to track the current session.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
bool is_running; // Controls the main game loop
|
||||||
|
float escape_timer; // Tracks how long the Escape key has been held (in ms)
|
||||||
|
int visible_chars; // Number of characters currently revealed in the typewriter text
|
||||||
|
uint32_t last_char_time; // Timestamp of the last character revealed
|
||||||
|
const char* message; // The string to be displayed
|
||||||
|
TTF_Font* font; // Pointer to the loaded TrueType font
|
||||||
|
Player player; // The player object
|
||||||
|
} Game;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes SDL, TTF, and the game state.
|
||||||
|
* Returns true if everything started correctly, false otherwise.
|
||||||
|
*/
|
||||||
|
bool init_game(Game *game) {
|
||||||
|
// Initialize SDL Video subsystem
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
||||||
|
printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize SDL_ttf for text rendering
|
||||||
|
if (TTF_Init() == -1) {
|
||||||
|
printf("SDL_ttf could not initialize! TTF_Error: %s\n", TTF_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the font file from assets. Size is 48 for a cinematic look.
|
||||||
|
game->font = TTF_OpenFont("assets/font.ttf", 48);
|
||||||
|
if (!game->font) {
|
||||||
|
printf("CRITICAL: Failed to load font! TTF_Error: %s\n", TTF_GetError());
|
||||||
|
} else {
|
||||||
|
printf("SUCCESS: Font loaded successfully!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup initial state
|
||||||
|
game->is_running = true;
|
||||||
|
game->escape_timer = 0.0f;
|
||||||
|
game->visible_chars = 0;
|
||||||
|
game->last_char_time = SDL_GetTicks();
|
||||||
|
game->message = "Hello, human.";
|
||||||
|
game->player = (Player){ .x = 100, .y = 100, .speed = 0.5f, .size = 32, .color = {255, 0, 0, 255} };
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes keyboard and window events.
|
||||||
|
* Handles the 'hold to quit' logic for the Escape key.
|
||||||
|
*/
|
||||||
|
void handle_input(Game *game, float delta_time) {
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
if (event.type == SDL_QUIT) {
|
||||||
|
game->is_running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t *state = SDL_GetKeyboardState(NULL);
|
||||||
|
|
||||||
|
// ESCAPE KEY LOGIC:
|
||||||
|
// If Escape is held, we add the actual time passed (delta_time) to the timer.
|
||||||
|
if (state[SDL_SCANCODE_ESCAPE]) {
|
||||||
|
game->escape_timer += delta_time;
|
||||||
|
if (game->escape_timer >= 1000.0f) { // Exactly 1000ms = 1 second
|
||||||
|
game->is_running = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Reset timer immediately if key is released
|
||||||
|
game->escape_timer = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Player Movement (Invisible for now)
|
||||||
|
if (state[SDL_SCANCODE_UP]) game->player.y -= game->player.speed;
|
||||||
|
if (state[SDL_SCANCODE_DOWN]) game->player.y += game->player.speed;
|
||||||
|
if (state[SDL_SCANCODE_LEFT]) game->player.x -= game->player.speed;
|
||||||
|
if (state[SDL_SCANCODE_RIGHT]) game->player.x += game->player.speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates game logic independent of rendering.
|
||||||
|
* Handles the typewriter timing.
|
||||||
|
*/
|
||||||
|
void update(Game *game) {
|
||||||
|
// TYPEWRITER LOGIC:
|
||||||
|
// Check if enough time has passed since the last letter appeared.
|
||||||
|
uint32_t current_time = SDL_GetTicks();
|
||||||
|
if (current_time - game->last_char_time >= 150) {
|
||||||
|
if (game->visible_chars < (int)strlen(game->message)) {
|
||||||
|
game->visible_chars++;
|
||||||
|
game->last_char_time = current_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep player within screen bounds
|
||||||
|
if (game->player.x < 0) game->player.x = 0;
|
||||||
|
if (game->player.x > SCREEN_WIDTH - game->player.size) game->player.x = SCREEN_WIDTH - game->player.size;
|
||||||
|
if (game->player.y < 0) game->player.y = 0;
|
||||||
|
if (game->player.y > SCREEN_HEIGHT - game->player.size) game->player.y = SCREEN_HEIGHT - game->player.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles all drawing operations.
|
||||||
|
* Clears screen and renders centered text.
|
||||||
|
*/
|
||||||
|
void render(SDL_Renderer *renderer, Game *game) {
|
||||||
|
// 1. Clear the screen to black
|
||||||
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||||
|
SDL_RenderClear(renderer);
|
||||||
|
|
||||||
|
// 2. Render the revealed portion of the text
|
||||||
|
if (game->font) {
|
||||||
|
char buffer[64] = {0};
|
||||||
|
if (game->visible_chars > 0) {
|
||||||
|
// Copy only the number of characters currently 'revealed'
|
||||||
|
strncpy(buffer, game->message, game->visible_chars);
|
||||||
|
buffer[game->visible_chars] = '\0';
|
||||||
|
|
||||||
|
SDL_Color white = {255, 255, 255, 255};
|
||||||
|
SDL_Surface* surface = TTF_RenderText_Blended(game->font, buffer, white);
|
||||||
|
|
||||||
|
if (surface) {
|
||||||
|
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
|
||||||
|
|
||||||
|
// CENTER THE TEXT:
|
||||||
|
// Subtract half the text width/height from center screen coordinates.
|
||||||
|
SDL_Rect text_rect = {
|
||||||
|
(SCREEN_WIDTH - surface->w) / 2,
|
||||||
|
(SCREEN_HEIGHT - surface->h) / 2,
|
||||||
|
surface->w,
|
||||||
|
surface->h
|
||||||
|
};
|
||||||
|
|
||||||
|
SDL_RenderCopy(renderer, texture, NULL, &text_rect);
|
||||||
|
SDL_FreeSurface(surface);
|
||||||
|
SDL_DestroyTexture(texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Swap the backbuffer to the screen
|
||||||
|
SDL_RenderPresent(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* args[]) {
|
||||||
|
Game game;
|
||||||
|
if (!init_game(&game)) return 1;
|
||||||
|
|
||||||
|
SDL_Window *window = SDL_CreateWindow("C Game Template", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
|
||||||
|
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||||
|
|
||||||
|
// FPS Locking variables
|
||||||
|
const int TARGET_FPS = 60;
|
||||||
|
const uint32_t FRAME_DELAY = 1000 / TARGET_FPS;
|
||||||
|
|
||||||
|
while (game.is_running) {
|
||||||
|
uint32_t frame_start = SDL_GetTicks();
|
||||||
|
|
||||||
|
// Calculate Delta Time (time since last frame) in milliseconds
|
||||||
|
// Since we lock to 60FPS, this is usually around 16.6ms.
|
||||||
|
float delta_time = 1000.0f / TARGET_FPS;
|
||||||
|
|
||||||
|
handle_input(&game, delta_time);
|
||||||
|
update(&game);
|
||||||
|
render(renderer, &game);
|
||||||
|
|
||||||
|
// FRAME CAPPER:
|
||||||
|
// Calculate how long the frame took and sleep the remaining time.
|
||||||
|
uint32_t frame_time = SDL_GetTicks() - frame_start;
|
||||||
|
if (FRAME_DELAY > frame_time) {
|
||||||
|
SDL_Delay(FRAME_DELAY - frame_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup memory
|
||||||
|
if (game.font) TTF_CloseFont(game.font);
|
||||||
|
SDL_DestroyRenderer(renderer);
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
TTF_Quit();
|
||||||
|
SDL_Quit();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue