aboutsummaryrefslogtreecommitdiff
path: root/design.tex
diff options
context:
space:
mode:
authorjaroWMR <jarorutjes07@gmail.com>2024-11-03 16:23:50 +0100
committerjaroWMR <jarorutjes07@gmail.com>2024-11-03 16:23:50 +0100
commitc8aad2f068d79407e81ec99aa03f9150d7f18e46 (patch)
tree320379d24163e32e387c1c4fdd6dcc923d33a23f /design.tex
parentb81399f199d7a1c2107813e2cb8865691e25c933 (diff)
parentd830c3ad0e1d749ca72c95a2c293cb67847e783f (diff)
merge with master
Diffstat (limited to 'design.tex')
-rw-r--r--design.tex250
1 files changed, 196 insertions, 54 deletions
diff --git a/design.tex b/design.tex
index c899f5d..f6717dd 100644
--- a/design.tex
+++ b/design.tex
@@ -123,13 +123,11 @@ the main part of the \gls{ecs}. The design of these eight systems in combination
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 and leading to
-unpredictable behavior.
-
-The game loop is mainly responsible for these 2 purposes:\noparbreak
+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 Making sure the gameloop timer is up to date
+ \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
@@ -139,9 +137,8 @@ reliability.
\subsection{Texture}
-% FIXME: our
-The textures in our game engine are represented by the \codeinline{Texture} class. It
-is implemented a \gls{facade} around the \gls{sdl} library.
+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}
@@ -220,9 +217,7 @@ following classes:\noparbreak
\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}
- and has a friendship relationship with \codeinline{ComponentManager} for tighter
- integration with component management.
+ \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
@@ -258,67 +253,68 @@ following classes:\noparbreak
\label{fig:class-renderingflowchart}
\end{figure}
-\subsubsection{Design}
+\subsubsection{Architecture}
-The game loop of this engine is integrated into the engine, this is done for the
-following reasons:\noparbreak
+This engine uses an integrated game loop for the following reasons:\noparbreak
\begin{description}
- \item[Simplify development] The user only has to call \codeinline{startGame()}.
- \item[Uniform system calls] The systems are always updated in the same order
- limiting overwrites and undefined system behavior.
- \item[Reliable timer update] Each cycle the game loop timer is always updated
- limiting timing issues.
+ \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 gameloop is divided into different
+As seen in \cref{fig:gameloop-flow}, the game loop consists of multiple
steps:\noparbreak
\begin{description}
- \item[Update loop timer] The loop timer gets updated and the expected frame time is
+ \item[Update loop timer] The loop timer is updated, and the expected frame time is
calculated.
- \item[Check events] Queued events get dispatched and callback functions are handled
- acordingly.
- \item[Process input] The input system is called and user input is processed.
- \item[Fixed update] A fixed loop for timing sensitive systems such as physics.
- \item[Update] A per frame update for all per frame updates.
- \item[Render] Calling the render system to render the frame.
+ \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}
-This is done as illustrated in \cref{fig:gameloop-flow}, the game loop continues to call
-the fixed update \cref{fig:fixed-update} function as long as sufficient time is available. Delta time,
-calculated using the time between the start of the last frame and the current frame,
-is used to measure the duration of each frame. This value is converted into a
-time-based unit, enabling other systems or game developers to create behavior
-independent of frame rate.
+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. 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.
+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 separately on a per-frame basis. A delay, in
-conjunction with the delta time calculation, is applied to maintain consistent visual
-behavior, even when frame rates vary. As seen in \cref{fig:gameloop-class} to access the
-deltaTime anywhere in the system a timerClass is created using a singleton desing
-pattern which ensures only one instance of the class is created; the gameloop updates
-the timing and delta time of this class to ensure it is accurate. The gameloops two
-main functions are the setup() and loop(). The first is called when the game starts
-and handles all startup procedures this function only runs once. The loop() function
-keeps looping as long as the game is running.
-
-The gamedeveloper start the game engine/game using the code example below:\noparbreak
+
+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}
-This starts calls the setup() and loop() functions and starts the game loop timer; To
-get the current frames' delta time, the \codeinline{getDeltaTime()} method on the
-\codeinline{LoopTimer} singleton can be used, which will return the expected frame
-time.
+% 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{Gameloop Flowchart Diagram}
+ \caption{Game loop flowchart diagram}
\label{fig:gameloop-flow}
\end{figure}
@@ -332,7 +328,7 @@ time.
\begin{figure}
\centering
\includepumldiag{img/gameloop-class.puml}
- \caption{Gameloop Flowchart Diagram}
+ \caption{Gameloop flowchart diagram}
\label{fig:gameloop-class}
\end{figure}
@@ -397,11 +393,18 @@ as:\noparbreak
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 EventManager is a singleton so all all callbacks are stored in one place and it
-can be called everywhere in the system or game. The user can use the EventManager for
-the following functions:\noparbreak
+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
@@ -832,6 +835,145 @@ 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}