-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.c
361 lines (294 loc) · 16.1 KB
/
main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
/*
main.c
Super Mario Clone
Created by phani srikar on 04/06/20.
Copyright © 2020 phani srikar. All rights reserved.
CHANGELOG :
- 31-7-2020
* Added Ray Collision functions to the project
- 1-8-2020
* Integrating Collison System
* Refactoring Input system such that it complements the collision system
* Igonoring the current state system and using a temporary solution until Samara2D is finished
- 2-8-2020
* Using multiple rays to avoid state overriding with the state machine
- 3-8-2020
* Working on adding GRavity to the player, removing manual overlap correction with the ground
Header Files Hierarchy :
* Player.h<--raylib.h && stdio.h
* CollisionManager.h<--player.h
* StateMachine.h<--player.h
*/
// Includes and libraries
#include "StateMachine.h"
#include "CollisionManager.h"
#define RAYGUI_IMPLEMENTATION
#include "raygui.h"
/*
* Macros
*/
//Constants
#define G -6 // WORLD GRAVITY
#define QS_FRAME_RATE 40
#define COLLISION_TILES_COUNT 3
//Custom Colors
#define MARIO_SKY_BLUE CLITERAL(Color){107,139,247}
// MARK:- Global Constants
const int screenWidth = 800;
const int screenHeight = 600;
// Function Declarations
//Core Mechanic Funcitons
void DrawGround(Texture2D groundTex, Rectangle *groundRect);
// Main Function
int main() {
InitWindow(screenWidth, screenHeight, "Mario clone");
SetTargetFPS(60);
GuiSetStyle(DEFAULT, TEXT_SIZE, 14);
GuiSetStyle(DEFAULT, BORDER_WIDTH, 2);
GuiSetStyle(DEFAULT, TEXT_PADDING, 5);
GuiSetStyle(DEFAULT, BACKGROUND_COLOR, 0x000000ff);
GuiSetStyle(DEFAULT,TEXT_COLOR_NORMAL, 0x000000ff);
GuiSetStyle(DEFAULT,TEXT_COLOR_FOCUSED, 0x0000ffff); //Slider bar focused color
GuiSetStyle(DEFAULT,TEXT_COLOR_PRESSED, 0x000000ff);
GuiSetStyle(DEFAULT,TEXT_COLOR_DISABLED, 0x000000ff);
GuiSetStyle(DEFAULT,BORDER_COLOR_NORMAL, 0x000000ff);
GuiSetStyle(DEFAULT,BORDER_COLOR_FOCUSED, 0x000000ff);
GuiSetStyle(DEFAULT,BORDER_COLOR_PRESSED, 0x000000ff);
GuiSetStyle(DEFAULT,BORDER_COLOR_DISABLED, 0x000000ff);
GuiSetStyle(DEFAULT,BASE_COLOR_PRESSED, 0xe74c3cff); // Slider bar fill color
GuiSetStyle(DEFAULT,BASE_COLOR_NORMAL, 0x000000ff); // Slider bar bg color
GuiSetStyle(DEFAULT,BASE_COLOR_FOCUSED, 0x00000ff);
GuiSetStyle(DEFAULT,BASE_COLOR_DISABLED, 0x000000ff);
// Load Textures
// Raylib logo
Texture2D RaylibLogoTexture = (Texture2D)LoadTexture("Resources/raylib_48x48.png");
// BG Elements
Texture2D bushSingleTexture = (Texture2D)LoadTexture("Resources/Environment/BushSingle.png");
Texture2D bushDoubleTexture = (Texture2D)LoadTexture("Resources/Environment/BushDouble.png");
Texture2D bushTripleTexture = (Texture2D)LoadTexture("Resources/Environment/BushTriple.png");
Texture2D cloudSingleTexture = (Texture2D)LoadTexture("Resources/Environment/CloudSingle.png");
Texture2D clouDoubleTexture = (Texture2D)LoadTexture("Resources/Environment/CloudDouble.png");
Texture2D cloudTripleTexture = (Texture2D)LoadTexture("Resources/Environment/CloudTriple.png");
Texture2D groundTex = (Texture2D)LoadTexture("Resources/Environment/Ground Tile.png");
Texture2D hillSmallTexture = (Texture2D)LoadTexture("Resources/Environment/HillSmall.png");
Texture2D hillLargeTexture = (Texture2D)LoadTexture("Resources/Environment/HillLarge.png");
// Interactables and props
Texture2D questionBlockTexture = (Texture2D)LoadTexture("Resources/Props/QuestionBlock.png");
Texture2D brickTex = (Texture2D)LoadTexture("Resources/Props/Bricks.png");
Texture2D pipeSmallTex = (Texture2D)LoadTexture("Resources/Props/Pipe-1.png");
Texture2D pipeMediumTex = (Texture2D)LoadTexture("Resources/Props/medium_pipe.png");
Texture2D pipeLargeTex = (Texture2D)LoadTexture("Resources/Props/large_pipe.png");
//Characters and Enemies
Texture2D playerIdleTex = (Texture2D)LoadTexture("Resources/Characters/Mario Idle.png");
Texture2D playerWalkingTex = (Texture2D)LoadTexture("Resources/Characters/Mario Walking.png");
Texture2D playerJumpingTex = (Texture2D)LoadTexture("Resources/Characters/Mario Jump.png");
//MARK:- Environment Variables
//MARK:- Player Variables
struct Player player = {};
player.Position.x = 1270; // Arbitrary start position
player.Position.y = 540; // Arbitrary start position
player.state = Idle; // Initial player state
// Player Animaiton state spritesheets
Texture2D playerSheets[] = {playerIdleTex, playerWalkingTex, playerJumpingTex};
float timeOfAscent = 0.294f;
float timeOfDescent = 0.623f;
//Player collision Variables
Vector2 ray_origin, horizontal_Coll_Ray, vertical_Coll_Ray,contact_normal, contact_point, probableContactPoints[2];
float ray_length = 8; // Player Velocity Vector Ray length
float player_Contact_Time;
const float velocity = 1.0f;
// Camera Settingsq
Camera2D camera = { 0 };
camera.offset = (Vector2){ screenWidth/2, screenHeight/2 + 200};
camera.rotation = 0.0f;
camera.zoom = 1.0f;
// Environment Object and props Rects
// BG Rects
Rectangle groundRect = (Rectangle){0,0, groundTex.width, groundTex.height};
// Props Rects
Rectangle a_questionBlockRec_1;
Rectangle a_questionBlockRec_2;
Rectangle a_questionBlockRec_3;
Rectangle a_questionBlockRec_4;
Rectangle brickRec_1 = (Rectangle){0,0, brickTex.width, brickTex.height};
Rectangle brickRec_2 = (Rectangle){0,0, brickTex.width, brickTex.height};
Rectangle brickRec_3 = (Rectangle){0,0, brickTex.width, brickTex.height};
Rectangle smallPipeRec_1 = (Rectangle){1000, 540, pipeSmallTex.width, pipeSmallTex.height};
Rectangle mediumPipeRec_1 = (Rectangle){1400, 510, pipeMediumTex.width, pipeMediumTex.height};
Rectangle largePipeRec_1 = (Rectangle){0, 0, pipeLargeTex.width, pipeLargeTex.height};
// Add all the collisions rects to a proper DS (emporarily to an array)
Rectangle Colliding_Tiles[COLLISION_TILES_COUNT] = {smallPipeRec_1, groundRect, mediumPipeRec_1};
// Characters Rects
//Debug Variables
bool drawGroundRect;
// Game Loop
while (!WindowShouldClose())
{
//Update
Colliding_Tiles[1] = groundRect;
// Camera Update
camera.target = (Vector2){ player.Position.x + 20, player.Position.y + 20 };
// Player Input
// move left right only if player is in Idle/Walking state
if(player.state == Idle || player.state == Walking){
if(IsKeyDown(KEY_LEFT)){
player.Velocity.x = -2;
player.state = Walking;
player.dir = LEFT;
}
else if(IsKeyDown(KEY_RIGHT)){
player.Velocity.x = 2;
player.state = Walking;
player.dir = RIGHT;
}
}
// Base state (Idle) transition
if(IsKeyUp(KEY_LEFT) && IsKeyUp(KEY_RIGHT) && player.state != Jumping){
player.Velocity.x = 0;
player.state = Idle;
}
// Test Input system
if(IsKeyDown(KEY_LEFT)) player.Velocity.x = -velocity;
else if(IsKeyDown(KEY_RIGHT)) player.Velocity.x = velocity;
if(IsKeyDown(KEY_UP)) player.Velocity.y = -velocity;
else if(IsKeyDown(KEY_DOWN)) player.Velocity.y = velocity;
/*
* Player Raycasting
*/
// The Collision detection Rays
// player.Velocity.y != 0 ? (player.Velocity.y > 0 ? 1 : -1) : player.Velocity.y
horizontal_Coll_Ray = (Vector2){GetFrameTime() * 100 * player.Velocity.x * ray_length, 0};
vertical_Coll_Ray = (Vector2){0, GetFrameTime() * 100 * (player.Velocity.y/4) * ray_length};
// Check against all the necessary Rectangles
for (register int r = 0; r < COLLISION_TILES_COUNT; r++){
if(DynamicRectVsRect(player.CollisionRect, horizontal_Coll_Ray, Colliding_Tiles[r], &contact_point, &contact_normal, &player_Contact_Time, probableContactPoints) && player_Contact_Time < 1 && player_Contact_Time > 0){
// resolve the velocity using the relative equation
player.Velocity.x += absF(player.Velocity.x) * (1 - player_Contact_Time) * contact_normal.x;
// Update the Player State
if(player.state != Jumping)
player.state = Idle;
}
if(DynamicRectVsRect(player.CollisionRect, vertical_Coll_Ray, groundRect, &contact_point, &contact_normal, &player_Contact_Time, probableContactPoints) && player_Contact_Time < 1 && player_Contact_Time > 0){
// resolve the velocity using the relative equation
player.Velocity.y += absF(player.Velocity.y) * (1 - player_Contact_Time) * contact_normal.y;
// Update the Player State
player.state = Idle;
}
}
// Jump Input
if(IsKeyPressed(KEY_SPACE)){
printf("%s\n", "Started Jumping...");
// Start Jumping
player.state = Jumping;
}
// Player Update
// update the player collision rect
player.CollisionRect = (Rectangle){player.Position.x, player.Position.y, player.playerWidth, player.playerHeight};
// Move Player based on Velocity Vector
// multiple the veloctiy by the total time elapsed from the last frame and add it to the current position.
player.Position.x += player.Velocity.x * GetFrameTime() * 100;
player.Position.y += player.Velocity.y * GetFrameTime() * 100;
// Update the ray origin as the player moves
ray_origin = (Vector2){player.Position.x + (player.playerWidth/2), player.Position.y + (player.playerHeight/2)};
// Manage Jump mechanics of the player here
Jump(&player, &timeOfAscent, &timeOfDescent);
// Sprite Animations
// props animations
AnimateSpriteSheetRec(questionBlockTexture, &a_questionBlockRec_1, QS_FRAME_RATE, 3);// Question Block 1
AnimateSpriteSheetRec(questionBlockTexture, &a_questionBlockRec_2, QS_FRAME_RATE, 3);// Question Block 2
AnimateSpriteSheetRec(questionBlockTexture, &a_questionBlockRec_3, QS_FRAME_RATE, 3);// Question Block 3
AnimateSpriteSheetRec(questionBlockTexture, &a_questionBlockRec_4, QS_FRAME_RATE, 3);// Question Block 4
// Player animations
AnimatePlayer(playerSheets, &player, 15, 3);
//Drawing
BeginDrawing();
ClearBackground(MARIO_SKY_BLUE);
BeginMode2D(camera); //This is used to draw in the world space
// Background elements
DrawTexture(hillLargeTexture, 100, 530, RAYWHITE);
DrawTexture(cloudSingleTexture, 360, 230, RAYWHITE);
DrawTexture(bushTripleTexture, 500, 570, RAYWHITE);
DrawTexture(hillSmallTexture, 630, 570, RAYWHITE);
DrawTexture(cloudSingleTexture, 700, 200, RAYWHITE);
DrawTexture(bushSingleTexture, 800, 570, RAYWHITE);
DrawTexture(cloudTripleTexture, 1000, 200, RAYWHITE);
DrawTexture(hillLargeTexture, 1870, 530, RAYWHITE);
DrawTexture(clouDoubleTexture, 1400, 200, RAYWHITE);
DrawTexture(bushDoubleTexture, 1600, 570, RAYWHITE);
DrawTexture(cloudSingleTexture, 1800, 210, RAYWHITE);
DrawGround(groundTex, &groundRect); // Ground
// Interactables and props
DrawTextureRec(brickTex, brickRec_1, (Vector2){700, 400}, WHITE);
DrawTextureRec(brickTex, brickRec_2, (Vector2){700 + brickTex.width + questionBlockTexture.width/3, 400}, WHITE);
DrawTextureRec(brickTex, brickRec_3, (Vector2){700 + (brickTex.width * 2) + (questionBlockTexture.width/3 * 2), 400}, WHITE);
DrawTextureV(pipeSmallTex, (Vector2){smallPipeRec_1.x, smallPipeRec_1.y}, RAYWHITE);
DrawTextureV(pipeMediumTex, (Vector2){mediumPipeRec_1.x, mediumPipeRec_1.y}, RAYWHITE);
DrawTextureRec(pipeLargeTex, largePipeRec_1, (Vector2){1800, 470}, RAYWHITE);
DrawTextureRec(questionBlockTexture, a_questionBlockRec_1, (Vector2){600, 400}, WHITE);
DrawTextureRec(questionBlockTexture, a_questionBlockRec_2, (Vector2){700 + brickTex.width, 400}, WHITE);
DrawTextureRec(questionBlockTexture, a_questionBlockRec_3, (Vector2){700 + (brickTex.width * 2) + questionBlockTexture.width/3, 400}, WHITE);
DrawTextureRec(questionBlockTexture, a_questionBlockRec_4, (Vector2){700 + (brickTex.width * 1) + (questionBlockTexture.width/3 * 1), 300}, WHITE);
//Characters
// Player
DrawTextureRec(player.playerTexture, player.AnimatableRect, (Vector2){player.Position.x,player.Position.y}, RAYWHITE);
// Enemies
//Debug Stuff
if(drawGroundRect) DrawRectangleLinesEx(groundRect, 4, RAYWHITE);
DrawRectangleLinesEx(smallPipeRec_1, 2, YELLOW);
// Player Collision Debug Rectangle
DrawRectangleLinesEx(player.CollisionRect, 2, GREEN);
DrawRectangleRec(GetCollisionRec(player.CollisionRect, groundRect), RED);
DrawRectangleRec(GetCollisionRec(player.CollisionRect, smallPipeRec_1), RED);
// PrintPlayerState(&player);
// Rect vs Rect example
DrawRectangleLinesEx(player.CollisionRect, 2,YELLOW); // Player rectangle
DrawCircleV(ray_origin, 6, WHITE);
Vector2 HorivelLine = (Vector2){ray_origin.x + horizontal_Coll_Ray.x , ray_origin.y + horizontal_Coll_Ray.y}; // Just for visualisation purpose
Vector2 VertivellLine = (Vector2){ray_origin.x + vertical_Coll_Ray.x , ray_origin.y + vertical_Coll_Ray.y}; // Just for visualisation purpose
DrawLineV(ray_origin, HorivelLine, RED);
DrawCircleV(HorivelLine, 6, BLUE);
DrawLineV(ray_origin, VertivellLine, RED);
DrawCircleV(VertivellLine, 6, BLUE);
EndMode2D();
/*
* Any drawing here is rendered on the screen and not relative to world space aka Camera
*/
// Draw made with Raylib Logo
DrawText("Made with ", 660, 580, 18, BLACK);
DrawTexture(RaylibLogoTexture, screenWidth/2 + 350, screenHeight/2 + 250, RAYWHITE);
// On Screen Debug Statistics
DrawRectangleRounded((Rectangle){500,20,290,240}, 0.07f, 5, Fade(YELLOW, 0.8));
DrawText("Debug Stats", 510, 30, 18, BLACK);
DrawText(FormatText("Current FPS is : %d", GetFPS()) , 520, 60, 14, BLACK);
DrawText(FormatText("Player State is : %s", GetPlayerStateString(&player)) , 520, 90, 14, BLACK);
timeOfAscent = GuiSliderBar((Rectangle){625,110,60,20}, "Time of Ascent", FormatText("TOA : %.3f", timeOfAscent), timeOfAscent, 0.001f, 0.5f);
timeOfDescent = GuiSliderBar((Rectangle){635,140,60,20}, "Time of Descent", FormatText("TOD : %.3f", timeOfDescent), timeOfDescent, 0.001f, 1.0f);
DrawText(FormatText("is Player Grounded : %s", player.isGrounded ? "True" : "False") , 520, 170, 14, BLACK);
drawGroundRect = GuiCheckBox((Rectangle){ 520, 200, 20, 20 }, "Show Ground Rect", drawGroundRect);
DrawText(FormatText("Velocity Vector X : %f Y : %f", player.Velocity.x, player.Velocity.y) , 520, 240, 12, BLACK);
EndDrawing();
}
CloseWindow();
// system("clear");
return 0;
}
//Core Mechanic Funcitons
void DrawGround(Texture2D groundTex, Rectangle *groundRect){
int groundXIterations = (int)3000/(float)groundTex.width;
int groundYIterations = 2;//(int)60/(float)groundTex.height; /* Use this logic if you need tesselated sprites along the Y-Axis.*/
Vector2 position = (Vector2){0,0};
//Ground for player
for(int i = 0; i < groundYIterations ; i++){
position = (Vector2){groundTex.width * 0,((screenHeight) + (groundTex.height * i))};
DrawTexture(groundTex, position.x, position.y, RAYWHITE);
for(int j = 1; j < groundXIterations ; j ++)
{
position = (Vector2){groundTex.width * j,((screenHeight) + (groundTex.height * i))};
DrawTexture(groundTex, position.x, position.y, RAYWHITE);
}
}
groundRect->x = 0;
groundRect->y = position.y - groundTex.height;
groundRect->width = position.x + groundTex.width;
groundRect->height = 2 * groundTex.height;
}