Skip to main content

ProjectileSpawnSystem.ixx File

System for spawning projectiles based on ShootComponent state. More...

Included Headers

Namespaces Index

namespacehelios
namespaceengine

Main engine module aggregating core infrastructure and game systems. More...

namespacemechanics

High-level gameplay systems and components for game logic. More...

namespacecombat

Combat-related gameplay systems, components, and commands. More...

namespacesystems

Gameplay systems for combat mechanics processing. More...

Classes Index

classProjectileSpawnSystem

System that spawns projectiles for entities with active ShootComponents. More...

Description

System for spawning projectiles based on ShootComponent state.

File Listing

The file content with the documentation metadata removed is:

1/**
2 * @file ProjectileSpawnSystem.ixx
3 * @brief System for spawning projectiles based on ShootComponent state.
4 */
5module;
6
7#include <cassert>
8
9export module helios.engine.mechanics.combat.systems.ProjectileSpawnSystem;
10
11
12
13import helios.engine.state.Bindings;
14import helios.engine.runtime.messaging.command.EngineCommandBuffer;
15import helios.engine.runtime.world.UpdateContext;
16import helios.engine.runtime.world.GameWorld;
17import helios.engine.mechanics.combat.components.ShootComponent;
18import helios.engine.mechanics.combat.components.Aim2DComponent;
19import helios.engine.modules.spatial.transform.components.TranslationStateComponent;
20import helios.engine.runtime.spawn.commands.SpawnCommand;
21import helios.engine.runtime.spawn.types.SpawnContext;
22import helios.engine.runtime.spawn.types.EmitterContext;
23import helios.engine.runtime.spawn.types.SpawnProfileId;
24
25import helios.math;
26
27import helios.engine.mechanics.lifecycle.components.Active;
28
29import helios.engine.common.tags.SystemRole;
30
33
34 /**
35 * @brief System that spawns projectiles for entities with active ShootComponents.
36 *
37 * @details ProjectileSpawnSystem iterates all entities that have a ShootComponent,
38 * Aim2DComponent, and TranslationStateComponent. When a ShootComponent has non-zero
39 * intensity and the aim component has a valid frequency, the system calculates how
40 * many projectiles to spawn based on accumulated time and cooldown.
41 *
42 * ## Fire Rate Mechanics
43 *
44 * The system implements an intensity-based fire rate:
45 * 1 Accumulates `deltaTime * intensity` into the cooldown timer
46 * 2 When timer >= cooldownDelta, spawns `floor(timer / cooldownDelta)` projectiles
47 * 3 Reduces timer by spawned amount, preserving fractional remainder
48 *
49 * This allows continuous fire with variable intensity affecting the effective rate.
50 *
51 * ## Spawn Context
52 *
53 * Each SpawnCommand includes an EmitterContext containing:
54 * - **position:** Current translation from TranslationStateComponent
55 * - **velocity:** sourceVelocity + (aimDirection * projectileSpeed)
56 *
57 * Example setup:
58 * ```cpp
59 * auto projectileSpawnSystem = std::make_unique<ProjectileSpawnSystem>(
60 * SpawnProfileId{1} // ID of the projectile spawn profile
61 * );
62 * mainPhase.addPass().add(std::move(projectileSpawnSystem));
63 * ```
64 *
65 * @note Entities must have ShootComponent, Aim2DComponent, and
66 * TranslationStateComponent to be processed.
67 *
68 * @see ShootComponent
69 * @see Aim2DComponent
70 * @see SpawnCommand
71 * @see EmitterContext
72 */
74
75 /**
76 * @brief The spawn profile ID used for projectile creation.
77 *
78 * References a SpawnProfile in the spawn system that defines how
79 * projectiles are placed and initialized.
80 */
82
83
84 public:
85
87
88 /**
89 * @brief Constructs a ProjectileSpawnSystem with the specified spawn profile.
90 *
91 * @param spawnProfileId The ID of the spawn profile to use for projectiles.
92 */
95 ) :
96 spawnProfileId_(spawnProfileId)
97 {}
98
99 /**
100 * @brief Processes all shooting entities and spawns projectiles.
101 *
102 * @details For each entity with ShootComponent, Aim2DComponent, and
103 * TranslationStateComponent:
104 *
105 * 1 Skips if aim frequency or intensity are near zero
106 * 2 Accumulates `deltaTime * intensity` into the cooldown timer
107 * 3 If timer < cooldownDelta, updates timer and continues
108 * 4 Calculates projectile count as `floor(timer / cooldownDelta)`
109 * 5 Reduces timer by `count * cooldownDelta`
110 * 6 Enqueues SpawnCommands with EmitterContext for each projectile
111 *
112 * @param updateContext The current frame's update context.
113 */
115
116 for (auto [entity, tsc, ac, sc, active] : updateContext.view<
121 >().whereEnabled()) {
122
123
124 const float intensity = sc->intensity();
125 const float cooldown = sc->cooldownDelta();
126
127 float timer = sc->cooldownTimer();
128
129 // only process for meaningful values
130 assert(cooldown > helios::math::EPSILON_LENGTH && "Unexpected cooldownDelta");
131 if (ac->frequency() <= helios::math::EPSILON_LENGTH ||
132 intensity <= helios::math::EPSILON_LENGTH) {
133 continue;
134 };
135
136 const float delta = updateContext.deltaTime();
137
138 // update the timer to accumulate frame times * intensity.
139 // will be updated in this frame if projectiles spawned,
140 timer += delta * intensity;
141
142 if (timer < cooldown) {
143 sc->setCooldownTimer(timer);
144 continue;
145 }
146
147 const unsigned int amount = static_cast<unsigned int>(timer / cooldown);
148
149 // reduce timer by the product of projectiles and cooldown,
150 // which leaves a fractional part that did not add a whole projectile
151 // to this frame
152 sc->setCooldownTimer(timer - (amount * cooldown));
153
154 const auto aimDirection = ac->direction().toVec3();
155 assert(aimDirection.isNormalized() && "Unexpected aimDirection.length()");
156
157 for (unsigned int i = 0; i < amount; i++) {
158 updateContext.queueCommand<
160 >(
161 spawnProfileId_,
164 tsc->translation(),
165 sc->sourceVelocity() + (aimDirection * sc->projectileSpeed()),
166 entity.entityHandle()
167 }
168 }
169 );
170 }
171 }
172 }
173
174 };
175
176
177}

Generated via doxygen2docusaurus 2.0.0 by Doxygen 1.15.0.