-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
[example] New example: core_3d_fps_controller
#5004
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
nezvers
wants to merge
8
commits into
raysan5:master
Choose a base branch
from
nezvers:example_quake_controller
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
eee9dd8
Example: core_3d_fps_controller
nezvers 4d5a56a
change game's resolution
nezvers 4233544
Update core_3d_fps_controller.c
nezvers a2cf878
cleanup variable shadowing
nezvers fca2317
remove //#define PLATFORM_WEB
nezvers fa9653d
Fix function call
nezvers f6b7168
Add preview image
nezvers ed022e8
fix preview crop
nezvers File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,360 @@ | ||
/******************************************************************************************* | ||
* | ||
* raylib [core] example - Input Gestures for Web | ||
* | ||
* Example complexity rating: [★★★☆] 3/4 | ||
* | ||
* Example originally created 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-2025 Agnis Aldins (@nezvers) | ||
* | ||
********************************************************************************************/ | ||
|
||
#include "raylib.h" | ||
#include "raymath.h" | ||
#include "rcamera.h" | ||
|
||
#if defined(PLATFORM_WEB) | ||
#include <emscripten/emscripten.h> | ||
#endif | ||
|
||
#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 | ||
|
||
typedef struct { | ||
Vector3 position; | ||
Vector3 velocity; | ||
Vector3 dir; | ||
bool isGrounded; | ||
Sound soundJump; | ||
}Body; | ||
|
||
const int screenWidth = 800; | ||
const int screenHeight = 450; | ||
Vector2 sensitivity = { 0.001f, 0.001f }; | ||
|
||
Body player; | ||
Camera camera; | ||
Vector2 lookRotation = { 0 }; | ||
float headTimer; | ||
float walkLerp; | ||
float headLerp; | ||
Vector2 lean; | ||
|
||
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); | ||
|
||
//------------------------------------------------------------------------------------ | ||
// Program main entry point | ||
//------------------------------------------------------------------------------------ | ||
int main(void) | ||
{ | ||
// Initialization | ||
//-------------------------------------------------------------------------------------- | ||
InitWindow(screenWidth, screenHeight, "Raylib Quake-like controller"); | ||
InitAudioDevice(); | ||
|
||
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 | ||
|
||
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); | ||
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); | ||
} | ||
|
||
lean.x = Lerp(lean.x, sideway * 0.02f, 10.f * delta); | ||
lean.y = Lerp(lean.y, forward * 0.015f, 10.f * delta); | ||
|
||
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(); | ||
//---------------------------------------------------------------------------------- | ||
} | ||
|
||
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; | ||
SetSoundPitch(body->soundJump, 1.f + (GetRandomValue(-100, 100) * 0.001)); | ||
PlaySound(body->soundJump); | ||
} | ||
|
||
Vector3 front_vec = (Vector3){ sin(rot), 0.f, cos(rot) }; | ||
Vector3 right_vec = (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, | ||
}; | ||
|
||
body->dir = Vector3Lerp(body->dir, desired_dir, CONTROL * delta); | ||
|
||
float decel = body->isGrounded ? FRICTION : AIR_DRAG; | ||
Vector3 hvel = (Vector3){ | ||
body->velocity.x * decel, | ||
0.f, | ||
body->velocity.z * decel | ||
}; | ||
|
||
float hvel_length = Vector3Length(hvel); // a.k.a. magnitude | ||
if (hvel_length < 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 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; | ||
|
||
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.f) | ||
{ | ||
body->position.y = 0.f; | ||
body->velocity.y = 0.f; | ||
body->isGrounded = true; // <= enables jumping | ||
} | ||
} | ||
|
||
void UpdateCameraAngle() | ||
{ | ||
const Vector3 up = (Vector3){ 0.f, 1.f, 0.f }; | ||
const Vector3 targetOffset = (Vector3){ 0.f, 0.f, -1.f }; | ||
|
||
/* Left & 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 & Down */ | ||
Vector3 right = Vector3Normalize(Vector3CrossProduct(yaw, up)); | ||
|
||
// Rotate view vector around right axis | ||
Vector3 pitch = Vector3RotateByAxisAngle(yaw, right, -lookRotation.y - lean.y); | ||
|
||
// Head animation | ||
// Rotate up direction around forward axis | ||
float _sin = sin(headTimer * PI); | ||
float _cos = cos(headTimer * PI); | ||
const float stepRotation = 0.01f; | ||
camera.up = Vector3RotateByAxisAngle(up, pitch, _sin * stepRotation + lean.x); | ||
|
||
/* 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)); | ||
|
||
camera.target = Vector3Add(camera.position, pitch); | ||
} | ||
|
||
|
||
void DrawLevel() | ||
{ | ||
const int floorExtent = 25; | ||
const float tileSize = 5.f; | ||
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.f, y * tileSize}, | ||
(Vector2) {tileSize, tileSize}, tileColor1); | ||
} | ||
else if(!(y & 1) && !(x & 1)) | ||
{ | ||
DrawPlane((Vector3) { x * tileSize, 0.f, y * tileSize}, | ||
(Vector2) {tileSize, tileSize}, LIGHTGRAY); | ||
} | ||
} | ||
} | ||
|
||
const Vector3 towerSize = (Vector3){ 16.f, 32.f, 16.f }; | ||
const Color towerColor = (Color){ 150, 200, 200, 255 }; | ||
|
||
Vector3 towerPos = (Vector3){ 16.f, 16.f, 16.f }; | ||
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.f, 300.f, 0.f }, 100.f, (Color) { 255, 0, 0, 255 }); | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.