Air-Trap 1.0.0
A multiplayer R-Type clone game engine built with C++23 and ECS architecture
Loading...
Searching...
No Matches
PlayingScene.cpp
Go to the documentation of this file.
1
15#include <SFML/Window/Joystick.hpp>
16
17#include <algorithm>
18
19namespace rtp::client {
20 namespace scenes {
21
23 // Public API
25
27 ecs::Registry& uiRegistry,
28 Settings& settings,
29 TranslationManager& translationManager,
30 NetworkSyncSystem& network,
31 graphics::UiFactory& uiFactory,
32 EntityBuilder& worldBuilder,
33 std::function<void(GameState)> changeState)
34 : _uiRegistry(uiRegistry),
35 _settings(settings),
36 _translationManager(translationManager),
37 _network(network),
38 _uiFactory(uiFactory),
39 _changeState(changeState),
40 _worldRegistry(worldRegistry),
41 _worldBuilder(worldBuilder)
42 {
43 }
44
46 {
47 log::info("Entering PlayingScene");
48
49 _uiScore = 0;
50
52
53 {
54 auto musicEntity = _worldRegistry.spawn();
55 if (musicEntity) {
57 levelMusic.audioPath = "assets/musics/lvl1.mp3";
58 levelMusic.volume = 80.0f;
59 levelMusic.loop = true;
60 levelMusic.isPlaying = true;
61 levelMusic.dirty = true;
62 _worldRegistry.add<ecs::components::audio::AudioSource>(musicEntity.value(), levelMusic);
63 log::info("Playing Level 1 music");
64 } else {
65 log::warning("Failed to spawn entity for Level 1 music");
66 }
67 }
68
71 {30.0f, 15.0f},
72 {715.0f, 40.0f},
73 "",
74 nullptr
75 );
76 if (auto buttonsOpt = _uiRegistry.get<ecs::components::ui::Button>()) {
77 auto &buttons = buttonsOpt.value().get();
78 if (buttons.has(_chatCompactPanel)) {
79 auto &panel = buttons[_chatCompactPanel];
80 panel.idleColor[0] = 20; panel.idleColor[1] = 20; panel.idleColor[2] = 20;
81 panel.hoverColor[0] = 20; panel.hoverColor[1] = 20; panel.hoverColor[2] = 20;
82 panel.pressedColor[0] = 20; panel.pressedColor[1] = 20; panel.pressedColor[2] = 20;
83 }
84 }
85
88 {45.0f, 22.0f},
89 "",
90 "assets/fonts/main.ttf",
91 18,
92 10,
93 {220, 220, 220}
94 );
95
96 auto makeHudText = [&](const Vec2f& pos, unsigned size, const graphics::color& color) -> ecs::Entity {
97 auto eRes = _uiRegistry.spawn();
98 if (!eRes) return {};
99
100 auto e = eRes.value();
102 t.content = "";
103 t.position = pos;
104 t.fontPath = "assets/fonts/main.ttf";
105 t.fontSize = size;
106 t.red = color.r; t.green = color.g; t.blue = color.b;
107 t.alpha = 220;
108 t.zIndex = 999;
110 return e;
111 };
112
113 _hudHealth = makeHudText({980.0f, 18.0f}, 18, {110, 220, 170});
114 _hudScore = makeHudText({980.0f, 42.0f}, 18, {120, 190, 230});
115 _hudAmmo = makeHudText({980.0f, 66.0f}, 16, {230, 190, 120});
116 _hudFps = makeHudText({980.0f, 90.0f}, 14, {180, 180, 190});
117
118 _hudPing = makeHudText({1020.0f, 660.0f}, 12, {140, 140, 150});
119 _hudEntities = makeHudText({1020.0f, 678.0f}, 12, {140, 140, 150});
120
123 {520.0f, 700.0f},
124 {240.0f, 6.0f},
125 0.0f,
126 1.0f,
127 0.0f,
128 nullptr
129 );
130 if (auto slidersOpt = _uiRegistry.get<ecs::components::ui::Slider>()) {
131 auto &sliders = slidersOpt.value().get();
132 if (sliders.has(_hudChargeBar)) {
133 auto &slider = sliders[_hudChargeBar];
134 slider.trackColor[0] = 25; slider.trackColor[1] = 25; slider.trackColor[2] = 25;
135 slider.fillColor[0] = 180; slider.fillColor[1] = 120; slider.fillColor[2] = 60;
136 slider.handleColor[0] = 25; slider.handleColor[1] = 25; slider.handleColor[2] = 25;
137 slider.zIndex = 999;
138 }
139 }
140
141 _hudInit = true;
142 _chargeTime = 0.0f;
143 }
144
146 {
147 _hudInit = false;
148 closeChat();
149 _chargeTime = 0.0f;
150 }
151
152
153 void PlayingScene::handleEvent(const sf::Event& e)
154 {
155 // Check gamepad pause button
156 if (_settings.getGamepadEnabled() && sf::Joystick::isConnected(0)) {
157 static bool wasPausePressed = false;
158 bool isPausePressed = sf::Joystick::isButtonPressed(0, _settings.getGamepadPauseButton());
159
160 if (isPausePressed && !wasPausePressed) {
161 if (_chatOpen) {
162 closeChat();
163 wasPausePressed = true;
164 return;
165 }
167 wasPausePressed = true;
168 return;
169 }
170 wasPausePressed = isPausePressed;
171 }
172
173 if (const auto* kp = e.getIf<sf::Event::KeyPressed>()) {
174 if (kp->code == sf::Keyboard::Key::Enter) {
175 if (!_chatOpen) {
176 openChat();
177 } else {
179 bool focused = false;
180 if (inputsOpt) {
181 auto &inputs = inputsOpt.value().get();
182 if (inputs.has(_chatInput)) {
183 focused = inputs[_chatInput].isFocused;
184 }
185 }
186 if (!focused) {
187 closeChat();
188 }
189 }
190 } else if (kp->code == sf::Keyboard::Key::Escape) {
191 if (_chatOpen) {
192 closeChat();
193 return;
194 }
196 }
197 }
198 }
199
200 void PlayingScene::update(float dt)
201 {
202 if (_network.consumeKicked()) {
204 return;
205 }
208 return;
209 }
210 if (!_hudInit) return;
211
212 _fpsTimer += dt;
213 _fpsFrames += 1;
214 if (_fpsTimer >= 0.5f) {
215 _uiFps = static_cast<uint32_t>(_fpsFrames / _fpsTimer);
216 _fpsFrames = 0;
217 _fpsTimer = 0.0f;
218 }
220 _uiScore = static_cast<uint32_t>(std::max(0, _network.getScore()));
221
222 auto textsOpt = _uiRegistry.get<ecs::components::ui::Text>();
223 if (!textsOpt) return;
224 auto& texts = textsOpt.value().get();
225
226 if (texts.has(_hudPing)) texts[_hudPing].content = "Ping " + std::to_string(_uiPing) + " ms";
227 if (texts.has(_hudFps)) texts[_hudFps].content = "FPS " + std::to_string(_uiFps);
228 if (texts.has(_hudScore)) texts[_hudScore].content = "SCORE " + std::to_string(_uiScore);
229 if (texts.has(_hudHealth)) {
230 const int current = _network.getHealthCurrent();
231 const int max = _network.getHealthMax();
232 if (max > 0) {
233 texts[_hudHealth].content = "HP " + std::to_string(current) + " / " + std::to_string(max);
234 } else {
235 texts[_hudHealth].content = "HP --";
236 }
237 }
238
239 auto spritesOpt = _worldRegistry.get<ecs::components::Sprite>();
240 if (spritesOpt && texts.has(_hudEntities)) {
241 const std::size_t count = spritesOpt.value().get().size();
242 texts[_hudEntities].content = "Entities " + std::to_string(count + 2);
243 }
244
245 if (texts.has(_hudAmmo)) {
246 const uint16_t current = _network.getAmmoCurrent();
247 const uint16_t max = _network.getAmmoMax();
248 std::string ammoText = "AMMO " + std::to_string(current) + " / " + std::to_string(max);
249 if (_network.isReloading()) {
250 const float remaining = _network.getReloadCooldownRemaining();
251 ammoText += " (Reloading " + std::to_string(remaining).substr(0, 4) + "s)";
252 }
253 texts[_hudAmmo].content = ammoText;
254 }
255
256 if (texts.has(_chatCompactText)) {
258 }
259
260 if (_chatOpen) {
262 }
263
264 auto slidersOpt = _uiRegistry.get<ecs::components::ui::Slider>();
265 if (slidersOpt && slidersOpt->get().has(_hudChargeBar)) {
266 constexpr float kChargeMax = 2.0f;
267 bool canCharge = !_network.isReloading() && _network.getAmmoCurrent() > 0;
268
269 if (canCharge) {
271 if (inputsOpt) {
272 auto &inputs = inputsOpt.value().get();
273 for (const auto &e : inputs.entities()) {
274 if (inputs[e].isFocused) {
275 canCharge = false;
276 break;
277 }
278 }
279 }
280 }
281
282 if (canCharge && sf::Keyboard::isKeyPressed(_settings.getKey(KeyAction::Shoot))) {
283 _chargeTime = std::min(_chargeTime + dt, kChargeMax);
284 } else {
285 _chargeTime = 0.0f;
286 }
287
288 auto &slider = slidersOpt->get()[_hudChargeBar];
289 slider.currentValue = (_chargeTime > 0.0f) ? (_chargeTime / kChargeMax) : 0.0f;
290 }
291 }
292
294 // Private API
296
298 {
299 constexpr float baseTextureWidth = 1280.0f;
300
301 auto spawnLayer = [&](EntityTemplate t) {
302 const float scaledW = baseTextureWidth * t.scale.x;
303
304 t.position.x = 0.0f;
305 auto a = _worldBuilder.spawn(t);
306 if (!a) {
307 log::error("Failed to spawn parallax A");
308 return;
309 }
310
311 t.position.x = scaledW;
312 auto b = _worldBuilder.spawn(t);
313 if (!b) {
314 log::error("Failed to spawn parallax B");
315 return;
316 }
317 };
318
319 const uint32_t levelId = _network.getCurrentLevelId();
320 switch (levelId) {
321 case 1:
324 break;
325 case 2:
327 //spawnLayer(EntityTemplate::createParallaxLvl2_2());
328 //spawnLayer(EntityTemplate::createParallaxLvl2_3());
329 //spawnLayer(EntityTemplate::createParallaxLvl2_4());
330 //spawnLayer(EntityTemplate::createParallaxLvl2_5());
331 //spawnLayer(EntityTemplate::createParallaxLvl2_6());
332 break;
333 case 3:
343 break;
344 case 4:
351 break;
352 }
353 }
354
356 {
358 if (!inputsOpt)
359 return;
360
361 auto &inputs = inputsOpt.value().get();
362 if (!inputs.has(_chatInput))
363 return;
364
365 auto &input = inputs[_chatInput];
366 const std::string message = input.value;
367 if (message.empty())
368 return;
369
370 _network.trySendMessage(message);
371 input.value.clear();
372 closeChat();
373 }
374
376 {
377 if (_chatOpen)
378 return;
379
380 _chatOpen = true;
381
384 {30.0f, 60.0f},
385 {715.0f, 220.0f},
386 "",
387 nullptr
388 );
389 if (auto buttonsOpt = _uiRegistry.get<ecs::components::ui::Button>()) {
390 auto &buttons = buttonsOpt.value().get();
391 if (buttons.has(_chatPanel)) {
392 auto &panel = buttons[_chatPanel];
393 panel.idleColor[0] = 20; panel.idleColor[1] = 20; panel.idleColor[2] = 20;
394 panel.hoverColor[0] = 20; panel.hoverColor[1] = 20; panel.hoverColor[2] = 20;
395 panel.pressedColor[0] = 20; panel.pressedColor[1] = 20; panel.pressedColor[2] = 20;
396 }
397 }
398
401 {45.0f, 70.0f},
402 "",
403 "assets/fonts/main.ttf",
404 18,
405 10,
406 {230, 230, 230}
407 );
408
411 {45.0f, 235.0f},
412 {685.0f, 30.0f},
413 "assets/fonts/main.ttf",
414 18,
415 120,
416 "Type message...",
417 nullptr,
418 nullptr
419 );
420 if (auto inputsOpt = _uiRegistry.get<ecs::components::ui::TextInput>()) {
421 auto &inputs = inputsOpt.value().get();
422 if (inputs.has(_chatInput)) {
423 inputs[_chatInput].onSubmit = [this](const std::string&) {
425 };
426 inputs[_chatInput].isFocused = true;
427 inputs[_chatInput].showCursor = true;
428 }
429 }
430
432 }
433
435 {
436 if (!_chatOpen)
437 return;
438
439 _chatOpen = false;
443 _chatPanel = {};
444 _chatHistoryText = {};
445 _chatInput = {};
446 }
447
449 {
450 auto textsOpt = _uiRegistry.get<ecs::components::ui::Text>();
451 if (!textsOpt)
452 return;
453 auto &texts = textsOpt.value().get();
454 if (!texts.has(_chatHistoryText))
455 return;
456
457 const auto &history = _network.getChatHistory();
458 std::string combined;
459 for (const auto &line : history) {
460 if (!combined.empty())
461 combined += "\n";
462 combined += line;
463 }
464 texts[_chatHistoryText].content = combined;
465 }
466
467 } // namespace scenes
468} // namespace rtp::client
auto spawn(const EntityTemplate &t) -> std::expected< ecs::Entity, Error >
System to handle network-related operations on the client side.
uint32_t getCurrentLevelId(void) const
Get the current level ID for client-side visuals (parallax)
void trySendMessage(const std::string &message) const
Send a chat message to the server.
std::string getLastChatMessage(void) const
Get the last received room chat message.
const std::deque< std::string > & getChatHistory(void) const
Get chat history buffer (most recent last)
Manages game settings and preferences.
Definition Settings.hpp:67
sf::Keyboard::Key getKey(KeyAction action) const
Definition Settings.cpp:171
bool getGamepadEnabled() const
Definition Settings.hpp:174
unsigned int getGamepadPauseButton() const
Definition Settings.hpp:234
Manages game translations for all UI elements.
Factory class for creating UI components in the ECS registry.
Definition UiFactory.hpp:72
static ecs::Entity createTextInput(ecs::Registry &registry, const position &position, const size &size, const std::string &fontPath, unsigned int fontSize, const int maxLength=64, const std::string &placeholder="", std::function< void(const std::string &)> onSubmit=nullptr, std::function< void(const std::string &)> onChange=nullptr)
static ecs::Entity createText(ecs::Registry &registry, const position &position, const std::string &content, const std::string &fontPath, unsigned int fontSize, const std::uint8_t zIndex=0, const color &textColor={255, 255, 255})
Definition UiFactory.cpp:43
static ecs::Entity createButton(ecs::Registry &registry, const position &position, const size &size, const std::string &label, std::function< void()> onClick=nullptr)
Create a button UI component.
Definition UiFactory.cpp:17
static ecs::Entity createSlider(ecs::Registry &registry, const position &position, const size &size, float minValue, float maxValue, float initialValue, std::function< void(float)> onChange=nullptr)
Definition UiFactory.cpp:75
uint32_t _uiPing
Current ping in the UI.
EntityBuilder & _worldBuilder
Reference to the world entity builder.
void handleEvent(const sf::Event &event) override
Handle an incoming event.
graphics::UiFactory & _uiFactory
UI Factory for creating UI components.
ecs::Entity _hudFps
Entity for displaying FPS in the HUD.
bool _chatOpen
Whether expanded chat is open.
PlayingScene(ecs::Registry &worldRegistry, ecs::Registry &uiRegistry, Settings &settings, TranslationManager &translationManager, NetworkSyncSystem &network, graphics::UiFactory &uiFactory, EntityBuilder &worldBuilder, std::function< void(GameState)> changeState)
Constructor for PlayingScene.
uint32_t _uiFps
Current FPS in the UI.
void onExit(void) override
Called when the scene is exited.
ChangeStateFn _changeState
Function to change the game state.
ecs::Entity _chatCompactPanel
Compact chat background panel.
void onEnter(void) override
Called when the scene is entered.
void spawnParallax(void)
Spawn parallax background entities.
ecs::Entity _hudHealth
Entity for displaying health.
bool _hudInit
Flag indicating if HUD is initialized.
float _fpsTimer
FPS timer accumulator.
ecs::Entity _hudEntities
Parent entity for HUD elements.
float _chargeTime
Local charge timer for HUD.
ecs::Entity _chatHistoryText
Text entity for chat history.
ecs::Entity _hudAmmo
Entity for displaying ammo.
Settings & _settings
Reference to the application settings.
ecs::Entity _chatPanel
Expanded chat panel.
void update(float dt) override
Update the scene state.
ecs::Entity _chatCompactText
Text entity for last chat message.
NetworkSyncSystem & _network
Reference to the client network.
ecs::Registry & _uiRegistry
Reference to the ECS registry.
ecs::Entity _hudScore
Entity for displaying score in the HUD.
ecs::Entity _hudPing
Entity for displaying ping in the HUD.
ecs::Registry & _worldRegistry
Reference to the world ECS registry.
uint32_t _fpsFrames
FPS frame count.
ecs::Entity _hudChargeBar
Entity for charged shot HUD bar.
uint32_t _uiScore
Player score in the UI.
ecs::Entity _chatInput
Text input entity for chat.
Represents an entity in the ECS (Entity-Component-System) architecture.
Definition Entity.hpp:63
constexpr bool isNull(void) const noexcept
auto spawn(void) -> std::expected< Entity, rtp::Error >
Definition Registry.cpp:51
void kill(Entity entity)
Definition Registry.cpp:73
auto add(Entity entity, Args &&...args) -> std::expected< std::reference_wrapper< T >, rtp::Error >
auto get(this const Self &self) -> std::expected< std::reference_wrapper< ConstLike< Self, SparseArray< T > > >, rtp::Error >
R-Type client namespace.
@ Shoot
Action for shooting.
@ GameOver
Game over state.
@ Menu
Main menu state.
@ Paused
Game paused state.
void error(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log an error message.
void warning(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log a warning message.
void info(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log an informational message.
static EntityTemplate createParallaxLvl3_1()
static EntityTemplate createParallaxLvl3_9()
static EntityTemplate createParallaxLvl4_3()
static EntityTemplate createParallaxLvl3_8()
static EntityTemplate createParallaxLvl3_4()
static EntityTemplate createParallaxLvl3_3()
static EntityTemplate createParallaxLvl4_4()
static EntityTemplate createParallaxLvl3_2()
static EntityTemplate createParallaxLvl3_5()
static EntityTemplate createParallaxLvl2_1()
static EntityTemplate createParrallaxLvl1_2()
static EntityTemplate createParallaxLvl4_5()
static EntityTemplate createParrallaxLvl1_1()
static EntityTemplate createParallaxLvl3_7()
static EntityTemplate createParallaxLvl4_1()
static EntityTemplate createParallaxLvl4_2()
static EntityTemplate createParallaxLvl4_6()
static EntityTemplate createParallaxLvl3_6()
Represents an RGB color.
Definition UiFactory.hpp:34
Component representing a sprite.
Definition Sprite.hpp:21
Component for entities that emit continuous sound (music, loops, ambient)
std::string audioPath
Path to the audio file.
bool isPlaying
Current playback state.
bool dirty
Flag to indicate changes need to be applied.
float volume
Volume level (0.0 - 1.0)
bool loop
Whether the audio should loop.
Component representing a clickable button.
Definition Button.hpp:30
Component for a draggable slider control.
Definition Slider.hpp:19
Component representing text to render.
Definition Text.hpp:19
uint8_t green
Green component.
Definition Text.hpp:25
int zIndex
Rendering order.
Definition Text.hpp:28
uint8_t blue
Blue component.
Definition Text.hpp:26
Vec2f position
Position of the text.
Definition Text.hpp:21
uint8_t red
Red component.
Definition Text.hpp:24
uint8_t alpha
Alpha (opacity)
Definition Text.hpp:27
unsigned int fontSize
Font size.
Definition Text.hpp:23
std::string fontPath
Path to the font file.
Definition Text.hpp:22
std::string content
Text content.
Definition Text.hpp:20