Files
Test_Game/main.c

580 lines
22 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "raylib.h"
#include "rcamera.h"
#include "raymath.h"
#include <stdbool.h>
#include <stddef.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 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
//----------------------------------------------------------------------------------
//bob
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;
// GLSL 330 core fragment shader (desktop)
// Discards fragments with alpha below a threshold so depth is only written on real pixels.
static const char *ALPHA_CLIP_FS =
"#version 330 core\n"
"in vec2 fragTexCoord;\n"
"in vec4 fragColor;\n"
"uniform sampler2D texture0;\n"
"uniform float u_alphaCutoff; // e.g., 0.3\n"
"out vec4 finalColor;\n"
"void main(){\n"
" vec4 texel = texture(texture0, fragTexCoord);\n"
" if(texel.a < u_alphaCutoff) discard;\n"
" finalColor = texel * fragColor;\n"
"}\n";
//------------------------------------------------------------------------------------
// 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("resources/3d_models/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("resources/sprites/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("resources/sprites/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("resources/textures/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;
Shader shAlphaClip = LoadShaderFromMemory(NULL, ALPHA_CLIP_FS);
int locCutoff = GetShaderLocation(shAlphaClip, "u_alphaCutoff");
float cutoff = 0.3f; // tweak to taste; 0.30.5 works well for tree PNGs
SetShaderValue(shAlphaClip, locCutoff, &cutoff, SHADER_UNIFORM_FLOAT);
//--------------------------------------------------------------------------------------
// 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);
BeginShaderMode(shAlphaClip);
{
// Draw your trees here with DrawBillboard / DrawBillboardPro
// Example using your positions:
DrawBillboard(g.player.camera, tree, treepos, treeSize, WHITE);
Vector3 p = treepos;
p.x += 3*5; p.z += 3*5; DrawBillboard(g.player.camera, tree, p, treeSize, WHITE);
p.x += 5*5; p.z += -2*5; DrawBillboard(g.player.camera, tree, p, treeSize, WHITE);
p.x += -4*5; p.z += 3*5; DrawBillboard(g.player.camera, tree, p, treeSize, WHITE);
p.x += 9*5; p.z += 10*5; DrawBillboard(g.player.camera, tree, p, treeSize, WHITE);
// Your sign as a cutout too (if it has a clean alpha)
}
EndShaderMode();
DrawBillboard(g.player.camera, sign, bbPos, bbSize, 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();
}