|
diff --git a/game.c b/game.c
|
| ... |
| 112 |
float x, y, z; |
112 |
float x, y, z; |
| 113 |
sscanf(e->properties[j].value, "%f %f %f", &x, &y, &z); |
113 |
sscanf(e->properties[j].value, "%f %f %f", &x, &y, &z); |
| 114 |
if (strcmp(classname, "info_player_start") == 0) { |
114 |
if (strcmp(classname, "info_player_start") == 0) { |
| 115 |
game.camera.position = (Vector3){ x, z, -y }; |
115 |
game.pos = (Vector3){ x, z, -y }; |
| 116 |
game.camera.target = Vector3Add(game.camera.position, (Vector3){0, 0, 1}); |
116 |
game.camera.position = game.pos; |
| 117 |
TraceLog(LOG_INFO, "Player spawn set to: %f, %f, %f", game.camera.position.x, game.camera.position.y, game.camera.position.z); |
117 |
float angle = 0; |
|
|
118 |
for (int k = 0; k < e->property_count; k++) { |
|
|
119 |
if (strcmp(e->properties[k].key, "angle") == 0) { |
|
|
120 |
angle = (float)atof(e->properties[k].value); |
|
|
121 |
} |
|
|
122 |
} |
|
|
123 |
game.yaw = (angle + 90.0f) * DEG2RAD; |
|
|
124 |
game.pitch = 0; |
|
|
125 |
game.camera.target = Vector3Add(game.pos, (Vector3){sinf(game.yaw), 0, cosf(game.yaw)}); |
|
|
126 |
TraceLog(LOG_INFO, "Player spawn set to: %f, %f, %f (yaw: %f)", game.pos.x, game.pos.y, game.pos.z, game.yaw); |
| 118 |
} |
127 |
} |
| 119 |
} |
128 |
} |
| 120 |
} |
129 |
} |
| ... |
| 219 |
game.world_models = NULL; |
228 |
game.world_models = NULL; |
| 220 |
game.world_model_count = 0; |
229 |
game.world_model_count = 0; |
| 221 |
|
230 |
|
|
|
231 |
// Load UI Font |
|
|
232 |
size_t font_size = 0; |
|
|
233 |
void *font_data = vfs_read("fonts/LiberationSans-Bold.ttf", &font_size); |
|
|
234 |
if (font_data) { |
|
|
235 |
game.font_ui = LoadFontFromMemory(".ttf", font_data, (int)font_size, 20, NULL, 0); |
|
|
236 |
vfs_free(font_data); |
|
|
237 |
} else { |
|
|
238 |
game.font_ui = GetFontDefault(); |
|
|
239 |
} |
|
|
240 |
|
| 222 |
LoadMap("maps/demo1.map"); |
241 |
LoadMap("maps/demo1.map"); |
| 223 |
|
242 |
|
| 224 |
game.cursor_captured = false; |
243 |
game.cursor_captured = false; |
| 225 |
EnableCursor(); |
244 |
EnableCursor(); |
|
|
245 |
|
|
|
246 |
game.move_mode = MOVE_NORMAL; |
|
|
247 |
game.velocity = (Vector3){ 0, 0, 0 }; |
|
|
248 |
game.is_grounded = false; |
| 226 |
} |
249 |
} |
| 227 |
|
250 |
|
| 228 |
void UpdateGame(void) { |
251 |
void UpdateGame(void) { |
| 229 |
float dt = GetFrameTime(); |
|
|
| 230 |
|
|
|
| 231 |
if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) { |
252 |
if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) { |
| 232 |
game.cursor_captured = !game.cursor_captured; |
253 |
game.cursor_captured = !game.cursor_captured; |
| 233 |
if (game.cursor_captured) DisableCursor(); |
254 |
if (game.cursor_captured) DisableCursor(); |
| ... |
| 248 |
} |
269 |
} |
| 249 |
} |
270 |
} |
| 250 |
|
271 |
|
| 251 |
// Manual first-person movement |
272 |
UpdatePlayer(); |
| 252 |
Vector3 forward = Vector3Normalize(Vector3Subtract(game.camera.target, game.camera.position)); |
|
|
| 253 |
Vector3 right = Vector3Normalize(Vector3CrossProduct(forward, game.camera.up)); |
|
|
| 254 |
|
|
|
| 255 |
Vector3 move = { 0 }; |
|
|
| 256 |
if (IsKeyDown(KEY_W)) move = Vector3Add(move, forward); |
|
|
| 257 |
if (IsKeyDown(KEY_S)) move = Vector3Subtract(move, forward); |
|
|
| 258 |
if (IsKeyDown(KEY_D)) move = Vector3Add(move, right); |
|
|
| 259 |
if (IsKeyDown(KEY_A)) move = Vector3Subtract(move, right); |
|
|
| 260 |
|
|
|
| 261 |
if (Vector3Length(move) > 0) { |
|
|
| 262 |
move = Vector3Scale(Vector3Normalize(move), PLAYER_MOVE_SPEED * dt); |
|
|
| 263 |
game.camera.position = Vector3Add(game.camera.position, move); |
|
|
| 264 |
game.camera.target = Vector3Add(game.camera.target, move); |
|
|
| 265 |
} |
|
|
| 266 |
|
|
|
| 267 |
if (game.cursor_captured) { |
|
|
| 268 |
// Manual rotation |
|
|
| 269 |
Vector2 mouseDelta = GetMouseDelta(); |
|
|
| 270 |
float yaw = -mouseDelta.x * PLAYER_MOUSE_SENSITIVITY; |
|
|
| 271 |
float pitch = -mouseDelta.y * PLAYER_MOUSE_SENSITIVITY; |
|
|
| 272 |
|
|
|
| 273 |
Vector3 view = Vector3Subtract(game.camera.target, game.camera.position); |
|
|
| 274 |
|
|
|
| 275 |
// Yaw |
|
|
| 276 |
view = Vector3RotateByAxisAngle(view, game.camera.up, yaw); |
|
|
| 277 |
|
|
|
| 278 |
// Pitch |
|
|
| 279 |
Vector3 axis = Vector3Normalize(Vector3CrossProduct(view, game.camera.up)); |
|
|
| 280 |
view = Vector3RotateByAxisAngle(view, axis, pitch); |
|
|
| 281 |
|
|
|
| 282 |
game.camera.target = Vector3Add(game.camera.position, view); |
|
|
| 283 |
} |
|
|
| 284 |
} |
273 |
} |
| 285 |
|
274 |
|
| 286 |
void DrawGame(void) { |
275 |
void DrawGame(void) { |
| ... |
| 300 |
int screenHeight = GetScreenHeight(); |
289 |
int screenHeight = GetScreenHeight(); |
| 301 |
DrawLine(screenWidth / 2 - 10, screenHeight / 2, screenWidth / 2 + 10, screenHeight / 2, GREEN); |
290 |
DrawLine(screenWidth / 2 - 10, screenHeight / 2, screenWidth / 2 + 10, screenHeight / 2, GREEN); |
| 302 |
DrawLine(screenWidth / 2, screenHeight / 2 - 10, screenWidth / 2, screenHeight / 2 + 10, GREEN); |
291 |
DrawLine(screenWidth / 2, screenHeight / 2 - 10, screenWidth / 2, screenHeight / 2 + 10, GREEN); |
| 303 |
DrawFPS(10, 10); |
292 |
|
| 304 |
DrawText(TextFormat("VSync: %s", game.vsync ? "ON" : "OFF"), 10, 30, 20, GREEN); |
293 |
DrawTextEx(game.font_ui, TextFormat("%i FPS", GetFPS()), (Vector2){ 10, 10 }, 20, 2, GREEN); |
|
|
294 |
DrawTextEx(game.font_ui, TextFormat("VSync: %s", game.vsync ? "ON" : "OFF"), (Vector2){ 10, 35 }, 20, 2, GREEN); |
|
|
295 |
DrawTextEx(game.font_ui, TextFormat("Speed: %.0f", game.horizontal_speed), (Vector2){ 10, 60 }, 20, 2, GREEN); |
|
|
296 |
|
| 305 |
EndDrawing(); |
297 |
EndDrawing(); |
| 306 |
} |
298 |
} |
|
diff --git a/player.c b/player.c
|
|
|
1 |
#include "all.h" |
|
|
2 |
#include "raymath.h" |
|
|
3 |
#include <float.h> |
|
|
4 |
|
|
|
5 |
static void PlayerRotate(void) { |
|
|
6 |
if (!game.cursor_captured) return; |
|
|
7 |
|
|
|
8 |
Vector2 mouseDelta = GetMouseDelta(); |
|
|
9 |
game.yaw += -mouseDelta.x * PLAYER_MOUSE_SENSITIVITY; |
|
|
10 |
game.pitch += mouseDelta.y * PLAYER_MOUSE_SENSITIVITY; |
|
|
11 |
|
|
|
12 |
// Clamp pitch to avoid gimbal lock/flipping (approx 89 degrees) |
|
|
13 |
if (game.pitch > 89.0f * DEG2RAD) game.pitch = 89.0f * DEG2RAD; |
|
|
14 |
if (game.pitch < -89.0f * DEG2RAD) game.pitch = -89.0f * DEG2RAD; |
|
|
15 |
} |
|
|
16 |
|
|
|
17 |
static bool CheckWorldCollision(Vector3 start, Vector3 end, RayCollision *outCollision) { |
|
|
18 |
Vector3 diff = Vector3Subtract(end, start); |
|
|
19 |
float maxDist = Vector3Length(diff); |
|
|
20 |
if (maxDist < 0.001f) return false; |
|
|
21 |
|
|
|
22 |
Ray ray = { 0 }; |
|
|
23 |
ray.position = start; |
|
|
24 |
ray.direction = Vector3Scale(diff, 1.0f / maxDist); |
|
|
25 |
|
|
|
26 |
RayCollision closest = { 0 }; |
|
|
27 |
closest.distance = FLT_MAX; |
|
|
28 |
closest.hit = false; |
|
|
29 |
|
|
|
30 |
for (int i = 0; i < game.world_model_count; i++) { |
|
|
31 |
Model model = game.world_models[i]; |
|
|
32 |
for (int m = 0; m < model.meshCount; m++) { |
|
|
33 |
RayCollision col = GetRayCollisionMesh(ray, model.meshes[m], model.transform); |
|
|
34 |
if (col.hit && col.distance < closest.distance && col.distance <= maxDist) { |
|
|
35 |
closest = col; |
|
|
36 |
} |
|
|
37 |
} |
|
|
38 |
} |
|
|
39 |
|
|
|
40 |
if (closest.hit) { |
|
|
41 |
if (outCollision) *outCollision = closest; |
|
|
42 |
return true; |
|
|
43 |
} |
|
|
44 |
return false; |
|
|
45 |
} |
|
|
46 |
|
|
|
47 |
static void MoveNormal(float dt) { |
|
|
48 |
float eyeHeight = PLAYER_EYE_HEIGHT - (game.crouch_amount * PLAYER_CROUCH_OFFSET); |
|
|
49 |
|
|
|
50 |
// Movement vectors based on yaw |
|
|
51 |
Vector3 forward = { sinf(game.yaw), 0, cosf(game.yaw) }; |
|
|
52 |
Vector3 right = { sinf(game.yaw - PI/2.0f), 0, cosf(game.yaw - PI/2.0f) }; |
|
|
53 |
|
|
|
54 |
Vector3 moveDir = { 0 }; |
|
|
55 |
if (IsKeyDown(KEY_W)) moveDir = Vector3Add(moveDir, forward); |
|
|
56 |
if (IsKeyDown(KEY_S)) moveDir = Vector3Subtract(moveDir, forward); |
|
|
57 |
if (IsKeyDown(KEY_D)) moveDir = Vector3Add(moveDir, right); |
|
|
58 |
if (IsKeyDown(KEY_A)) moveDir = Vector3Subtract(moveDir, right); |
|
|
59 |
|
|
|
60 |
float speed = PLAYER_MOVE_SPEED; |
|
|
61 |
if (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)) speed *= PLAYER_SPRINT_MULTIPLIER; |
|
|
62 |
|
|
|
63 |
// Apply crouch speed penalty |
|
|
64 |
speed *= Lerp(1.0f, PLAYER_CROUCH_MOVE_MULTIPLIER, game.crouch_amount); |
|
|
65 |
|
|
|
66 |
if (Vector3Length(moveDir) > 0) { |
|
|
67 |
moveDir = Vector3Scale(Vector3Normalize(moveDir), speed * dt); |
|
|
68 |
} |
|
|
69 |
|
|
|
70 |
// Apply gravity |
|
|
71 |
game.velocity.y -= PLAYER_GRAVITY * dt; |
|
|
72 |
|
|
|
73 |
// Jump |
|
|
74 |
if (game.is_grounded && IsKeyPressed(KEY_SPACE)) { |
|
|
75 |
game.velocity.y = PLAYER_JUMP_FORCE; |
|
|
76 |
game.is_grounded = false; |
|
|
77 |
} |
|
|
78 |
|
|
|
79 |
// Horizontal movement with sliding collision |
|
|
80 |
Vector3 remainingMove = moveDir; |
|
|
81 |
for (int iter = 0; iter < 4 && Vector3Length(remainingMove) > 0.001f; iter++) { |
|
|
82 |
RayCollision closestHit = { 0 }; |
|
|
83 |
closestHit.distance = FLT_MAX; |
|
|
84 |
bool hitFound = false; |
|
|
85 |
|
|
|
86 |
float heights[] = { -eyeHeight + 10.0f, -eyeHeight / 2.0f, 0.0f }; |
|
|
87 |
Vector3 currentPos = game.pos; |
|
|
88 |
|
|
|
89 |
for (int i = 0; i < 3; i++) { |
|
|
90 |
Vector3 start = Vector3Add(currentPos, (Vector3){0, heights[i], 0}); |
|
|
91 |
Vector3 dir = Vector3Normalize(remainingMove); |
|
|
92 |
float dist = Vector3Length(remainingMove) + PLAYER_RADIUS; |
|
|
93 |
Vector3 end = Vector3Add(start, Vector3Scale(dir, dist)); |
|
|
94 |
|
|
|
95 |
RayCollision col; |
|
|
96 |
if (CheckWorldCollision(start, end, &col)) { |
|
|
97 |
// Adjust distance to be relative to the player boundary |
|
|
98 |
float adjustedDist = col.distance - PLAYER_RADIUS; |
|
|
99 |
if (adjustedDist < closestHit.distance) { |
|
|
100 |
closestHit = col; |
|
|
101 |
closestHit.distance = adjustedDist; |
|
|
102 |
hitFound = true; |
|
|
103 |
} |
|
|
104 |
} |
|
|
105 |
} |
|
|
106 |
|
|
|
107 |
if (hitFound) { |
|
|
108 |
// Move as far as possible |
|
|
109 |
float moveDist = fmaxf(0, closestHit.distance - 0.1f); |
|
|
110 |
Vector3 moveStep = Vector3Scale(Vector3Normalize(remainingMove), moveDist); |
|
|
111 |
game.pos.x += moveStep.x; |
|
|
112 |
game.pos.z += moveStep.z; |
|
|
113 |
|
|
|
114 |
// Project remaining movement onto the plane of the wall |
|
|
115 |
Vector3 slideNormal = { closestHit.normal.x, 0, closestHit.normal.z }; |
|
|
116 |
if (Vector3Length(slideNormal) > 0.001f) { |
|
|
117 |
slideNormal = Vector3Normalize(slideNormal); |
|
|
118 |
remainingMove = Vector3Subtract(remainingMove, moveStep); |
|
|
119 |
float dot = Vector3DotProduct(remainingMove, slideNormal); |
|
|
120 |
remainingMove = Vector3Subtract(remainingMove, Vector3Scale(slideNormal, dot)); |
|
|
121 |
} else { |
|
|
122 |
remainingMove = (Vector3){0, 0, 0}; |
|
|
123 |
} |
|
|
124 |
} else { |
|
|
125 |
// No collision found, move the rest of the way |
|
|
126 |
game.pos.x += remainingMove.x; |
|
|
127 |
game.pos.z += remainingMove.z; |
|
|
128 |
break; |
|
|
129 |
} |
|
|
130 |
} |
|
|
131 |
|
|
|
132 |
// Vertical movement with collision |
|
|
133 |
float verticalMove = game.velocity.y * dt; |
|
|
134 |
Vector3 vStart = game.pos; |
|
|
135 |
|
|
|
136 |
if (verticalMove < 0) { // Falling/Down |
|
|
137 |
Vector3 start = vStart; |
|
|
138 |
// Check slightly below feet |
|
|
139 |
Vector3 end = Vector3Add(vStart, (Vector3){0, verticalMove - eyeHeight, 0}); |
|
|
140 |
RayCollision vCol; |
|
|
141 |
if (CheckWorldCollision(start, end, &vCol) && vCol.normal.y > 0.5f) { |
|
|
142 |
game.pos.y = vCol.point.y + eyeHeight; |
|
|
143 |
game.velocity.y = 0; |
|
|
144 |
game.is_grounded = true; |
|
|
145 |
} else { |
|
|
146 |
game.pos.y += verticalMove; |
|
|
147 |
game.is_grounded = false; |
|
|
148 |
} |
|
|
149 |
} else if (verticalMove > 0) { // Jumping/Up |
|
|
150 |
Vector3 start = vStart; |
|
|
151 |
// Check above head |
|
|
152 |
float headHeight = PLAYER_HEIGHT - PLAYER_EYE_HEIGHT; |
|
|
153 |
Vector3 end = Vector3Add(vStart, (Vector3){0, verticalMove + headHeight, 0}); |
|
|
154 |
RayCollision vCol; |
|
|
155 |
if (CheckWorldCollision(start, end, &vCol)) { |
|
|
156 |
game.pos.y = vCol.point.y - headHeight - 1.0f; |
|
|
157 |
game.velocity.y = 0; |
|
|
158 |
} else { |
|
|
159 |
game.pos.y += verticalMove; |
|
|
160 |
} |
|
|
161 |
game.is_grounded = false; |
|
|
162 |
} else { |
|
|
163 |
// Not moving vertically, but check if we are still on ground |
|
|
164 |
Vector3 start = vStart; |
|
|
165 |
Vector3 end = Vector3Add(vStart, (Vector3){0, -eyeHeight - 2.0f, 0}); |
|
|
166 |
RayCollision vCol; |
|
|
167 |
if (CheckWorldCollision(start, end, &vCol) && vCol.normal.y > 0.5f) { |
|
|
168 |
game.is_grounded = true; |
|
|
169 |
// Snap to floor if very close |
|
|
170 |
if (vCol.distance < eyeHeight + 1.0f) { |
|
|
171 |
game.pos.y = vCol.point.y + eyeHeight; |
|
|
172 |
} |
|
|
173 |
} else { |
|
|
174 |
game.is_grounded = false; |
|
|
175 |
} |
|
|
176 |
} |
|
|
177 |
} |
|
|
178 |
|
|
|
179 |
static void MoveFly(float dt) { |
|
|
180 |
// Full 3D movement based on yaw/pitch |
|
|
181 |
Vector3 forward = { |
|
|
182 |
cosf(game.pitch) * sinf(game.yaw), |
|
|
183 |
-sinf(game.pitch), |
|
|
184 |
cosf(game.pitch) * cosf(game.yaw) |
|
|
185 |
}; |
|
|
186 |
Vector3 right = { sinf(game.yaw - PI/2.0f), 0, cosf(game.yaw - PI/2.0f) }; |
|
|
187 |
|
|
|
188 |
Vector3 move = { 0 }; |
|
|
189 |
if (IsKeyDown(KEY_W)) move = Vector3Add(move, forward); |
|
|
190 |
if (IsKeyDown(KEY_S)) move = Vector3Subtract(move, forward); |
|
|
191 |
if (IsKeyDown(KEY_D)) move = Vector3Add(move, right); |
|
|
192 |
if (IsKeyDown(KEY_A)) move = Vector3Subtract(move, right); |
|
|
193 |
|
|
|
194 |
// Fly up/down |
|
|
195 |
if (IsKeyDown(KEY_SPACE)) move.y += 1.0f; |
|
|
196 |
if (IsKeyDown(KEY_LEFT_CONTROL)) move.y -= 1.0f; |
|
|
197 |
|
|
|
198 |
float speed = PLAYER_FLY_SPEED; |
|
|
199 |
if (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)) speed *= PLAYER_SPRINT_MULTIPLIER; |
|
|
200 |
|
|
|
201 |
if (Vector3Length(move) > 0) { |
|
|
202 |
move = Vector3Scale(Vector3Normalize(move), speed * dt); |
|
|
203 |
game.pos = Vector3Add(game.pos, move); |
|
|
204 |
} |
|
|
205 |
|
|
|
206 |
game.velocity = (Vector3){0, 0, 0}; |
|
|
207 |
game.is_grounded = false; |
|
|
208 |
} |
|
|
209 |
|
|
|
210 |
void UpdatePlayer(void) { |
|
|
211 |
float dt = GetFrameTime(); |
|
|
212 |
Vector3 oldPos = game.pos; |
|
|
213 |
|
|
|
214 |
if (IsKeyPressed(KEY_F)) { |
|
|
215 |
game.move_mode = (game.move_mode == MOVE_NORMAL) ? MOVE_FLY : MOVE_NORMAL; |
|
|
216 |
TraceLog(LOG_INFO, "Movement mode: %s", (game.move_mode == MOVE_NORMAL) ? "NORMAL" : "FLY"); |
|
|
217 |
} |
|
|
218 |
|
|
|
219 |
PlayerRotate(); |
|
|
220 |
|
|
|
221 |
// Crouching logic |
|
|
222 |
bool canStand = true; |
|
|
223 |
if (game.crouch_amount > 0.1f) { |
|
|
224 |
Vector3 headPos = game.pos; |
|
|
225 |
Vector3 headEnd = Vector3Add(headPos, (Vector3){0, PLAYER_HEIGHT - PLAYER_EYE_HEIGHT + PLAYER_CROUCH_OFFSET, 0}); |
|
|
226 |
RayCollision col; |
|
|
227 |
if (CheckWorldCollision(headPos, headEnd, &col)) { |
|
|
228 |
canStand = false; |
|
|
229 |
} |
|
|
230 |
} |
|
|
231 |
|
|
|
232 |
float targetCrouch = 0.0f; |
|
|
233 |
if (IsKeyDown(KEY_LEFT_CONTROL) || !canStand) targetCrouch = 1.0f; |
|
|
234 |
game.crouch_amount = Lerp(game.crouch_amount, targetCrouch, PLAYER_CROUCH_SPEED * dt); |
|
|
235 |
|
|
|
236 |
// Leaning logic |
|
|
237 |
float targetLean = 0.0f; |
|
|
238 |
if (IsKeyDown(KEY_Q)) targetLean -= 1.0f; |
|
|
239 |
if (IsKeyDown(KEY_E)) targetLean += 1.0f; |
|
|
240 |
|
|
|
241 |
game.lean_amount = Lerp(game.lean_amount, targetLean, PLAYER_LEAN_SPEED * dt); |
|
|
242 |
|
|
|
243 |
if (game.move_mode == MOVE_FLY) { |
|
|
244 |
MoveFly(dt); |
|
|
245 |
} else { |
|
|
246 |
MoveNormal(dt); |
|
|
247 |
} |
|
|
248 |
|
|
|
249 |
// Apply lean as a pivot from the neck/waist |
|
|
250 |
float leanAngle = game.lean_amount * PLAYER_LEAN_ANGLE * DEG2RAD; |
|
|
251 |
Vector3 bodyForward = { sinf(game.yaw), 0, cosf(game.yaw) }; |
|
|
252 |
|
|
|
253 |
float currentEyeHeight = PLAYER_EYE_HEIGHT - (game.crouch_amount * PLAYER_CROUCH_OFFSET); |
|
|
254 |
float pivotDist = fminf(PLAYER_LEAN_PIVOT_DISTANCE, currentEyeHeight * 0.8f); |
|
|
255 |
|
|
|
256 |
Vector3 neckToEye = { 0, pivotDist, 0 }; |
|
|
257 |
Vector3 rotatedOffset = Vector3RotateByAxisAngle(neckToEye, bodyForward, leanAngle); |
|
|
258 |
Vector3 pivot = Vector3Subtract(game.pos, (Vector3){ 0, pivotDist, 0 }); |
|
|
259 |
|
|
|
260 |
// Update camera based on physical pos + visual lean pivot |
|
|
261 |
game.camera.position = Vector3Add(pivot, rotatedOffset); |
|
|
262 |
|
|
|
263 |
// Apply roll to up vector |
|
|
264 |
Vector3 forward = { |
|
|
265 |
cosf(game.pitch) * sinf(game.yaw), |
|
|
266 |
-sinf(game.pitch), |
|
|
267 |
cosf(game.pitch) * cosf(game.yaw) |
|
|
268 |
}; |
|
|
269 |
Vector3 up = { 0, 1, 0 }; |
|
|
270 |
up = Vector3RotateByAxisAngle(up, forward, leanAngle); |
|
|
271 |
game.camera.up = up; |
|
|
272 |
|
|
|
273 |
game.camera.target = Vector3Add(game.camera.position, forward); |
|
|
274 |
|
|
|
275 |
// Calculate horizontal speed for UI |
|
|
276 |
if (dt > 0) { |
|
|
277 |
Vector2 velH = { game.pos.x - oldPos.x, game.pos.z - oldPos.z }; |
|
|
278 |
game.horizontal_speed = Vector2Length(velH) / dt; |
|
|
279 |
} else { |
|
|
280 |
game.horizontal_speed = 0; |
|
|
281 |
} |
|
|
282 |
} |