diff options
| author | pack <pack@packgekko.xyz> | 2026-08-07 14:20:21 +0000 |
|---|---|---|
| committer | pack <pack@packgekko.xyz> | 2026-08-07 14:20:21 +0000 |
| commit | d10aa45825dcd9c7ed08dd66356a8c0f86870806 (patch) | |
| tree | a45f02bf15aa1591da07e2c197ae61727cd27ff5 | |
| parent | f1eff54aa5821ba2a09f29e684280f79e36a3104 (diff) | |
| download | tiny-d10aa45825dcd9c7ed08dd66356a8c0f86870806.tar.gz | |
final commit
project complete
| -rw-r--r-- | mus/mus_rêverie_-_debussy.ogg | bin | 0 -> 3948954 bytes | |||
| -rw-r--r-- | tiny.c | 505 |
2 files changed, 482 insertions, 23 deletions
diff --git a/mus/mus_rêverie_-_debussy.ogg b/mus/mus_rêverie_-_debussy.ogg Binary files differnew file mode 100644 index 0000000..222c750 --- /dev/null +++ b/mus/mus_rêverie_-_debussy.ogg @@ -1,7 +1,13 @@ #include "raylib.h" +#include "raymath.h" #include <math.h> +#include <stdlib.h> #define STAR_COUNT 200 +#define PLAYER_MAX_HP 100.0f +#define ENEMY_MAX_HP 100.0f +#define SWORD_DAMAGE 25.0f +#define SWORD_RANGE 5.0f typedef struct { @@ -18,9 +24,24 @@ typedef enum PAUSE_QUIT } PauseAction; +typedef struct +{ + bool swinging; + float time; + float duration; + bool damage_dealt; /* Prevents multi-hit per swing. */ +} Sword; + +typedef enum +{ + STATE_TITLE, + STATE_PLAYING +} GameState; + /* --- INIT --- */ -static void InitStars(Star *stars, int count, int width, int height) +static void +InitStars(Star *stars, int count, int width, int height) { for (int i = 0; i < count; i++) { @@ -33,7 +54,8 @@ static void InitStars(Star *stars, int count, int width, int height) /* --- DRAW: 2D BACKGROUND --- */ -static void DrawSky(int width, int height) +static void +DrawSky(int width, int height) { /* Two vertical gradients: white->blue (top), blue->black (bottom). Replaces 480 DrawLine calls — fixes the tearing. */ @@ -44,7 +66,8 @@ static void DrawSky(int width, int height) DrawRectangleGradientV(0, height / 2, width, height - height / 2, blue, black); } -static void DrawStars(const Star *stars, int count, float time) +static void +DrawStars(const Star *stars, int count, float time) { for (int i = 0; i < count; i++) { @@ -56,7 +79,8 @@ static void DrawStars(const Star *stars, int count, float time) } } -static void DrawCrosshair(int width, int height) +static void +DrawCrosshair(int width, int height) { int cx = width / 2; int cy = height / 2; @@ -67,9 +91,26 @@ static void DrawCrosshair(int width, int height) DrawLine(cx, cy + 3, cx, cy + 8, c); } +static void +DrawHealthBar(int x, int y, int w, int h, float hp, float max_hp, Color color) +{ + /* Background. */ + DrawRectangle(x, y, w, h, (Color){ 30, 30, 30, 200 }); + /* Fill proportional to HP (clamped 0..1). */ + float ratio = hp / max_hp; + if (ratio < 0.0f) + ratio = 0.0f; + if (ratio > 1.0f) + ratio = 1.0f; + DrawRectangle(x, y, (int)(w * ratio), h, color); + /* Border. */ + DrawRectangleLines(x, y, w, h, RAYWHITE); +} + /* --- DRAW: PAUSE MENU --- */ -static PauseAction DrawPauseMenu(int width, int height) +static PauseAction +DrawPauseMenu(int width, int height) { /* Dim overlay. */ DrawRectangle(0, 0, width, height, (Color){ 0, 0, 0, 160 }); @@ -100,9 +141,239 @@ static PauseAction DrawPauseMenu(int width, int height) return PAUSE_NONE; } +/* --- DRAW: TITLE SCREEN --- */ +static bool +DrawTitleScreen(int width, int height, float alpha) +{ + /* Title text. */ + const char *title = "prj tiny"; + int title_size = 80; + int title_w = MeasureText(title, title_size); + DrawText(title, (width - title_w) / 2, height / 2 - 120, title_size, + (Color){ 255, 255, 255, (unsigned char)(255 * alpha) }); + /* Subtitle. */ + const char *subtitle = "A raylib project"; + int sub_w = MeasureText(subtitle, 20); + DrawText(subtitle, (width - sub_w) / 2, height / 2 - 40, 20, + (Color){ 200, 200, 200, (unsigned char)(200 * alpha) }); + /* Play button. */ + Rectangle play_btn = { width / 2 - 80, height / 2 + 20, 160, 50 }; + bool play_hover = CheckCollisionPointRec(GetMousePosition(), play_btn); + unsigned char a = (unsigned char)(255 * alpha); + DrawRectangleRec(play_btn, + play_hover ? (Color){ 80, 80, 130, a } : (Color){ 50, 50, 90, a }); + DrawRectangleLinesEx(play_btn, 2, (Color){ 255, 255, 255, a }); + DrawText("PLAY", play_btn.x + 50, play_btn.y + 12, 30, + (Color){255, 255, 255, a }); + /* Hint. */ + const char *hint = "Click PLAY or press ENTER"; + int hint_w = MeasureText(hint, 18); + DrawText(hint, (width - hint_w) / 2, height - 60, 18, + (Color){ 150, 150, 150, (unsigned char)(180 * alpha) }); + if (play_hover && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) + return true; + return false; +} + +/* --- DRAW: SWORD --- */ + +static void +DrawSword(const Camera3D *camera, const Sword *sword) +{ + /* Camera basis vectors (forward, right, up). */ + Vector3 fwd = Vector3Normalize(Vector3Subtract(camera->target, camera->position)); + Vector3 right = Vector3Normalize(Vector3CrossProduct(fwd, (Vector3){ 0.0f, 1.0f, 0.0f })); + Vector3 up = Vector3CrossProduct(right, fwd); + /* Hand position: forward, to the right, and below the camera. */ + Vector3 hand = camera->position; + hand = Vector3Add(hand, Vector3Scale(fwd, 2.0f)); + hand = Vector3Add(hand, Vector3Scale(right, 1.1f)); + hand = Vector3Add(hand, Vector3Scale(up, -1.0f)); + /* Swing progress 0..1, eased (fast strike, slow recovery). */ + float t = 0.0f; + if (sword->swinging) + { + t = sword->time / sword->duration; + if (t > 1.0f) + t = 1.0f; + } + float ease = 1.0f - (1.0f - t) * (1.0f - t); /* easeOutQuad. */ + /* Blade direction via pitch (up/down) and yaw (left/right) */ + float pitch; + float yaw; + if (sword->swinging) + { + pitch = Lerp(1.0f, -0.6f, ease); /* raised -> lowered */ + yaw = Lerp(0.9f, -0.7f, ease); /* right -> left */ + } + else + { + pitch = -0.25f; + yaw = 0.15f; + } + /* Direction in camera-local space, then transformed to world. */ + Vector3 dir_local = + { + sinf(yaw) * cosf(pitch), + sinf(pitch), + cosf(yaw) * cosf(pitch) + }; + Vector3 dir_world = Vector3Add( + Vector3Add(Vector3Scale(right, dir_local.x), + Vector3Scale(up, dir_local.y)), + Vector3Scale(fwd, dir_local.z)); + float blade_len = 2.2f; + Vector3 tip = Vector3Add(hand, Vector3Scale(dir_world, blade_len)); + /* Handle: short stub behind the hand. */ + Vector3 handle_end = Vector3Subtract(hand, Vector3Scale(dir_world, 0.5f)); + DrawCylinderEx(handle_end, hand, 0.07f, 0.07f, 6, BROWN); + /* Guard: small block at the hand. */ + DrawCube(hand, 0.35f, 0.12f, 0.12f, DARKGRAY); + /* Blade: tapered cylinder from hand to tip. */ + DrawCylinderEx(hand, tip, 0.06f, 0.01f, 6, (Color){ 180, 190, 200, 255 }); + /* Tip cap. */ + DrawSphere(tip, 0.04f, (Color){ 220, 225, 235, 255 }); +} + +static void +DrawEnemySword(Vector3 origin, Vector3 face_dir, const Sword *sword) +{ + /* Basis vectors from the enemy's facing direction. */ + Vector3 fwd = Vector3Normalize(face_dir); + Vector3 right = Vector3Normalize(Vector3CrossProduct(fwd, (Vector3){ 0.0f, 1.0f, 0.0f })); + Vector3 up = { 0.0f, 1.0f, 0.0f }; + /* Hand: to the enemy's right, slightly forward and up. */ + Vector3 hand = origin; + hand = Vector3Add(hand, Vector3Scale(right, 1.3f)); + hand = Vector3Add(hand, Vector3Scale(fwd, 0.5f)); + hand = Vector3Add(hand, Vector3Scale(up, 0.5f)); + /* Swing progress 0..1, eased (same as player sword). */ + float t = 0.0f; + if (sword->swinging) + { + t = sword->time / sword -> duration; + if (t > 1.0f) + t = 1.0f; + } + float ease = 1.0f - (1.0f - t) * (1.0f - t); + float pitch; + float yaw; + if (sword->swinging) + { + pitch = Lerp(1.0f, 0.6f, ease); + yaw = Lerp(0.9f, -0.7f, ease); + } + else + { + pitch = -0.25f; + yaw = 0.15f; + } + Vector3 dir_local = { + sinf(yaw) * cosf(pitch), + sinf(pitch), + cosf(yaw) * cosf(pitch) + }; + Vector3 dir_world = Vector3Add( + Vector3Add(Vector3Scale(right, dir_local.x), + Vector3Scale(up, dir_local.y)), + Vector3Scale(fwd, dir_local.z)); + float blade_len = 2.2f; + Vector3 tip = Vector3Add(hand, Vector3Scale(dir_world, blade_len)); + Vector3 handle_end = Vector3Subtract(hand, Vector3Scale(dir_world, 0.5f)); + DrawCylinderEx(handle_end, hand, 0.07f, 0.07f, 6, BROWN); + DrawCube(hand, 0.35f, 0.12f, 0.12f, DARKGRAY); + /* Darker blade tint to distinguish from the player's sword. */ + DrawCylinderEx(hand, tip, 0.06f, 0.01f, 6, (Color){ 120, 125, 135, 255 }); + DrawSphere(tip, 0.04f, (Color){ 150, 155, 165, 255 }); +} + +/* --- SOUND GENERATION (procedural, no files needed) --- */ + +static Sound +MakeSwordSwingSound(void) +{ + /* Whoosh: noise burst + frequency sweep, fast decay. */ + int sampleRate = 44100; + int sampleCount = (int)(sampleRate * 0.25f); + short *data = malloc(sampleCount * sizeof(short)); + for (int i = 0; i < sampleCount; i++) + { + float t = (float)i / (float)sampleCount; + float env = expf(-t * 7.0f); + float noise = ((float)rand() / (float)RAND_MAX * 2.0f - 1.0f); + float sweep = sinf(t * 1200.0f) * (1.0f - t); + float sample = (noise * 0.6f + sweep * 0.4f) * env; + data[i] = (short)(sample * 28000.0f); + } + Wave wave = { 0 }; + wave.frameCount = sampleCount; + wave.sampleRate = sampleRate; + wave.sampleSize = 16; + wave.channels = 1; + wave.data = data; + Sound snd = LoadSoundFromWave(wave); + free(data); + return snd; +} + +static Sound +MakeFootstepSound(void) +{ + /* Short thud: low sine + noise, very fast decay. */ + int sampleRate = 44100; + int sampleCount = (int)(sampleRate * 0.09f); + short *data = malloc(sampleCount * sizeof(short)); + for (int i = 0; i < sampleCount; i++) + { + float t = (float)i / (float)sampleCount; + float env = expf(-t * 22.0f); + float noise = ((float)rand() / (float)RAND_MAX * 2.0f - 1.0f); + float thud = sinf(t * 70.0f) * 0.5f; + float sample = (noise * 0.5f + thud) * env * 0.5f; + data[i] = (short)(sample * 28000.0f); + } + Wave wave = { 0 }; + wave.frameCount = sampleCount; + wave.sampleRate = sampleRate; + wave.sampleSize = 16; + wave.channels = 1; + wave.data = data; + Sound snd = LoadSoundFromWave(wave); + free(data); + return snd; +} + +static Sound +MakeAmbienceSound(void) +{ + /* Low drone: two sine layers + faint wind noise. Loops. */ + int sampleRate = 44100; + int sampleCount = (int)(sampleRate * 3.0f); + short *data = malloc(sampleCount * sizeof(short)); + for (int i = 0; i < sampleCount; i++) + { + float t = (float)i / (float)sampleRate; + float drone = sinf(t * 2.0f * 3.14159265f * 55.0f) * 0.3f; + drone += sinf(t * 2.0f * 3.14159265f * 82.5f) * 0.15f; + float wind = ((float)rand() / (float)RAND_MAX * 2.0f - 1.0f) * 0.08f; + float sample = drone + wind; + data[i] = (short)(sample * 20000.0f); + } + Wave wave = { 0 }; + wave.frameCount = sampleCount; + wave.sampleRate = sampleRate; + wave.sampleSize = 16; + wave.channels = 1; + wave.data = data; + Sound snd = LoadSoundFromWave(wave); + free(data); + return snd; +} + /* --- MAIN --- */ -int main(int argc, char *argv[]) +int +main(int argc, char *argv[]) { (void)argc; (void)argv; @@ -111,6 +382,17 @@ int main(int argc, char *argv[]) InitWindow(screen_width, screen_height, "prj tiny"); SetTargetFPS(60); SetExitKey(KEY_NULL); /* Take over ESC ourselves. */ + InitAudioDevice(); + Sound snd_swing = MakeSwordSwingSound(); + Sound snd_footstep = MakeFootstepSound(); + Sound snd_ambience = MakeAmbienceSound(); + SetSoundVolume(snd_swing, 0.5f); + SetSoundVolume(snd_footstep, 0.35f); + SetSoundVolume(snd_ambience, 0.3f); + /* Background music (your own .ogg file). */ + Music music = LoadMusicStream("mus_rêverie_-_debussy.ogg"); + SetMusicVolume(music, 0.4f); + PlayMusicStream(music); /* 3D Camera. */ Camera3D camera = { 0 }; camera.position = (Vector3){ 0.0f, 2.0f, 8.0f }; @@ -121,11 +403,45 @@ int main(int argc, char *argv[]) Star stars[STAR_COUNT]; InitStars(stars, STAR_COUNT, screen_width, screen_height); bool paused = false; - DisableCursor(); /* Capture mouse for camera look. */ + Sword sword = { false, 0.0f, 0.35f, false }; + Sword enemy_sword = { false, 0.0f, 0.35f, false }; + float enemy_swing_timer = 3.0f; /* Seconds until next enemy swing. */ + float player_hp = PLAYER_MAX_HP; + float enemy_hp = ENEMY_MAX_HP; + float death_timer = 0.0f; /* Counts down respawn delay. */ + float end_fade = 0.0f; /* Fade-to-black after enemy death (KH-style). */ + bool game_ending = false; /* True once enemy slain; game fades out. */ + GameState game_state = STATE_TITLE; + float title_alpha = 0.0f; + bool title_exiting = false; + Vector3 prev_pos = camera.position; + float footstep_timer = 0.0f; + EnableCursor(); /* Cursor visible on title screen for clicking. */ while (!WindowShouldClose()) { + float dt = GetFrameTime(); + if (game_state == STATE_TITLE) + { + /* Fade in on enter, fade out when exiting. */ + if (title_exiting) + { + title_alpha -= dt * 3.0f; + if (title_alpha <= 0.0f) + { + title_alpha = 0.0f; + game_state = STATE_PLAYING; + DisableCursor(); + } + } + else + { + title_alpha += dt * 2.0f; /* ~0.5s fade in. */ + if (title_alpha > 1.0f) + title_alpha = 1.0f; + } + } /* --- INPUT --- */ - if (IsKeyPressed(KEY_ESCAPE)) + if (game_state == STATE_PLAYING && IsKeyPressed(KEY_ESCAPE)) { paused = !paused; if (paused) @@ -133,36 +449,179 @@ int main(int argc, char *argv[]) else DisableCursor(); } - if (!paused) + if (game_state == STATE_PLAYING && !paused) UpdateCamera(&camera, CAMERA_FIRST_PERSON); + /* Footsteps: detect horizontal movement, play at intervals. */ + if (game_state == STATE_PLAYING && !paused && player_hp > 0.0f) + { + float moved = Vector3Distance(camera.position, prev_pos); + if (moved > 0.001f) + { + footstep_timer -= dt; + if (footstep_timer <= 0.0f) + { + PlaySound(snd_footstep); + footstep_timer = 0.35f; + } + } + else + footstep_timer = 0.0f; + prev_pos = camera.position; + } + /* Sword swing: left-click to strike, frame-time timer. */ + if (game_state == STATE_PLAYING && !paused && IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && !sword.swinging && player_hp > 0.0f) + { + sword.swinging= true; + sword.time = 0.0f; + sword.damage_dealt = false; + PlaySound(snd_swing); + } + if (sword.swinging) + { + sword.time += GetFrameTime(); + /* Deal damage at swing midpoint if enemy is in range. */ + float t = sword.time / sword.duration; + if (t >= 0.4f && !sword.damage_dealt) + { + sword.damage_dealt = true; + float dist = Vector3Distance(camera.position, (Vector3){ 0.0f, 1.05f, 0.0f }); + if (dist <= SWORD_RANGE && enemy_hp > 0.0f) + enemy_hp -= SWORD_DAMAGE; + } + if (sword.time >= sword.duration) + sword.swinging = false; + } + /* Enemy sword: auto-swings on a random timer (2-4 seconds). */ + if (game_state == STATE_PLAYING && !paused) + { + if (!enemy_sword.swinging) + { + enemy_swing_timer -= GetFrameTime(); + if (enemy_swing_timer <= 0.0f && enemy_hp > 0.0f) + { + enemy_sword.swinging = true; + enemy_sword.time = 0.0f; + enemy_sword.damage_dealt = false; + SetSoundPitch(snd_swing, 0.8f); + PlaySound(snd_swing); + SetSoundPitch(snd_swing, 1.0f); + } + } + else + { + enemy_sword.time += GetFrameTime(); + /* Deal damage to player at swing midpoint if in range. */ + float et = enemy_sword.time / enemy_sword.duration; + if (et >= 0.4 && !enemy_sword.damage_dealt) + { + enemy_sword.damage_dealt = true; + float dist = Vector3Distance(camera.position, (Vector3){ 0.0f, 1.05f, 0.0f }); + if (dist <= SWORD_RANGE && player_hp > 0.0f) + player_hp -= SWORD_DAMAGE; + } + if (enemy_sword.time >= enemy_sword.duration) + { + enemy_sword.swinging = false; + enemy_swing_timer = (float)GetRandomValue(20, 40) / 10.0f; + } + } + } + /* --- DEATH / RESPAWN --- */ + if (game_state == STATE_PLAYING && !paused) + { + /* Enemy slain: trigger KH-style fade-to-black ending. */ + if (enemy_hp <= 0.0f && !game_ending) + game_ending = true; + if (game_ending) + { + end_fade += dt * 0.5f; /* ~2s fade to black. */ + if (end_fade >= 1.0f) + { + end_fade = 1.0f; + break; /* End the game. */ + } + } + /* Player death: respawn after a delay. */ + if (player_hp <= 0.0f && !game_ending) + { + death_timer += dt; + if (death_timer >= 2.0f) + { + player_hp = PLAYER_MAX_HP; + death_timer = 0.0f; + } + } + } + /* Looping ambient drone — replay when it finishes. */ + if (!IsSoundPlaying(snd_ambience)) + PlaySound(snd_ambience); + /* Keep music streaming (must be called every frame). */ + UpdateMusicStream(music); + /* --- DRAW --- */ float time = (float)GetTime(); BeginDrawing(); ClearBackground(BLACK); DrawSky(screen_width, screen_height); DrawStars(stars, STAR_COUNT, time); - BeginMode3D(camera); - DrawPlane((Vector3){ 0, 0, 0 }, (Vector2){ 256, 256 }, BEIGE); - DrawCube((Vector3){ 0, 1.05f, 0 }, 2, 2, 2, RED); - EndMode3D(); - if (!paused) + if (game_state == STATE_TITLE) { - DrawCrosshair(screen_width, screen_height); - DrawText("prj tiny - ESC to pause", 10, 10, 20, DARKGRAY); + bool play_clicked = DrawTitleScreen(screen_width, screen_height, title_alpha); + if ((play_clicked || IsKeyPressed(KEY_ENTER)) && !title_exiting) + title_exiting = true; } - else + if (game_state == STATE_PLAYING) { - PauseAction action = DrawPauseMenu(screen_width, screen_height); - if (action == PAUSE_RESUME) + BeginMode3D(camera); + DrawPlane((Vector3){ 0, 0, 0 }, (Vector2){ 256, 256 }, BEIGE); + DrawCube((Vector3){ 0, 1.05f, 0 }, 2, 2, 2, RED); + if (!paused) + { + DrawSword(&camera, &sword); + Vector3 enemy_pos = { 0.0f, 1.05f, 0.0f }; + Vector3 face_dir = Vector3Subtract(camera.position, enemy_pos); + face_dir.y = 0.0f; /* Stay on the horizontal plane. */ + DrawEnemySword(enemy_pos, face_dir, &enemy_sword); + } + EndMode3D(); + if (!paused) { - paused = false; - DisableCursor(); + DrawCrosshair(screen_width, screen_height); + DrawText("prj tiny - ESC to pause", 10, 10, 20, DARKGRAY); + /* Player health bar (top-left). */ + DrawHealthBar(10, 40, 200, 20, player_hp, PLAYER_MAX_HP, GREEN); + DrawText("PLAYER", 10, 62, 16, RAYWHITE); + /* Enemy health bar (top-right). */ + DrawHealthBar(screen_width - 210, 40, 200, 20, enemy_hp, ENEMY_MAX_HP, RED); + DrawText("ENEMY", screen_width - 210, 62, 16, RAYWHITE); + /* Death messages. */ + if (player_hp <= 0.0f) + DrawText("YOU DIED", screen_width / 2 - 80, screen_height / 2, 40, RED); + else if (enemy_hp <= 0.0f) + DrawText("ENEMY SLAIN", screen_width / 2 - 110, screen_height / 2, 40, GREEN); + } + else + { + PauseAction action = DrawPauseMenu(screen_width, screen_height); + if (action == PAUSE_RESUME) + { + paused = false; + DisableCursor(); + } + else if (action == PAUSE_QUIT) + break; } - else if (action == PAUSE_QUIT) - break; } + if (game_ending) + DrawRectangle(0, 0, screen_width, screen_height, + (Color){ 0, 0, 0, (unsigned char)(255 * end_fade) }); EndDrawing(); } + UnloadMusicStream(music); + UnloadSound(snd_swing); + UnloadSound(snd_footstep); + UnloadSound(snd_ambience); + CloseAudioDevice(); CloseWindow(); return 0; } |