Zen 0.3.0
Loading...
Searching...
No Matches
GameLayer.cpp
1#include "GameLayer.h"
2#include "ZEN_Event.h"
3#include "ZEN_Log.h"
4#include "ZEN_RenderCommand.h"
5#include "ZEN_Renderer.h"
6#include "ZEN_Shader.h"
7#include "camera/ZEN_CameraController.h"
8#include "editor/src/Obstacle.h"
9#include "editor/src/QuadBuilder.h"
10#include "imgui.h"
11#include "inputs/ZEN_KeyCodes.h"
12#include "time/ZEN_DeltaTime.h"
13#include <SDL3/SDL_scancode.h>
14#include <glm/gtc/matrix_transform.hpp>
15#include <glm/gtc/random.hpp>
16#include <memory>
17
18using namespace Zen;
19
20void GameLayer::onAttach() {
21 glEnable(GL_BLEND);
22 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
23
24 m_camera.setPosition({0, 0, 0});
25 m_camera.setOrthographic(-10, 10, -5.625f, 5.625f);
26 m_cameraController.setWorldBounds(-10.0f, 10.0f, -5.625f, 5.625f);
27 m_cameraController.enableWorldBounds(true);
28
29 m_shader = std::make_shared<Shader>("data/particle.vert", "data/particle.frag");
30 m_particleSystem = std::make_unique<ParticleSystem>(5000);
31 m_quadBuilder.init(m_shader);
32
33 m_player.pos = {-7.0f, 0};
34
35 m_player.emitter.pos = m_player.pos;
36 m_player.emitter.size = {m_player.width, m_player.height};
37 m_player.emitter.colour = m_player.colour;
38 m_player.emitter.spawnRate = 80.0f;
39
40 m_player.emitter.props.position = m_player.pos;
41 m_player.emitter.props.velocity = {-5.0f, 0};
42 m_player.emitter.props.lifeTime = 0.6f;
43 m_player.emitter.props.sizeBegin = 0.4f;
44 m_player.emitter.props.sizeEnd = 0;
45 m_player.emitter.props.colourBegin = {1.f, 0, 0, 1.0f};
46 m_player.emitter.props.colourEnd = {0.1f, 0.4f, 0.2f, 0};
47 m_player.emitter.vRand.coneDeg = 30.0f;
48 m_player.emitter.vRand.speedMinMul = 0.5f;
49 m_player.emitter.vRand.speedMaxMul = 2.0f;
50 m_player.emitter.vRand.noiseSigma = 1.0f;
51
52 for (int i = 0; i < 10; ++i) {
53 m_obstacles.emplace_back(ObstacleType::SmallBox, 50.0f);
54 m_obstacles[i].active = false;
55 }
56}
57
58void GameLayer::onUpdate(DeltaTime deltaTime) {
59 m_cameraController.onUpdate(deltaTime);
60
61 RenderCommand::setClearColour({0.1f, 0.1f, 0.1f, 1.0f});
62 RenderCommand::clear();
63
64 if (m_gameState == GameState::Playing) {
65 updateGame(deltaTime);
66 } else if (m_gameState == GameState::GameOver) {
67 updateGameOver(deltaTime);
68 } else if (m_gameState == GameState::Paused) {
69 updateGamePaused(deltaTime);
70 }
71
72 m_particleSystem->update(deltaTime);
73 m_particleSystem->upload();
74
75 Renderer::beginScene(m_camera);
76
77 drawGround();
78 drawObstacles();
79 drawPlayer();
80
81 Renderer::submit(m_particleSystem->shader(), m_particleSystem->vao());
82
83 Renderer::endScene();
84
85 drawUI();
86}
87bool GameLayer::onEvent(const ZenEvent &event) {
88 m_cameraController.onEvent(event);
89 if (event.header.type == EventType::WindowResize) {
90 RenderCommand::setViewport(event.windowResize.width, event.windowResize.height);
91 ZEN_LOG_TRACE("resize viewport");
92 }
93 return false;
94}
95
96void GameLayer::updateGame(DeltaTime deltaTime) {
97 if (Input::isKeyPressed(Key::P)) {
98 m_gameState = GameState::Paused;
99 return;
100 }
101 const bool jumpPressed = Input::isKeyPressed(Key::Space);
102 m_player.update(deltaTime, jumpPressed);
103
104 if (m_emitJumpBurst && m_player.justJumped()) {
105 emitJumpBurst();
106 }
107 if (m_playerEmitTrail) {
108 m_particleSystem->updateEmitter(m_player.emitter, deltaTime);
109 }
110
111 m_gameSpeed += m_speedIncreaseRate * deltaTime.seconds();
112 m_gameSpeed = glm::min(m_gameSpeed, 3.0f);
113
114 for (auto &obstacle : m_obstacles) {
115 if (obstacle.active) {
116
117 float originalSpeed = obstacle.speed;
118 obstacle.speed = originalSpeed * m_gameSpeed;
119 obstacle.update(deltaTime);
120
121 if (m_obstacleEmitTrail) {
122 m_particleSystem->updateEmitter(obstacle.emitter, deltaTime);
123 }
124
125 obstacle.speed = originalSpeed;
126 }
127 }
128
129 m_spawnTimer += deltaTime.seconds();
130 if (m_spawnTimer >= m_spawnInterval) {
131 spawnObstacle();
132 m_spawnTimer = 0;
133
134 m_spawnInterval = glm::max(m_spawnInterval - 0.05f, m_minSpawnInterval);
135 }
136
137 checkCollisions();
138
139 m_score += deltaTime.seconds() * m_gameSpeed * 5.0f;
140}
141
142void GameLayer::updateGamePaused(DeltaTime deltaTime) {
143 if (Input::isKeyPressed(Key::P) || Input::isKeyPressed(Key::Space)) {
144 m_gameState = GameState::Playing;
145 return;
146 }
147
148 for (auto &obstacle : m_obstacles) {
149 if (obstacle.active && m_obstacleEmitTrail) {
150 m_particleSystem->updateEmitter(obstacle.emitter, deltaTime);
151 }
152 }
153}
154void GameLayer::updateGameOver(DeltaTime deltaTime) {
155
156 if (Input::isKeyPressed(Key::R) || Input::isKeyPressed(Key::Space)) {
157 restartGame();
158 }
159
160 for (auto &obstacle : m_obstacles) {
161 if (obstacle.active && m_obstacleEmitTrail) {
162 m_particleSystem->updateEmitter(obstacle.emitter, deltaTime);
163 }
164 }
165}
166
167void GameLayer::spawnObstacle() {
168 Obstacle *obstacle = nullptr;
169 for (auto &obs : m_obstacles) {
170 if (!obs.active) {
171 obstacle = &obs;
172 break;
173 }
174 }
175
176 if (!obstacle) {
177 obstacle = &m_obstacles[m_nextObstacleIndex];
178 m_nextObstacleIndex = (m_nextObstacleIndex + 1) % m_obstacles.size();
179 }
180
181 ObstacleType type = getRandomObstacleType();
182 *obstacle = Obstacle(type, 12.0f);
183 obstacle->active = true;
184
185 ZEN_LOG_TRACE("Spawned obstacle of type {}", static_cast<int>(type));
186}
187
188void GameLayer::checkCollisions() {
189 for (auto &obstacle : m_obstacles) {
190 if (obstacle.active && obstacle.collidesWith(m_player.pos, m_player.getSize())) {
191
192 ZEN_LOG_INFO("Collision detected! Game Over!");
193 m_gameState = GameState::GameOver;
194
195 if (m_score > m_highScore) {
196 m_highScore = m_score;
197 ZEN_LOG_INFO("New high score: {}", m_highScore);
198 }
199
200 emitCollisionEffect();
201 break;
202 }
203 }
204}
205
206void GameLayer::restartGame() {
207 ZEN_LOG_INFO("Restarting game...");
208
209 m_gameState = GameState::Playing;
210 m_score = 0;
211 m_gameSpeed = 1.0f;
212 m_spawnTimer = 0;
213 m_spawnInterval = 2.0f;
214
215 m_player.pos = {-7.0f, 0};
216 m_player.vy = 0;
217
218 for (auto &obstacle : m_obstacles) {
219 obstacle.active = false;
220 }
221
222 m_particleSystem->clear();
223}
224
225void GameLayer::emitJumpBurst() {
226 const int burstCount = 30;
227 for (int i = 0; i < burstCount; ++i) {
228 ParticleProps p;
229 p.position = m_player.pos;
230
231 float angle = glm::linearRand(0.0f, glm::two_pi<float>());
232 float speed = glm::linearRand(3.0f, 6.0f);
233 p.velocity = {glm::cos(angle) * speed, glm::sin(angle) * speed};
234
235 p.lifeTime = 0.6f;
236 p.sizeBegin = 0.5f;
237 p.sizeEnd = 0;
238 p.colourBegin = {1.0f, 1.0f, 0.3f, 1.0f};
239 p.colourEnd = {1.0f, 0.5f, 0, 0};
240
241 m_particleSystem->emit(p);
242 }
243}
244
245void GameLayer::emitCollisionEffect() {
246 const int explosionCount = 100;
247 for (int i = 0; i < explosionCount; ++i) {
248 ParticleProps explosion;
249 explosion.position = m_player.pos;
250
251 // Explosion in all directions
252 float angle = glm::linearRand(0.0f, glm::two_pi<float>());
253 float speed = glm::linearRand(3.0f, 10.0f);
254 explosion.velocity = {glm::cos(angle) * speed, glm::sin(angle) * speed};
255
256 explosion.lifeTime = 1.0f;
257 explosion.sizeBegin = 0.6f;
258 explosion.sizeEnd = 0;
259 explosion.colourBegin = {1.0f, 0.3f, 0.1f, 1.0f};
260 explosion.colourEnd = {0.5f, 0.1f, 0, 0};
261
262 m_particleSystem->emit(explosion);
263 }
264}
265
266ObstacleType GameLayer::getRandomObstacleType() {
267 int random = rand() % 100;
268
269 if (random < 40) {
270 return ObstacleType::SmallBox;
271 }
272 if (random < 75) {
273 return ObstacleType::TallBox;
274 }
275 return ObstacleType::FlyingBox;
276}
277
278void GameLayer::drawPlayer() {
279 glm::vec4 colour = m_player.colour;
280 if (m_gameState == GameState::GameOver) {
281 colour = {0.5f, 0.5f, 0.5f, 1.0f};
282 }
283 m_quadBuilder.drawQuad(m_player.pos, m_player.getSize(), colour);
284}
285
286void GameLayer::drawObstacles() {
287 for (const auto &obstacle : m_obstacles) {
288 if (obstacle.active) {
289 m_quadBuilder.drawQuad(obstacle.pos, obstacle.size, obstacle.colour);
290 }
291 }
292}
293
294void GameLayer::drawGround() { m_quadBuilder.drawQuad(m_groundPos, m_groundSize, m_groundColour); }
295
296void GameLayer::drawUI() {
297 ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_Always);
298 ImGui::SetNextWindowBgAlpha(0.6f);
299 ImGui::Begin("Score",
300 nullptr,
301 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
302 ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove);
303
304 ImGui::Text("Score: %.0f", m_score);
305 ImGui::Text("High: %.0f", m_highScore);
306 ImGui::Text("Speed: %.1fx", m_gameSpeed);
307
308 if (m_gameState == GameState::GameOver) {
309 ImGui::Separator();
310 ImGui::TextColored(ImVec4(1.0f, 0.2f, 0.2f, 1.0f), "GAME OVER!");
311 ImGui::Text("Press R or SPACE to restart");
312 }
313
314 if (m_gameState == GameState::Paused) {
315 ImGui::Separator();
316 ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), "PAUSED!");
317 ImGui::Text("Press P or SPACE to Resume");
318 }
319
320 ImGui::End();
321}
322
323void GameLayer::onGUIRender() {
324 ImGui::SetNextWindowSize(ImVec2(400, 600), ImGuiCond_Once);
325 ImGui::SetNextWindowPos(ImVec2(800, 10), ImGuiCond_Once);
326 ImGui::Begin("Editor");
327
328 ImGui::SeparatorText("Game State");
329
330 const char *stateNames[] = {"Playing", "GameOver", "Paused"};
331 ImGui::Text("State: %s", stateNames[static_cast<int>(m_gameState)]);
332 ImGui::Text("Score: %.0f", m_score);
333 ImGui::Text("High Score: %.0f", m_highScore);
334
335 if (ImGui::Button("Restart Game (R)")) {
336 restartGame();
337 }
338 ImGui::SameLine();
339 if (m_gameState == GameState::Playing) {
340 if (ImGui::Button("Pause Game (P)")) {
341 m_gameState = GameState::Paused;
342 }
343 } else if (m_gameState == GameState::Paused) {
344 if (ImGui::Button("Resume Game (P)")) {
345 m_gameState = GameState::Playing;
346 }
347 }
348 ImGui::SameLine();
349 if (ImGui::Button("Spawn Obstacle")) {
350 spawnObstacle();
351 }
352
353 ImGui::SeparatorText("Difficulty");
354 ImGui::DragFloat("Game Speed", &m_gameSpeed, 0.1f, 0.5f, 5.0f);
355 ImGui::DragFloat("Speed Increase Rate", &m_speedIncreaseRate, 0.01f, 0, 1.0f);
356 ImGui::DragFloat("Spawn Interval", &m_spawnInterval, 0.1f, 0.5f, 5.0f);
357 ImGui::DragFloat("Min Spawn Interval", &m_minSpawnInterval, 0.1f, 0.3f, 2.0f);
358
359 ImGui::SeparatorText("Player");
360 ImGui::DragFloat("Jump Power", &m_player.jumpPower, 0.1f, 5.0f, 20.0f);
361 ImGui::DragFloat("Gravity", &m_player.gravity, 0.5f, -50.0f, -5.0f);
362 ImGui::DragFloat2("Player Size", &m_player.width, 0.1f, 0.1f, 3.0f);
363 ImGui::ColorEdit4("Player Colour", &m_player.colour[0]);
364
365 ImGui::SeparatorText("Player Particles");
366 ImGui::Checkbox("Emit Trail", &m_playerEmitTrail);
367 ImGui::Checkbox("Emit Jump Burst", &m_emitJumpBurst);
368 ImGui::DragFloat("Trail Spawn Rate", &m_player.emitter.spawnRate, 1.0f, 0, 200.0f);
369 ImGui::DragFloat2("Trail Velocity", &m_player.emitter.props.velocity[0], 0.1f, -10.0f, 10.0f);
370 ImGui::ColorEdit4("Trail Colour Start", &m_player.emitter.props.colourBegin[0]);
371 ImGui::ColorEdit4("Trail Colour End", &m_player.emitter.props.colourEnd[0]);
372 ImGui::DragFloat("Trail Life", &m_player.emitter.props.lifeTime, 0.1f, 0.1f, 3.0f);
373 ImGui::DragFloat("Cone (deg)", &m_player.emitter.vRand.coneDeg, 0.1f, 0, 60.0f);
374 ImGui::DragFloat("Speed min x", &m_player.emitter.vRand.speedMinMul, 0.01f, 0, 2.0f);
375 ImGui::DragFloat("Speed max x", &m_player.emitter.vRand.speedMaxMul, 0.01f, 0, 3.0f);
376 ImGui::DragFloat("Noise sigma", &m_player.emitter.vRand.noiseSigma, 0.01f, 0, 1.0f);
377
378 ImGui::SeparatorText("Obstacles");
379 ImGui::Checkbox("Emit Obstacle Trails", &m_obstacleEmitTrail);
380
381 int activeCount = 0;
382 for (const auto &obs : m_obstacles) {
383 if (obs.active) {
384 activeCount++;
385 }
386 }
387 ImGui::Text("Active Obstacles: %d / %zu", activeCount, m_obstacles.size());
388
389 if (ImGui::Button("Clear All Obstacles")) {
390 for (auto &obs : m_obstacles) {
391 obs.active = false;
392 }
393 }
394
395 if (ImGui::TreeNode("Obstacle Types")) {
396 for (size_t i = 0; i < m_obstacles.size() && i < 5; ++i) {
397 ImGui::PushID(static_cast<int>(i));
398 if (m_obstacles[i].active) {
399 const char *typeNames[] = {"Small", "Tall", "Flying"};
400 ImGui::Text("Obstacle %zu: %s (%.1f, %.1f)",
401 i,
402 typeNames[static_cast<int>(m_obstacles[i].type)],
403 m_obstacles[i].pos.x,
404 m_obstacles[i].pos.y);
405 }
406 ImGui::PopID();
407 }
408 ImGui::TreePop();
409 }
410
411 ImGui::SeparatorText("Test Spawns");
412 if (ImGui::Button("Spawn Small Box")) {
413 m_obstacles.push_back(Obstacle(ObstacleType::SmallBox, 12.0f));
414 }
415 if (ImGui::Button("Spawn Tall Box")) {
416 m_obstacles.push_back(Obstacle(ObstacleType::TallBox, 12.0f));
417 }
418 if (ImGui::Button("Spawn Flying Box")) {
419 m_obstacles.push_back(Obstacle(ObstacleType::FlyingBox, 12.0f));
420 }
421
422 ImGui::SeparatorText("Particle System");
423 ImGui::Text("Particles: %zu", m_particleSystem->capacity());
424 if (ImGui::Button("Clear All Particles")) {
425 m_particleSystem->clear();
426 }
427
428 ImGui::End();
429}