Aller au contenu

Rapport Complet : Interfaces Utilisateur R-Type Client

Competences couvertes: 10 (interfaces GUI/TUI/CLI), 13 (code operationnel, conventions)


1. ABSTRACTION GRAPHIQUE : Pattern Multi-Backend

1.1 Architecture IWindow

Le systeme graphique utilise une interface abstraite IWindow implementee par deux backends :

Fichier : src/client/include/graphics/IWindow.hpp:20-62

class IWindow {
    virtual Vec2u getSize() const = 0;
    virtual bool isOpen() = 0;
    virtual void close() = 0;
    virtual events::Event pollEvent() = 0;
    virtual void draw(const IDrawable& drawable) = 0;
    virtual void drawRect(float x, float y, float width, float height, rgba color) = 0;
    virtual void drawImg(graphics::IDrawable, float x, float y, float scaleX, float scaleY) = 0;
    virtual bool loadTexture(const std::string& key, const std::string& filepath) = 0;
    virtual void drawSprite(const std::string& textureKey, float x, float y, float width, float height) = 0;
    virtual bool loadFont(const std::string& key, const std::string& filepath) = 0;
    virtual void drawText(const std::string& fontKey, const std::string& text, float x, float y, unsigned int size, rgba color) = 0;
    // Post-processing shaders (SFML only)
    virtual bool loadShader(const std::string& key, const std::string& vertexPath, const std::string& fragmentPath) = 0;
    virtual void setPostProcessShader(const std::string& key) = 0;
    virtual void clearPostProcessShader() = 0;
    // Fullscreen support
    virtual void setFullscreen(bool enabled) = 0;
    virtual void toggleFullscreen() = 0;
    virtual bool isFullscreen() const = 0;
};

1.2 Implementations Multi-Backend

SFML Backend : src/client/lib/sfml/src/SFMLWindow.cpp - Support shader post-processing (colorblind filters) - Gestion du letterboxing 16:9 avec calcul de viewport - Conversion de coordonnees pixel -> logique (1920x1080)

Fichier exemple : src/client/lib/sfml/src/SFMLWindow.cpp:336-384

void SFMLWindow::setFullscreen(bool enabled) {
    if (enabled) {
        _window.create(sf::VideoMode::getDesktopMode(), _windowTitle, sf::Style::None);
        _window.setPosition({0, 0});
    } else {
        _window.create(sf::VideoMode({1920, 1080}), _windowTitle, sf::Style::Default);
    }
    auto size = _window.getSize();
    handleResize(size.x, size.y);
    _isFullscreen = enabled;
}

void SFMLWindow::handleResize(unsigned int newWidth, unsigned int newHeight) {
    constexpr float targetRatio = 1920.f / 1080.f;
    float windowRatio = static_cast<float>(newWidth) / static_cast<float>(newHeight);
    sf::View view(sf::FloatRect({0.f, 0.f}, {1920.f, 1080.f}));
    if (windowRatio > targetRatio) {
        float viewportWidth = targetRatio / windowRatio;
        view.setViewport(sf::FloatRect({(1.f - viewportWidth) / 2.f, 0.f}, {viewportWidth, 1.f}));
    }
    _window.setView(view);
}

SDL2 Backend : src/client/lib/sdl2/src/SDL2Window.cpp - Logical size 1920x1080 via SDL_RenderSetLogicalSize() - Pas de support shader (retourne false pour supportsShaders()) - Gestion DPI Windows (desactive le scaling)

Fichier exemple : src/client/lib/sdl2/src/SDL2Window.cpp:85-119

SDL2Window::SDL2Window(Vec2u winSize, const std::string& name) {
    SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitorv2");
    SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "0");
    _window = SDL_CreateWindow(name.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
        static_cast<int>(winSize.x), static_cast<int>(winSize.y),
        SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
    _renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    SDL_RenderSetLogicalSize(_renderer, 1920, 1080);  // Scaling automatique
}

1.3 Plugin Architecture

Fichier : src/client/include/graphics/IGraphicPlugin.hpp:19-39

class IGraphicPlugin {
    virtual const char* getName() const = 0;
    virtual std::shared_ptr<IWindow> createWindow(Vec2u winSize, const std::string& name) = 0;
    virtual std::shared_ptr<core::IRenderer> createRenderer(std::shared_ptr<graphics::IWindow> window) = 0;
};

Les plugins (SFML/SDL2) sont charges dynamiquement avec dlopen() et exposent des symboles C :

extern "C" {
    graphics::IGraphicPlugin* create() { return new sdl2::SDL2Plugin(); }
    void destroy(graphics::IGraphicPlugin* graphPlugin) { delete graphPlugin; }
}


2. SYSTEME DE SCENES : Stack-Based Navigation

2.1 Architecture IScene

Fichier : src/client/include/scenes/IScene.hpp:20-42

struct GameContext {
    std::shared_ptr<graphics::IWindow> window;
    std::shared_ptr<client::network::UDPClient> udpClient;
    std::shared_ptr<client::network::TCPClient> tcpClient;
    std::string sessionToken;
};

class IScene {
public:
    virtual void handleEvent(const events::Event& event) = 0;
    virtual void update(float deltatime) = 0;
    virtual void render() = 0;
    void setSceneManager(SceneManager* manager) { _sceneManager = manager; }
    void setContext(const GameContext& ctx) { _context = ctx; }
protected:
    SceneManager* _sceneManager = nullptr;
    GameContext _context;
};

2.2 SceneManager : Stack Navigation

Fichier : src/client/include/scenes/SceneManager.hpp:17-72

class SceneManager {
public:
    void changeScene(std::unique_ptr<IScene> newScene);  // Remplace toute la pile
    void pushScene(std::unique_ptr<IScene> scene);       // Overlay (pause, menu)
    void popScene();                                     // Ferme overlay
    bool hasOverlay() const;
    size_t sceneCount() const;
    void handleEvent(const events::Event& event);
    void update(float deltatime);
    void render();
private:
    std::stack<std::unique_ptr<IScene>> _sceneStack;
    enum class PendingAction { None, Change, Push, Pop };
    PendingAction _pendingAction = PendingAction::None;
};

2.3 Diagramme Navigation Scenes

SCENE FLOW DIAGRAM

  LoginScene ─────────┐
                      ├──-> MainMenuScene
  (Auth)             |       |
                     |       ├──-> LobbyScene
                     |       |    (Ready/Start)
                     |       |       |
                     |       ├──-> RoomBrowserScene
                     |       ├──-> LeaderboardScene
                     |       ├──-> FriendsScene
                     |       └──-> SettingsScene
                     |            (Accessibility)
                     |
                     └──-> GameScene (Gameplay)
                           ├──-> PauseOverlay
                           └──-> PrivateChatScene

2.4 Implementations des Scenes Principales

Scene Fichier Header Description
LoginScene include/scenes/LoginScene.hpp Auth, register, version check, server config
MainMenuScene include/scenes/MainMenuScene.hpp Room creation/join, leaderboard, friends, settings
LobbyScene include/scenes/LobbyScene.hpp Waiting room, chat, ship skins, game speed (host)
GameScene include/scenes/GameScene.hpp Gameplay complet, HUD, chat, voice
SettingsScene include/scenes/SettingsScene.hpp Key remapping, colorblind, audio devices
LeaderboardScene include/scenes/LeaderboardScene.hpp Leaderboards, stats, achievements
FriendsScene include/scenes/FriendsScene.hpp Friends list, requests, blocking
PrivateChatScene include/scenes/PrivateChatScene.hpp Private messaging
RoomBrowserScene include/scenes/RoomBrowserScene.hpp Room listing + filtering
BreakoutScene include/scenes/BreakoutScene.hpp Game bonus breakout
ConnectionScene include/scenes/ConnectionScene.hpp Loading/transition

3. SYSTEME D'EVENEMENTS : Variant-Based Dispatch

3.1 Event Types

Fichier : src/client/include/events/Event.hpp:17-78

namespace events {
    enum class Key {
        A, B, C, ..., Z,  // Lettres
        Num0-9,           // Chiffres
        F1-F12,           // Fonction
        Space, Enter, Escape, Tab, Backspace,
        Up, Down, Left, Right,
        LShift, RShift, LCtrl, RCtrl, LAlt, RAlt,
        Unknown
    };

    enum class MouseButton { Left, Right, Middle, Unknown };

    struct KeyPressed { Key key; };
    struct KeyReleased { Key key; };
    struct MouseMoved { int x, y; };
    struct MouseButtonPressed { MouseButton button; int x, y; };
    struct MouseButtonReleased { MouseButton button; int x, y; };
    struct TextEntered { uint32_t unicode; };
    struct WindowClosed {};
    struct None {};

    using Event = std::variant<None, KeyPressed, KeyReleased, MouseMoved,
                              MouseButtonPressed, MouseButtonReleased,
                              TextEntered, WindowClosed>;
}

3.2 Conversion SFML -> Event

Fichier : src/client/lib/sfml/src/SFMLWindow.cpp:25-93, 119-170

static events::Key scancodeToKey(sf::Keyboard::Scancode scancode) {
    switch (scancode) {
        case sf::Keyboard::Scancode::A: return events::Key::A;
        // ... 90+ mapping lines
    }
}

events::Event SFMLWindow::pollEvent() {
    if (auto ev = _window.pollEvent()) {
        if (ev->is<sf::Event::Closed>()) {
            return events::WindowClosed{};
        }
        if (const auto* keyPressed = ev->getIf<sf::Event::KeyPressed>()) {
            return events::KeyPressed{scancodeToKey(keyPressed->scancode)};
        }
        if (const auto* mousePressed = ev->getIf<sf::Event::MouseButtonPressed>()) {
            sf::Vector2f worldPos = _window.mapPixelToCoords(mousePressed->position);
            return events::MouseButtonPressed{
                sfmlButtonToMouseButton(mousePressed->button),
                static_cast<int>(worldPos.x), static_cast<int>(worldPos.y)
            };
        }
        if (const auto* resized = ev->getIf<sf::Event::Resized>()) {
            handleResize(resized->size.x, resized->size.y);
            return pollEvent();  // Continue polling
        }
    }
    return events::None{};
}


4. SYSTEME UI : Component-Based Architecture

4.1 Hierarchie Composants

Fichier : src/client/include/ui/IUIElement.hpp:20-52

class IUIElement {
public:
    virtual ~IUIElement() = default;
    virtual Vec2f getPos() const = 0;
    virtual void setPos(const Vec2f& pos) = 0;
    virtual Vec2f getSize() const = 0;
    virtual void setSize(const Vec2f& size) = 0;
    virtual bool contains(float x, float y) const {
        Vec2f pos = getPos();
        Vec2f size = getSize();
        return x >= pos.x && x <= pos.x + size.x &&
               y >= pos.y && y <= pos.y + size.y;
    }
    virtual void handleEvent(const events::Event& event) = 0;
    virtual void update(float deltaTime) = 0;
    virtual void render(graphics::IWindow& window) = 0;
    virtual bool isFocused() const = 0;
    virtual void setFocused(bool focused) = 0;
    virtual bool isVisible() const { return _visible; }
    virtual bool isEnabled() const { return _enabled; }
protected:
    bool _visible = true;
    bool _enabled = true;
};

4.2 Button Component

Fichier : src/client/include/ui/Button.hpp:20-78

class Button : public IUIElement {
public:
    enum class State { Normal, Hovered, Pressed, Disabled };
    using ClickCallback = std::function<void()>;

    Button(const Vec2f& pos, const Vec2f& size, const std::string& text,
           const std::string& fontKey);

    void setOnClick(ClickCallback callback) { _onClick = std::move(callback); }
    void setText(const std::string& text) { _text = text; }
    void setNormalColor(rgba color) { _normalColor = color; }
    void setHoveredColor(rgba color) { _hoveredColor = color; }
    void setPressedColor(rgba color) { _pressedColor = color; }
private:
    Vec2f _pos, _size;
    State _state = State::Normal;
    ClickCallback _onClick;
    rgba _normalColor{60, 60, 80, 255};
    rgba _hoveredColor{80, 80, 110, 255};
    rgba _pressedColor{40, 40, 60, 255};
};

4.3 TextInput Component

Fichier : src/client/include/ui/TextInput.hpp:20-88

class TextInput : public IUIElement {
public:
    using OnChangeCallback = std::function<void(const std::string&)>;
    using OnSubmitCallback = std::function<void(const std::string&)>;

    TextInput(const Vec2f& pos, const Vec2f& size, const std::string& placeholder,
              const std::string& fontKey, bool isPassword = false);

    const std::string& getText() const { return _text; }
    void setText(const std::string& text);
    void setPlaceholder(const std::string& placeholder) { _placeholder = placeholder; }
    void setMaxLength(size_t len) { _maxLength = len; }
    void setPassword(bool isPassword) { _isPassword = isPassword; }
    void setOnChange(OnChangeCallback callback) { _onChange = std::move(callback); }
    void setOnSubmit(OnSubmitCallback callback) { _onSubmit = std::move(callback); }

private:
    std::string _text;
    std::string _placeholder;
    size_t _cursorPos = 0;
    float _cursorBlinkTimer = 0.0f;
    bool _showCursor = true;
    static constexpr float CURSOR_BLINK_RATE = 0.5f;
};


5. ACCESSIBILITE : Multi-Modal Support

5.1 AccessibilityConfig

Fichier : src/client/include/accessibility/AccessibilityConfig.hpp:22-131

enum class ColorBlindMode {
    None,
    Protanopia,      // Red-blind
    Deuteranopia,    // Green-blind
    Tritanopia,      // Blue-yellow blind
    HighContrast
};

enum class GameAction {
    MoveUp, MoveDown, MoveLeft, MoveRight,
    Shoot, Pause, PushToTalk,
    WeaponPrev, WeaponNext,  // Q, E
    OpenChat,                 // T
    ExpandChat,              // O
    ForceToggle,             // F
    ToggleControls,          // H
    ActionCount
};

class AccessibilityConfig {
public:
    static AccessibilityConfig& getInstance();

    void setKeyBinding(GameAction action, events::Key primaryKey,
                       events::Key secondaryKey = events::Key::Unknown);
    events::Key getPrimaryKey(GameAction action) const;
    events::Key getSecondaryKey(GameAction action) const;
    bool isActionKey(GameAction action, events::Key key) const;
    void resetKeyBindings();

    void setColorBlindMode(ColorBlindMode mode);
    ColorBlindMode getColorBlindMode() const;

    void setGameSpeedMultiplier(float multiplier);
    float getGameSpeedMultiplier() const;

    void setShipSkin(uint8_t skinId);
    uint8_t getShipSkin() const;

    bool loadFromFile(const std::string& filepath);
    bool saveToFile(const std::string& filepath) const;

private:
    std::array<std::array<events::Key, 2>, static_cast<size_t>(GameAction::ActionCount)> _keyBindings;
    ColorBlindMode _colorBlindMode = ColorBlindMode::None;
    float _gameSpeedMultiplier = 1.0f;
    uint8_t _shipSkin = 1;
    bool _keepChatOpenAfterSend = false;
};

5.2 ColorblindShaderManager

Fichier : src/client/include/accessibility/ColorblindShaderManager.hpp:17-60

class ColorblindShaderManager {
public:
    static ColorblindShaderManager& getInstance();

    bool initialize(std::shared_ptr<graphics::IWindow> window);
    void updateFromConfig();
    bool isAvailable() const;
    void shutdown();

private:
    std::shared_ptr<graphics::IWindow> _window;
    bool _initialized = false;
    bool _shadersAvailable = false;
    ColorBlindMode _currentMode = ColorBlindMode::None;

    static constexpr const char* SHADER_KEY = "colorblind";
    static constexpr const char* VERTEX_SHADER_PATH = "assets/shaders/colorblind.vert";
    static constexpr const char* FRAGMENT_SHADER_PATH = "assets/shaders/colorblind.frag";
};

6. CONVENTIONS DE CODE C++

6.1 Nommage

Categorie Convention Exemple Fichier:Ligne
Classes PascalCase class GameScene, class Button GameScene.hpp:26, Button.hpp:20
Interfaces Prefixe I + PascalCase class IWindow, class IScene, class IUIElement IWindow.hpp:20, IScene.hpp:27, IUIElement.hpp:20
Enums PascalCase (Type) + PascalCase (Value) enum class State { Normal, Hovered } Button.hpp:22-27
Fonctions camelCase void handleEvent(), void renderBackground() GameScene.hpp:43-44
Methodes privees camelCase void loadAssets(), void initUI() GameScene.hpp:74
Variables membres _prefixWithUnderscore Vec2f _pos, rgba _normalColor Button.hpp:59-70
Constantes SCREAMING_SNAKE_CASE static constexpr float MOVE_SPEED = 200.0f GameScene.hpp:121
Namespaces lowercase namespace graphics, namespace ui, namespace events Multiple

6.2 Formatage et Style

Exemple 1 : Class Constructor Initializer List Fichier : src/client/src/ui/Button.cpp:13-22

Button::Button(const Vec2f& pos, const Vec2f& size, const std::string& text,
               const std::string& fontKey)
    : _pos(pos)
    , _size(size)
    , _text(text)
    , _fontKey(fontKey)
    , _state(State::Normal)
    , _focused(false)
{
}
- Initialiseurs alignes verticalement - Chaque initialiseur sur sa ligne avec virgule en tete

Exemple 2 : Method Implementation avec Variant Fichier : src/client/src/ui/Button.cpp:24-73

void Button::handleEvent(const events::Event& event) {
    if (!_enabled) {
        _state = State::Disabled;
        return;
    }

    if (auto* moved = std::get_if<events::MouseMoved>(&event)) {
        bool isHovered = contains(static_cast<float>(moved->x),
                                 static_cast<float>(moved->y));
        if (_state == State::Pressed) {
            // Keep pressed state while mouse button is down
        } else if (isHovered) {
            _state = State::Hovered;
        } else {
            _state = State::Normal;
        }
    }

    if (auto* pressed = std::get_if<events::MouseButtonPressed>(&event)) {
        if (pressed->button == events::MouseButton::Left &&
            contains(static_cast<float>(pressed->x), static_cast<float>(pressed->y))) {
            _state = State::Pressed;
        }
    }
}
- Utilise std::get_if<>() pour variant dispatch - Cast explicites avec static_cast<> - Commentaires pour les branches logiques critiques

Exemple 3 : constexpr et Constants Fichier : src/client/include/scenes/GameScene.hpp:121-174

static constexpr float MOVE_SPEED = 200.0f;
static constexpr float SHIP_WIDTH = 64.0f;
static constexpr float SHIP_HEIGHT = 30.0f;
static constexpr float HUD_HEALTH_BAR_WIDTH = 200.0f;
static constexpr float HUD_MARGIN = 20.0f;
static constexpr uint8_t MAX_HEALTH = 100;
static constexpr float SHOOT_COOLDOWN_TIME = 0.3f;
static constexpr const char* SHIP_TEXTURE_KEY = "ship";
static constexpr const char* FONT_KEY = "main";
static constexpr unsigned int LABEL_FONT_SIZE = 14;
static constexpr float SCREEN_WIDTH = 1920.0f;
static constexpr float SCREEN_HEIGHT = 1080.0f;

6.3 Headers Structure

Pattern Standard : src/client/include/ui/Button.hpp

/*
** EPITECH PROJECT, 2025
** rtype
** File description:
** Button
*/

#ifndef BUTTON_HPP_
#define BUTTON_HPP_

#include "IUIElement.hpp"
#include "../graphics/IWindow.hpp"
#include "../events/Event.hpp"
#include "../utils/Vecs.hpp"
#include <string>
#include <functional>

namespace ui {

class Button : public IUIElement {
    // ... declaration
};

} // namespace ui

#endif /* !BUTTON_HPP_ */


7. USER EXPERIENCE : Feedback Visuel et Navigation

7.1 GameScene HUD System

Fichier : src/client/include/scenes/GameScene.hpp:47-73

private:
    void renderBackground();
    void renderHUD();
    void renderPlayers();
    void renderMissiles();
    void renderEnemies();
    void renderEnemyMissiles();
    void renderDeathScreen();
    void renderKickedScreen();
    void renderChatOverlay();
    void renderVoiceIndicator();
    void renderScoreHUD();              // Score, combo, wave info
    void renderTeamScoreboard();        // All players' scores (multiplayer)
    void renderWeaponHUD();             // Current weapon indicator
    void renderBoss();                  // Boss sprite
    void renderBossHealthBar();         // Boss HP bar
    void renderWaveCannons();           // Wave Cannon beams
    void renderPowerUps();              // Power-up items
    void renderForcePods();             // Force pods
    void renderBitDevices();            // Bit devices (orbiting)
    void renderChargeGauge();           // Wave Cannon charge indicator
    void renderSpeedIndicator();        // Speed upgrade level
    void renderControlsHUD();           // Controls help (bottom-right)
    void renderPauseOverlay();          // Pause menu overlay
    void renderGlobalRank();            // Rank badge and best score

7.2 Chat Overlay System

Fichier : src/client/include/scenes/GameScene.hpp:176-191

struct ChatDisplayMessage {
    std::string displayName;
    std::string message;
    float displayTime;      // Time remaining (0 = expired/archived)
    bool expired = false;   // True when timer reached 0
};
std::vector<ChatDisplayMessage> _chatDisplayMessages;
std::unique_ptr<ui::TextInput> _chatInput;
bool _chatInputOpen = false;
bool _chatExpanded = false;  // Show all messages when expanded

static constexpr float CHAT_MESSAGE_DISPLAY_TIME = 8.0f;
static constexpr size_t MAX_CHAT_DISPLAY_MESSAGES = 20;
static constexpr size_t ALWAYS_VISIBLE_MESSAGES = 3;  // Last N never expire

7.3 Fullscreen & Letterboxing

Implementation SFML - Maintient aspect ratio 16:9 avec barres noires :

// Viewport adjustment pour letterboxing
if (windowRatio > targetRatio) {
    // Fenetre plus large -> barres noires sur les cotes
    float viewportWidth = targetRatio / windowRatio;
    view.setViewport(sf::FloatRect({(1.f - viewportWidth) / 2.f, 0.f},
                                   {viewportWidth, 1.f}));
} else {
    // Fenetre plus haute -> barres noires haut/bas
    float viewportHeight = windowRatio / targetRatio;
    view.setViewport(sf::FloatRect({0.f, (1.f - viewportHeight) / 2.f},
                                   {1.f, viewportHeight}));
}


RESUME : Interfaces Utilisateur R-Type

Aspect Implementation Fichier Cle
Abstraction graphique Plugin architecture + IWindow IGraphicPlugin.hpp, IWindow.hpp
Backends SFML + SDL2 SFMLWindow.cpp, SDL2Window.cpp
Navigation scenes Stack-based scene manager SceneManager.hpp/cpp
Evenements Variant-based dispatch Event.hpp
UI Components IUIElement (Button, TextInput) IUIElement.hpp, Button.hpp
Accessibilite Key remapping, colorblind, game speed AccessibilityConfig.hpp
Fullscreen Letterboxing 16:9 SFMLWindow.cpp:336-384
Chat System In-game overlay avec timer GameScene.hpp:176-191
State Machines Enum-based (Button states, etc) Button.hpp:22-27
Persistence Settings files + MongoDB AccessibilityConfig.hpp

CONCLUSION

Le client R-Type implemente des interfaces utilisateur professionnelles et accessibles avec: - Abstraction multi-backend (SFML/SDL2) via plugin system - Navigation par pile de scenes - Systeme d'evenements type-safe via std::variant - Composants UI reutilisables (Button, TextInput) - Support accessibilite complet (colorblind, key remapping) - Conventions de code C++ coherentes et lisibles