From 7037f131dce990efbf838fe53eeef62408b61640 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 14 Aug 2025 20:37:52 +0200 Subject: [PATCH] REVIEWED: example: `core_3d_fps_controller` --- examples/core/core_3d_fps_controller.c | 419 ++++++++++++------------- examples/core/resources/huh_jump.wav | Bin 19050 -> 0 bytes tools/rexm/examples_report.md | 1 + 3 files changed, 194 insertions(+), 226 deletions(-) delete mode 100644 examples/core/resources/huh_jump.wav diff --git a/examples/core/core_3d_fps_controller.c b/examples/core/core_3d_fps_controller.c index c4ef367e9..68fec1af9 100644 --- a/examples/core/core_3d_fps_controller.c +++ b/examples/core/core_3d_fps_controller.c @@ -1,10 +1,10 @@ /******************************************************************************************* * -* raylib [core] example - Input Gestures for Web +* raylib [core] example - 3d first-person camera controller * * Example complexity rating: [★★★☆] 3/4 -* -* Example originally created with raylib 5.5 +* +* 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) * @@ -16,65 +16,60 @@ ********************************************************************************************/ #include "raylib.h" + #include "raymath.h" #include "rcamera.h" -#if defined(PLATFORM_WEB) -#include -#endif +//---------------------------------------------------------------------------------- +// 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 -#if defined(PLATFORM_DESKTOP) -#define GLSL_VERSION 330 -#else // PLATFORM_ANDROID, PLATFORM_WEB -#define GLSL_VERSION 100 -#endif - - -/* Movement constants */ -#define GRAVITY 32.f -#define MAX_SPEED 20.f -#define CROUCH_SPEED 5.f -#define JUMP_FORCE 12.f -#define MAX_ACCEL 150.f -/* 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.f -#define CROUCH_HEIGHT 0.f -#define STAND_HEIGHT 1.f -#define BOTTOM_HEIGHT 0.5f - -#define NORMALIZE_INPUT 0 +#define NORMALIZE_INPUT 0 +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// Body structure typedef struct { Vector3 position; Vector3 velocity; Vector3 dir; bool isGrounded; - Sound soundJump; -}Body; +} Body; -const int screenWidth = 800; -const int screenHeight = 450; -Vector2 sensitivity = { 0.001f, 0.001f }; +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static Vector2 sensitivity = { 0.001f, 0.001f }; -Body player; -Camera camera; -Vector2 lookRotation = { 0 }; -float headTimer; -float walkLerp; -float headLerp; -Vector2 lean; +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 }; -void UpdateDrawFrame(void); // Update and Draw one frame - -void DrawLevel(); - -void UpdateCameraAngle(); - -void UpdateBody(Body* body, float rot, char side, char forward, bool jumpPressed, bool crouchHold); +//---------------------------------------------------------------------------------- +// Module functions declaration +//---------------------------------------------------------------------------------- +static void DrawLevel(void); +static void UpdateCameraAngle(Camera *camera); +static void UpdateBody(Body *body, float rot, char side, char forward, bool jumpPressed, bool crouchHold); //------------------------------------------------------------------------------------ // Program main entry point @@ -83,213 +78,186 @@ int main(void) { // Initialization //-------------------------------------------------------------------------------------- - InitWindow(screenWidth, screenHeight, "Raylib Quake-like controller"); - InitAudioDevice(); + const int screenWidth = 800; + const int screenHeight = 450; - player = (Body){ Vector3Zero(), Vector3Zero(), Vector3Zero(), false, LoadSound("resources/huh_jump.wav")}; - camera = (Camera){ 0 }; - camera.fovy = 60.f; // Camera field-of-view Y - camera.projection = CAMERA_PERSPECTIVE; // Camera projection type + InitWindow(screenWidth, screenHeight, "raylib [core] example - 3d first-person camera controller"); - lookRotation = Vector2Zero(); - headTimer = 0.f; - walkLerp = 0.f; - headLerp = STAND_HEIGHT; - lean = Vector2Zero(); - - camera.position = (Vector3){ - player.position.x, - player.position.y + (BOTTOM_HEIGHT + headLerp), - player.position.z, - }; - UpdateCameraAngle(); - - DisableCursor(); // Limit cursor to relative movement inside the window - -#if defined(PLATFORM_WEB) - emscripten_set_main_loop(UpdateDrawFrame, 0, 1); -#else - - SetTargetFPS(60); // Set our game to run at 60 frames-per-second - //-------------------------------------------------------------------------------------- - - // Main game loop - while (!WindowShouldClose()) // Detect window close button or ESC key - { - UpdateDrawFrame(); - } -#endif - - // De-Initialization - //-------------------------------------------------------------------------------------- - UnloadSound(player.soundJump); - CloseAudioDevice(); - CloseWindow(); // Close window and OpenGL context - //-------------------------------------------------------------------------------------- - - return 0; -} - -void UpdateDrawFrame(void) -{ - // Update - //---------------------------------------------------------------------------------- - - Vector2 mouse_delta = GetMouseDelta(); - lookRotation.x -= mouse_delta.x * sensitivity.x; - lookRotation.y += mouse_delta.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.f * delta); + // Initialize camera variables + // NOTE: UpdateCameraAngle() 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, }; - if (player.isGrounded && (forward != 0 || sideway != 0)) { - headTimer += delta * 3.f; - walkLerp = Lerp(walkLerp, 1.f, 10.f * delta); - camera.fovy = Lerp(camera.fovy, 55.f, 5.f * delta); - } - else { - walkLerp = Lerp(walkLerp, 0.f, 10.f * delta); - camera.fovy = Lerp(camera.fovy, 60.f, 5.f * delta); + UpdateCameraAngle(&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 mouse_delta = GetMouseDelta(); + lookRotation.x -= mouse_delta.x*sensitivity.x; + lookRotation.y += mouse_delta.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); + + UpdateCameraAngle(&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(); + //---------------------------------------------------------------------------------- } - lean.x = Lerp(lean.x, sideway * 0.02f, 10.f * delta); - lean.y = Lerp(lean.y, forward * 0.015f, 10.f * delta); + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- - UpdateCameraAngle(); - - // Draw - //---------------------------------------------------------------------------------- - BeginDrawing(); - - ClearBackground(RAYWHITE); - - BeginMode3D(camera); - - DrawLevel(); - - EndMode3D(); - - // Draw info box - DrawRectangle(5, 5, 330, 100, Fade(SKYBLUE, 0.5f)); - DrawRectangleLines(5, 5, 330, 100, 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(); - //---------------------------------------------------------------------------------- + return 0; } -void UpdateBody(Body* body, float rot, char side, char forward, bool jumpPressed, bool crouchHold) +//---------------------------------------------------------------------------------- +// Module functions definition +//---------------------------------------------------------------------------------- +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); - } + if ((side != 0) && (forward != 0)) input = Vector2Normalize(input); #endif float delta = GetFrameTime(); - if (!body->isGrounded) - { - body->velocity.y -= GRAVITY * delta; - } - if (body->isGrounded && jumpPressed) + if (!body->isGrounded) body->velocity.y -= GRAVITY*delta; + + if (body->isGrounded && jumpPressed) { body->velocity.y = JUMP_FORCE; body->isGrounded = false; - SetSoundPitch(body->soundJump, 1.f + (GetRandomValue(-100, 100) * 0.001)); - PlaySound(body->soundJump); + + // Sound can be played at this moment + //SetSoundPitch(fxJump, 1.0f + (GetRandomValue(-100, 100)*0.001)); + //PlaySound(fxJump); } - Vector3 front_vec = (Vector3){ sin(rot), 0.f, cos(rot) }; - Vector3 right_vec = (Vector3){ cos(-rot), 0.f, sin(-rot) }; + Vector3 front = (Vector3){ sin(rot), 0.f, cos(rot) }; + Vector3 right = (Vector3){ cos(-rot), 0.f, sin(-rot) }; - Vector3 desired_dir = (Vector3){ - input.x * right_vec.x + input.y * front_vec.x, - 0.f, - input.x * right_vec.z + input.y * front_vec.z, - }; + 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); - body->dir = Vector3Lerp(body->dir, desired_dir, CONTROL * delta); + float decel = (body->isGrounded ? FRICTION : AIR_DRAG); + Vector3 hvel = (Vector3){ body->velocity.x*decel, 0.0f, body->velocity.z*decel }; - float decel = body->isGrounded ? FRICTION : AIR_DRAG; - Vector3 hvel = (Vector3){ - body->velocity.x * decel, - 0.f, - body->velocity.z * decel - }; + float hvelLength = Vector3Length(hvel); // Magnitude + if (hvelLength < (MAX_SPEED*0.01f)) hvel = (Vector3){ 0 }; - float hvel_length = Vector3Length(hvel); // a.k.a. magnitude - if (hvel_length < MAX_SPEED * 0.01f) { - hvel = (Vector3){ 0 }; - } - - /* This is what creates strafing */ + // 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 max_speed = crouchHold ? CROUCH_SPEED : MAX_SPEED; - float accel = Clamp(max_speed - speed, 0.f, MAX_ACCEL * delta); - hvel.x += body->dir.x * accel; - hvel.z += body->dir.z * accel; + // 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; + 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.f) + // Fancy collision system against the floor + if (body->position.y <= 0.0f) { - body->position.y = 0.f; - body->velocity.y = 0.f; - body->isGrounded = true; // <= enables jumping + body->position.y = 0.0f; + body->velocity.y = 0.0f; + body->isGrounded = true; // Enable jumping } } -void UpdateCameraAngle() +// Update camera +static void UpdateCameraAngle(Camera *camera) { - const Vector3 up = (Vector3){ 0.f, 1.f, 0.f }; - const Vector3 targetOffset = (Vector3){ 0.f, 0.f, -1.f }; + const Vector3 up = (Vector3){ 0.0f, 1.0f, 0.0f }; + const Vector3 targetOffset = (Vector3){ 0.0f, 0.0f, -1.0f }; - /* Left & Right */ + // Left and right Vector3 yaw = Vector3RotateByAxisAngle(targetOffset, up, lookRotation.x); // Clamp view up float maxAngleUp = Vector3Angle(up, yaw); - maxAngleUp -= 0.001f; // avoid numerical errors + 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 + maxAngleDown *= -1.0f; // Downwards angle is negative + maxAngleDown += 0.001f; // Avoid numerical errors if ( -(lookRotation.y) < maxAngleDown) { lookRotation.y = -maxAngleDown; } - /* Up & Down */ + // Up and down Vector3 right = Vector3Normalize(Vector3CrossProduct(yaw, up)); // Rotate view vector around right axis @@ -297,49 +265,48 @@ void UpdateCameraAngle() // Head animation // Rotate up direction around forward axis - float _sin = sin(headTimer * PI); - float _cos = cos(headTimer * PI); + float headSin = sin(headTimer*PI); + float headCos = cos(headTimer*PI); const float stepRotation = 0.01f; - camera.up = Vector3RotateByAxisAngle(up, pitch, _sin * stepRotation + lean.x); + camera->up = Vector3RotateByAxisAngle(up, pitch, headSin*stepRotation + lean.x); - /* BOB */ + // Camera BOB const float bobSide = 0.1f; const float bobUp = 0.15f; - Vector3 bobbing = Vector3Scale(right, _sin * bobSide); - bobbing.y = fabsf(_cos * bobUp); - camera.position = Vector3Add(camera.position, Vector3Scale(bobbing, walkLerp)); + Vector3 bobbing = Vector3Scale(right, headSin*bobSide); + bobbing.y = fabsf(headCos*bobUp); - camera.target = Vector3Add(camera.position, pitch); + camera->position = Vector3Add(camera->position, Vector3Scale(bobbing, walkLerp)); + camera->target = Vector3Add(camera->position, pitch); } - -void DrawLevel() +// Draw game level +static void DrawLevel(void) { const int floorExtent = 25; - const float tileSize = 5.f; + const float tileSize = 5.0f; const Color tileColor1 = (Color){ 150, 200, 200, 255 }; + // Floor tiles - for (int y = -floorExtent; y < floorExtent; y++) + for (int y = -floorExtent; y < floorExtent; y++) { - for (int x = -floorExtent; x < floorExtent; x++) + for (int x = -floorExtent; x < floorExtent; x++) { - if ((y & 1) && (x & 1)) + if ((y & 1) && (x & 1)) { - DrawPlane((Vector3) { x * tileSize, 0.f, y * tileSize}, - (Vector2) {tileSize, tileSize}, tileColor1); + DrawPlane((Vector3){ x*tileSize, 0.0f, y*tileSize}, (Vector2){ tileSize, tileSize }, tileColor1); } - else if(!(y & 1) && !(x & 1)) + else if (!(y & 1) && !(x & 1)) { - DrawPlane((Vector3) { x * tileSize, 0.f, y * tileSize}, - (Vector2) {tileSize, tileSize}, LIGHTGRAY); + DrawPlane((Vector3){ x*tileSize, 0.0f, y*tileSize}, (Vector2){ tileSize, tileSize }, LIGHTGRAY); } } } - - const Vector3 towerSize = (Vector3){ 16.f, 32.f, 16.f }; + + const Vector3 towerSize = (Vector3){ 16.0f, 32.0f, 16.0f }; const Color towerColor = (Color){ 150, 200, 200, 255 }; - Vector3 towerPos = (Vector3){ 16.f, 16.f, 16.f }; + Vector3 towerPos = (Vector3){ 16.0f, 16.0f, 16.0f }; DrawCubeV(towerPos, towerSize, towerColor); DrawCubeWiresV(towerPos, towerSize, DARKBLUE); @@ -356,5 +323,5 @@ void DrawLevel() DrawCubeWiresV(towerPos, towerSize, DARKBLUE); // Red sun - DrawSphere((Vector3) { 300.f, 300.f, 0.f }, 100.f, (Color) { 255, 0, 0, 255 }); + DrawSphere((Vector3){ 300.0f, 300.0f, 0.0f }, 100.0f, (Color){ 255, 0, 0, 255 }); } diff --git a/examples/core/resources/huh_jump.wav b/examples/core/resources/huh_jump.wav deleted file mode 100644 index 9ca8a2e683ef87d19f594be54d04a40dd0bade10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19050 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