R-Type
Description
This project is a recreation of the R-Type game using our own custom game engine. Inspired by the classic Shoot'em'up genre, players take control of a spaceship to combat waves of enemies and challenging bosses.
You can check the online documentation to this url : "https://pizza-aux-crevettes.github.io/R-Type/clang-format.html"
Table of contents
Prerequisites
Before getting started, make sure you have the following installed on your machine:
- A C++ compiler supporting C++17
-
Clone the repository:
git clone --recurse-submodules git@github.com:EpitechPromo2027/B-CCP-500-TLS-5-2-rtype-anastasia.bouby.git cd B-CCP-500-TLS-5-2-rtype-anastasia.bouby -
Install UDev library (if necessary)
On ubuntu:
sudo apt install libxrandr-dev libxcursor-dev libudev-dev libopenal-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev libcriterion-dev libfreetype-dev libfreetype6 libfreetype6-dev git gcc g++ make cmakeOn fedora:
sudo dnf install freetype-devel libglvnd-opengl libXrandr-devel libXcursor-devel xrandr freetype glew libjpeg-turbo libsndfile openal-soft libvorbis-devel flac-devel libX11-devel libGL-devel systemd-devel openal-soft-devel git gcc g++ make cmake -
Build the project:
cmake -B build . cd build/ make
Run the game
-
Start the Server:
Make sure to be in B-CCP-500-TLS-5-2-rtype-anastasia.bouby/
./build/server/Server -
Start the Client:
Open a new terminal and make sure to be in B-CCP-500-TLS-5-2-rtype-anastasia.bouby/
./build/client/Client
Project structure
assets/: Game resources such as textures, sounds.
client/: Contains the client-side game logic and graphics.
external/: External library needed.
game_engine/: Handles server-side game_engine.
server/: Handles server-side networking.
User Guide
Welcome to the user guide for our game R-Type.
This guide will help you understand how to play and use the various options in the game. To learn how to install and launch our game, please click here.
Main Menu
Once the game is launched, you will be on the main menu. This menu consists of three buttons:
- [PLAY]: Starts the game.
- [OPTIONS]: Opens the options menu.
- [EXIT]: Exits the game.
[OPTIONS] Menu
In the [OPTIONS] menu, you will find several settings to customize:
-
At the top of the screen, you can adjust the sound settings for the game:
- The first slider allows you to adjust the volume of the sound effects (e.g., mouse click sounds, player shots during the game).
- The second slider allows you to adjust the volume of the overall game music (whether in the menu or during gameplay). Drag the slider to adjust the volumes.
-
Below that, you will find five buttons to change the control keys. By default:
- The Up Arrow moves the player up.
- The Down Arrow moves the player down.
- The Right Arrow moves the player right.
- The Left Arrow moves the player left.
- The Enter key toggles the autofire mode (the player shoots automatically).
- The Spacebar allows you to shoot when autofire is disabled.
To change a key, click the corresponding button for the action, then press the new key on your keyboard (for example, if you want the "Shoot" action to be the "L" key, click the "Shoot" button and then press the "L" key).
-
Below, you can check or uncheck an option to enable or disable a font change for people with reading difficulties.
-
There is also a third slider that allows you to adjust the text size in the game.
To exit this menu and return to the main menu, click the "Back" button in the top-right corner of the screen. Your settings will be saved automatically.
Game Settings and IP Field
On the main menu, you will also find two text fields:
-
The first field allows you to enter your player name. If you leave it blank and click [PLAY], your default username will be "Guest".
-
The second field allows you to enter the IP address of another computer to play multiplayer. Simply get the IP address of the PC running the server and enter it here. If this field is left empty, the default IP address will be your own PC's address (127.0.0.1). This feature is useful if you want to play multiplayer across different PCs.
Once you've filled out this information, you're ready to start the game by clicking [PLAY]!
In-Game
Once in the game, you can move by using the arrow keys on your keyboard or the keys you configured in the game options.
To shoot once, press the Spacebar (or the key you configured in the options).
To activate autofire, press Enter (or the configured key), and press it again to deactivate it.
Enemies and the Boss
During the game, you will encounter up to 4 types of enemies, each with different characteristics (some have more health or deal more damage than others, for example).
At the end of the level, you will face a boss, recognizable by its massive size. To win, you must defeat the boss, but be careful not to get eliminated before that!
Map Creation
You also have the option to create your own maps. Click here and follow the guide to generate a new map.
We hope you have a great time playing our game!
Development Team:
Anastasia Bouby, Elyne Masse, Eddy Gardes, Alexandre Chouteau, Benjamin Gayaud.
Map Editor
Description
This is a map generator using the Tkinter library to create a graphical user interface (GUI). The application allows users to generate their own maps with various configurations and options.
Table of contents
Prerequisites
Before you begin, ensure that you have the following installed:
- Python 3.x (recommended version: 3.8 or higher)
- Tkinter (comes pre-installed with Python)
- pip (Python package manager)
- pillow (recommended version: 8.0.0 or higher)
- Tkmacos (only for macOS)
Check Python and Tkinter Installation
To verify if Python and Tkinter are installed, run the following commands in your terminal:
python3 --version
python3 -m tkinter
macOS Installation Guide
Step 1: Install Python (if necessary)
If Python is not installed, or if you want to use a different version, you can install it via Homebrew:
-
Install Homebrew (if not already installed):
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -
Install Python using Homebrew:
brew install python
Step 2: Create and Activate a Virtual Environment
-
Create a virtual environment:
python3 -m venv map_editor_env -
Activate the virtual environment:
source map_editor_env/bin/activate
Step 3: Install Tkinter (if necessary)
Tkinter usually comes pre-installed with Python on macOS. If it is not installed, you can install it with:
brew install python-tk
Step 4: Install pip (if necessary)
If you encounter the error zsh: command not found: pip, install pip manually:
-
Download the get-pip.py script:
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py -
Install pip using Python:
python3 get-pip.py -
Verify that pip is installed:
pip3 --version
Step 5: Install Tkmacos (if necessary)
Inside the activated virtual environment, run:
pip3 install tkmacosx
Step 6: Install Pillow
pip install Pillow
Step 7: Run the Application
cd map-editor/macos
python3 map_editor
Linux Installation Guide (Ubuntu)
Step 1: Install Python (if necessary)
On most Linux distributions, Python is pre-installed. To check the installed version:
python3 --version
If it's not installed, you can install it using the package manager for your distribution.
sudo apt update
sudo apt install python3
Step 2: Install Tkinter (if necessary)
Tkinter is usually included with Python. If you need to install it, use the following command :
sudo apt install python3-tk
Step 3: Install pip (if necessary)
To install pip if it’s not already installed:
sudo apt install python3-pip
Verify that pip is installed:
pip3 --version
Step 4: Install Pillow
pip install Pillow
Step 5: Run the Application
cd map-editor/linux
python3 map_editor
Once the map editor is launched, you can place your enemies and obstacles as you wish. Make sure to save your work in our project at this path: /server/maps/, and name your file “map1.map”.
Commit Standard for R-Type
This document outlines the commit standard applied on R-Type project. This convention ensures clear and structured messages for better readability and code management.
Commit Message Structure
Each commit message must follow this format:
<TYPE>: <DESCRIPTION>
- TYPE: The type of change, written in uppercase.
- DESCRIPTION: A concise and precise description of the modification.
Main Types
| Type | Description |
|---|---|
| ADD | Adding a new feature |
| FIX | Fixing a bug |
| DOCS | Modifying or adding documentation |
| STYLE | Changes related to formatting (e.g., spaces) without functional impact |
| REFACTOR | Refactoring code without adding features or fixing bugs |
| TEST | Adding or updating tests |
| RM | Delete one thing |
Example Messages
-
Adding a feature:
ADD: collision system for missiles -
Fixing a bug:
FIX: Fixed crash when moving player -
Updating documentation:
DOCS: Updated installation instructions -
Improving style:
STYLE: Applying clang-format to source files
Additional Rules
- Language: All commit messages must be written in English.
- Length: Descriptions should fit in a single line (around 72 characters).
- Conciseness: Avoid unnecessary details. Detailed explanations must be added in an extended description.
Extended Format
For complex commits, you must add an extended description after a blank line:
<TYPE>: <DESCRIPTION>
<EXTENDED DESCRIPTION>
Example:
FIX: Fixed poorly rendered borders on enemies
Borders were misaligned due to a problem in the method
render() of the Enemy object. The function has been fixed to handle sizes.
Merging the code
To participate in the project, you must create GitHub branches. It is not possible to push directly to the main or dev branches. Your new branch must be based on the dev branch. Once you want to share your work, you need to create a pull request from your branch to the dev branch. This must be accepted by one of the project creators. Once the pull request is accepted, you can then merge your work. During regular project updates, the dev branch is merged into the main branch.
clang-format for R-Type
This document explains how to configure and use clang-format to ensure consistent coding standards in the R-Type project.
Why use clang-format?
- Ensures a uniform coding style for all contributors.
- Compatible with most IDEs (Visual Studio Code, CLion, etc.).
- Automates code formatting to focus on development.
Installing clang-format
Linux
- Install clang-format using your package manager:
sudo apt install clang-format
MacOS
- Install clang-format using Homebrew:
brew install clang-format
Verification
- Make sure clang-format is installed correctly:
clang-format --version
Configuration with .clang-format
Add the following .clang-format file to the root of the R-Type project to define the style rules.
BasedOnStyle: LLVM
IndentWidth: 4
ColumnLimit: 100
BreakBeforeBraces: Allman
AllowShortIfStatementsOnASingleLine: false
AlwaysBreakTemplateDeclarations: true
SortIncludes: true
IncludeBlocks: Preserve
SpacesInParentheses: false
Using clang-format
Manual
-
Format a specific file:
clang-format -i path/to/file.cpp -
Format the entire project:
find . -name '*.cpp' -o -name '*.h' | xargs clang-format -i
With IDEs
VS Code
- Install the C/C++ and Clang-Format extensions.
- Add the following to
settings.json:{ "C_Cpp.clang_format_style": "file", "editor.formatOnSave": true }
CLion
- Navigate to File > Settings > Editor > Code Style > C/C++.
- Enable Use clang-format file.
CI/CD Integration
Add a step in your CI pipeline to validate formatting:
- name: clang-format check
run: |
find . -name '*.cpp' -o -name '*.h' | xargs clang-format --dry-run --Werror
Essential Commands
| Action | Command |
|---|---|
| Format a file | clang-format -i file.cpp |
| Format all files | `find . -name '.cpp' -o -name '.h' |
| Verify without modifying | clang-format --dry-run file.cpp |
References
Accessibility
We designed the game with a strong focus on accessibility to ensure an inclusive experience for all players. Here are the key features available:
Visual Options
- Colorblind-friendly palettes: An optimized color palette is available to help players with colorblindness better distinguish game elements.
- High contrast mode: Enhances visibility of enemies, projectiles, and game objects by improving the differentiation of elements.
Interaction Options
- Customizable controls: Players can remap keys according to their preferences.
- “Auto-Fire” mode: Enables automatic shooting to reduce the need for holding down buttons.
Audio Options
- Volume settings: Adjust the volume of music, sound effects, and dialogue independently.
Interface Options
- HUD and text size adjustments: Allows resizing of UI elements for improved readability.
Progression Options
- Frequent checkpoints: Automatic saving and strategically placed checkpoints prevent the need to replay long sections.
- Multiple difficulty levels: You can create your own map and adjust the difficulty level yourself.
Comparison: ECS (Entity-Component-System) vs COI (Composition-Over-Inheritance)
This document compares two fundamental approaches used in game engines: ECS (Entity-Component-System) and COI (Composition-Over-Inheritance). These paradigms shape how entities, data, and logic are structured in a project.
ECS (Entity-Component-System)
Description:
- Entities are simple identifiers.
- Components store only data.
- Systems contain business logic and manipulate the associated components.
Advantages:
- Modularity: Highly flexible, components can be dynamically added or removed.
- Performance: Optimized for multicore processors through parallelization.
- Clear separation: Data and logic are well isolated.
Disadvantages:
- Higher initial complexity.
- Performance management requires special attention (e.g., frequent memory allocations).
Use Cases:
- AAA engines like Unity and Unreal Engine.
- Games requiring the simultaneous management of thousands of entities.
References:
COI (Composition-Over-Inheritance)
Description:
- Prefers object aggregation (objects containing other objects) over inheritance.
- Features are divided into reusable and composable components.
Advantages:
- Increased flexibility: Components can be added or removed without modifying entities.
- Independence: Each behavior is autonomous and replaceable.
- Avoids deep hierarchies: Reduces rigid dependencies.
Disadvantages:
- May require more code to explicitly manage components.
- Less performant and more scattered than ECS for projects with numerous entities.
Use Cases:
- Frameworks like Godot (using combinable Nodes).
- Medium-sized projects or those needing flexible behavior.
Comparison: ECS vs COI
| Aspect | ECS | COI |
|---|---|---|
| Description | Separate identifiers, data, and systems. | Independent components combined in entities. |
| Entity structure | Simple identifiers linked to components. | Entities directly containing their components. |
| Data | Data containers without logic. | Contain both data and behaviors. |
| Logic | Centralized in reusable systems. | Encapsulated locally in each component. |
| Modularity | Dynamic: components easily added/removed. | Directly integrated into the entity. |
| Performance | Excellent (contiguous storage, cache-friendly). | Lower performance (frequent cache misses). |
| Complexity | More complex to set up and understand. | Simpler for medium-sized projects. |
| Examples | Unity, Unreal Engine. | Godot, flexible medium-sized projects. |
Recommendation
- ECS: Ideal for complex games with thousands of entities to manage simultaneously.
- COI: Perfect for medium-sized projects where readability and flexibility are priorities.
Comparison of Graphics Libraries for R-Type
This document compares three major graphics libraries: Raylib, SFML, and SDL, based on their usability, performance, features, and documentation. It also explains why SFML was chosen for the R-Type project.
Library Comparison
| Criteria | Raylib | SFML | SDL |
|---|---|---|---|
| Usability | Intuitive | Simple and well-known | Complex but flexible |
| Performance | Good for medium-sized projects | Optimized for 2D | Excellent performance |
| Features | 2D/3D games, audio, input handling, basic UI | 2D games and applications, audio, networking, text rendering | 2D games, advanced platform handling |
| Documentation | Very well-documented with many examples | Good documentation and tutorials | Good but more technical |
| Platforms | Windows, macOS, Linux, Web, Android, iOS | Windows, macOS, Linux | Windows, macOS, Linux, Android |
Why We Chose SFML
We chose to use SFML for the following reasons:
- Adequate performance: SFML is optimized for 2D games like R-Type and provides performance suitable for the project’s needs.
- Ease of use: Its simplicity allows faster development and debugging, which is crucial for meeting project deadlines.
- Past experience: We have previously worked with SFML during our studies, reducing the learning curve.
- Quality documentation: SFML offers clear documentation along with practical tutorials, speeding up development.
References
Comparison of Programming Languages for R-Type Development
To undertake an ambitious project like R-Type, it is crucial to choose a programming language that meets the technical requirements of the game and the project’s objectives. Key criteria include performance, resource management, flexibility for working with graphics and network libraries, and ease of development.
1. C++
Advantages:
- Optimal execution speed: C++ compiles directly to machine code, offering unmatched performance ideal for real-time games requiring high fluidity and immediate responsiveness.
- Fine memory management: With pointers and modern tools like
std::unique_ptr, C++ allows efficient resource management, essential in a game where objects (sprites, sounds, etc.) are frequently manipulated. - Mature ecosystem: Libraries like SFML or OpenGL for graphics provide powerful solutions.
Disadvantages:
- Complexity: C++ can be harder to learn and master, particularly for manual memory management.
- Development time: Compared to more modern languages, C++ development may take longer, though this is offset by control and performance.
For R-Type: C++ is particularly suited for games requiring fine-tuned performance and resource management, both critical for a project like R-Type.
2. C#
Advantages:
- Clear and modern syntax: C# offers great code readability, reducing the time needed to implement features.
- Integration with Unity: Paired with Unity, C# enables rapid development thanks to integrated tools for graphics, animations, and physics.
- Automatic memory management: C# simplifies resource handling, avoiding common errors like memory leaks.
Disadvantages:
- Lower performance: While performant, C# relies on the CLR (Common Language Runtime), introducing slight latency.
- Dependency on frameworks: C#’s full potential often relies on tools like Unity, which can limit customization options.
For R-Type: C# is ideal for rapid development, but it may reach its limits in terms of performance for a game requiring ultra-optimized execution.
3. Python
Advantages:
- Ease of use: Python is renowned for its simplicity, making it ideal for quickly testing gameplay concepts.
- Accessible ecosystem: Libraries like Pygame allow effortless creation of 2D games.
Disadvantages:
- Limited performance: Python is an interpreted language, making it unsuitable for games requiring intensive calculations or real-time responsiveness.
- Abstract memory management: Garbage collection can cause unpredictable slowdowns.
For R-Type: Python is a good choice for prototyping or simple games, but it is not suitable for a final version requiring consistent performance.
4. Rust
Advantages:
- Performance comparable to C++: Rust compiles to native code, offering similar speed.
- Memory safety: Rust’s memory management system eliminates memory-related bugs while ensuring high performance.
- Modern syntax: More intuitive than C++, Rust combines safety and expressiveness.
Disadvantages:
- Steep learning curve: Rust takes time to fully grasp.
- Limited ecosystem: Rust has fewer libraries dedicated to game development compared to C++.
For R-Type: Rust is an interesting option for a project focused on safety and performance, but its young ecosystem can complicate development.
5. Java
Advantages:
- High portability: Thanks to the Java Virtual Machine, Java games can run on many platforms without modification.
- Ease of development: Java is easy to learn and offers automatic memory management.
Disadvantages:
- Lower performance: The JVM introduces latency, making Java less performant for complex real-time games.
- Limited graphics libraries: While solutions like LWJGL exist, they are less powerful than those available in C++.
For R-Type: Java can be suitable for a basic 2D game, but its performance limitations and less rich ecosystem make it less fitting for a demanding project like R-Type.
Conclusion
To recreate R-Type, a game demanding fluidity, optimization, and precise resource management, C++ emerges as the most suitable choice. Its combination of raw performance, flexibility, and a rich ecosystem places it above other options. While C# and Rust offer compelling alternatives, they cannot match the level of control and efficiency that C++ provides.
Comparison of Networking Methods for R-Type
This document provides a direct comparison of TCP, UDP, and a hybrid TCP+UDP approach, tailored for the R-Type multiplayer game project. Each method is evaluated based on performance, reliability, and suitability for a networked gaming environment.
Comparison Criteria
1. Latency
- TCP: High latency due to retransmission and sequencing mechanisms. Packet loss causes additional delays.
- UDP: Low latency, packets are sent immediately without acknowledgment.
- TCP+UDP: Balanced latency, with TCP handling critical data and UDP managing frequent updates.
2. Reliability
- TCP: Very reliable, ensures all packets arrive in the correct order.
- UDP: Less reliable, packets may be lost, duplicated, or arrive out of order.
- TCP+UDP: Modulated reliability. TCP ensures reliability for critical communications, while UDP tolerates losses in non-critical updates.
3. Implementation Complexity
- TCP: Easy to implement; retransmission and sequencing are handled by network libraries.
- UDP: More complex; requires custom mechanisms to manage packet loss, latency, and synchronization.
- TCP+UDP: High complexity; developers must integrate and synchronize both protocols.
4. Suitability for Fast-Paced Games
- TCP: Poor, delays from retransmissions affect responsiveness.
- UDP: Excellent, ideal for frequent updates with low latency.
- TCP+UDP: Good, each protocol is used according to its strengths.
Comparison Table
| Criteria | TCP | UDP | Hybrid (TCP+UDP) |
|---|---|---|---|
| Latency | High | Low | Medium |
| Reliability | Very good | Moderate | Very good |
| Complexity | Low | High | High |
| Suitable for fast games | No | Yes | Yes |
Recommendation for R-Type
For a fast-paced game like R-Type, the hybrid TCP+UDP approach is highly recommended:
- TCP: Used for critical data such as connection management, initial synchronization, and important game events.
- UDP: Used for real-time updates of entities, movements, shots, and other elements requiring low latency.
This approach ensures a balance between reliability and responsiveness, crucial for a smooth gaming experience.
References and Tools
References
- Fast-Paced Multiplayer Game Networking (Gabriel Gambetta)
- Quake 3 Networking Model
- Overwatch Netcode Explained
Client
The Client class manages the overall operation of the client.
It orchestrates the various components required for the game’s operation, from initializing the server to managing the entities to be displayed.
| Function | Description |
|---|---|
| runNetworkClient | This function starts the server. |
| initializeNetwork | This function handles the server initialization. |
| manageBackground | This function simulates the game background being in motion. In reality, it replicates the given image and scrolls it. |
| manageSound | This function setup the sound of the game. |
| processEvents | This is the function containing the loop that handles all game events (mouse clicks, keyboard key presses, etc.). |
| handleAutoFire | This function handles the auto-fire event when the corresponding key is pressed. |
| initializeServer | The initializeServer function establishes the network connection with the server (TCP/UDP) and initializes the required resources. |
| updateGameState | This function calls the GameEngine and requests it to display all the entities contained in a list. |
| manageClient | The manageClient function is the main loop of the client. It initializes the game window, the necessary components (sound, network, menus, etc.), and manages the game lifecycle, including rendering, user events, state management (menu, gameplay, victory, defeat), and communication with the server. It runs as long as the window remains open. |
Entity Manager
The EntityManager class manages the creation, deletion, and updating of entities.
| Functions | Description |
|---|---|
| CompareEntities | This function compares the entity IDs with the list of already existing entities. If the ID exists, the entity should be updated; if it does not exist, a new entity should be created. |
| CreateEntity | This function creates the entities through the game engine based on the requested parameters. |
| setPlayerColor | It allows targeting the correct sprite color on an image (to apply setTextureRect() function) based on the requested entity. |
| setEnemy | It allows targeting the correct enemy sprite on an image (to apply setTextureRect() function) based on the requested entity. |
| manageBackground | The function that creates the background entity. |
| winGame | The function checks if the player has won by verifying if the boss’s health is 0 or not. |
| loseGame | The function checks if the player has lost by verifying if his health is 0 or not. |
LifeBar
The LifeBar class manages the health bar functionality for a player, including creating health-related UI elements, updating health points, and rendering the life bar on the screen. It uses a singleton pattern to ensure a single instance is accessible globally.
| Functions | Description |
|---|---|
| createEntityText | Creates a text entity with specified attributes (text, position, font size) and adjusts its color based on health points. |
| displayLifeBar | Displays the life bar with updated health points, rendering the entities to the window. |
| manageHealth | Updates the health points if the provided entity ID matches the player ID. |
HotkeyManager
The HotkeysManager class handles hotkey-to-keyboard mappings, key reassignment, and autofire state. It processes input events, triggers actions, and sends hotkey data over the network, using a singleton design.s
| Functions | Description |
|---|---|
| getAutoFireState | Returns the current state of the autofire feature (enabled or disabled). |
| checkKey | Checks if a triggered key event matches a registered hotkey and sends a network packet for the corresponding action. |
| isKeyUsed | Determines if a specific keyboard key is currently mapped to a hotkey. |
| isAutoFire | Toggles the autofire state when the Enter hotkey is pressed. |
| keyToString | Converts a keyboard key to its corresponding string representation for display or debugging purposes. |
Menu
The Menu class manages the user interface for the game’s menu system, including buttons, input fields, and sprites. It handles the creation and rendering of menu entities, input handling, and transitions between different menu states.
| Functions | Description |
|---|---|
| createEntityButton | Creates a button entity with a title, font, size, position, and a callback function to be triggered on click. |
| createEntitySprite | Creates a sprite entity with given size, texture, texture rectangle, and position. |
| createEntityRect | Creates a rectangular button entity with size, position, color, and a callback function to be triggered on click. |
| createEntityInput | Creates an input field entity with a font, size, position, and default text value for user input. |
| isClickedInput | Updates the state of whether the input fields (IP, port, or username) have been clicked. |
| setupInput | Handles text input events to update the IP, port, or username fields based on user input. |
| initMainMenu | Initializes and displays the main menu, creating and positioning all necessary UI elements. |
| displayMenu | Displays the appropriate menu (Main or Options) based on the current menu state, calling the relevant display functions. |
OptionMenu
The OptionMenu class is responsible for managing the options menu in a game.
| Functions | Description |
|---|---|
| createEntityText | Creates an entity containing text with a specified font, size, and position. |
| createEntityOptionButton | Creates an option button with a callback, position, and predefined size. |
| createEntityButton | Creates a standard button with a title, font, size, position, and callback for the action. |
| createEntitySlider | Creates a slider with a value range and a callback to adjust volume or element size. |
| createEntityRect | Creates an entity with a rectangle (button) of a defined size and an interaction callback. |
| createEntitySprite | Creates an entity with an image (sprite) and applies a texture at the specified position. |
| setNewKey | Handles changing a keyboard shortcut based on a key press event and updates the associated key. |
| displayOptionMenu | Initializes and displays the options menu, adjusting interface elements based on window size. |
WinMenu
The WinMenu class handles the creation and display of the lose menu in the game, where the player can view the game over screen and exit the game. It provides methods to create various entities such as buttons, text, sprites, and rectangles, and arranges them on the screen in a responsive manner. The class also includes a method to handle user interaction, specifically when the Exit button is clicked, closing the game window.
| Functions | Description |
|---|---|
| createEntityButton | Creates and returns a button entity with specified properties, including a callback function for interaction. |
| createEntityText | Creates and returns a text entity with specified text, position, and font size, applying different colors based on text. |
| createEntitySprite | Creates and returns a sprite entity with specified size, texture, texture rectangle, and position. |
| createEntityRect | Creates and returns a rectangle entity, which is typically used as a button, with a color and an interaction callback. |
| isClickedExit | Closes the window when the “Exit” button is clicked. |
| displayWinMenu | Initializes and renders all entities for the Win menu, including sprites, text, and buttons, with responsive positioning based on window size. |
LoseMenu
The LoseMenu class handles the creation and display of the lose menu in the game, where the player can view the game over screen and exit the game. It provides methods to create various entities such as buttons, text, sprites, and rectangles, and arranges them on the screen in a responsive manner. The class also includes a method to handle user interaction, specifically when the Exit button is clicked, closing the game window.
| Functions | Description |
|---|---|
| createEntityButton | Creates and returns a button entity with specified properties, including a callback function for interaction. |
| createEntityText | Creates and returns a text entity with specified text, position, and font size, applying different colors based on text. |
| createEntitySprite | Creates and returns a sprite entity with specified size, texture, texture rectangle, and position. |
| createEntityRect | Creates and returns a rectangle entity, which is typically used as a button, with a color and an interaction callback. |
| isClickedExit | Closes the window when the “Exit” button is clicked. |
| displayLoseMenu | Initializes and renders all entities for the Win menu, including sprites, text, and buttons, with responsive positioning based on window size. |
NetworkClient
The NetworkClient class manages network communication for the client, handling both TCP and UDP connections. It initializes and connects the sockets, and runs two separate threads to handle incoming messages for TCP and UDP protocols. It uses the SmartBuffer to send and receive messages, and relies on the Protocol class to handle the messages based on their operation codes. Errors during communication are logged using the Logger class.
| Functions | Description |
|---|---|
| init | Initializes both the TCP and UDP sockets by calling their init() methods. |
| connectTCP | Establishes a TCP connection to the server and logs the connection status. |
| connectUDP | Initializes UDP communication and sends a default message to the server. |
| run | Creates and runs two threads to handle TCP and UDP messages concurrently. |
| handleTcpMessages | Continuously receives and processes TCP messages in a loop, handling any exceptions with error logging. |
| handleUdpMessages | Continuously receives and processes UDP messages in a loop, handling exceptions by logging errors. |
Protocol
The Protocol class handles the processing of various game-related messages, such as player creation, entity updates, health management, and more.The class includes methods for handling different types of network operations, including updating player positions, enemy data, bullets, and obstacles.
| Functions | Description |
|---|---|
| handleMessage | Handles incoming messages based on the opCode extracted from the buffer and calls the appropriate function for each message type. |
| handleCreatePlayerCallback | Handles player creation by receiving the player’s ID and dimensions, then initializes their properties in the EntityManager. |
| handleCreatePlayerBroadcast | Handles player creation via a broadcast message, initializing the player’s ID, name, and properties in the EntityManager. It allows retrieving information about the existence of other players. |
| handleUpdatePlayer | Updates a player’s position based on the received data and updates the EntityManager. |
| handleUpdateViewport | Updates the client’s viewport with the new viewport value. |
| handleUpdateBlocks | Updates the obstacles based on the ID, coordinates, size, and type, then updates the EntityManager. |
| handleUpdateEnemies | Updates the position and properties of enemies based on their type and adds them to the EntityManager. |
| handleUpdatePlayerInfos | Updates the player’s information such as score and kills based on the received data. |
| handleUpdateBullets | Updates bullet information, including position and type, and adds them to the EntityManager. |
| handleDeleteEntity | Deletes an entity from the EntityManager based on its ID, including related entities. |
| handleUpdateEntityHealth | Updates an entity’s health and manages win/loss conditions based on the entity’s health. |
Singleton
The Singleton class implements the Singleton pattern, ensuring that there is only one instance of the class. It manages TCP and UDP sockets as well as a server address.
Socket
The Socket class manages the creation and closure of a network socket. During instantiation, it configures the server address (IP address and port) using the inet_pton method to validate the IP address. If the address is invalid, an exception is thrown. The close method checks if the socket is valid before closing it and resetting its value.
TcpSocket
The TcpSocket class represents a TCP socket that allows connecting to a server, sending, and receiving data. It inherits from the Socket class, with specific methods to initialize the socket (init), establish a connection (connect), send data (send), and receive data (receive). It handles errors by throwing exceptions in case of network operation failures and uses a singleton to store the active TCP socket.
| Functions | Description |
|---|---|
| init | Create a TCP socket using socket() and save it in the singleton. Throw an exception in case of creation failure. |
| connect | Try to connect to the TCP server using connect(). Throw an exception if the connection fails. |
| send | Send a message through the TCP socket using send(). Throw an exception if the sending fails. |
| receive | Receive a message through the TCP socket using recv(), then return a SmartBuffer containing the received data. Throw an exception if the reception fails. |
UdpSocket
The UdpSocket class manages a UDP socket for sending and receiving data. It inherits from the Socket class and allows for initializing the socket, connecting to the server, sending, and receiving messages through the send and receive functions. It handles errors by throwing exceptions if an operation fails and closes the socket upon destruction.
| Functions | Description |
|---|---|
| init | Create a UDP socket using socket(), then save it in the singleton. Throw an exception if the creation fails. |
| send | Send a message through the UDP socket using sendto(). Throw an exception if the sending fails. |
| receive | Receive a message through the UDP socket using recvfrom(), then return a SmartBuffer containing the received data. Throw an exception if the reception fails. |
GetResponsiveValue
This class provides utility methods to calculate responsive positions and sizes for graphical elements, based on the ratio between base and current screen dimensions. It ensures consistent scaling of positions and dimensions across different screen sizes.
Logger
This class implements a color-coded logging utility for different log levels (e.g., info, success, error). It includes timestamped messages for easy debugging and supports additional categories like socket, packet, and thread logs.
Entity Component System (ECS)
Entity Component System (ECS) is a design architecture commonly used in game development and simulations. It emphasizes separation of concerns by dividing the functionality into three core components:
- Entities: These are unique identifiers that represent individual objects in the system.
- Components: These are data containers that hold specific attributes or properties of an entity.
- Systems: These are responsible for the behavior and logic, operating on entities that have the required components.
By decoupling data (components) from logic (systems) and organizing them around entities, ECS enables flexibility, performance, and easier management of complex systems.
For more details, refer to:
Components
A component is a data container that holds specific attributes of an entity.
| Component | Description | Values |
|---|---|---|
| Button | Defines entity as a button | • std::string text• std::string fontFile• unsigned int characterSize |
| OptionButton | Defines entity as a checkbox button | • std::pair<double, double> size |
| Slider | Defines entity as a slider button | • std::pair<double, double> length• std::pair<double, double> size |
| ButtonRect | Defines entity as an input text | • const std::pair<int, int> sizeRect• sf::Color color• bool showOutline |
| Sprite | Defines entity as a Sprite | • std::pair<float, float> size |
| Texture | Defines the texture of an entity | • std::string texturePath• std::vector<int> textureRect |
| Text | Defines entity as a text | • std::string text• std::string fontFile• unsigned int characterSize |
| Position | Stores positions of an entity | • std::vector<std::pair<float, float>> positions |
| Color | Defines the color of an entity. | • std::vector<double> color |
| Shape | Defines entity as shape (rectangle or circle) | • ShapeType type• std::pair<double, double> size• float radius |
| Sound | Defines a sound at an entity | • std::string soundFile |
| Link | Link an entity to another entity | • int id |
Entity
An entity is identified by an ID and contains all the components that represent its data.
Functions
Entity(int id, Args&&... args)-> Creates an entity with an ID and adds all the components provided inargs.void addComponent(const ComponentType& component)-> Adds a component in an entity.void removeComponent()-> Removes component from an entity.ComponentType& getComponent()-> Retrieves a component from an entity.bool hasComponent() const-> Checks if an entity has a component.int getEntityId() const-> Retrieves the ID of an entity.
Example
#include <Entity.hpp>
#include <components/Position.hpp>
#include <components/Text.hpp>
#include <components/Color.hpp>
#include <iostream>
int main() {
GameEngine::Entity entity(1, Text("Hello, World!", "Arial.ttf"), Position({{0, 0}, {1, 1}}));
entity.addComponent(Color({255, 0, 0, 1}));
entity.removeComponent<Color>();
if (entity.hasComponent<Position>()) {
std::cout << "Entity has a position component" << std::endl;
}
return 0;
}
Systems
Systems are the core of the ECS architecture. They are the place where the logic of your game is implemented.
Functions
void render(sf::RenderWindow& window, std::map<int, Entity>& entities)-> Renders all entities that have a component capable of being rendered.void update(int id, std::map<int, Entity>& entities, UpdateType type, const std::any& value, int posId = 0)-> Update the entity with the given id. The type of update is specified by theUpdateTypeenum. The value is the new value of the component.
Example
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <SFML/System.hpp>
#include <map>
#include <components/Position.hpp>
#include <components/Link.hpp>
#include <components/Sprite.hpp>
#include <components/Text.hpp>
#include <components/Texture.hpp>
#include "Entity.hpp"
#include "System.hpp"
int main() {
sf::RenderWindow window(sf::VideoMode(800, 600), "Client Game");
std::map<int, GameEngine::Entity> entities;
auto player = GameEngine::Entity(1, Sprite(), Texture("assets/sprite/spaceship.png", {0, 0, 34, 15}), Position({{0, 0}}));
auto nametag = GameEngine::Entity(2, Text("Name", "assets/font/arial.ttf", 10), Position({{0, -14}}), Link(1));
entities.emplace(1, std::move(player));
entities.emplace(2, std::move(nametag));
GameEngine::System system;
float x, y = 0.0f;
float deltaTime = 0.0f;
float speed = 200.0f;
sf::Clock clock;
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed)
window.close();
}
deltaTime = clock.restart().asSeconds();
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Z)) {
y -= speed * deltaTime;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)) {
y += speed * deltaTime;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Q)) {
x -= speed * deltaTime;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) {
x += speed * deltaTime;
}
system.update(1, entities, GameEngine::UpdateType::Position, std::pair(x, y));
window.clear();
system.render(window, entities);
window.display();
}
return 0;
}
R-Type Network Architecture Documentation
This document provides a detailed explanation of the R-Type network implementation. It describes the structure, threading model, client management, and mechanisms for handling communication between the server and clients using TCP and UDP protocols.
Overview
The R-Type network is designed to handle real-time multiplayer interactions with a balance between reliability and performance. The server uses a dual-protocol approach:
- TCP: Ensures reliable delivery of critical operations such as player creation and key press events.
- UDP: Optimized for real-time updates like player positions, bullet movements, and obstacle updates, where occasional packet loss is acceptable.
Architecture and Structure
Server Components
-
Main Server Class (
Server)- Orchestrates the overall network flow.
- Initializes both TCP and UDP sockets.
- Starts separate threads for handling TCP and UDP operations.
-
TCP Socket (
TcpSocket)- Manages reliable client-server communication.
- Handles client connections, disconnections, and data reception.
- Binds each client connection to a specific player.
-
UDP Socket (
UdpSocket)- Manages real-time communication.
- Broadcasts updates to all clients.
- Operates three main loops:
- Read Loop: Reads incoming UDP packets.
- Update Loop: Processes game logic.
- Send Loop: Sends updates to clients.
-
SmartBuffer
- Handles serialization and deserialization of data.
- Used to encode and decode protocol-compliant messages.
Threading Model
The server uses a multi-threaded approach to handle TCP and UDP operations concurrently:
Threads
-
TCP Thread:
- Handles client connections and disconnections via the
TcpSocket. - Reads and processes incoming data using a dedicated thread per client.
- Handles client connections and disconnections via the
-
UDP Threads:
- Read Thread: Continuously receives packets from clients and processes them.
- Update Thread: Runs the game logic, updating player positions, bullets, and obstacles.
- Send Thread: Sends game state updates to all clients.
Thread Synchronization
- Mutexes: Used to protect shared resources such as the list of connected clients and game entities.
- Locks: Ensures that no two threads modify the same data simultaneously.
Client Management
Storing Clients
Clients are stored in separate lists for TCP and UDP communications:
-
TCP Clients:
- Maintained as a vector of socket file descriptors.
- Each client is associated with a player ID, stored in the
PlayerManager.
-
UDP Clients:
- Stored as a vector of
sockaddr_instructures. - Each client’s address is added when a UDP packet is received.
- Stored as a vector of
Mapping Players to Clients
- When a new player is created via TCP, their
playerIdis mapped to their TCP socket. - The same
playerIdis used for UDP communications to identify the player.
TCP Workflow
Connection Handling
-
Initialization:
- The TCP socket is created and bound to a port.
- The socket listens for incoming connections.
-
Accepting Connections:
- New connections are accepted in a loop.
- Each accepted client socket is passed to a dedicated thread for communication.
Data Handling
-
Receiving Data:
- Each client thread reads data from its associated socket.
- Data is passed to the
Protocolclass for decoding and handling.
-
Sending Data:
- Responses are serialized using
SmartBufferand sent back to the client. - Broadcasts are handled by iterating over the list of clients.
- Responses are serialized using
UDP Workflow
Initialization
- The UDP socket is created and bound to a port.
- Unlike TCP, there is no need to establish connections.
Read Loop
- Continuously reads incoming packets from any client.
- Decodes messages using
Protocoland updates the game state.
Update Loop
- Executes game logic, such as updating player positions, bullets, and obstacles.
- Uses mutexes to synchronize shared resources.
Send Loop
- Serializes the current game state into protocol-compliant messages.
- Sends updates to all connected clients using their stored addresses.
Key Classes and Responsibilities
1. Server
- Entry point of the application.
- Initializes
TcpSocketandUdpSocket. - Manages threads for TCP and UDP workflows.
2. TcpSocket
- Handles client connections and reliable communication.
- Maintains a list of connected clients.
- Maps client sockets to player IDs.
3. UdpSocket
- Handles real-time communication.
- Maintains a list of client addresses.
- Reads, updates, and sends game state in separate threads.
4. Protocol
- Decodes incoming messages and routes them to the appropriate handler.
- Encodes outgoing messages to ensure protocol compliance.
5. SmartBuffer
- Provides serialization and deserialization utilities.
- Ensures efficient handling of binary data.
Communication Flow
-
Player Creation:
- A client sends a
CREATE_PLAYERrequest via TCP. - The server creates a player and sends back a
CREATE_PLAYER_CALLBACKresponse. - The server broadcasts a
CREATE_PLAYER_BROADCASTmessage via UDP.
- A client sends a
-
Game Updates:
- The server’s update thread processes the game state (e.g., player movements, bullets).
- The send thread broadcasts updates via UDP to all clients.
-
Hotkey Presses:
- A client sends a
HOTKEY_PRESSEDmessage via TCP. - The server updates the player’s state based on the hotkey.
- A client sends a
Summary
The R-Type network implementation leverages the strengths of both TCP and UDP protocols to deliver reliable and efficient communication. The architecture ensures scalability and real-time performance while maintaining a clear separation of responsibilities between components. Mutexes and threads ensure thread safety, enabling smooth operation in a multiplayer environment.
R-Type Miscellaneous Guide
This section covers various features of the project, such as adding enemies, obstacles, editing configurations, and understanding the logging system.
Adding New Enemies
To add new enemies, you need to update both EnemyManager and EnemyType enum in Enemy.hpp.
Steps to Add a New Enemy
-
Define a New Enemy in the Enum
- Open
Enemy.hpp. - Add a new entry to the
EnemyTypeenum:enum class EnemyType { NONE, GRUNT, SNIPER, TANK, SWARMER, BOSS, DRONE, MINION, CANNON, NEW_TYPE // Add your new enemy here };
- Open
-
Configure the New Enemy
- Go to
EnemyManager.cpp. - Add the new enemy type to
_enemyMapping:{"E009", {EnemyType::NEW_TYPE, 5, 60, 60, 800, 10, 1200, 700, 200}}, - Here's a breakdown of the parameters:
| Parameter | Description |
|-----------------|---------------------------------------------------------------------------------|
|
EnemyType| The type of enemy, linked to the enum. | |speed| Movement speed of the enemy. | |width| Width of the enemy sprite (in pixels). | |height| Height of the enemy sprite (in pixels). | |bulletSpeed| Speed of the bullets fired by the enemy. | |bulletDamage| Damage dealt by the enemy's bullets. | |shootCooldown| Time between each shot (in milliseconds). | |shootRange| Range at which the enemy will start shooting (in pixels). | |health| Total health points of the enemy. |
- Go to
-
Map Integration
- Use the new enemy code (e.g.,
E009) in your map files to place the enemy. - Example map snippet:
B000B000B000E009B000B000
- Use the new enemy code (e.g.,
Example for Adding "E009"
{"E009", {EnemyType::DRONE, 4, 40, 40, 700, 8, 1000, 600, 100}},
This defines a new drone enemy:
- Moves at speed 4.
- Dimensions are 40x40 pixels.
- Bullet speed is 700 and damage is 8.
- Shoots every 1000 ms within a 600-pixel range.
- Health is 100.
Adding New Obstacles
To add new obstacles:
-
Update the ObstacleType Enum
- Open
ObstacleManager.hpp. - Add a new type to
ObstacleType:enum class ObstacleType { NONE, OBSTACLE, OBSTACLE2, OBSTACLE3, NEW_OBSTACLE };
- Open
-
Update the Obstacle Mapping
- Go to
ObstacleManager.cpp. - Add the new obstacle type:
{"B005", ObstacleType::NEW_OBSTACLE},
- Go to
-
Map Integration
- Use the new obstacle code (e.g.,
B005) in your map files. - Example:
B000B005B000B000B000
- Use the new obstacle code (e.g.,
Configuration (Config.hpp)
The Config.hpp file defines constants for gameplay, server settings, and default values. Here's a table of key configurations:
| Category | Constant | Description |
|---|---|---|
| Socket | DEFAULT_BYTES | Default buffer size for socket communication. |
PORT | Port number for the server. | |
| Server | CADENCY | Frame rate interval in ms (affects TICK_PER_SECOND). |
TICK_PER_SECOND | Server update frequency. | |
| Program | SUCCESS | Exit code for successful operations. |
ERROR | Exit code for errors. | |
| Player | PLAYER_WIDTH | Width of player sprites. |
PLAYER_SPEED | Movement speed of players. | |
| Map | MAP_SPEED | Speed at which the map scrolls. |
MAP_WIDTH/HEIGHT | Dimensions of the map. | |
| Obstacle | OBSTACLE_SIZE | Size of obstacle sprites. |
Logging System
The Logger provides different log levels for debugging and monitoring.
Log Levels and Colors
| Log Level | Color | Usage |
|---|---|---|
info | Blue | General information. |
success | Green | Successful operations. |
warning | Yellow | Warnings that don't halt execution. |
error | Red | Critical errors. |
socket | Cyan | Socket-related logs. |
packet | Magenta | Packet-specific logs. |
thread | Pink | Thread operations. |
debug | White | Debugging information. |
trace | Gray | Detailed execution traces. |
How to Log Messages
Use the static methods from Logger:
Logger::info("Server started.");
Logger::error("Failed to bind socket.");
Logger::debug("Player initialized.");
Logs are printed with timestamps for easy tracking.
R-Type Protocol Documentation
This document provides a detailed overview of the communication protocol for the R-Type server. It outlines the purpose, payload, and transmission method (TCP/UDP) for each operation code (OpCode). The protocol ensures consistent communication between the server and clients.
Overview
Transport Protocols
- TCP: Reliable communication for operations requiring acknowledgment (e.g., player creation, hotkey inputs).
- UDP: Lightweight and fast communication for real-time updates (e.g., player positions, bullets, obstacles).
Message Structure
All messages follow this general structure:
| Field | Type | Size (bytes) | Description |
|---|---|---|---|
| OpCode | int16_t | 2 | Identifies the operation (see below). |
| Payload | Varies | Variable | Data relevant to the specific operation. |
OpCode Definitions
1. DEFAULT
- Value:
0 - Description: Used to save the client on the server.
- Payload: None.
- Sent To: Server or specific client.
- Transport: TCP.
2. HOTKEY_PRESSED
- Value:
1 - Description: Notifies the server of a key press by a specific player.
- Payload:
playerId(int32_t): The ID of the player who pressed the key.hotkeyCode(int16_t): The code of the pressed key.
- Sent To: Server.
- Transport: TCP.
3. CREATE_PLAYER
- Value:
10 - Description: Requests the creation of a new player on the server.
- Payload:
playerName(string): The name of the player to create.
- Sent To: Server.
- Transport: TCP.
4. CREATE_PLAYER_CALLBACK
- Value:
11 - Description: Confirms the creation of a new player to the requesting client.
- Payload:
playerId(int32_t): The ID of the newly created player.width(int16_t): The width of the player’s sprite.height(int16_t): The height of the player’s sprite.
- Sent To: Specific client (the one who requested the creation).
- Transport: TCP.
5. CREATE_PLAYER_BROADCAST
- Value:
12 - Description: Broadcasts the creation of a new player to all connected clients.
- Payload:
playerId(int32_t): The ID of the new player.playerName(string): The name of the new player.width(int16_t): The width of the player’s sprite.height(int16_t): The height of the player’s sprite.
- Sent To: All clients.
- Transport: UDP.
6. UPDATE_PLAYERS
- Value:
20 - Description: Updates player positions and states.
- Payload:
playerId(int32_t): The ID of the player.posX(int32_t): The player’s X-coordinate.posY(int32_t): The player’s Y-coordinate.
- Sent To: All clients.
- Transport: UDP.
7. UPDATE_VIEWPORT
- Value:
21 - Description: Updates the current map viewport.
- Payload:
viewport(double): The viewport’s position.
- Sent To: All clients.
- Transport: UDP.
8. UPDATE_OBSTACLES
- Value:
22 - Description: Updates obstacle positions or states.
- Payload:
obstacleId(int32_t): The ID of the obstacle.posX(int32_t): The obstacle’s X-coordinate.posY(int32_t): The obstacle’s Y-coordinate.size(int16_t): The size of the obstacle.type(int16_t): The type of the obstacle.
- Sent To: All clients.
- Transport: UDP.
9. UPDATE_BULLETS
- Value:
23 - Description: Updates bullet positions or states.
- Payload:
bulletId(int32_t): The ID of the bullet.posX(int32_t): The bullet’s X-coordinate.posY(int32_t): The bullet’s Y-coordinate.type(int16_t): The type of the bullet.
- Sent To: All clients.
- Transport: UDP.
10. UPDATE_ENEMIES
- Value:
24 - Description: Updates enemy positions or states.
- Payload:
enemyId(int32_t): The ID of the enemy.posX(int32_t): The enemy’s X-coordinate.posY(int32_t): The enemy’s Y-coordinate.width(int16_t): The width of the enemy’s sprite.height(int16_t): The height of the enemy’s sprite.type(int16_t): The type of the enemy.
- Sent To: All clients.
- Transport: UDP.
11. UPDATE_ENTITY_HEALTH
- Value:
25 - Description: Broadcasts changes in the health of an entity (player or enemy).
- Payload:
entityId(int32_t): The ID of the entity.health(int16_t): The current health of the entity.maxHealth(int16_t): The maximum health of the entity.
- Sent To: All clients.
- Transport: UDP.
12. UPDATE_PLAYER_INFOS
- Value:
26 - Description: Updates player's infos.
- Payload:
playerId(int32_t): The ID of the player.kills(int16_t): The kills amount of the player.score(int32_t): The score of the player.
- Sent To: Specific client.
- Transport: UDP.
13. DELETE_ENTITY
- Value:
30 - Description: Deletes an entity from the game (e.g., player, enemy, bullet).
- Payload:
entityId(int32_t): The ID of the entity to delete.
- Sent To: All clients.
- Transport: UDP.
Notes
- The server and clients must strictly adhere to the protocol to ensure consistency.
- Payloads must be interpreted exactly as defined.
- TCP is used for operations requiring reliable delivery, while UDP is optimized for real-time updates.
SmartBuffer: Fast and Flexible Data Buffer
SmartBuffer is a custom library I built for managing binary data efficiently. It’s simple, fast, and works in any C++ project. The main idea is to make it easy to read, write, and manipulate raw data without worrying about memory leaks or performance hits.
Why SmartBuffer?
- Performance: Optimized for fast reads/writes with minimal overhead.
- No Memory Leaks: Fully manages its memory using
std::vector. - Universal: Works with any trivially copyable type (like
int,float, etc.) and strings. - Reusable: You can use it in any C++ project. It’s modular and lightweight.
How It Works
Core Features:
- Dynamic Resizing: The buffer grows automatically when needed.
- Serialization: Supports trivially copyable types and strings.
- Operator Overloads: Use
<<and>>for easy read/write. - Buffer Reset: Clear all data or just reset the read pointer.
Structure
The buffer is built around:
std::vector<uint8_t>: Handles the raw data.readOffsetandwriteOffset: Keep track of where to read and write.- Templates for
readandwrite: Allow type-safe operations.
Key Methods
Initialization
SmartBuffer(size_t initialCapacity = 8);
- Reserves initial capacity (default: 8 bytes).
- The buffer expands as needed.
Writing Data
template <typename T>
void write(const T& value);
- Supports any trivially copyable type.
- For strings, writes the length first, then the content.
Reading Data
template <typename T>
T read();
- Reads data back in the same order it was written.
- Throws an exception if you try to read beyond the buffer.
Injecting Raw Data
void inject(const uint8_t* rawData, size_t size);
- Adds raw binary data directly into the buffer.
Resetting
void reset(); // Clears all data
void resetRead(); // Resets the read pointer
reset(): Clears the whole buffer.resetRead(): Resets the read pointer to the start.
Getters
size_t getSize() const; // Total size of written data
const uint8_t* getBuffer() const; // Pointer to raw buffer data
- Use these to access buffer stats or get the raw data.
Why .inl?
SmartBuffer’s implementation uses .inl for templates. Templates must be defined in the header (or included inline) so the compiler can generate code for specific types during compilation. This keeps the header clean and avoids duplication.
Example Usage
Writing and Reading
SmartBuffer buffer;
// Writing data
buffer << 42 << 3.14f << std::string("Hello");
// Reading data
int intValue;
float floatValue;
std::string stringValue;
buffer >> intValue >> floatValue >> stringValue;
// Output: 42, 3.14, "Hello"
Injecting Raw Data
uint8_t rawData[] = {0x01, 0x02, 0x03};
buffer.inject(rawData, sizeof(rawData));
Resetting
buffer.reset(); // Clear everything
buffer.resetRead(); // Start reading from the beginning
Technical Highlights
-
Memory Management:
- Uses
std::vectorfor automatic memory handling. - Ensures no leaks even with dynamic resizing.
- Uses
-
Trivial Type Optimization:
- Writes and reads trivially copyable types directly with
std::memcpy.
- Writes and reads trivially copyable types directly with
-
String Support:
- Writes the length first as a
uint32_t, then the content. - Reads back the length to reconstruct the string.
- Writes the length first as a
-
Error Handling:
- Throws
std::runtime_errorfor out-of-bounds reads.
- Throws
Performance
- Speed: Minimal overhead, only resizes when necessary.
- Safety: No manual memory management.
- Efficiency: Avoids unnecessary copies with
std::vector.
Where to Use It?
- Networking (serialize/deserialize packets).
- Game development (handling binary assets or messages).
- General-purpose C++ projects needing binary buffers.