r-type  0.0.0
R-Type main
Loading...
Searching...
No Matches
Collision.hpp
Go to the documentation of this file.
1///
2/// @file Collision.hpp
3/// @brief This file contains the collision system definitions
4/// @namespace gme
5///
6
7#pragma once
8
9#include <array>
10#include <cmath>
11#include <ranges>
12#include <string>
13#include <vector>
14
15#include "ECS/Component.hpp"
17#include "ECS/Registry.hpp"
19#include "Utils/Common.hpp"
21
22namespace gme
23{
24
25 class CollisionSystem final : public ecs::ASystem
26 {
27 public:
28 explicit CollisionSystem(const std::shared_ptr<eng::IRenderer> &renderer, bool &showDebug)
29 : m_renderer(renderer), m_showDebug(showDebug)
30 {
32 }
33 ~CollisionSystem() override = default;
34
39
40 bool isEnable() override { return true; }
41 void setEnable(const bool enable) override { (void)enable; }
42
43 void update(ecs::Registry &registry, float dt) override
44 {
45 const bool hasPlayer = !registry.getAll<ecs::Player>().empty();
46
47 std::optional<float> ceilingBottomY;
48 std::optional<float> floorTopY;
49 for (const auto &entity : registry.getAll<ecs::Ceiling>() | std::views::keys)
50 {
51 const auto *t = registry.getComponent<ecs::Transform>(entity);
52 const auto *s = registry.getComponent<ecs::Scale>(entity);
53 const auto *scroll = registry.getComponent<ecs::Scrolling>(entity);
54 if ((t == nullptr) || (scroll == nullptr))
55 {
56 continue;
57 }
58 const float scaledHeight = (s ? s->y : 1.0f) * scroll->original_height;
59 ceilingBottomY = t->y + scaledHeight;
60 break;
61 }
62 for (const auto &entity : registry.getAll<ecs::Floor>() | std::views::keys)
63 {
64 const auto *t = registry.getComponent<ecs::Transform>(entity);
65 if (t == nullptr)
66 {
67 continue;
68 }
69 floorTopY = t->y;
70 break;
71 }
72
73 std::vector<ecs::Entity> projectilesToRemove;
74 std::vector<ecs::Entity> enemiesToRemove;
75
76 for (auto &[projectileEntity, projectile] : registry.getAll<ecs::Projectile>())
77 {
78 const auto *projectileTransform = registry.getComponent<ecs::Transform>(projectileEntity);
79 const auto *projectileHitbox = registry.getComponent<ecs::Hitbox>(projectileEntity);
80 if ((projectileTransform == nullptr) || (projectileHitbox == nullptr))
81 {
82 continue;
83 }
84
85 for (auto &[enemyEntity, enemy] : registry.getAll<ecs::Enemy>())
86 {
87 const auto *enemyTransform = registry.getComponent<ecs::Transform>(enemyEntity);
88 const auto *enemyHitbox = registry.getComponent<ecs::Hitbox>(enemyEntity);
89 if ((enemyTransform == nullptr) || (enemyHitbox == nullptr))
90 {
91 continue;
92 }
93
94 if (checkCircularCollision(*projectileTransform, *projectileHitbox, *enemyTransform,
95 *enemyHitbox))
96 {
97 enemy.health -= projectile.damage;
98 if (projectile.type == ecs::Projectile::SUPERCHARGED && projectile.pierce_remaining > 1)
99 {
100 projectile.pierce_remaining -= 1;
101 }
102 else
103 {
104 projectilesToRemove.push_back(projectileEntity);
105 }
106
107 if (enemy.health <= 0.0f)
108 {
109 createExplosion(registry, enemyTransform->x, enemyTransform->y);
110 enemiesToRemove.push_back(enemyEntity);
111 for (auto &score : registry.getAll<ecs::Score>() | std::views::values)
112 {
113 score.value += 100;
114 break;
115 }
116 playEnemyDeathSound(registry);
117 }
118 break;
119 }
120 }
121 }
122
123 for (const ecs::Entity entity : projectilesToRemove)
124 {
125 removeProjectile(registry, entity);
126 }
127 for (const ecs::Entity entity : enemiesToRemove)
128 {
129 removeEnemy(registry, entity);
130 }
131 if (ceilingBottomY.has_value() || floorTopY.has_value())
132 {
133 for (const auto &playerEntity : registry.getAll<ecs::Player>() | std::views::keys)
134 {
135 auto *t = registry.getComponent<ecs::Transform>(playerEntity);
136 auto *hb = registry.getComponent<ecs::Hitbox>(playerEntity);
137 auto *vel = registry.getComponent<ecs::Velocity>(playerEntity);
138 if ((t == nullptr) || (hb == nullptr))
139 {
140 continue;
141 }
142 const float hitboxY = t->y + hb->offsetY;
143 if (ceilingBottomY.has_value() && (hitboxY - hb->radius < ceilingBottomY.value()))
144 {
145 t->y = ceilingBottomY.value() + hb->radius - hb->offsetY;
146 if (vel != nullptr)
147 {
148 vel->y = std::max(0.0f, vel->y);
149 }
150 }
151 if (floorTopY.has_value() && (hitboxY + hb->radius > floorTopY.value()))
152 {
153 t->y = floorTopY.value() - hb->radius - hb->offsetY;
154 if (vel != nullptr)
155 {
156 vel->y = std::min(0.0f, vel->y);
157 }
158 }
159 }
160 for (const auto &enemyEntity : registry.getAll<ecs::Enemy>() | std::views::keys)
161 {
162 auto *t = registry.getComponent<ecs::Transform>(enemyEntity);
163 auto *hb = registry.getComponent<ecs::Hitbox>(enemyEntity);
164 auto *vel = registry.getComponent<ecs::Velocity>(enemyEntity);
165 if ((t == nullptr) || (hb == nullptr))
166 {
167 continue;
168 }
169 const float hitboxY = t->y + hb->offsetY;
170 if (ceilingBottomY.has_value() && (hitboxY - hb->radius < ceilingBottomY.value()))
171 {
172 t->y = ceilingBottomY.value() + hb->radius - hb->offsetY;
173 if (vel != nullptr)
174 {
175 vel->y = std::max(0.0f, vel->y);
176 }
177 }
178 if (floorTopY.has_value() && (hitboxY + hb->radius > floorTopY.value()))
179 {
180 t->y = floorTopY.value() - hb->radius - hb->offsetY;
181 if (vel != nullptr)
182 {
183 vel->y = std::min(0.0f, vel->y);
184 }
185 }
186 }
187 }
188 if (m_wasPlayerPresent && !hasPlayer)
189 {
190 playPlayerDeathSound(registry);
191 }
192 m_wasPlayerPresent = hasPlayer;
193 }
194
195 private:
196 const std::shared_ptr<eng::IRenderer> &m_renderer;
197 std::array<ecs::Entity, 4> m_enemyDeathAudioEntities{};
199 std::size_t m_nextEnemyDeathChannel = 0;
200 bool m_wasPlayerPresent = false;
201 bool &m_showDebug;
202
203 static bool checkCircularCollision(const ecs::Transform &transform1, const ecs::Hitbox &hitbox1,
204 const ecs::Transform &transform2, const ecs::Hitbox &hitbox2)
205 {
206 const float x1 = transform1.x + hitbox1.offsetX;
207 const float y1 = transform1.y + hitbox1.offsetY;
208 const float x2 = transform2.x + hitbox2.offsetX;
209 const float y2 = transform2.y + hitbox2.offsetY;
210
211 const float dx = x1 - x2;
212 const float dy = y1 - y2;
213 const float distance = std::sqrt(dx * dx + dy * dy);
214 const float combinedRadius = hitbox1.radius + hitbox2.radius;
215
216 return distance < combinedRadius;
217 }
218
219 static void removeProjectile(ecs::Registry &registry, ecs::Entity entity)
220 {
221 if (registry.hasComponent<ecs::Projectile>(entity))
222 {
223 registry.removeComponent<ecs::Projectile>(entity);
224 }
225 if (registry.hasComponent<ecs::Transform>(entity))
226 {
227 registry.removeComponent<ecs::Transform>(entity);
228 }
229 if (registry.hasComponent<ecs::Velocity>(entity))
230 {
231 registry.removeComponent<ecs::Velocity>(entity);
232 }
233 if (registry.hasComponent<ecs::Rect>(entity))
234 {
235 registry.removeComponent<ecs::Rect>(entity);
236 }
237 if (registry.hasComponent<ecs::Texture>(entity))
238 {
239 registry.removeComponent<ecs::Texture>(entity);
240 }
241 if (registry.hasComponent<ecs::Scale>(entity))
242 {
243 registry.removeComponent<ecs::Scale>(entity);
244 }
245 if (registry.hasComponent<ecs::Animation>(entity))
246 {
247 registry.removeComponent<ecs::Animation>(entity);
248 }
249 if (registry.hasComponent<ecs::Hitbox>(entity))
250 {
251 registry.removeComponent<ecs::Hitbox>(entity);
252 }
253 }
254
255 static void removeEnemy(ecs::Registry &registry, ecs::Entity entity)
256 {
257 if (registry.hasComponent<ecs::Enemy>(entity))
258 {
259 registry.removeComponent<ecs::Enemy>(entity);
260 }
261 if (registry.hasComponent<ecs::Transform>(entity))
262 {
263 registry.removeComponent<ecs::Transform>(entity);
264 }
265 if (registry.hasComponent<ecs::Velocity>(entity))
266 {
267 registry.removeComponent<ecs::Velocity>(entity);
268 }
269 if (registry.hasComponent<ecs::Rect>(entity))
270 {
271 registry.removeComponent<ecs::Rect>(entity);
272 }
273 if (registry.hasComponent<ecs::Texture>(entity))
274 {
275 registry.removeComponent<ecs::Texture>(entity);
276 }
277 if (registry.hasComponent<ecs::Scale>(entity))
278 {
279 registry.removeComponent<ecs::Scale>(entity);
280 }
281 if (registry.hasComponent<ecs::Animation>(entity))
282 {
283 registry.removeComponent<ecs::Animation>(entity);
284 }
285 if (registry.hasComponent<ecs::Hitbox>(entity))
286 {
287 registry.removeComponent<ecs::Hitbox>(entity);
288 }
289 if (registry.hasComponent<ecs::Projectile>(entity))
290 {
291 registry.removeComponent<ecs::Projectile>(entity);
292 }
293 }
294
295 static void createExplosion(ecs::Registry &registry, float x, float y)
296 {
297 registry.createEntity()
298 .with<ecs::Transform>("explosion_transform", x, y, 0.0f)
299 .with<ecs::Rect>("explosion_rect", 0.0f, 0.0f,
302 .with<ecs::Scale>("explosion_scale", utl::GameConfig::Explosion::SCALE,
304 .with<ecs::Texture>("explosion_texture", utl::Path::Texture::TEXTURE_EXPLOSION)
305 .with<ecs::Explosion>(
310 .build();
311 }
312
313 void ensureEnemyDeathChannel(ecs::Registry &registry, const std::size_t channelIndex)
314 {
315 if (channelIndex >= m_enemyDeathAudioEntities.size())
316 {
317 return;
318 }
319
320 ecs::Entity &entity = m_enemyDeathAudioEntities[channelIndex];
321 if (entity != ecs::INVALID_ENTITY && registry.hasComponent<ecs::Audio>(entity))
322 {
323 return;
324 }
325
326 entity = registry.createEntity()
327 .with<ecs::Audio>("enemy_death_" + std::to_string(channelIndex),
328 utl::Path::Audio::AUDIO_DEATH_ENEMIES, 1.5F, false, false)
329 .build();
330 }
331
333 {
337
338 if (entity == ecs::INVALID_ENTITY)
339 {
340 return;
341 }
342
343 if (auto *audio = registry.getComponent<ecs::Audio>(entity))
344 {
345 audio->play = true;
346 }
347 }
348
350 {
353 {
354 return;
355 }
356
358 registry.createEntity()
359 .with<ecs::Audio>("player_death", utl::Path::Audio::AUDIO_DEATH_ALLIES, 1.5F, false, false)
360 .build();
361 }
362
364 {
365 ensurePlayerDeathAudio(registry);
367 {
368 return;
369 }
370
371 if (auto *audio = registry.getComponent<ecs::Audio>(m_playerDeathAudioEntity))
372 {
373 audio->play = true;
374 }
375 }
376 }; // class CollisionSystem
377} // namespace gme
This file contains the component definitions.
Configuration constants for the multiplayer game.
This file contains the IRenderer class declaration.
This file contains the interface for systems.
This file contains the Registry class declaration.
Abstract class for system.
Definition ISystems.hpp:34
EntityBuilder & with(Args &&...args)
Definition Registry.hpp:40
Class for managing entities and their components.
Definition Registry.hpp:25
EntityBuilder createEntity()
Definition Registry.hpp:53
bool hasComponent(Entity e)
Definition Registry.hpp:79
std::unordered_map< Entity, T > & getAll()
Definition Registry.hpp:77
T * getComponent(Entity e)
Definition Registry.hpp:71
void removeComponent(Entity e)
Definition Registry.hpp:85
Server-authoritative collision detection and response system.
CollisionSystem(CollisionSystem &&)=delete
void update(ecs::Registry &registry, float dt) override
Definition Collision.hpp:43
static void removeEnemy(ecs::Registry &registry, ecs::Entity entity)
static bool checkCircularCollision(const ecs::Transform &transform1, const ecs::Hitbox &hitbox1, const ecs::Transform &transform2, const ecs::Hitbox &hitbox2)
~CollisionSystem() override=default
void playEnemyDeathSound(ecs::Registry &registry)
void setEnable(const bool enable) override
Definition Collision.hpp:41
CollisionSystem & operator=(CollisionSystem &&)=delete
std::array< ecs::Entity, 4 > m_enemyDeathAudioEntities
CollisionSystem(const std::shared_ptr< eng::IRenderer > &renderer, bool &showDebug)
Definition Collision.hpp:28
static void removeProjectile(ecs::Registry &registry, ecs::Entity entity)
std::size_t m_nextEnemyDeathChannel
ecs::Entity m_playerDeathAudioEntity
bool isEnable() override
Definition Collision.hpp:40
const std::shared_ptr< eng::IRenderer > & m_renderer
Definition Collision.hpp:99
void playPlayerDeathSound(ecs::Registry &registry)
CollisionSystem & operator=(const CollisionSystem &)=delete
static void createExplosion(ecs::Registry &registry, float x, float y)
void ensureEnemyDeathChannel(ecs::Registry &registry, const std::size_t channelIndex)
void ensurePlayerDeathAudio(ecs::Registry &registry)
CollisionSystem(const CollisionSystem &)=delete
This file contains common definitions and constants.
std::uint32_t Entity
Definition Entity.hpp:13
constexpr Entity INVALID_ENTITY
Definition Entity.hpp:14
constexpr float SPRITE_HEIGHT
constexpr float SPRITE_WIDTH
constexpr float ANIMATION_DURATION
constexpr auto AUDIO_DEATH_ALLIES
Definition Common.hpp:88
constexpr auto AUDIO_DEATH_ENEMIES
Definition Common.hpp:89
constexpr auto TEXTURE_EXPLOSION
Definition Common.hpp:123