| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- /*******************************************************************************************
- *
- * 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 Aldiņš (@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 Aldiņš (@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 = 800;
- const int screenHeight = 450;
- 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){ sinf(rot), 0.f, cosf(rot) };
- Vector3 right = (Vector3){ cosf(-rot), 0.f, sinf(-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 = sinf(headTimer*PI);
- float headCos = cosf(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 });
- }
|