From 85b3f8c6217da5872581f02ad0ea4a789b1196bf Mon Sep 17 00:00:00 2001 From: cwj3rcb Date: Sun, 14 Sep 2025 11:13:09 -0500 Subject: [PATCH] Set up CMAKE for Linux and Windows. --- .gitignore | 54 ++++++++ CMakeLists.txt | 60 +++++++++ How_To_Build.md | 26 ++++ run.sh | 8 ++ src/main.c | 329 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 477 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 How_To_Build.md create mode 100755 run.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..15071d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# ------------------------ +# Build artifacts +# ------------------------ +/build/ +/CMakeFiles/ +/CMakeCache.txt +/Makefile +/*.ninja +/.ninja_deps +/.ninja_log +/*.sln +/*.vcxproj* +/compile_commands.json +/install_manifest.txt + +# ------------------------ +# Binary outputs +# ------------------------ +/*.exe +/*.dll +/*.so +/*.dylib +/*.a +/*.lib +/*.obj +/*.o +/*.pdb + +# ------------------------ +# OS / editor junk +# ------------------------ +.DS_Store +Thumbs.db +*.swp +*.swo +*.user +*.VC.db +*.idea/ +*.vscode/ +*.vs/ + +# ------------------------ +# Logs / temp +# ------------------------ +*.log +*.tmp + +# ------------------------ +# Python venv (if you use scripts/tools) +/venv/ + +# ------------------------ +# Keep empty asset dirs if needed +assets/**/.gitkeep diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..34de0a5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,60 @@ +cmake_minimum_required(VERSION 3.20) +project(FPS_DRUID LANGUAGES C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) + +# Default build type for single-config generators +if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE) +endif() + +# ---------- Fetch raylib ---------- +include(FetchContent) +set(FETCHCONTENT_QUIET FALSE) +set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) +set(BUILD_GAMES OFF CACHE BOOL "" FORCE) + +FetchContent_Declare( + raylib + GIT_REPOSITORY https://github.com/raysan5/raylib.git + GIT_TAG 5.5 # stable release + GIT_PROGRESS TRUE +) +FetchContent_MakeAvailable(raylib) + +# ---------- Paths (match your tree) ---------- +set(SRC_DIR "${CMAKE_CURRENT_LIST_DIR}/src") +set(INC_DIR "${CMAKE_CURRENT_LIST_DIR}/include") +set(ASSETS_DIR "${CMAKE_CURRENT_LIST_DIR}/assets") + +# ---------- Sources ---------- +file(GLOB_RECURSE PROJECT_SOURCES CONFIGURE_DEPENDS "${SRC_DIR}/*.c") + +add_executable(${PROJECT_NAME} ${PROJECT_SOURCES}) +target_include_directories(${PROJECT_NAME} PRIVATE "${SRC_DIR}" "${INC_DIR}") +target_link_libraries(${PROJECT_NAME} PRIVATE raylib) + +# ---------- Warnings / ASan ---------- +if(MSVC) + target_compile_options(${PROJECT_NAME} PRIVATE /W4 /permissive-) +else() + target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wshadow -Wconversion -Wno-unused-parameter) + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + target_compile_options(${PROJECT_NAME} PRIVATE -fsanitize=address -fno-omit-frame-pointer) + target_link_options(${PROJECT_NAME} PRIVATE -fsanitize=address) + endif() +endif() + +# ---------- Assets: copy next to the exe ---------- +add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${ASSETS_DIR}" "$/assets" + COMMENT "Copying assets next to the executable" +) + +# Compile-time asset root: absolute in Debug; relative otherwise +target_compile_definitions(${PROJECT_NAME} PRIVATE + $<$:ASSETS_PATH="${ASSETS_DIR}/"> + $<$>:ASSETS_PATH="./assets/"> +) \ No newline at end of file diff --git a/How_To_Build.md b/How_To_Build.md new file mode 100644 index 0000000..1f8902f --- /dev/null +++ b/How_To_Build.md @@ -0,0 +1,26 @@ +Use these commands in your base directory the first time. +----------------------------------------------------- +LINUX +cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug +cmake --build build -j +./build/FPS_DRUID + +WINDOWS (MSVC) +cmake -S . -B build -G "Visual Studio 17 2022" -A x64 +cmake --build build --config Debug +build\Debug\FPS_DRUID.exe +----------------------------------------------------- + +To subsequently rebuild and run use +----------------------------------------------------- +cmake --build build -j +./build/FPS_DRUID +----------------------------------------------------- + +If you want a clean rebuild +----------------------------------------------------- +rm -rf build +cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug +cmake --build build -j +./build/FPS_DRUID +----------------------------------------------------- \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..088ac43 --- /dev/null +++ b/run.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -e + +# Rebuild +cmake --build build -j + +# Run +./build/FPS_DRUID \ No newline at end of file diff --git a/src/main.c b/src/main.c index e69de29..e65fa02 100644 --- a/src/main.c +++ b/src/main.c @@ -0,0 +1,329 @@ +/******************************************************************************************* +* +* raylib [core] example - 3d camera fps +* +* Example complexity rating: [★★★☆] 3/4 +* +* Example originally created with raylib 5.5, last time updated with raylib 5.5 +* +* Example contributed by Agnis Aldins (@nezvers) and reviewed by Ramon Santamaria (@raysan5) +* +* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software +* +* Copyright (c) 2025 Agnis Aldins (@nezvers) +* +********************************************************************************************/ + +#include "raylib.h" + +#include "raymath.h" + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +// Movement constants +#define GRAVITY 32.0f +#define MAX_SPEED 20.0f +#define CROUCH_SPEED 5.0f +#define JUMP_FORCE 12.0f +#define MAX_ACCEL 150.0f +// Grounded drag +#define FRICTION 0.86f +// Increasing air drag, increases strafing speed +#define AIR_DRAG 0.98f +// Responsiveness for turning movement direction to looked direction +#define CONTROL 15.0f +#define CROUCH_HEIGHT 0.0f +#define STAND_HEIGHT 1.0f +#define BOTTOM_HEIGHT 0.5f + +#define NORMALIZE_INPUT 0 + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// Body structure +typedef struct { + Vector3 position; + Vector3 velocity; + Vector3 dir; + bool isGrounded; +} Body; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static Vector2 sensitivity = { 0.001f, 0.001f }; + +static Body player = { 0 }; +static Vector2 lookRotation = { 0 }; +static float headTimer = 0.0f; +static float walkLerp = 0.0f; +static float headLerp = STAND_HEIGHT; +static Vector2 lean = { 0 }; + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- +static void DrawLevel(void); +static void UpdateCameraFPS(Camera *camera); +static void UpdateBody(Body *body, float rot, char side, char forward, bool jumpPressed, bool crouchHold); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 1980; + const int screenHeight = 1080; + + InitWindow(screenWidth, screenHeight, "raylib [core] example - 3d camera fps"); + + // Initialize camera variables + // NOTE: UpdateCameraFPS() takes care of the rest + Camera camera = { 0 }; + camera.fovy = 60.0f; + camera.projection = CAMERA_PERSPECTIVE; + camera.position = (Vector3){ + player.position.x, + player.position.y + (BOTTOM_HEIGHT + headLerp), + player.position.z, + }; + + UpdateCameraFPS(&camera); // Update camera parameters + + DisableCursor(); // Limit cursor to relative movement inside the window + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + Vector2 mouseDelta = GetMouseDelta(); + lookRotation.x -= mouseDelta.x*sensitivity.x; + lookRotation.y += mouseDelta.y*sensitivity.y; + + char sideway = (IsKeyDown(KEY_D) - IsKeyDown(KEY_A)); + char forward = (IsKeyDown(KEY_W) - IsKeyDown(KEY_S)); + bool crouching = IsKeyDown(KEY_LEFT_CONTROL); + UpdateBody(&player, lookRotation.x, sideway, forward, IsKeyPressed(KEY_SPACE), crouching); + + float delta = GetFrameTime(); + headLerp = Lerp(headLerp, (crouching ? CROUCH_HEIGHT : STAND_HEIGHT), 20.0f*delta); + camera.position = (Vector3){ + player.position.x, + player.position.y + (BOTTOM_HEIGHT + headLerp), + player.position.z, + }; + + if (player.isGrounded && ((forward != 0) || (sideway != 0))) + { + headTimer += delta*3.0f; + walkLerp = Lerp(walkLerp, 1.0f, 10.0f*delta); + camera.fovy = Lerp(camera.fovy, 55.0f, 5.0f*delta); + } + else + { + walkLerp = Lerp(walkLerp, 0.0f, 10.0f*delta); + camera.fovy = Lerp(camera.fovy, 60.0f, 5.0f*delta); + } + + lean.x = Lerp(lean.x, sideway*0.02f, 10.0f*delta); + lean.y = Lerp(lean.y, forward*0.015f, 10.0f*delta); + + UpdateCameraFPS(&camera); + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + BeginMode3D(camera); + DrawLevel(); + EndMode3D(); + + // Draw info box + DrawRectangle(5, 5, 330, 75, Fade(SKYBLUE, 0.5f)); + DrawRectangleLines(5, 5, 330, 75, BLUE); + + DrawText("Camera controls:", 15, 15, 10, BLACK); + DrawText("- Move keys: W, A, S, D, Space, Left-Ctrl", 15, 30, 10, BLACK); + DrawText("- Look around: arrow keys or mouse", 15, 45, 10, BLACK); + DrawText(TextFormat("- Velocity Len: (%06.3f)", Vector2Length((Vector2){ player.velocity.x, player.velocity.z })), 15, 60, 10, BLACK); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- +// Update body considering current world state +void UpdateBody(Body *body, float rot, char side, char forward, bool jumpPressed, bool crouchHold) +{ + Vector2 input = (Vector2){ (float)side, (float)-forward }; + +#if defined(NORMALIZE_INPUT) + // Slow down diagonal movement + if ((side != 0) && (forward != 0)) input = Vector2Normalize(input); +#endif + + float delta = GetFrameTime(); + + if (!body->isGrounded) body->velocity.y -= GRAVITY*delta; + + if (body->isGrounded && jumpPressed) + { + body->velocity.y = JUMP_FORCE; + body->isGrounded = false; + + // Sound can be played at this moment + //SetSoundPitch(fxJump, 1.0f + (GetRandomValue(-100, 100)*0.001)); + //PlaySound(fxJump); + } + + Vector3 front = (Vector3){ sin(rot), 0.f, cos(rot) }; + Vector3 right = (Vector3){ cos(-rot), 0.f, sin(-rot) }; + + Vector3 desiredDir = (Vector3){ input.x*right.x + input.y*front.x, 0.0f, input.x*right.z + input.y*front.z, }; + body->dir = Vector3Lerp(body->dir, desiredDir, CONTROL*delta); + + float decel = (body->isGrounded ? FRICTION : AIR_DRAG); + Vector3 hvel = (Vector3){ body->velocity.x*decel, 0.0f, body->velocity.z*decel }; + + float hvelLength = Vector3Length(hvel); // Magnitude + if (hvelLength < (MAX_SPEED*0.01f)) hvel = (Vector3){ 0 }; + + // This is what creates strafing + float speed = Vector3DotProduct(hvel, body->dir); + + // Whenever the amount of acceleration to add is clamped by the maximum acceleration constant, + // a Player can make the speed faster by bringing the direction closer to horizontal velocity angle + // More info here: https://youtu.be/v3zT3Z5apaM?t=165 + float maxSpeed = (crouchHold? CROUCH_SPEED : MAX_SPEED); + float accel = Clamp(maxSpeed - speed, 0.f, MAX_ACCEL*delta); + hvel.x += body->dir.x*accel; + hvel.z += body->dir.z*accel; + + body->velocity.x = hvel.x; + body->velocity.z = hvel.z; + + body->position.x += body->velocity.x*delta; + body->position.y += body->velocity.y*delta; + body->position.z += body->velocity.z*delta; + + // Fancy collision system against the floor + if (body->position.y <= 0.0f) + { + body->position.y = 0.0f; + body->velocity.y = 0.0f; + body->isGrounded = true; // Enable jumping + } +} + +// Update camera for FPS behaviour +static void UpdateCameraFPS(Camera *camera) +{ + const Vector3 up = (Vector3){ 0.0f, 1.0f, 0.0f }; + const Vector3 targetOffset = (Vector3){ 0.0f, 0.0f, -1.0f }; + + // Left and right + Vector3 yaw = Vector3RotateByAxisAngle(targetOffset, up, lookRotation.x); + + // Clamp view up + float maxAngleUp = Vector3Angle(up, yaw); + maxAngleUp -= 0.001f; // Avoid numerical errors + if ( -(lookRotation.y) > maxAngleUp) { lookRotation.y = -maxAngleUp; } + + // Clamp view down + float maxAngleDown = Vector3Angle(Vector3Negate(up), yaw); + maxAngleDown *= -1.0f; // Downwards angle is negative + maxAngleDown += 0.001f; // Avoid numerical errors + if ( -(lookRotation.y) < maxAngleDown) { lookRotation.y = -maxAngleDown; } + + // Up and down + Vector3 right = Vector3Normalize(Vector3CrossProduct(yaw, up)); + + // Rotate view vector around right axis + float pitchAngle = -lookRotation.y - lean.y; + pitchAngle = Clamp(pitchAngle, -PI/2 + 0.0001f, PI/2 - 0.0001f); // Clamp angle so it doesn't go past straight up or straight down + Vector3 pitch = Vector3RotateByAxisAngle(yaw, right, pitchAngle); + + // Head animation + // Rotate up direction around forward axis + float headSin = sin(headTimer*PI); + float headCos = cos(headTimer*PI); + const float stepRotation = 0.01f; + camera->up = Vector3RotateByAxisAngle(up, pitch, headSin*stepRotation + lean.x); + + // Camera BOB + const float bobSide = 0.1f; + const float bobUp = 0.15f; + Vector3 bobbing = Vector3Scale(right, headSin*bobSide); + bobbing.y = fabsf(headCos*bobUp); + + camera->position = Vector3Add(camera->position, Vector3Scale(bobbing, walkLerp)); + camera->target = Vector3Add(camera->position, pitch); +} + +// Draw game level +static void DrawLevel(void) +{ + const int floorExtent = 25; + const float tileSize = 5.0f; + const Color tileColor1 = (Color){ 150, 200, 200, 255 }; + + // Floor tiles + for (int y = -floorExtent; y < floorExtent; y++) + { + for (int x = -floorExtent; x < floorExtent; x++) + { + if ((y & 1) && (x & 1)) + { + DrawPlane((Vector3){ x*tileSize, 0.0f, y*tileSize}, (Vector2){ tileSize, tileSize }, tileColor1); + } + else if (!(y & 1) && !(x & 1)) + { + DrawPlane((Vector3){ x*tileSize, 0.0f, y*tileSize}, (Vector2){ tileSize, tileSize }, LIGHTGRAY); + } + } + } + + const Vector3 towerSize = (Vector3){ 16.0f, 32.0f, 16.0f }; + const Color towerColor = (Color){ 150, 200, 200, 255 }; + + Vector3 towerPos = (Vector3){ 16.0f, 16.0f, 16.0f }; + DrawCubeV(towerPos, towerSize, towerColor); + DrawCubeWiresV(towerPos, towerSize, DARKBLUE); + + towerPos.x *= -1; + DrawCubeV(towerPos, towerSize, towerColor); + DrawCubeWiresV(towerPos, towerSize, DARKBLUE); + + towerPos.z *= -1; + DrawCubeV(towerPos, towerSize, towerColor); + DrawCubeWiresV(towerPos, towerSize, DARKBLUE); + + towerPos.x *= -1; + DrawCubeV(towerPos, towerSize, towerColor); + DrawCubeWiresV(towerPos, towerSize, DARKBLUE); + + // Red sun + DrawSphere((Vector3){ 300.0f, 300.0f, 0.0f }, 100.0f, (Color){ 255, 0, 0, 255 }); +} \ No newline at end of file