aboutsummaryrefslogtreecommitdiff
path: root/design.tex
diff options
context:
space:
mode:
authorMax-001 <80035972+Max-001@users.noreply.github.com>2024-11-05 11:33:16 +0100
committerMax-001 <80035972+Max-001@users.noreply.github.com>2024-11-05 11:33:16 +0100
commit65fd4db4aead74c0bb28c8f099522536d5ce5f6c (patch)
tree7f9a7510b18e8eefbcd7129cf987c9f3f3b69e34 /design.tex
parent8fd4ebc33c2eba64e6be15c750ae8a821b72fd36 (diff)
parente696e7a6ac438c4af2e3d597c12950ad72b88520 (diff)
Merge branch 'master' of https://github.com/lonkaars/crepe-docs into max/time
Diffstat (limited to 'design.tex')
-rw-r--r--design.tex995
1 files changed, 971 insertions, 24 deletions
diff --git a/design.tex b/design.tex
index 08ea7cc..3f91893 100644
--- a/design.tex
+++ b/design.tex
@@ -1,5 +1,7 @@
\documentclass{projdoc}
+\usepackage{eso-pic}
+
\title{Software Design}
\begin{document}
@@ -15,7 +17,7 @@ similar to Jetpack Joyride.
The cr\^epe engine is designed to ease the transition for developers familiar with
Unity, ensuring minimal friction when switching platforms. Our aim is to preserve
-many of Unity’s core features while introducing a lightweight and open-source
+many of Unity's core features while introducing a lightweight and open-source
alternative, licensed under the MIT License.
The engine is primarily aimed at indie developers who have prior experience with
@@ -24,16 +26,550 @@ workflows.
\section{Overview}
-\subsection{Core}
+As described above, the cr\^epe game engine's goal is to offer a Unity-like
+experience tailored for developing 2D games similar to Jetpack Joyride. That is why
+Jetpack Joyride and Unity provided the main inputs for this game engine design.
+Firstly, a quick overview will be given of the Unity game engine, in particular the
+\gls{ecs}. Secondly, this Overview will quickly talk you through some of the most
+important parts of the game engine, and why these parts are needed to create the
+Jetpack Joyride game.
+
+\subsection{ECS}
+
+The Unity game engine is structured using the Entity Component System (\gls{ecs}) (as
+shown in \cref{fig:ecs-block-diagram}). The \gls{ecs} is made out of three main
+subsystems, namely entities, components and systems. Entities are just IDs. An entity
+is also called a GameObject in Unity and it is made out of one (or more) components.
+Components are the classes that hold the data. The components determine what kind of
+entity it is (e.g. an enemy, audio, and so on). Systems take care of the behavior of
+the entities. Systems mainly read and write the enity's components data. The
+\gls{ecs} clearly distinguishes the data (components) from the functionality
+(systems).
+
+\begin{figure}
+ \centering
+ \includegraphics[width=0.5\textwidth]{img/ECSBlockDiagram.png}
+ \caption{ECS design pattern}
+ Source: \autocite{img:ecs-block-diag}
+ \label{fig:ecs-block-diagram}
+\end{figure}
+
+The \gls{ecs} will also be used at the cr\^epe game engine. Everything (from the
+protagonist and bullets to the walls and enemies) in the cr\^epe game engine will be
+a GameObject (i.e.~entity). The game programmer must program his game by creating all
+kind of GameObjects and placing them in one (or multiple) scenes, just like Unity.
+
+\subsection{Jetpack Joyride}
+
+Firstly, some background information about Jetpack Joyride. Jetpack Joyride is a
+side-scrolling endless runner action video game created by Halfbrick Studios. The
+protagonist is called Barry Steakfries, who the player controls as he steals a
+bullet-powered jet pack from a top-secret laboratory
+\autocite{wikipedia:jetpack-joyride}. A screenshot from the game can be seen in
+\cref{fig:jetpack-joyride} (please be aware that the goal of this project is not to
+create an exact replica of Jetpack Joyride, it is only used as a source of
+inspiration).
+
+\begin{figure}
+ \centering
+ \includegraphics[width=0.5\textwidth]{img/JetpackJoyride.jpg}
+ \caption{Jetpack Joyride}
+ Source: \autocite{img:jetpack-joyride}
+ \label{fig:jetpack-joyride}
+\end{figure}
+
+The protagonist wears a jetpack with which he can float in the air. The player must
+avoid obstacles (such as lasers, missiles and zappers) by floating at the right
+height. The player can control the protagonist's jetpack, thereby also controlling
+the protagonist's height. The protagonist experiences gravity and other forces (like
+the force from his jetpack pushing him upwards). These forces should be easily
+programmable by the game programmer. That is why a physics system is needed in the
+cr\^epe game engine. Only very limited/easy physics are needed for Jetpack Joyride,
+that is why this is only supported by the cr\^epe game engine.
+
+The protagonist must avoid obstacles. That is why the cr\^epe game engine should also
+support a collision system. Again, only very limited/easy collision is needed for
+Jetpack Joyride, that is why only very limited/easy collision is supported by the
+cr\^epe game engine.
+
+The game must, of course, also be visible to and playable by the user. A rendering
+system will take care of rendering (displaying) the game and its GameObjects. An
+input system will take care of all the inputs (mouse and keyboard).
+
+Jetpack Joyride also offers audio. A system will take care of the audio in the
+cr\^epe game engine.
-\subsection{Patterns}
+Particles are very common in Jetpack Joyride, e.g. underneath the jetpack and behind
+the rockets. Particles will be supported by the particle system.
+
+The start of a scene is described in a scene. However, the game programmer might also
+want to write game logic code which is running during the game (e.g. to switch to a
+new scene or to perform a custom action at a collision). For these purposes, Unity
+uses scripts. These scripts will also be supported by the cr\^epe game engine.
+
+Finally, as an extra, replay functionality will be supported by the cr\^epe game
+engine. A dedicated replay system will be used to support replay.
+
+It turns out that a physics, collision, rendering, input, audio, particle, script,
+and replay system are needed to create the Jetpack Joyride game. These systems form
+the main part of the \gls{ecs}. The design of these eight systems in combination with
+\gls{ecs}, will be briefly discussed in the next parts of this design document.
\section{Design}
+%TODO: lag uitleggen uit diagram
+%TODO: process input
+\subsection{Game Loop}
+
+\subsubsection{Problem statement}
+
+A game loop is essential for maintaining a continuous flow of game actions, ensuring
+that updates to game logic, physics, and rendering occur in a synchronized manner.
+Without a game loop, the game would lack consistent timing, leading to unpredictable
+behavior. The game loop is primarily responsible for two main tasks:\noparbreak
+\begin{itemize}
+ \item Updating all systems in the correct order
+ \item Ensuring the game loop timer remains accurate
+\end{itemize}
+
+The game loop can be external where the user has the ability to update the systems
+themselves or an intergrated game loop which is managed by the gameloop. Both of
+these approaches have advantages and disadvantages when it comes to flexibility and
+reliability.
+\subsubsection{Architecture}
+
+This engine uses an integrated game loop for the following reasons:\noparbreak
+\begin{description}
+ \item[Simplifies development] The user only needs to call \codeinline{startGame()}.
+ \item[Ensures uniform system calls] Systems are always updated in the same order,
+ reducing the likelihood of overwrites and undefined system behavior.
+ \item[Provides a reliable timer update] The game loop timer is updated with each
+ cycle, minimizing timing issues.
+\end{description}
+
+As seen in \cref{fig:gameloop-flow}, the game loop consists of multiple
+steps:\noparbreak
+\begin{description}
+ \item[Update loop timer] The loop timer is updated, and the expected frame time is
+ calculated.
+ \item[Check events] Queued events are dispatched, and callback functions are
+ executed accordingly.
+ \item[Process input] The input system is called, and user inputs are processed.
+ \item[Fixed update] A fixed loop for timing-sensitive systems, such as physics.
+ \item[Update] A per-frame update for all necessary frame-dependent changes.
+ \item[Render] The render system is called to display the current frame.
+\end{description}
+
+The game loop continues to call the fixed update function as long as there is
+sufficient time. Delta time, calculated as the time between the last frame’s start
+and the current frame, is used to measure the duration of each frame. This value is
+converted into a time-based unit, enabling systems to create frame rate-independent
+behavior.
+
+The fixed update has a specific order to update seperate systems as seen in
+\cref{fig:fixed-update}. The scripts update is called first so a gamedevelop can use
+the onupdate() in a script to move objects. after this movement the PhysicsSystem
+will move objects as well. after all movement is done the collision system will use
+the velocity and the current position to determine if something collided. Then the
+collisions system will call all collision handelers. After all collisions are
+handeled the particle system will update.
+
+This order can not be changed because the systems work in a specific way. Collisions
+looks back in the past and the emitter can be moved so the particle update must be
+the last in the fixed update.
+Rendering and animations are handled on a per-frame basis. A delay, combined with
+delta time calculation, ensures consistent visuals even at varying frame rates.
+\Cref{fig:gameloop-class} shows a \codeinline{TimerClass} using a singleton design
+pattern, allowing access to \codeinline{deltaTime} throughout the system. The game
+loop updates the timing and delta time in this class to keep it accurate.
+
+The two main functions of the game loop are \codeinline{setup()} and
+\codeinline{loop()}. \codeinline{setup()} handles all startup procedures and is
+called only once when the game begins. The \codeinline{loop()} function repeats as
+long as the game is running.
+
+The code example below shows how to start the game engine/game:\noparbreak
+\begin{blockcode}
+Gameloop loop;
+loop.start();
+\end{blockcode}
+
+% FIXME: not a technical reference
+This code calls both \codeinline{setup()} and \codeinline{loop()}, starting the game
+loop timer. The current frame’s delta time can be accessed using
+\codeinline{LoopTimer::getInstance().getDeltaTime()}, which returns the expected
+frame time.
+
+\begin{figure}
+ \centering
+ \includepumldiag{img/gameloop-flow.puml}
+ \caption{Game loop flowchart diagram}
+ \label{fig:gameloop-flow}
+\end{figure}
+
+\begin{figure}
+ \centering
+ \fitimg{\includegraphics[scale=0.7]{img/Fixed_update.png}}
+ \caption{Fixed update}
+ \label{fig:fixed-update}
+\end{figure}
+
+\begin{figure}
+ \centering
+ \includepumldiag{img/gameloop-class.puml}
+ \caption{Gameloop flowchart diagram}
+ \label{fig:gameloop-class}
+\end{figure}
+\subsection{Texture}
+
+The textures in cr\^epe game engine are represented by the \codeinline{Texture}
+class. It is implemented as an \gls{facade} around the \gls{sdl} library.
+
+\subsubsection{Architecture}
+
+\Cref{fig:class-texture} shows a class diagram of the texture \gls{facade}. It
+contains the following classes:\noparbreak
+\begin{description}
+ \item[SDLContext] This is a facade around the \codeinline{SDL2} library which is
+ used around different parts of the engine, and is therefore implemented as a
+ singleton. This class is friends with \codeinline{Texture},
+ \codeinline{LoopManager}, \codeinline{RenderSystem} and
+ \codeinline{AnimatorSystem}.
+ \item[Texture] This is a wrapper around the \codeinline{SDL_Texture} class, and
+ uses cr\^epe's \codeinline{Asset} class to load an Texture instead.
+\end{description}
+
+\begin{figure}
+ \centering
+ % TODO: export as vector format instead
+ \includegraphics[width=\textwidth]{img/texture.png}
+ \caption{User texture class diagram}
+ \label{fig:class-texture}
+\end{figure}
+%TODO: lijntjes verbeteren in de diagrammen
+%TODO: verwijzen naar assets
+\subsection{AssetManager}
+
+The AssetManager is a \gls{api} class that the user can use to make a
+\codeinline{Asset} available from different scenes.
+
+\subsubsection{Architecture}
+
+\Cref{fig:class-assetmanager} shows a class diagram of the AssetManager. It contains
+the following classes:\noparbreak
+\begin{description}
+ \item[AssetManager] is a Singleton class, meaning only one instance of this class
+ exists throughout the application. This ensures a single point of access and
+ control over asset management, simplifying resource handling and avoiding
+ duplicated assets in memory.
+\end{description}
+
+\begin{figure}
+ \centering
+ % TODO: export as vector format instead
+ \includegraphics[width=0.5\textwidth]{img/AssesManager.png}
+ \caption{User AssetManager class diagram}
+ \label{fig:class-assetmanager}
+\end{figure}
+%TODO geen 2 actien
+%TODO: is er geen transform mag wel
+%TODO: nog kijken wel of geen spirte keuze in het diagram
+%TODO: in het diagram kijken of er een particle emiter
\subsection{Rendering}
+Every game engine has an rendering structure to present all the different enities and
+components on the screen.
+
+\subsubsection{Architecture}
+
+% TODO: anyone read this?, fix disgram lines, add particle emitter and camera and reference other classes better
+\Cref{fig:class-rendering} shows a class diagram of the RenderSystem. It contains the
+following classes:\noparbreak
+\begin{itemize}
+ \item The system architecture is centered around rendering and component
+ management, with the following key components:\noparbreak
+ \begin{description}
+ \item[\codeinline{System}] An interface class containing the virtual
+ \codeinline{update()} function.
+ \item[\codeinline{RenderSystem}] A derived class of \codeinline{System}
+ responsible for rendering operations.
+ \end{description}
+ \item The \codeinline{System::get_instance()} function provides a static, singleton
+ instance for managing system-wide operations.
+ \item The \codeinline{RenderSystem} class implements various rendering
+ functions:\noparbreak
+ \begin{description}
+ \item[\codeinline{sort_layers()}] Organizes the rendering layers.
+ \item[\codeinline{clear_screen()}] Clears the screen prior to rendering.
+ \item[\codeinline{update_sprites()}] Updates sprite positions and states.
+ \item[\codeinline{update_camera()}] Manages the camera view.
+ \item[\codeinline{present_screen()}] Presents the final rendered image to the
+ screen.
+ \end{description}
+ \item The \codeinline{SdlContext} class, another singleton, manages the \gls{sdl}
+ library
+ \item Components are organized as follows:\noparbreak
+ \begin{itemize}
+ \item The \codeinline{Component} base class allows for generic handling of
+ components.
+ \item Derived component classes include:\noparbreak
+ \begin{description}
+ \item[\codeinline{Sprite}] Represents visual elements with attributes like
+ \codeinline{sprite}, \codeinline{color}, \codeinline{flip},
+ \codeinline{sortingLayer}, and \codeinline{orderInLayer}.
+ \item[\codeinline{Transform}] Manages positional attributes, including
+ \codeinline{position}, \codeinline{rotation}, and \codeinline{scale}.
+ \end{description}
+ \end{itemize}
+ \item Both \codeinline{Sprite} and \codeinline{Transform} components provide a
+ \codeinline{get_instances_max()} function to retrieve the maximum instance count.
+\end{itemize}
+
+\begin{figure}
+ \centering
+ % TODO: export as vector format instead
+ \includegraphics[width=\textwidth]{img/Rendering.png}
+ \caption{System Rendering class diagram}
+ \label{fig:class-rendering}
+\end{figure}
+
+\subsubsection{System}
+
+\begin{figure}
+ \centering
+ % TODO: export as vector format instead
+ \includegraphics[width=\textwidth]{img/flowchart_rendering.png}
+ \caption{System Rendering flowchart }
+ \label{fig:class-renderingflowchart}
+\end{figure}
+
+
+%TODO: blokken voor callback in sequence
+\subsection{Event system}
+
+\subsubsection{Problem Statement}
+
+The game engine utilizes the \gls{ecs} architecture, where components store data, and
+systems process that data to apply changes. Each system is responsible for managing a
+specific domain, such as physics in the physics system and rendering in the rendering
+system. To facilitate communication between systems without introducing direct
+dependencies, a method of inter-system communication is required to maintain loose
+coupling. Additionally, a mechanism that allows one object's trigger to manipulate
+adn affect multiple other objects is beneficial for game developers, providing
+greater flexibility in designing interactions within the game.
+
+\subsubsection{Architecture}
+
+The sollution to connect the various systems and BehaviorScripts together without
+inducing high coupling is an event system that facilitates communication between
+systems and BehaviorScripts using various types of events. The event system includes
+several pre-defined events, all derived from a parent Event class, capable of
+handling user input and application-level events, such as window resizing.
+Furthermore, a specific event is designated for the collision handler within the
+physics system, which can be triggered when two objects collide. The event system
+also allows developers to create custom events, such as "onPlayerDeath," and assign
+callback functions that execute when the event is triggered.
+
+\begin{figure}
+ \centering
+ % TODO: export as vector format instead
+ \includegraphics[width=\linewidth]{img/event-uml.drawio.png}
+ \caption{Event system class diagram}
+ \label{fig:event-uml}
+\end{figure}
+
+The event system as seen in \cref{fig:event-uml} includes several parts such
+as:\noparbreak
+\begin{description}
+ \item[eventManager] The manager has the functions to
+ subscribe/trigger/queue/dispatch events. It also stores all callback functions
+ corresponding to specific event. The manager is a singleton and can therefor only
+ exist once so all events are stored in one place.
+ \item[IEventWrapper] This is a EventWrapper \emph{interface} which is used to store
+ all the different templated eventshandlers in one map in the event manager. this
+ wrapper contains the logic to convert the parent class \emph{event} to the
+ correct subclasses. It also contains a variable onSuccessDestroy which can be set
+ to destroy the callback call onces completed. This can be used to make a one time
+ only event.
+ \item[Event] This is the parent class where all specific event classes are derived
+ from. Each event contains a---
+ \begin{itemize}
+ % TODO: the design document is not a technical reference, so implementation
+ % details shouldn't even be in here. Also, are getter functions used to set
+ % things nowadays?
+ \item \emph{\codeinline{static std::uint32_t getStaticEventType()}} to set type
+ during compiling.
+ \item \emph{\codeinline{virtual std::uint32_t getEventType() const override }}
+ function to manage the type conversion during runtime.
+ \end{itemize}
+ Other functions can be freely added when creating a custom function. When an
+ event is triggered a specific derived class must be used to indicate which event
+ is triggered. A reference to this event is then transfered to all callback
+ functions subscribed.
+ \item[IKeyListener] This is an interface designed to match the API. By deriving
+ from this interface the game developer can override the key event functions to
+ add their own functionality. The derived class will immediately be subscribed to
+ the EventManager and will Unsubscibe when deleted.
+ \item[IMouseListener] Like the IKeyListener this is also an interface to interact
+ with the eventManager. The derived classes from this interface handle various
+ mouse related events. These events are triggered when the mouse is
+ clicked/pressed/released or moved.
+\end{description}
+
+The gamedeveloper can interact with the EventManager using the following
+functions:\noparbreak
+\begin{description}
+ \item[Subscribe] This subscribes a function pointer or lambda function to a given
+ event. The function can be subscribed either to all event triggers or a specifc
+ ID.
+ \item[Trigger] This triggers a given event and all callbacks correlating to this
+ event are executed immediately.
+ \item[Queue event] This queues an event to be executed at a fixed time during the
+ gameloop.
+ \item[Unsubscibe] This removes the callback function from the event and it will no
+ longer be executed.
+\end{description}
+
+\Cref{fig:event-seq} shows that when a specific function is triggered or dispatched
+using the callback(eventHandler) is executed.
+
+\begin{figure}
+ \centering
+ \includepumldiag{img/event-sequence.puml}
+ \caption{Sequence diagram for event calling}
+ \label{fig:event-seq}
+\end{figure}
+
\subsection{Physics}
+The Physics in the game engine are handled by the \emph{PhysicsSystem}. The physics
+calculate the velocity and moves each component with a Rigidbody. This game engine
+does not use any third party tools for calculating physics.
+
+\subsubsection{Architecture}
+
+The \emph{PhysicsSystem} is a system and therefor a singleton in this engine. Besides
+the \codeinline{getinstance()} and \codeinline{update()} function it does not include
+more functions. The \emph{PhysicsSystem} uses a couple components:\noparbreak
+\begin {description}
+ \item[Transform] The \emph{PhysicsSystem} is the only system to change the values
+ in the transform. A user is able to change these values through a script.
+ \item[Rigidbody] The \emph{PhysicsSystem} uses this to know what the next velocity
+ should be and if it can move. What the physics system includes is shown in
+ \cref{fig:physics-system}
+\end{description}
+
+\begin{figure}
+ \centering
+ \fitimg{\includegraphics[scale=0.7]{img/Physics_system.png}}
+ \caption{Physics system}
+ \label{fig:physics-system}
+\end{figure}
+
+\subsubsection{Design}
+
+The physics system is not a complex system. It works in three steps. It will request
+all physicsbodies. If there are no physicsbodies linked to a gameobject than that
+object does not have physics. It will read the values within the rigidbody and update
+the velocities. after this update it will move all objects. This is shown in the
+\cref{fig:physics-system-flowchart}.
+
+\begin{figure}
+ \centering
+ \fitimg{\includegraphics[scale=0.7]{img/Physics_system_flowchart.png}}
+ \caption{Physics system flowchart}
+ \label{fig:physics-system-flowchart}
+\end{figure}
+%TODO: andere lijn in het diagram
+\subsection{Collisions}
+
+The Collisions in the game engine are handled by the \emph{CollisionSystem}. This
+system check if a collider collided with another collider.
+
+\subsubsection{Architecture}
+
+The \emph{CollisionSystem} is a system and therefor a singleton in this engine.
+Besides the \codeinline{getinstance()} and \codeinline{update()} function it does not
+include more functions. The \emph{CollisionSystem} uses a couple
+components:\noparbreak
+\begin {description}
+ \item[Transform] The \emph{CollisionSystem} Read the location and rotation value to
+ know where all colliders are located.
+ \item[Rigidbody] The \emph{CollisionSystem} uses this to know if collision needs to
+ be check, how collisions should be checked and how they are handled.
+ \item[BoxCollider] The box collider is a square with a width and height used to
+ check for collisions.
+ \item[CircleCollider] The circle collider is a circle with a radius used to check
+ for collisions.
+\end{description}
+This is shown in \cref{fig:collision-system}.
+
+\begin{figure}
+ \centering
+ \fitimg{\includegraphics[scale=0.7]{img/Collision_system.png}}
+ \caption{Collision system}
+ \label{fig:collision-system}
+\end{figure}
+
+\subsubsection{Design}
+
+The collision system is complex compared to other systems. This is shown in
+\cref{fig:collision-system-flowchart}. Because multiple colliders of different types
+can be added to one gameobject this system is complex. For this game it is not needed
+to check for more than one collider per gameobject but this functionality is added to
+the design. If the engine needs to be able to do this it can be added without
+changing the design. The same is for child and parent gameobjects.
+
+\begin{figure}
+ \centering
+ \fitimg{\includegraphics[scale=0.7]{img/Collision_system_flowchart.png}}
+ \caption{Collision system flowchart}
+ \label{fig:collision-system-flowchart}
+\end{figure}
+%TODO: diagram particle
+\subsection{Particles}
+
+The Particles in the game engine are handled by the \emph{ParticlesSystem}. This
+system uses particleEmitters to create and move particles.
+
+\subsubsection{Architecture}
+
+The \emph{ParticlesSystem} is a system and therefor a singleton in this engine.
+Besides the \codeinline{getinstance()} and \codeinline{update()} function it does not
+include more functions. The \emph{ParticlesSystem} uses a couple
+components:\noparbreak
+\begin {description}
+ \item[Transform] The \emph{ParticlesSystem} Read the location and rotation value to
+ know where the emitter is located
+ \item[ParticleEmitter] The \emph{ParticlesSystem} uses the particle emitter to know
+ what the values and configuration of the emitter are.
+ \item[Particle] info for each particle.
+\end{description}
+
+This is shown in \cref{fig:particle-system}.
+
+\begin{figure}
+ \centering
+ \fitimg{\includegraphics[scale=0.7]{img/Particle_system.png}}
+ \caption{Particle system}
+ \label{fig:particle-system}
+\end{figure}
+
+\subsubsection{Design}
+
+The particle system is a bit strange because it uses a component which has objects
+(particles). How this system works is shown in the
+\cref{fig:particle-system-flowchart}. Because each particle needs to be create
+pooling is used to increase efficientcy in the calulcation time of all the particles.
+Pooling decreases the calculation time by a factor of 10.
+
+\begin{figure}
+ \centering
+ \fitimg{\includegraphics[scale=0.7]{img/Particle_system_flowchart.png}}
+ \caption{Particle system flowchart}
+ \label{fig:particle-system-flowchart}
+\end{figure}
+
\subsection{Scripting}
The scripting interface was designed around a `target' \gls{api} (described by
@@ -69,6 +605,7 @@ architecture:\noparbreak
\end{itemize}
\subsubsection{Architecture}
+\label{sec:scripts:architecture}
The restrictions detailed at the start of this section are mitigated as
follows:\noparbreak
@@ -91,10 +628,11 @@ follows:\noparbreak
contains the following classes:\noparbreak
\begin{description}
\item[Script] This is the script \emph{interface}, and is used by the game
- programmer to create derived script classes. All methods in this class are
- declared virtual and have an empty implementation.
+ programmer to create derived script classes. All virtual methods in this class
+ have an empty implementation by default, and are optionally implemented by the
+ game programmer.
- This class' methods are protected by default, and a friend relation to
+ This class' virtual methods are protected by default, and a friend relation to
\codeinline{ScriptSystem} is used to ensure only \codeinline{ScriptSystem} is
able to call these methods.
@@ -103,6 +641,10 @@ contains the following classes:\noparbreak
function returns a reference to the \codeinline{BehaviorScript} instance it was
called on so it can be chained after the call to
\codeinline{GameObject::add_component}.
+
+ \codeinline{Script} also has a reference to its parent
+ \codeinline{BehaviorScript} instance so components can easily be retrieved using
+ the component manager.
\item[BehaviorScript]
This is the script \emph{component}, and is given as the template parameter to
\codeinline{GameObject::add_component}.
@@ -110,7 +652,8 @@ contains the following classes:\noparbreak
This class also uses a friend relation to \codeinline{ScriptSystem} to restrict
access to its private reference member \codeinline{script}.
\item[ScriptSystem] This is the system class that runs the methods implemented in
- the derivative instances of \codeinline{Script}.
+ the derivative instances of \codeinline{Script}. Described further in
+ \cref{sec:scripts:sytem}.
\end{description}
\begin{figure}
@@ -120,11 +663,37 @@ contains the following classes:\noparbreak
\label{fig:class-scripts}
\end{figure}
+\subsubsection{System}
+\label{sec:scripts:sytem}
+
+Because most of the complexity in the scripting interface comes from the containers
+described in \cref{sec:scripts:architecture}, the script system class itself is
+relatively simple. The script system provides a method
+\codeinline{ScriptSystem::update} that calls all active script's update functions.
+
+Because of the limitation that types cannot be passed as parameters in C++, the
+user-defined script class (derived from \codeinline{Script}) can not directly be
+instantiated when adding the component to the component manager. To work around this
+limitation, the method \codeinline{BehaviorScript::set_script} was created. This
+results in the possibility that an instance of \codeinline{BehaviorScript} does not
+reference an instance of \codeinline{Script}. In addition to the non-active script
+components, the script system skips over these `invalid' instances. This is
+illustrated in \cref{fig:activity-scripts}.
+
+\begin{figure}
+ \centering
+ \includepumldiag{img/activity-scripts.puml}
+ \caption{Script system update method}
+ \label{fig:activity-scripts}
+\end{figure}
+
+A \gls{poc} for the script system is shown in \cref{poc:scripts}.
+
\subsection{Audio}
Since writing a custom real-time audio mixing engine is outside the scope of this
project\mref and C++ does not provide a built-in cross-platform audio \gls{api}, the
-audio system inside the cr\^epe engine is implemented as a fa\c{c}ade around an
+audio system inside the cr\^epe engine is implemented as a \gls{facade} around an
existing audio library.
\subsubsection{Libraries}
@@ -135,15 +704,7 @@ searching for libraries (search terms: `dynamic/adaptive audio', `real-time audi
`audio library', `game audio engine'), several libraries were found. These libraries
were checked against the audio engine requirements \autocite{crepe:requirements} and
then tested by writing the same benchmark-style \gls{poc} using the remaining
-qualifying libraries:\noparbreak
-\begin{enumerate}
- \item Load a background track (Ogg Vorbis)
- \item Load three short samples (WAV)
- \item Start the background track
- \item Play each sample sequentially while pausing and resuming the background track
- \item Play all samples simultaniously
- \item Stop all audio and exit
-\end{enumerate}
+qualifying libraries. These \glspl{poc} are detailed in \cref{poc:audio}.
Of these libraries the following were determined to be unsuitable for use in this
project:\noparbreak
@@ -159,12 +720,12 @@ project:\noparbreak
The only library that remained after these tests is SoLoud \autocite{lib:soloud}. It
is Zlib/LibPng licensed and provides a high-level object-oriented C++ \gls{api}.
-\Cref{sec:audio:architecture} describes the fa\c{c}ade written for this library.
+\Cref{sec:audio:architecture} describes the \gls{facade} written for this library.
\subsubsection{Architecture}
\label{sec:audio:architecture}
-\Cref{fig:class-audio-facade} shows a class diagram of the audio fa\c{c}ade. It
+\Cref{fig:class-audio-facade} shows a class diagram of the audio \gls{facade}. It
contains the following classes:
\begin{description}
\item[SoundContext] This is a wrapper around the \codeinline{SoLoud::soloud}
@@ -180,17 +741,403 @@ contains the following classes:
\begin{figure}
\centering
\includepumldiag{img/facade-audio.puml}
- \caption{Audio fa\c{c}ade class diagram}
+ \caption{Audio \glsfmtshort{facade} class diagram}
\label{fig:class-audio-facade}
\end{figure}
-\subsection{Input}
+A \gls{poc} for the final Audio \gls{facade} is also showcased in \cref{poc:audio}.
+
+% \subsection{Save manager}
+%
+% The save manager \gls{api} is designed to give the game programmer an easy to use
+% interface for retrieving and storing game-specific data (\cref{req:savemgr}).
+%
+% Because the engine validation app only stores statistics and highscores, the save
+% manager is not required to support loading different save files
+% (\cref{req:savemgr:multi-file}), nor storing complicated data types
+% (\cref{req:savemgr:types-custom}). The save manager only supports storing simple
+% types (\cref{req:savemgr:types-scalar,req:savemgr:types-string}).
+%
+% In order to reduce complexity for the game programmer further, the following
+% requirements were also set:\noparbreak
+%
+% \begin{itemize}
+% \item Prevent data loss in the case of crashes (\cref{req:savemgr:journalling})
+% \item Handle opening/closing/flushing of the underlying file automatically
+% (\cref{req:savemgr:file-manage})
+% \item Save file variables are uniquely identified (\cref{req:savemgr:var-key})
+% \end{itemize}
+%
+% \subsubsection{Architecture}
+% \label{sec:savemgr:architecture}
+%
+% \begin{figure}
+% \centering
+% \includepumldiag{img/class-savemgr.puml}
+% \caption{Save manager class diagram}
+% \label{fig:class-savemgr}
+% \end{figure}
+%
+% In order to realize \cref{req:savemgr:journalling,req:savemgr:var-key}, a third-party
+% key-value database library is used.
+
+\subsection{Global configuration interface}
+
+Because the game programmer only has access to interfaces within the public \gls{api}
+namespace (\codeinline{crepe::api}), they would not be able to configure aspects of
+engine-internal components. To work around this access restriction, a global
+interface was made that stores arbitrary data, which can be accessed both internally
+and via the public \gls{api}.
+
+\subsubsection{Architecture}
+\label{sec:config:architecture}
+
+The global configuration interface consists of a single singleton class that can be
+accessed globally (see \cref{fig:class-config}). This class holds several anonymous
+structs, which are used to organize options per system or engine component.
+
+\begin{figure}
+ \centering
+ \includepumldiag{img/class-config.puml}
+ \caption{Global configuration interface class diagram}
+ \label{fig:class-config}
+\end{figure}
+
+% \subsection{Input}
+
+% \subsection{Physics}
+
+\appendix
+
+\section{Proof-of-concepts}
+
+The full (documented) source code of these \glspl{poc} is available on GitHub
+\autocite{crepe:code-repo}.
+
+\subsection{Script system}
+\label{poc:scripts}
+
+The script system \gls{poc} \autocite[script example]{crepe:code-repo} consists of
+the following:\noparbreak
+\begin{itemize}
+ \item A user-defined class (\codeinline{MyScript}) derived from
+ \codeinline{Script}, which only implements the \codeinline{update()} function.
+ \item A main function that---
+ \begin{itemize}
+ \item Creates a game object with \codeinline{Transform} and
+ \codeinline{BehaviorScript} components.
+ \item A call to \codeinline{ScriptSystem::update}, which results in
+ \codeinline{MyScript::update} being called.
+ \end{itemize}
+\end{itemize}
+
+Running the \gls{poc} results in the output shown in \cref{fig:poc-output-scripts},
+demonstrating that the system works as intended.
+
+\begin{figure}
+ \centering
+ \fitimg{\includegraphics[scale=0.7]{img/poc-output-scripts.png}}
+ \caption{Script system \glsfmtshort{poc} output}
+ \label{fig:poc-output-scripts}
+\end{figure}
+
+\subsection{Logging utilities}
+\label{poc:log}
+
+A small \gls{poc} was written to test the engine's logging functions \autocite[log
+example]{crepe:code-repo}. The following calls are used in this example:
+
+\begin{blockcode}
+dbg_trace(); // the dbg_* macros automatically show
+dbg_logf("test printf parameters: %d", 3); // where the message is coming from
+logf(LogLevel::INFO, "info message");
+logf(LogLevel::WARNING, "warning");
+logf(LogLevel::ERROR, "error");
+\end{blockcode}
+
+The output of this test is shown in \cref{fig:poc-log}.
+
+\begin{figure}
+ \centering
+ \includegraphics[scale=0.7]{img/poc-log.png}
+ \caption{Logging function outputs}
+ \label{fig:poc-log}
+\end{figure}
+
+\subsection{Audio}
+\label{poc:audio}
+
+A test that consists of the following steps was written for each audio library
+mentioned in \cref{sec:audio:libs}:\noparbreak
+\begin{enumerate}
+ \item Load a background track (Ogg Vorbis)
+ \item Load three short samples (WAV)
+ \item Start the background track
+ \item Play each sample sequentially while pausing and resuming the background track
+ \item Play all samples simultaniously
+ \item Stop all audio and exit
+\end{enumerate}
+
+The repository \autocite{crepe:code-repo} contains two finished \glspl{poc} under the
+\codeinline{mwe/audio/} subdirectory for miniaudio and SoLoud. The SoLoud \gls{poc}
+was later converted to a full audio \gls{facade}, which is currently part of the
+cr\^epe engine. The \gls{poc} using the audio \gls{facade} is available from the same
+repository, under the \codeinline{src/example/audio_internal.cpp} file.
+
+\subsection{Gameloop}
+\label{poc:gameloop}
+
+\begin{figure}
+ \centering
+ \includegraphics[scale=0.4]{img/gameloop-squares.png}
+ \caption{Gameloop running example}
+ \label{fig:poc-gameloop-squares}
+\end{figure}
+
+This \gls{poc} shows the functionality of the different loops and the approaches to
+decouple the updates from the framerate. \Cref{fig:poc-gameloop-squares} shows two
+squares that keep moving at the same speed no matter how high or low the framerate
+gets. This is done by updating the squares in the per frame update loop with the
+delta time generated by the timer. By multipling deltaTime to the velocity of the
+square the velocity is converted to a time instead of frames. this can be seen in de
+following code example\noparbreak
+\begin{blockcode}
+float delta = timer.getDeltaTime(); // get delta time of current frame
+ if (obj->direction == 1) {
+ obj->setX(obj->getX() + 50 * delta); // multiply velocity by delta time for consistent behavior.
+ } else {
+ obj->setX(obj->getX() - 50 * delta);
+ }
+\end{blockcode}
+
+The second functionality this \gls{poc} shows is how the fixed loop is executed.
+Based on the framerate of the gameloop the fixed loop is executed multiple times per
+frame or skips a certain amount of frames as seen in
+\cref{fig:poc-gameloop-500fps,fig:poc-gameloop-10fps}.
+
+\begin{figure}
+ \centering
+ \begin{subfigure}[b]{0.45\textwidth}
+ \centering
+ \includegraphics[scale=0.5]{img/gameloop-console.png}
+ \caption{Fixed loop (50Hz) with 500 fps}
+ \label{fig:poc-gameloop-500fps}
+ \end{subfigure}
+ \hfill
+ \begin{subfigure}[b]{0.45\textwidth}
+ \centering
+ \includegraphics[scale=0.5]{img/gameloop-console-10.png}
+ \caption{Fixed loop (50Hz) with 10 fps}
+ \label{fig:poc-gameloop-10fps}
+ \end{subfigure}
+ \caption{Comparison of game loop performance at different frame rates}
+ \label{fig:poc-gameloop-console-comparison}
+\end{figure}
+
+\subsection{Events/input system}
+\label{poc:event}
+
+\begin{figure}
+ \centering
+ \includegraphics[scale=0.4]{img/poc-button.png}
+ \caption{Gameloop running example}
+ \label{fig:poc-event}
+\end{figure}
+
+The event/input system \gls{poc} shows the different ways the event system can be
+used. It also serves as a basis on which the input system is developed. the first two
+features show the workings of the two interfaces which are created so the game
+developer doesnt need to subscribe each function individualy. The gamedeveloper only
+needs to create a derived class from the parent IKeyListener or IMouseListener and
+override the functions with their own implementation as shown below.\noparbreak
+\begin{description}
+ \item[KeyListener] \codeinline{KeyListenerTest keyListener(testListenerId);} this
+ line creates a concrete KeyListener that has been created as follows.
+ \begin{blockcode}
+ class KeyListenerTest : public IKeyListener {
+ public:
+ KeyListenerTest(int listenerId);
+ ~KeyListenerTest();
+
+ void onKeyPressed(const KeyPressedEvent& event) override;
+ void onKeyReleased(const KeyReleasedEvent& event) override;
+ };
+ \end{blockcode}
+ The functions have been overridden to add custom functionality, which is called
+ when a key is pressed or released.
+ \item[MouseListener] Like the KeyListener, \codeinline{MouseListenerTest
+ mouseListener(testListenerId);} is a concrete derived class of IMouseListener,
+ created as follows:\noparbreak
+ \begin{blockcode}
+ class MouseListenerTest : public IMouseListener {
+ public:
+ MouseListenerTest(int listenerId);
+ ~MouseListenerTest();
+
+ void onMouseClicked(const MouseClickEvent& event) override;
+ void onMousePressed(const MousePressedEvent& event) override;
+ void onMouseReleased(const MouseReleasedEvent& event) override;
+ void onMouseMoved(const MouseMovedEvent& event) override;
+ };
+ \end{blockcode}
+\end{description}
+
+Secondly the \gls{poc} shows how the gamedeveloper can create custom events by
+creating a derived class from the parent class Event. This custom event can then be
+used to subscribe callbacks to which can use the data within the derived class. Below
+is an example of such a custom event.\noparbreak
+\begin{blockcode}
+class PlayerDamagedEvent : public Event {
+public:
+ PlayerDamagedEvent(int damage, int playerID)
+ : Event("PlayerDamaged"), damage(damage), playerID(playerID) {}
+
+ REGISTER_EVENT_TYPE(PlayerDamagedEvent);
+
+ int getDamage() const { return damage; }
+ int getPlayerID() const { return playerID; }
+
+private:
+ int damage;
+ int playerID;
+};
+\end{blockcode}
+
+An example of a callback function using the custom PlayerDamagedEvent:\noparbreak
+\begin{blockcode}
+void onPlayerDamaged(const PlayerDamagedEvent & e) {
+ std::cout << "Player " << e.getPlayerID() << " took " << e.getDamage()
+ << " damage." << std::endl;
+}
+\end{blockcode}
+
+Lastly the \gls{poc} shows how a button can be handled using a input sytem. The blue
+square in \cref{poc:event} represents a button component which will call its
+onClick() function when the mouse click event triggers within its boundary. The
+result can be seen in \cref{fig:poc-event-button}.
+
+\begin{figure}
+ \centering
+ \includegraphics[scale=1]{img/poc-event-button.png}
+ \caption{gameloop running example}
+ \label{fig:poc-event-button}
+\end{figure}
+
+\subsection{Camera}
+\label{poc:camera}
+
+The camera \gls{poc} \autocite[camera example]{crepe:code-repo} consists of the
+following:\noparbreak
+\begin{itemize}
+ \item An \codeinline{on_key_pressed} function, which listens for key presses and
+ adjusts camera position and zoom based on key inputs.
+ \item A user-defined script class (\codeinline{MyCameraScript}) derived from
+ \codeinline{Script}, implementing only the \codeinline{update()} function. To
+ update the camera movements and zoom.
+ \item A main function that---
+ \begin{itemize}
+ \item Subscribes the \codeinline{on_key_pressed} function to handle
+ \codeinline{KeyPressedEvent} events.
+ \item Creates a \codeinline{GameObject} for the camera and adds
+ \codeinline{Camera} and \codeinline{BehaviorScript} components, with
+ \codeinline{MyCameraScript} attached to manage the camera's transformation.
+ \item Instantiates a background \codeinline{GameObject} with
+ \codeinline{Transform} and \codeinline{Sprite} components, loading an
+ external texture as its background.
+ \end{itemize}
+\end{itemize}
+
+Running this \gls{poc} allows for controlled camera movement and zoom in response to
+key inputs. The \codeinline{MyCameraScript::update} function ensures that these
+transformations are applied each frame, as demonstrated by the output in
+\cref{fig:poc-output-camera}.
+
+\begin{figure}
+ \centering
+ \fitimg{\includegraphics[scale=0.7]{img/poc-camera.pdf}}
+ \caption{Camera \glsfmtshort{poc} output}
+ \label{fig:poc-output-camera}
+\end{figure}
-\subsection{Physics}
-\section{Tools}
+\subsection{Particles}
+\label{poc:particle}
+
+The particles \gls{poc} \autocite[particles example]{crepe:code-repo} consists of the
+one particle emitter that is shown in \cref{fig:poc-particles}. This particle
+emmitter is controlled by the particle system using ECS. I can generate particles in
+a specified direction and velocity. With min and max values the system will determine
+what the exact value of eacht particle will be.
+
+This \gls{poc} showed that pooling is a must, even with lower amounts of particles.
+The calculation time of 100 particles was about 0.09\,ms and with pooling 0.009\,ms.
+Decreasing calculation times of physics is important because everything needs to eb
+calculated in 16.6\,ms (60\,hz).
+
+\begin{figure}
+ \centering
+ \fitimg{\includegraphics[scale=0.7]{img/poc-particles.png}}
+ \caption{Particles \glsfmtshort{poc} output}
+ \label{fig:poc-particles}
+\end{figure}
+
+\subsection{Collision}
+\label{poc:collision}
+
+The collision \gls{poc} \autocite[collision example]{crepe:code-repo} uses a couple
+of systems. It uses ECS, Physics system, Collisions system and Render system. It uses
+a lot of components like a gamedeveloper would use. This poc shows that multiple
+systems can work together and shows physics described by the gamedeveloper.
+
+This \gls{poc} shows two boxes with some distance from each other
+\cref{fig:poc-no-collision}, and collide \cref{fig:poc-collision}. The red box is
+static and can not be moved be physics even if it has gravity. The green box is
+dynamic and is moved by gravity. These movements are done to add the velocity to the
+transform. the velocity is calculated by the physics and saved in the rigidbody.
+before moving the collision system checks if there is collison, so it looks at the
+future. if the green box wants to move through the red box it is pushed back by the
+collision handler because the green box is static.
+
+This \gls{poc} showed that it is better to do the opposite. Move all object then look
+back if the collided with anything.
+
+\begin{figure}
+ \centering
+ \fitimg{\includegraphics[scale=0.7]{img/poc-collision-1.png}}
+ \caption{No collision \glsfmtshort{poc} output}
+ \label{fig:poc-no-collision}
+\end{figure}
+
+\begin{figure}
+ \centering
+ \fitimg{\includegraphics[scale=0.7]{img/poc-collision-2.png}}
+ \caption{Collision \glsfmtshort{poc} output}
+ \label{fig:poc-collision}
+\end{figure}
-\section{Conclusion}
+\makeatletter%
+\newbox\full@class@diag%
+\newlength\full@class@diag@width%
+\newlength\full@class@diag@height%
+\savebox\full@class@diag{\includegraphics{img/class-api-full.pdf}}%
+\settowidth\full@class@diag@width{\usebox\full@class@diag}%
+\settoheight\full@class@diag@height{\usebox\full@class@diag}%
+\begingroup%
+\eject%
+\thispagestyle{empty}%
+\pdfpagewidth=\full@class@diag@width%
+\pdfpageheight=\full@class@diag@height%
+\AddToShipoutPictureBG*{%
+ \AtPageUpperLeft{%
+ \raisebox{-\full@class@diag@height}{%
+ \usebox\full@class@diag%
+ }%
+ }%
+}%
+\section{Full API class diagram}%
+\newpage%
+\endgroup%
+\makeatother%
\end{document}