diff --git a/bad_sphere.glb b/bad_sphere.glb new file mode 100644 index 0000000..38a1318 Binary files /dev/null and b/bad_sphere.glb differ diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..c3857af --- /dev/null +++ b/build.sh @@ -0,0 +1 @@ +cc main.c -lraylib -lGL -lm -lpthread -ldl -lrt -lX11 \ No newline at end of file diff --git a/diard-charles.jpg b/diard-charles.jpg new file mode 100644 index 0000000..80da426 Binary files /dev/null and b/diard-charles.jpg differ diff --git a/grass_textures/Grass_01.png b/grass_textures/Grass_01.png new file mode 100644 index 0000000..44d33b1 Binary files /dev/null and b/grass_textures/Grass_01.png differ diff --git a/grass_textures/Grass_01_Nrm.png b/grass_textures/Grass_01_Nrm.png new file mode 100644 index 0000000..782da26 Binary files /dev/null and b/grass_textures/Grass_01_Nrm.png differ diff --git a/grass_textures/Grass_02.png b/grass_textures/Grass_02.png new file mode 100644 index 0000000..a941042 Binary files /dev/null and b/grass_textures/Grass_02.png differ diff --git a/grass_textures/Grass_02_Nrm.png b/grass_textures/Grass_02_Nrm.png new file mode 100644 index 0000000..3979b9b Binary files /dev/null and b/grass_textures/Grass_02_Nrm.png differ diff --git a/grass_textures/Grass_03.png b/grass_textures/Grass_03.png new file mode 100644 index 0000000..6df88a8 Binary files /dev/null and b/grass_textures/Grass_03.png differ diff --git a/grass_textures/Grass_03_Nrm.png b/grass_textures/Grass_03_Nrm.png new file mode 100644 index 0000000..f4e44e5 Binary files /dev/null and b/grass_textures/Grass_03_Nrm.png differ diff --git a/grass_textures/Grass_04.png b/grass_textures/Grass_04.png new file mode 100644 index 0000000..76a5810 Binary files /dev/null and b/grass_textures/Grass_04.png differ diff --git a/grass_textures/Grass_04_Nrm.png b/grass_textures/Grass_04_Nrm.png new file mode 100644 index 0000000..9ea4850 Binary files /dev/null and b/grass_textures/Grass_04_Nrm.png differ diff --git a/little_mouse_warrior.glb b/little_mouse_warrior.glb new file mode 100644 index 0000000..fd1bd98 Binary files /dev/null and b/little_mouse_warrior.glb differ diff --git a/main.c b/main.c new file mode 100644 index 0000000..feffc1d --- /dev/null +++ b/main.c @@ -0,0 +1,559 @@ +#include "raylib.h" +#include "rcamera.h" +#include "raymath.h" +#include + +//---------------------------------------------------------------------------------- +// 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 2.0f +#define BOTTOM_HEIGHT 0.5f + +// Additional definitions +#define NORMALIZE_INPUT 0 +#define ARRLEN(a) (sizeof(a) / sizeof((a)[0])) + +enum game_state { + STATE_MENU, // 0 by default + STATE_GAME, // 1 +}; + +//---------------------------------------------------------------------------------- +//Defining program structure +//---------------------------------------------------------------------------------- + +typedef struct +{ + Camera camera; // <- now part of the game state + Model model; // you can keep model & its position here too + Vector3 position; + Vector3 velocity; + Vector3 dir; + Vector2 sensitivity; + Vector2 lookRotation; + Vector2 lean; + float headTimer; + float walkLerp; + float headLerp; + bool isGrounded; + int cameraMode; +} Player; + +typedef struct +{ + int index; + enum game_state state; + int shouldExit; + Player player; +} Master; + +//------------------------------------------------------------------------------------ +// Function Initation +//------------------------------------------------------------------------------------ +void Simple_Center_Box(double percent_verticle_gap, double percent_horizontal_gap, int screenWidth, int screenHeight, Color color); +void Thick_Center_Box(double percent_verticle_gap, double percent_horizontal_gap, float line_width, int screenWidth, int screenHeight, Color color); +void Simple_Menu(const char *array[], int count, int index, int x1, int y1, int total_width, int total_height, int line_thickess, int font_size, Color color, Color alt_color); +void UpdateBody(Master* g, float rot, char side, char forward, bool jumpPressed, bool crouchHold); +static void UpdateCameraAngle(Master* g); +static void DrawLevel(Model mdlTile); +static void CenterMouseAndFlush(void); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + //Define window size, create window, establish fps target, fullscreen. + const int screenWidth = 1440*2; + const int screenHeight = 960*2; + + InitWindow(screenWidth, screenHeight, "raylib"); + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + ToggleFullscreen(); + CenterMouseAndFlush(); + DisableCursor(); // Limit cursor to relative movement inside the window + + //Define menu characteristics + const char *menu[] = {"Game", "Option 2", "Exit"}; + int index_length = ARRLEN(menu); + + //General game state initiation + Master g = (Master){0}; + g.index = 0; + g.state = STATE_MENU; + g.shouldExit = 0; + + //Define + g.player.sensitivity = (Vector2){ 0.001f, 0.001f }; + g.player.lookRotation = (Vector2){ 0 }; + g.player.headTimer = 0.0f; + g.player.walkLerp = 0.0f; + g.player.headLerp = STAND_HEIGHT; + g.player.lean = (Vector2){ 0 }; + + //Define the player camera + g.player.camera = (Camera){0}; + g.player.camera.position = (Vector3){ + g.player.position.x, + g.player.position.y + (BOTTOM_HEIGHT + g.player.headLerp), + g.player.position.z, + }; + + //g.player.camera.target = (Vector3){ 0.0f, 0.0f, 1.0f }; // Camera target + g.player.camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Up vector + g.player.camera.fovy = 60.0f; + g.player.camera.projection = CAMERA_PERSPECTIVE; + g.player.cameraMode = CAMERA_FIRST_PERSON; + + UpdateCameraAngle(&g); + + // Load GLTF model (no animations) + //Model model = LoadModel("bad_sphere.glb"); + //Vector3 position = { 0.0f, 0.0f, 0.0f }; + + // Load a 2D image as texture for the billboard + //Billboards always point toward the active camera. + Texture2D sign = LoadTexture("diard-charles.jpg"); + GenTextureMipmaps(&sign); + SetTextureFilter(sign, TEXTURE_FILTER_BILINEAR); // optional but looks nicer + Vector3 bbPos = (Vector3){ 1.0f, 2.5f, 1.0f }; // where in 3D world to place it + float bbSize = 5.0f; // billboard height in world units + + // Load a 2D image as texture for the billboard + //Billboards always point toward the active camera. + Texture2D tree = LoadTexture("tree.png"); + GenTextureMipmaps(&tree); + SetTextureFilter(tree, TEXTURE_FILTER_BILINEAR); // optional but looks nicer + Vector3 treepos = (Vector3){ 0.0f, 7.0f, 0.0f }; // where in 3D world to place it + float treeSize = 15.0f; // billboard height in world units + + // --- Grass tile setup --- + Texture2D texGrass = LoadTexture("grass_textures/Grass_01.png"); // adjust path + GenTextureMipmaps(&texGrass); + SetTextureFilter(texGrass, TEXTURE_FILTER_TRILINEAR); // nicer at angles + SetTextureWrap(texGrass, TEXTURE_WRAP_REPEAT); + + // A 3D plane (centered at origin) of size tileSize x tileSize + const float tileSize = 5.0f; // match your DrawLevel tileSize + Mesh meshTile = GenMeshPlane(tileSize, tileSize, 1, 1); + Model mdlTile = LoadModelFromMesh(meshTile); + mdlTile.materials[0].maps[MATERIAL_MAP_ALBEDO].texture = texGrass; + + //-------------------------------------------------------------------------------------- + + // Main game loop + //-------------------------------------------------------------------------------------- + while (!g.shouldExit && !WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + switch (g.state) + { + case STATE_MENU: + { + if (IsKeyPressed(KEY_UP) && g.index > 0) + { + g.index -= 1; + break; + }; + + if (IsKeyPressed(KEY_DOWN) && g.index < index_length - 1) + { + g.index += 1; + break; + }; + + if (IsKeyPressed(KEY_ENTER)) + { + switch (g.index) + { + case 0: + { + g.state = 1; + CenterMouseAndFlush(); + break; + } + + case 1: + { + break; + } + + case 2: + { + g.shouldExit = 1; + break; + } + } + }; + break; + }; + case STATE_GAME: + { + Vector2 mouseDelta = GetMouseDelta(); + g.player.lookRotation.x -= mouseDelta.x*g.player.sensitivity.x; + g.player.lookRotation.y += mouseDelta.y*g.player.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(&g, g.player.lookRotation.x, sideway, forward, IsKeyDown(KEY_SPACE), crouching); + + float delta = GetFrameTime(); + g.player.headLerp = Lerp(g.player.headLerp, (crouching ? CROUCH_HEIGHT : STAND_HEIGHT), 20.0f*delta); + g.player.camera.position = (Vector3){ + g.player.position.x, + g.player.position.y + (BOTTOM_HEIGHT + g.player.headLerp), + g.player.position.z, + }; + + if (g.player.isGrounded && ((forward != 0) || (sideway != 0))) + { + g.player.headTimer += delta*3.0f; + g.player.walkLerp = Lerp(g.player.walkLerp, 1.0f, 10.0f*delta); + g.player.camera.fovy = Lerp(g.player.camera.fovy, 55.0f, 5.0f*delta); + } + else + { + g.player.walkLerp = Lerp(g.player.walkLerp, 0.0f, 10.0f*delta); + g.player.camera.fovy = Lerp(g.player.camera.fovy, 60.0f, 5.0f*delta); + } + + g.player.lean.x = Lerp(g.player.lean.x, sideway*0.02f, 10.0f*delta); + g.player.lean.y = Lerp(g.player.lean.y, forward*0.015f, 10.0f*delta); + + UpdateCameraAngle(&g); + + + + + + int bbdifx = g.player.position.x - bbPos.x; + int bbdifz = g.player.position.z - bbPos.z; + + bbPos.x += 0.01 * bbdifx; + bbPos.z += 0.01 * bbdifz; + + treepos = (Vector3){ 0.0f, 7.0f, 0.0f }; + + break; + }; + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + switch (g.state) + { + case STATE_MENU: + { + ClearBackground(BLACK); + + Simple_Menu(menu, ARRLEN(menu), g.index, screenWidth * 0.15, screenHeight * 0.15, 500, screenHeight * 0.7, 10, 50, GREEN, LIGHTGRAY); + + Thick_Center_Box(0.1, 0.1, 10, screenWidth, screenHeight, GREEN); + break; + } + case STATE_GAME: + { + ClearBackground(BLACK); + + BeginMode3D(g.player.camera); + DrawLevel(mdlTile); + DrawBillboard(g.player.camera, sign, bbPos, bbSize, WHITE); + + DrawBillboard(g.player.camera, tree, treepos, treeSize, WHITE); + + treepos.x += 3*5; + treepos.z += 3*5; + DrawBillboard(g.player.camera, tree, treepos, treeSize, WHITE); + + treepos.x += 5*5; + treepos.z += -2*5; + DrawBillboard(g.player.camera, tree, treepos, treeSize, WHITE); + + treepos.x += -4*5; + treepos.z += 3*5; + DrawBillboard(g.player.camera, tree, treepos, treeSize, WHITE); + + treepos.x += 9*5; + treepos.z += 10*5; + DrawBillboard(g.player.camera, tree, treepos, treeSize, WHITE); + + 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){ g.player.velocity.x, g.player.velocity.z })), 15, 60, 10, BLACK); + + break; + } + } + EndDrawing(); + //---------------------------------------------------------------------------------- + }; + //-------------------------------------------------------------------------------------- + + // De-Initialization + //-------------------------------------------------------------------------------------- + // Detach the texture from the model so UnloadModel won't try to free it. + mdlTile.materials[0].maps[MATERIAL_MAP_ALBEDO].texture = (Texture2D){0}; + + // Now free resources you loaded: + UnloadModel(mdlTile); // frees the model and its mesh/VBOs + UnloadTexture(texGrass); // frees the grass texture you loaded + UnloadTexture(sign); // frees billboard texture + UnloadTexture(tree); // frees tree texture + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + return 0; +}; + +//------------------------------------------------------------------------------------ +// Function Definitions +//------------------------------------------------------------------------------------ +//Function creates a simple box outline centered in the screen with a symetric ofset from the screen borders. +void Simple_Center_Box(double percent_verticle_gap, double percent_horizontal_gap, int screenWidth, int screenHeight, Color color) +{ + int x1 = percent_horizontal_gap * screenWidth; + int y1 = percent_verticle_gap * screenHeight; + + int w = screenWidth * (1 - (2 * percent_horizontal_gap)); + int h = screenHeight * (1 - (2 * percent_verticle_gap)); + + DrawRectangleLines(x1, y1, w, h, color); +} + +//Function creates a thick walled box outline centered in the screen with a symetric ofset from the screen borders. +void Thick_Center_Box(double percent_verticle_gap, double percent_horizontal_gap, float line_width, int screenWidth, int screenHeight, Color color) +{ + int x1 = percent_horizontal_gap * screenWidth; + int y1 = percent_verticle_gap * screenHeight; + + int w = screenWidth * (1 - (2 * percent_horizontal_gap)); + int h = screenHeight * (1 - (2 * percent_verticle_gap)); + + struct Rectangle rec = {x1,y1,w,h}; + + DrawRectangleLinesEx(rec, line_width, color); +} + +//Make a simple menu with selection features +void Simple_Menu(const char *array[], int count, int index, int x1, int y1, int total_width, int total_height, int line_thickess, int font_size, Color color, Color alt_color) +{ + float height = 0.8f * total_height; + int space = 0.2 * total_height / (count - 1); + + struct Rectangle rec = {x1,y1,total_width,(float)height/count}; + + for (int i = 0; i < count; ++i) + { + int text_width = MeasureText(array[i], font_size); + + if(i == index) + { + DrawRectangleLinesEx(rec, line_thickess, alt_color); + DrawText(array[i], rec.x + (((total_width - 2.0 * line_thickess) - text_width)/2), rec.y + (height/count/2) - font_size/2.0f, font_size, alt_color); + } + else + { + DrawRectangleLinesEx(rec, line_thickess, color); + DrawText(array[i], rec.x + (((total_width - 2.0 * line_thickess) - text_width)/2), rec.y + (height/count/2) - font_size/2.0f, font_size, color); + } + + rec.y = rec.y + (float)height/count + space; + } +} + +void UpdateBody(Master* g, 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 (!g->player.isGrounded) g->player.velocity.y -= GRAVITY*delta; + + if (g->player.isGrounded && jumpPressed) + { + g->player.velocity.y = JUMP_FORCE; + g->player.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, }; + g->player.dir = Vector3Lerp(g->player.dir, desiredDir, CONTROL*delta); + + float decel = (g->player.isGrounded ? FRICTION : AIR_DRAG); + Vector3 hvel = (Vector3){ g->player.velocity.x*decel, 0.0f, g->player.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, g->player.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 += g->player.dir.x*accel; + hvel.z += g->player.dir.z*accel; + + g->player.velocity.x = hvel.x; + g->player.velocity.z = hvel.z; + + g->player.position.x += g->player.velocity.x*delta; + g->player.position.y += g->player.velocity.y*delta; + g->player.position.z += g->player.velocity.z*delta; + + // Fancy collision system against the floor + if (g->player.position.y <= 0.0f) + { + g->player.position.y = 0.0f; + g->player.velocity.y = 0.0f; + g->player.isGrounded = true; // Enable jumping + } +} + +// Update camera +static void UpdateCameraAngle(Master* g) +{ + 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, g->player.lookRotation.x); + + // Clamp view up + float maxAngleUp = Vector3Angle(up, yaw); + maxAngleUp -= 0.001f; // Avoid numerical errors + if ( -(g->player.lookRotation.y) > maxAngleUp) { g->player.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 ( -(g->player.lookRotation.y) < maxAngleDown) { g->player.lookRotation.y = -maxAngleDown; } + + // Up and down + Vector3 right = Vector3Normalize(Vector3CrossProduct(yaw, up)); + + // Rotate view vector around right axis + float pitchAngle = -g->player.lookRotation.y - + g->player.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(g->player.headTimer*PI); + float headCos = cos(g->player.headTimer*PI); + const float stepRotation = 0.005f; + g->player.camera.up = Vector3RotateByAxisAngle(up, pitch, headSin*stepRotation + g->player.lean.x); + + // Camera BOB + const float bobSide = 0.01f; + const float bobUp = 0.01f; + Vector3 bobbing = Vector3Scale(right, headSin*bobSide); + bobbing.y = fabsf(headCos*bobUp); + + g->player.camera.position = Vector3Add(g->player.camera.position, Vector3Scale(bobbing, g->player.walkLerp)); + g->player.camera.target = Vector3Add(g->player.camera.position, pitch); +} + +// Draw game level +static void DrawLevel(Model mdlTile) +{ + 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); + } + */ + Vector3 pos = (Vector3){ x*tileSize, 0.0f, y*tileSize }; + // mdlTile must be accessible here: make it global/static or pass it in + DrawModel(mdlTile, pos, 1.0f, WHITE); + + } + } + + /* + 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 }); + */ +} + +//fix mouse +static void CenterMouseAndFlush() { + SetMousePosition(GetRenderWidth()/2, GetRenderHeight()/2); + (void)GetMouseDelta(); +} \ No newline at end of file diff --git a/tree.png b/tree.png new file mode 100644 index 0000000..7482d9c Binary files /dev/null and b/tree.png differ