core_3d_camera_fps.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. /*******************************************************************************************
  2. *
  3. * raylib [core] example - 3d camera fps
  4. *
  5. * Example complexity rating: [★★★☆] 3/4
  6. *
  7. * Example originally created with raylib 5.5, last time updated with raylib 5.5
  8. *
  9. * Example contributed by Agnis Aldiņš (@nezvers) and reviewed by Ramon Santamaria (@raysan5)
  10. *
  11. * Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
  12. * BSD-like license that allows static linking with closed source software
  13. *
  14. * Copyright (c) 2025 Agnis Aldiņš (@nezvers)
  15. *
  16. ********************************************************************************************/
  17. #include "raylib.h"
  18. #include "raymath.h"
  19. //----------------------------------------------------------------------------------
  20. // Defines and Macros
  21. //----------------------------------------------------------------------------------
  22. // Movement constants
  23. #define GRAVITY 32.0f
  24. #define MAX_SPEED 20.0f
  25. #define CROUCH_SPEED 5.0f
  26. #define JUMP_FORCE 12.0f
  27. #define MAX_ACCEL 150.0f
  28. // Grounded drag
  29. #define FRICTION 0.86f
  30. // Increasing air drag, increases strafing speed
  31. #define AIR_DRAG 0.98f
  32. // Responsiveness for turning movement direction to looked direction
  33. #define CONTROL 15.0f
  34. #define CROUCH_HEIGHT 0.0f
  35. #define STAND_HEIGHT 1.0f
  36. #define BOTTOM_HEIGHT 0.5f
  37. #define NORMALIZE_INPUT 0
  38. //----------------------------------------------------------------------------------
  39. // Types and Structures Definition
  40. //----------------------------------------------------------------------------------
  41. // Body structure
  42. typedef struct {
  43. Vector3 position;
  44. Vector3 velocity;
  45. Vector3 dir;
  46. bool isGrounded;
  47. } Body;
  48. //----------------------------------------------------------------------------------
  49. // Global Variables Definition
  50. //----------------------------------------------------------------------------------
  51. static Vector2 sensitivity = { 0.001f, 0.001f };
  52. static Body player = { 0 };
  53. static Vector2 lookRotation = { 0 };
  54. static float headTimer = 0.0f;
  55. static float walkLerp = 0.0f;
  56. static float headLerp = STAND_HEIGHT;
  57. static Vector2 lean = { 0 };
  58. //----------------------------------------------------------------------------------
  59. // Module Functions Declaration
  60. //----------------------------------------------------------------------------------
  61. static void DrawLevel(void);
  62. static void UpdateCameraFPS(Camera *camera);
  63. static void UpdateBody(Body *body, float rot, char side, char forward, bool jumpPressed, bool crouchHold);
  64. //------------------------------------------------------------------------------------
  65. // Program main entry point
  66. //------------------------------------------------------------------------------------
  67. int main(void)
  68. {
  69. // Initialization
  70. //--------------------------------------------------------------------------------------
  71. const int screenWidth = 800;
  72. const int screenHeight = 450;
  73. InitWindow(screenWidth, screenHeight, "raylib [core] example - 3d camera fps");
  74. // Initialize camera variables
  75. // NOTE: UpdateCameraFPS() takes care of the rest
  76. Camera camera = { 0 };
  77. camera.fovy = 60.0f;
  78. camera.projection = CAMERA_PERSPECTIVE;
  79. camera.position = (Vector3){
  80. player.position.x,
  81. player.position.y + (BOTTOM_HEIGHT + headLerp),
  82. player.position.z,
  83. };
  84. UpdateCameraFPS(&camera); // Update camera parameters
  85. DisableCursor(); // Limit cursor to relative movement inside the window
  86. SetTargetFPS(60); // Set our game to run at 60 frames-per-second
  87. //--------------------------------------------------------------------------------------
  88. // Main game loop
  89. while (!WindowShouldClose()) // Detect window close button or ESC key
  90. {
  91. // Update
  92. //----------------------------------------------------------------------------------
  93. Vector2 mouseDelta = GetMouseDelta();
  94. lookRotation.x -= mouseDelta.x*sensitivity.x;
  95. lookRotation.y += mouseDelta.y*sensitivity.y;
  96. char sideway = (IsKeyDown(KEY_D) - IsKeyDown(KEY_A));
  97. char forward = (IsKeyDown(KEY_W) - IsKeyDown(KEY_S));
  98. bool crouching = IsKeyDown(KEY_LEFT_CONTROL);
  99. UpdateBody(&player, lookRotation.x, sideway, forward, IsKeyPressed(KEY_SPACE), crouching);
  100. float delta = GetFrameTime();
  101. headLerp = Lerp(headLerp, (crouching ? CROUCH_HEIGHT : STAND_HEIGHT), 20.0f*delta);
  102. camera.position = (Vector3){
  103. player.position.x,
  104. player.position.y + (BOTTOM_HEIGHT + headLerp),
  105. player.position.z,
  106. };
  107. if (player.isGrounded && ((forward != 0) || (sideway != 0)))
  108. {
  109. headTimer += delta*3.0f;
  110. walkLerp = Lerp(walkLerp, 1.0f, 10.0f*delta);
  111. camera.fovy = Lerp(camera.fovy, 55.0f, 5.0f*delta);
  112. }
  113. else
  114. {
  115. walkLerp = Lerp(walkLerp, 0.0f, 10.0f*delta);
  116. camera.fovy = Lerp(camera.fovy, 60.0f, 5.0f*delta);
  117. }
  118. lean.x = Lerp(lean.x, sideway*0.02f, 10.0f*delta);
  119. lean.y = Lerp(lean.y, forward*0.015f, 10.0f*delta);
  120. UpdateCameraFPS(&camera);
  121. //----------------------------------------------------------------------------------
  122. // Draw
  123. //----------------------------------------------------------------------------------
  124. BeginDrawing();
  125. ClearBackground(RAYWHITE);
  126. BeginMode3D(camera);
  127. DrawLevel();
  128. EndMode3D();
  129. // Draw info box
  130. DrawRectangle(5, 5, 330, 75, Fade(SKYBLUE, 0.5f));
  131. DrawRectangleLines(5, 5, 330, 75, BLUE);
  132. DrawText("Camera controls:", 15, 15, 10, BLACK);
  133. DrawText("- Move keys: W, A, S, D, Space, Left-Ctrl", 15, 30, 10, BLACK);
  134. DrawText("- Look around: arrow keys or mouse", 15, 45, 10, BLACK);
  135. DrawText(TextFormat("- Velocity Len: (%06.3f)", Vector2Length((Vector2){ player.velocity.x, player.velocity.z })), 15, 60, 10, BLACK);
  136. EndDrawing();
  137. //----------------------------------------------------------------------------------
  138. }
  139. // De-Initialization
  140. //--------------------------------------------------------------------------------------
  141. CloseWindow(); // Close window and OpenGL context
  142. //--------------------------------------------------------------------------------------
  143. return 0;
  144. }
  145. //----------------------------------------------------------------------------------
  146. // Module Functions Definition
  147. //----------------------------------------------------------------------------------
  148. // Update body considering current world state
  149. void UpdateBody(Body *body, float rot, char side, char forward, bool jumpPressed, bool crouchHold)
  150. {
  151. Vector2 input = (Vector2){ (float)side, (float)-forward };
  152. #if defined(NORMALIZE_INPUT)
  153. // Slow down diagonal movement
  154. if ((side != 0) && (forward != 0)) input = Vector2Normalize(input);
  155. #endif
  156. float delta = GetFrameTime();
  157. if (!body->isGrounded) body->velocity.y -= GRAVITY*delta;
  158. if (body->isGrounded && jumpPressed)
  159. {
  160. body->velocity.y = JUMP_FORCE;
  161. body->isGrounded = false;
  162. // Sound can be played at this moment
  163. //SetSoundPitch(fxJump, 1.0f + (GetRandomValue(-100, 100)*0.001));
  164. //PlaySound(fxJump);
  165. }
  166. Vector3 front = (Vector3){ sinf(rot), 0.f, cosf(rot) };
  167. Vector3 right = (Vector3){ cosf(-rot), 0.f, sinf(-rot) };
  168. Vector3 desiredDir = (Vector3){ input.x*right.x + input.y*front.x, 0.0f, input.x*right.z + input.y*front.z, };
  169. body->dir = Vector3Lerp(body->dir, desiredDir, CONTROL*delta);
  170. float decel = (body->isGrounded ? FRICTION : AIR_DRAG);
  171. Vector3 hvel = (Vector3){ body->velocity.x*decel, 0.0f, body->velocity.z*decel };
  172. float hvelLength = Vector3Length(hvel); // Magnitude
  173. if (hvelLength < (MAX_SPEED*0.01f)) hvel = (Vector3){ 0 };
  174. // This is what creates strafing
  175. float speed = Vector3DotProduct(hvel, body->dir);
  176. // Whenever the amount of acceleration to add is clamped by the maximum acceleration constant,
  177. // a Player can make the speed faster by bringing the direction closer to horizontal velocity angle
  178. // More info here: https://youtu.be/v3zT3Z5apaM?t=165
  179. float maxSpeed = (crouchHold? CROUCH_SPEED : MAX_SPEED);
  180. float accel = Clamp(maxSpeed - speed, 0.f, MAX_ACCEL*delta);
  181. hvel.x += body->dir.x*accel;
  182. hvel.z += body->dir.z*accel;
  183. body->velocity.x = hvel.x;
  184. body->velocity.z = hvel.z;
  185. body->position.x += body->velocity.x*delta;
  186. body->position.y += body->velocity.y*delta;
  187. body->position.z += body->velocity.z*delta;
  188. // Fancy collision system against the floor
  189. if (body->position.y <= 0.0f)
  190. {
  191. body->position.y = 0.0f;
  192. body->velocity.y = 0.0f;
  193. body->isGrounded = true; // Enable jumping
  194. }
  195. }
  196. // Update camera for FPS behaviour
  197. static void UpdateCameraFPS(Camera *camera)
  198. {
  199. const Vector3 up = (Vector3){ 0.0f, 1.0f, 0.0f };
  200. const Vector3 targetOffset = (Vector3){ 0.0f, 0.0f, -1.0f };
  201. // Left and right
  202. Vector3 yaw = Vector3RotateByAxisAngle(targetOffset, up, lookRotation.x);
  203. // Clamp view up
  204. float maxAngleUp = Vector3Angle(up, yaw);
  205. maxAngleUp -= 0.001f; // Avoid numerical errors
  206. if ( -(lookRotation.y) > maxAngleUp) { lookRotation.y = -maxAngleUp; }
  207. // Clamp view down
  208. float maxAngleDown = Vector3Angle(Vector3Negate(up), yaw);
  209. maxAngleDown *= -1.0f; // Downwards angle is negative
  210. maxAngleDown += 0.001f; // Avoid numerical errors
  211. if ( -(lookRotation.y) < maxAngleDown) { lookRotation.y = -maxAngleDown; }
  212. // Up and down
  213. Vector3 right = Vector3Normalize(Vector3CrossProduct(yaw, up));
  214. // Rotate view vector around right axis
  215. float pitchAngle = -lookRotation.y - lean.y;
  216. pitchAngle = Clamp(pitchAngle, -PI/2 + 0.0001f, PI/2 - 0.0001f); // Clamp angle so it doesn't go past straight up or straight down
  217. Vector3 pitch = Vector3RotateByAxisAngle(yaw, right, pitchAngle);
  218. // Head animation
  219. // Rotate up direction around forward axis
  220. float headSin = sinf(headTimer*PI);
  221. float headCos = cosf(headTimer*PI);
  222. const float stepRotation = 0.01f;
  223. camera->up = Vector3RotateByAxisAngle(up, pitch, headSin*stepRotation + lean.x);
  224. // Camera BOB
  225. const float bobSide = 0.1f;
  226. const float bobUp = 0.15f;
  227. Vector3 bobbing = Vector3Scale(right, headSin*bobSide);
  228. bobbing.y = fabsf(headCos*bobUp);
  229. camera->position = Vector3Add(camera->position, Vector3Scale(bobbing, walkLerp));
  230. camera->target = Vector3Add(camera->position, pitch);
  231. }
  232. // Draw game level
  233. static void DrawLevel(void)
  234. {
  235. const int floorExtent = 25;
  236. const float tileSize = 5.0f;
  237. const Color tileColor1 = (Color){ 150, 200, 200, 255 };
  238. // Floor tiles
  239. for (int y = -floorExtent; y < floorExtent; y++)
  240. {
  241. for (int x = -floorExtent; x < floorExtent; x++)
  242. {
  243. if ((y & 1) && (x & 1))
  244. {
  245. DrawPlane((Vector3){ x*tileSize, 0.0f, y*tileSize}, (Vector2){ tileSize, tileSize }, tileColor1);
  246. }
  247. else if (!(y & 1) && !(x & 1))
  248. {
  249. DrawPlane((Vector3){ x*tileSize, 0.0f, y*tileSize}, (Vector2){ tileSize, tileSize }, LIGHTGRAY);
  250. }
  251. }
  252. }
  253. const Vector3 towerSize = (Vector3){ 16.0f, 32.0f, 16.0f };
  254. const Color towerColor = (Color){ 150, 200, 200, 255 };
  255. Vector3 towerPos = (Vector3){ 16.0f, 16.0f, 16.0f };
  256. DrawCubeV(towerPos, towerSize, towerColor);
  257. DrawCubeWiresV(towerPos, towerSize, DARKBLUE);
  258. towerPos.x *= -1;
  259. DrawCubeV(towerPos, towerSize, towerColor);
  260. DrawCubeWiresV(towerPos, towerSize, DARKBLUE);
  261. towerPos.z *= -1;
  262. DrawCubeV(towerPos, towerSize, towerColor);
  263. DrawCubeWiresV(towerPos, towerSize, DARKBLUE);
  264. towerPos.x *= -1;
  265. DrawCubeV(towerPos, towerSize, towerColor);
  266. DrawCubeWiresV(towerPos, towerSize, DARKBLUE);
  267. // Red sun
  268. DrawSphere((Vector3){ 300.0f, 300.0f, 0.0f }, 100.0f, (Color){ 255, 0, 0, 255 });
  269. }