- Introduction
- Installation
- Code Structure
- Game Object Model
- Timeline System
- Networking Setup
- Event Management
- Replay System
- JavaScript Scripting with V8
- Input Handling
- Movement System
- Games
- Future Scope
This is a C++ project for a Game Engine that can be run on any Linux-based system. The engine supports features such as event management, networking, and scripting with the V8 JavaScript engine.
To run this project, ensure that the following dependencies are installed:
- SDL2
- ZeroMQ (ZMQ)
- libnode-dev (for V8 JavaScript engine)
-
Update the package list:
sudo apt update
-
Install SDL2:
sudo apt install libsdl2-dev
-
Install ZeroMQ:
sudo apt install libzmq3-dev
-
Install libnode-dev:
sudo apt install libnode-dev
-
Navigate to the project directory in your terminal.
-
Use the following commands to build the project:
make clean # Cleans the existing build files. make # Compiles the project.
-
Start the Server:
./main server
-
Start the Client:
./main client
Note: Scaling can be toggled by pressing the Left Shift
key. By default, scaling is not enabled (constant size).
The codebase is organized into several key components:
-
Main Modules
main.cpp
andmain.hpp
: Entry point of the application.init.cpp
andinit.hpp
: Initialization routines for the game engine.cleanup.cpp
andcleanup.hpp
: Handles resource cleanup.
-
Entities and Components
Entity.hpp
andEntity.cpp
: Defines the base entity class.EntityManager.hpp
andEntityManager.cpp
: Manages all entities within the game.MovementPattern.hpp
andMovementPattern.cpp
: Defines movement patterns for entities.
-
Graphics and Rendering
draw.cpp
anddraw.hpp
: Functions for rendering entities and shapes.Shape.hpp
andShape.cpp
: Defines geometric shapes used in the game.Camera.hpp
andCamera.cpp
: Implements camera functionality.scaling.hpp
andscaling.cpp
: Handles scaling transformations.
-
Input and Physics
input.cpp
andinput.hpp
: Handles user input.Physics.hpp
andPhysics.cpp
: Implements physics calculations.collision_utils.hpp
andcollision_utils.cpp
: Contains collision detection utilities.
-
Networking
Server.hpp
andServer.cpp
: Server-side networking implementation.Client.hpp
andClient.cpp
: Client-side networking implementation.- Note: The P2P architecture is currently deprecated but remains in the codebase.
-
Event Management
event.hpp
: Contains generic event-related definitions.event_manager.hpp
andevent_manager.cpp
: Manages event queueing and dispatching.
-
Scripting
script_manager.hpp
andscript_manager.cpp
: Manages JavaScript scripting with V8.v8helpers.hpp
andv8helpers.cpp
: Helper functions for integrating V8 scripts.
-
Utilities
.clang-format
: Coding style guidelines for consistent formatting.defs.hpp
: Contains macro definitions and global constants.structs.hpp
andstructs.cpp
: Defines common data structures.
The Game Object Model revolves around the Entity class, which serves as the core building block for all game objects. Each entity possesses various attributes such as position, velocity, maximum velocity, acceleration, color, shape, movement pattern, timeline, and flags indicating whether it is affected by gravity or is movable.
The design follows a component-based architecture, emphasizing composition over inheritance. This approach allows for greater flexibility and modularity.
-
Has-A Relationship:
- Shape: Each entity has a shape, defined by the
Shape
class. - Movement Pattern: Entities can have movement patterns, defined by the
MovementPattern
class. - Timeline: Each entity has its own timeline, anchored to the global timeline.
- Shape: Each entity has a shape, defined by the
-
Is-A Relationship:
- The entity class does not inherit from other classes but interacts with various components to achieve desired behaviors.
While entities possess attributes, they do not directly manipulate them. Instead, specialized components handle specific aspects:
- Physics System: Applies gravity and other physics-related effects to entities.
- EntityManager: Manages entities and updates their movement patterns.
- Timeline: Manages time for each entity, allowing for synchronized and independent time flows.
This design ensures that entities remain lightweight and focused on their core responsibilities, while components handle complex behaviors.
The Timeline system manages time within the game engine, allowing for precise control over game events, synchronization, and time scaling. It is especially useful for features like pausing the game, time dilation, and syncing events in networked multiplayer environments.
The Timeline is implemented using high-resolution timers to accurately track elapsed time. Depending on the compiler and platform, it utilizes appropriate methods:
- Windows (MSVC Compiler):
- Uses the
__rdtsc()
intrinsic to read the CPU's timestamp counter.
- Uses the
- GCC Compiler:
- Uses inline assembly to execute the
rdtsc
instruction directly.
- Uses inline assembly to execute the
This approach ensures a consistent and high-resolution time source across different systems.
Key aspects of the Timeline
class:
- Time Retrieval: Provides the current time since the timeline started, adjusted for any pauses and time scaling.
- Pausing and Unpausing: Allows pausing and resuming the timeline without affecting the overall time flow.
- Time Scaling (Tic): Adjusts the speed at which time progresses, enabling effects like slow motion or fast forward.
- Anchoring: Timelines can be anchored to other timelines, creating a hierarchy that allows for synchronized or relative time flows.
getTime()
: Returns the current time, accounting for pauses and tics.pause()
: Pauses the timeline.unpause()
: Unpauses the timeline.changeTic(int new_tic)
: Changes the tic value to scale the timeline speed.isPaused()
: Checks if the timeline is currently paused.getTic()
: Retrieves the current tic value.getAnchorTic()
: Retrieves the tic value from the anchor timeline, if any.
The Timeline system integrates with various components:
- Game Loop: Controls the timing for updates and rendering.
- Event Scheduling: Schedules events based on the game time.
- Animation and Physics: Ensures smooth animations and physics calculations by providing consistent time steps.
- Network Synchronization: Helps synchronize actions between clients and servers in multiplayer modes.
timeline.hpp
timeline.cpp
- Pause/Unpause: Toggle with the
ESC
key. - Speed Up: Press the
P
key. - Slow Down: Press the
M
key.
We support both client-server and Peer-to-Peer (P2P) architectures. The required libzmq.dll
file is included in the x64
folder, so no manual installation is needed.
Note: The Peer-to-Peer (P2P) architecture is currently deprecated and not supported, but it remains included in the project for reference.
- Start the Server:
./main server
- Start the Client:
./main client
- Start a Listen-Server:
./main server client
- Start a P2P Client:
./main client P2P
Note: Ensure that clients are binding and connecting to the correct IP addresses and ports when starting P2P clients.
The game engine utilizes an event-driven architecture to handle various game events efficiently. This design allows for decoupled communication between different parts of the engine, making the system more modular and maintainable.
The Event Manager is responsible for:
- Queueing Events: It collects events generated during the game loop.
- Dispatching Events: It distributes events to registered handlers based on event types.
- Managing Handlers: It allows registering and unregistering of event handlers, including wildcard handlers that can listen to multiple event types.
Relevant files:
event_manager.hpp
event_manager.cpp
event.hpp
Event handlers are functions or methods that respond to specific events. Each handler is associated with one or more event types.
- Input Events: Handle user inputs such as keyboard and mouse actions.
- Collision Events: Triggered when entities collide within the game environment.
- Death Events: Occur when an entity (e.g., a player or enemy) is destroyed.
- Respawn Events: Manage the logic for respawning entities after death.
- Disconnect Events (Networked): Handle network disconnections of clients.
- Movement Events: Respond to movement commands, including normal movement and dashing.
- Replay Events: Used by the replay system to record and playback gameplay.
- Position Events (Networked): Synchronize entity positions across the network.
Note: The dash movement can be performed by pressing Left Shift
along with any direction key (W
, A
, S
, D
). Diagonal dashes are possible by pressing two direction keys simultaneously.
To integrate new events into the system:
- Define the Event Handler:
- Define what needs to happen when that event takes place.
- Update Event:
- If needed update the variant type of event to include different data types that can be passed as parameters.
- Register the Handler:
- Use the Event Manager to register the new handler for the event type.
- Modularity: Components can be developed and tested independently.
- Scalability: Easy to add new features without affecting existing code.
- Maintainability: Simplifies debugging and updates by isolating event logic.
The replay system enables recording and replaying of gameplay events, allowing players to capture their game sessions and replay them later. This feature is useful for debugging, analyzing player behavior, or creating gameplay videos.
The replay system operates by:
- Event Recording: Capturing all events during gameplay by registering a wildcard event handler using the
EventManager
. This ensures every event, regardless of type, is recorded. - State Saving: Storing the initial states of all entities at the start of recording. This includes positions, velocities, and any other relevant properties.
- Event Replaying: Re-injecting the recorded events into the event queue at their original timestamps relative to the replay start time.
- State Restoration: Before playback, entities are reset to their initial recorded states. After replaying, entities can be restored to their final states if needed.
- Header File:
replay_recorder.hpp
- Source File:
replay_recorder.cpp
The ReplayRecorder
class manages the recording and replaying process.
Important Methods:
-
ReplayRecorder(Timeline *timeline, std::vector<std::shared_ptr<EntityManager>> &entityManagers);
- Constructor that initializes the recorder with the main
Timeline
and a list ofEntityManager
instances.
- Constructor that initializes the recorder with the main
-
void on_event(const Event &event);
- Handles events related to starting/stopping recording and playback. Also captures events when recording is active.
-
void start_recording();
- Begins recording events and saves the initial state of all entities.
-
void stop_recording();
- Stops the recording process.
-
void record_event(const Event &event);
- Records an individual event by adjusting its timestamp relative to the recording start time.
-
void play_recording();
- Initiates playback by restoring entities to their initial states and scheduling recorded events.
-
void replay_complete();
- Called when replaying is finished. Restores entities to their states at the end of the recording session.
- Files:
event_manager.hpp
andevent_manager.cpp
Enhancements to the EventManager
support the replay functionality:
-
Wildcard Handlers:
- Allows the
ReplayRecorder
to listen to all events without needing to register for each specific event type.
- Allows the
-
Replay Mode:
- A
replay_only_mode
flag ensures that during replay, only replay events are processed. - Live events are ignored to prevent interference with the replayed events.
- A
-
Replay Event Counting:
- Keeps track of how many replay events are left to process.
- Once all replay events have been handled, the system exits replay mode and triggers a
replay_complete
event.
- Files:
timeline.hpp
andtimeline.cpp
The Timeline
class provides accurate timing for event scheduling:
-
Time Management:
- Ensures events are replayed at the correct times by using high-resolution timers.
- Handles pausing and unpausing, which is essential during recording and playback.
-
Synchronization:
- Both the recorder and the event manager use the same timeline to maintain synchronization between recording and replaying.
-
Start Recording:
- Press the
Enter
key to begin recording gameplay. - The system saves the current state of all entities and starts capturing events.
- Press the
-
Stop Recording:
- Press the
Enter
key again to stop recording. - Recording can be stopped at any time, and events up to that point are saved.
- Press the
-
Play Recording:
- Press the
Right Shift
key to start playback. - Entities are reset to their initial states, and recorded events are replayed in order.
- Press the
-
Entity State Serialization:
- Entity states are serialized and deserialized to capture their exact conditions during recording.
- This includes all necessary properties to fully restore an entity's state.
-
Thread Safety:
- Mutexes are used in classes like
Timeline
andEventManager
to ensure thread-safe operations, as event processing and timing can occur across multiple threads.
- Mutexes are used in classes like
-
Replay Constraints:
- The replay system is designed for single-session use. Recordings are not persisted to disk and will be lost when the application exits.
- Extending the system to save and load recordings from files would require additional implementation.
replay_recorder.hpp
andreplay_recorder.cpp
event_manager.hpp
andevent_manager.cpp
timeline.hpp
andtimeline.cpp
Integrate the V8 JavaScript engine to enable scripting within the game engine.
- Ensure the V8 engine is included in the
third_party
directory. - Include V8 headers and libraries in your project:
sudo apt update && sudo apt -y install build-essential libnode-dev
Scripts should be placed in the scripts
directory. They can access player data and raise events.
// scripts/hello_world.js
print('Hello World from V8 JavaScript Engine!');
// scripts/player.js
function runScript() {
print(player.position.x);
print(player.position.y);
const params = {
player: player,
};
raise_event("death", params);
}
runScript();
Here is an example of how to use the JavaScript scripting system in the game engine:
- Create a JavaScript script in the
scripts
directory. - Load and execute the script in your C++ code during the appropriate game events (e.g., start, update, end).
sm->addScript("player", "scripts/player.js");
Make sure there are proper bindings present between the V8 Javascript engine and C++ engine. Refer to the entity_bindings.cpp
and event_manager_bindings.cpp
files for more information on this.
By using JavaScript scripting with V8, you can easily modify game behavior without recompiling the entire project, making the development process more efficient and flexible.
The game engine handles various types of inputs to control entity actions and game events. This section describes the different input types and how they are processed.
-
Keyboard Inputs:
- Movement:
W
,A
,S
,D
keys for moving entities up, left, down, and right respectively. - Dash:
Left Shift
key combined with direction keys for dashing. - Jump:
Space
key for jumping. - Pause/Unpause:
ESC
key to toggle pause. - Speed Control:
P
key to speed up andM
key to slow down the game. - Recording Controls:
Enter
key to start/stop recording andRight Shift
key to play recording. - Script Execution:
H
key to run thehello_world
script andK
key to run theplayer
script.
- Movement:
-
Mouse Inputs:
- Currently, mouse inputs are not implemented but can be added for future features like aiming or clicking.
- Header File:
input_handler.hpp
- Source File:
input_handler.cpp
The InputHandler
class processes input events and updates entity states accordingly.
Important Methods:
-
InputHandler(Timeline *timeline);
- Constructor that initializes the handler with the main
Timeline
.
- Constructor that initializes the handler with the main
-
void on_event(const Event &event);
- Handles input events and updates entity states.
-
void handle_input(std::shared_ptr<Entity> player, size_t input_type, float acceleration_rate, float jump_force, Vector2 dash_vector);
- Applies input logic to the specified entity based on the input type and parameters.
- Header File:
input.hpp
- Source File:
input.cpp
These functions handle the actual input polling and event raising.
Important Functions:
-
void doInput(std::shared_ptr<Entity> entity, Timeline *globalTimeline, ScriptManager *sm, float accelerationRate, float dash_speed, float dash_duration);
- Polls for input states and raises corresponding events.
-
void raiseMovementEvent(const std::string &input_type, std::shared_ptr<Entity> entity, float rate, Timeline *timeline);
- Helper function to raise movement events based on input.
The input handling system integrates with various components:
- Event Management: Raises events based on input states, which are then processed by the
EventManager
. - Movement System: Updates entity positions and states based on input events.
- Scripting: Allows running scripts based on specific key presses.
input_handler.hpp
andinput_handler.cpp
input.hpp
andinput.cpp
The movement system in the game engine handles the movement of entities based on user input, predefined patterns, and physics calculations. This system ensures smooth and realistic movement for all game entities.
Movement is managed through the MovementHandler
class, which processes movement-related events and updates entity positions accordingly.
- Header File:
movement_handler.hpp
- Source File:
movement_handler.cpp
Important Methods:
-
MovementHandler(Timeline *timeline);
- Constructor that initializes the handler with the main
Timeline
.
- Constructor that initializes the handler with the main
-
void on_event(const Event &event);
- Handles movement events and updates entity positions.
-
void handle_movement(std::shared_ptr<Entity> entity);
- Applies movement logic to the specified entity, including velocity and acceleration updates.
The MovementPattern
class defines movement patterns for entities, allowing for complex and predefined movement behaviors.
- Header File:
movement_pattern.hpp
- Source File:
movement_pattern.cpp
Important Methods:
-
MovementPattern();
- Constructor that initializes an empty movement pattern.
-
void addStep(const MovementStep &step);
- Adds a movement step to the pattern.
-
void update(Entity &entity);
- Updates the entity's position based on the current movement step and elapsed time.
The MovementStep
class represents a single step in a movement pattern, including velocity, duration, and whether the step is a pause.
Important Methods:
MovementStep(const Vector2 &vel, float dur, bool pause = false);
- Constructor that initializes a movement step with the specified velocity, duration, and pause flag.
The movement system integrates with various components:
- Input Handling: Processes user input to control entity movement.
- Physics Calculations: Ensures realistic movement by applying physics-based calculations.
- Movement Patterns: Allows entities to follow predefined movement patterns for complex behaviors.
movement_handler.hpp
andmovement_handler.cpp
movement_pattern.hpp
andmovement_pattern.cpp
The game engine supports various games, each available in different branches. The games demonstrate the engine's capabilities and serve as examples for different genres and features.
Platformer games are marked with a 4
and support the event-driven architecture but do not include the replay system. These games showcase the engine's ability to handle complex movement patterns, collision detection, and platform-specific mechanics.
Non-platformer games, such as brick breaker, snake, and space invaders, are marked with a 5
. These games support the replay system, allowing players to record and replay their gameplay sessions. They demonstrate the engine's versatility in handling different game mechanics and genres.
Note: None of the games currently support scripting, but this feature is planned for future updates.
The primary focus of this project is to continuously improve the game engine by enhancing the current codebase and adding new features. Future developments may include:
-
Input Abstraction:
- Implementing a more flexible input system to support various input devices and configurations.
-
Enhanced Scripting Support:
- Expanding the scripting capabilities to allow for more complex game logic and interactions.
- Adding support for additional scripting languages beyond JavaScript.
-
Performance Optimization:
- Improving the engine's performance to handle larger and more complex game worlds.
-
Networking Enhancements:
- Enhancing the networking architecture to support more robust and scalable multiplayer experiences.
By focusing on these areas, the game engine aims to provide a powerful and flexible platform for creating a wide range of games.