Repository navigation
[Holtzman Engine] Graphic
- Introduction
- Why SFML
- Resource Management
- Integration with ECS
- System Optimization Overview
- Flexibility and External Compilation
- Integrating a New Graphics Library with the RType Game Engine
- Detailed Method Descriptions
The RenderSystemSFML is a custom rendering system for the RType game, utilizing the SFML library for graphics, sound, and user input. It manages the game window, renders sprites and text, handles user interactions like keyboard and mouse events, and manages audio playback.
The SFML library was chosen for the RType project due to its simplicity, modern C++ API, and efficient handling of 2D graphics and audio. Here's a comparison with other similar libraries:
-
SFML vs SDL:
SFML provides a more intuitive, object-oriented API, making it easier to manage graphics and sounds. SDL is more low-level and versatile, offering broader platform support but requires more manual management. -
SFML vs OpenGL:
SFML simplifies 2D game development with built-in features for graphics and audio. OpenGL, while more powerful and flexible for 3D rendering, requires more complex setup and knowledge. -
SFML vs Allegro:
SFML offers a more modern API and better integration with C++'s object-oriented features, while Allegro, an older library, is known for its cross-platform capabilities but has a less user-friendly API.
The RenderSystemSFML handles resources like textures, sprites, and audio through a caching system for efficient memory management.
-
Loading Textures:
Textures are loaded from files and stored in a std::map for quick access during rendering.
std::shared_ptr<sf::Texture> RenderSystemSFML::loadTexture(const std::string& filePath)
{
if (_textures.find(filePath) != _textures.end())
{
return _textures[filePath];
}
auto texture = std::make_shared<sf::Texture>();
if (!texture->loadFromFile(filePath))
{
std::cerr << "SFMLRenderSystem: Error loading texture: " << filePath << std::endl;
return nullptr;
}
_textures[filePath] = texture;
return texture;
}-
Caching System:
The textures and sprites are stored in _textures and _sprites maps, allowing for efficient reuse without reloading from disk. -
Memory Management:
Resources like textures and sounds are stored using std::unique_ptr to ensure proper memory cleanup when they are no longer needed.
The RenderSystemSFML is tightly integrated with the Entity-Component-System (ECS) architecture of RType, making it easy to manage graphical components.
-
Rendering Sprites:
The system iterates over entities that have graphical components and draws their sprites using SFML.
void RenderSystemSFML::drawSprite(const std::string& spriteName, mlg::vec2 position)
{
auto it = _spriteCache.find(spriteId);
if (it != _spriteCache.end())
{
it->second->setPosition(position.x, position.y);
if (_activeShader)
{
_window.draw(*it->second, _activeShader);
}
else
{
_window.draw(*it->second);
}
}
else
{
std::cerr << "SFMLRenderSystem: Error drawing sprite: " << spriteId << std::endl;
}
}-
Handling User Input:
The pollEvents method captures user input, like key presses or mouse movements, and updates entity states based on these events.
void RenderSystemSFML::pollEvents() {
sf::Event event;
while (_window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
_currentKeys[KeyCode::Close] = true;
}
// Additional event handling...
}
}-
System Architecture:
The RenderSystemSFML serves as a system in the ECS framework, working alongside other systems like physics or collision handling to provide a seamless game experience.
In our project, we have implemented an efficient resource management system that leverages caching and ID mapping to optimize the use and reusability of graphical and audio assets. This ensures that resources are loaded once, cached, and easily accessed without redundant loading, reducing the overall memory footprint and improving performance. Below, we detail the structure and functionality of our optimization approach.
Resource Caches and Management Structures
The system use std::unordered_map and std::map data structures to store and manage resources. Each type of resource has its own cache, identified by unique integer IDs. The use of std::shared_ptr ensures proper memory management and avoids duplication of resources.
Key data structures include:
-
std::unordered_map<int, std::shared_ptr<sf::Sprite>> _spriteCache: Stores sprites associated with their unique integer IDs. -
std::unordered_map<int, std::shared_ptr<sf::Music>> _musics: Holds music resources indexed by IDs. -
std::unordered_map<int, std::shared_ptr<sf::SoundBuffer>> _sounds: Manages sound buffers by their IDs. -
std::unordered_map<int, std::shared_ptr<sf::Shader>> _shaders: Contains shader programs linked to unique IDs. -
std::unordered_map<int, std::shared_ptr<sf::Font>> _fonts: Caches font resources indexed by IDs.
In addition, we maintain a cache of textures indexed by their file paths:
std::unordered_map<std::string, std::shared_ptr<sf::Texture>> _textures
Resource lookup and ID mapping:
- We use
std::map<std::string, int>structures (e.g.,_musicCache,_soundCache,_fontCache,_shaderCache) to map resource file paths to their corresponding IDs, facilitating quick access and preventing duplicate loads.
ID counters:
- Integer counters such as
_nextSpriteId,_nextShaderId,_nextFontId,_nextMusicId, and_nextSoundIdensure each resource receives a unique identifier when added to the cache.
Our program is designed to be flexible and can be compiled with different graphics libraries, enhancing its adaptability to various environments and user needs. For example:
- SFML (Simple and Fast Multimedia Library): An alternative library that can be used for cross-platform development, allowing for similar functionality but with a different API.
This approach ensures that our codebase can adapt to different performance requirements and platform specifications by substituting SFML with SDL or other libraries as needed. The abstraction layer within our project is designed to seamlessly switch between these libraries without extensive code modifications, making the system versatile and maintainable.
Advantages of the System
- Improved Performance: By caching assets, the system avoids repeated loading, reducing latency and enhancing real-time performance.
- Memory Management:: The use of std::shared_ptr ensures that resources are automatically freed when no longer in use, preventing memory leaks.
- Extensibility: The modular structure allows for the easy integration of additional libraries and new types of resources.
- Scalability: Unique ID assignment and mapped caches provide a scalable solution for handling a large number of resources efficiently.
This design optimizes our game engine’s performance while providing flexibility in resource management and cross-library compatibility.
To extend the RType game engine with a new graphics library, developers must adhere to the IRuntime interface provided by the project. This interface standardizes the methods needed for rendering, user input, and window control, ensuring compatibility and seamless integration with the existing architecture.
Overview of the IRuntime Interface
The IRuntime interface defines essential methods that any rendering system must implement. These methods handle various aspects of the rendering process, input polling, and window management. By implementing this interface, you can integrate different graphical libraries, such as SDL or OpenGL, while maintaining consistency with the RType game engine.
Key Requirements
- Event Handling: Implement methods to poll user input events and process window interactions.
- Rendering: Implement methods for rendering shapes, sprites, and text to the window.
- Audio Management: Implement methods for loading, playing, and unloading sound and music resources.
- Window Management: Implement methods for window creation, title setting, and fullscreen toggling.
- Resource Handling: Implement caching and resource management methods to efficiently manage textures, sounds, and shaders.
Event and Input Management
-
pollEvents(): Implement event polling to capture user inputs and window events. -
getKey(KeyCode key),getKeyUp(KeyCode key),getKeyDown(KeyCode key): Implement these methods to handle keyboard input states. -
getMousePosition(): Implement this method to retrieve the current position of the mouse cursor.
Rendering and Drawing
-
clearWindow(): Clear the window to prepare for the next frame. -
updateWindow(): Display the content rendered in the current frame. -
drawRectangle(mlg::vec4& spriteCoords, bool full, const mlg::vec3& color): Draw a rectangle with the specified attributes. -
drawSprite(int spriteId, mlg::vec3 position, mlg::vec4 spriteRect, mlg::vec3 scale, float rotation): Draw a sprite with specified properties. -
drawText(int fontID, const std::string& textStr, const mlg::vec3 position, unsigned int fontSize, const mlg::vec3& color, bool centered): Render text on the screen.
Resource Loading and Management
-
loadFont(const std::string& filePath),loadSprite(const std::string& filePath),loadTexture(const std::string& filePath): Implement these methods to load resources from file paths. -
loadMusic(const std::string& filePath),playMusic(int musicID, bool loop),stopCurrentMusic(),unloadMusic(int musicID): Manage music loading, playback, and unloading. -
loadSound(const std::string& filePath),playSound(int soundID),unloadSound(int soundID): Handle sound effect loading and playback. -
loadShader(const std::string& vertexShaderPath, const std::string& fragmentShaderPath),setShader(int shaderId),resetShader(),unloadShader(int shaderId): Draw a sprite with specified properties. -
drawText(int fontID, const std::string& textStr, const mlg::vec3 position, unsigned int fontSize, const mlg::vec3& color, bool centered): Implement shader management methods.
Window and Display Controls
-
setGameIcon(const std::string& filePath): Set the window icon. -
setMouseCursorVisible(bool visible): Control the visibility of the mouse cursor. -
FullScreenWindow(bool fullscreen): Toggle fullscreen mode. -
isWindowOpen(): Check if the window is open. -
setFramerateLimit(unsigned int limit),setVerticalSyncEnabled(bool enabled): Manage framerate and vertical synchronization.
Implementation Guidelines
When adding a new graphics library, ensure that all pure virtual methods in the IRuntime interface are implemented. This guarantees that the library integrates smoothly with the existing ECS (Entity-Component-System) framework.
Example of Compliance
Implementing a method from the interface might look like this:
class SDLRuntime : public RType::IRuntime {
public:
void pollEvents() override {
// Implementation using SDL's event handling.
}
bool getKey(KeyCode key) override {
// Implementation for key input using SDL.
}
// Implement other methods as required.
};
By following these guidelines and implementing the IRuntime interface correctly, developers can extend the RType game engine to support various graphics libraries, enhancing its versatility and adaptability.
Previous page: Holtzman Engine
Next page: Network
This project was developed and documented by the MarmotteurInc team.