aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--design.tex240
-rw-r--r--img/custom-event-output.pngbin0 -> 2088 bytes
-rw-r--r--img/event-uml.drawio.pngbin217362 -> 453179 bytes
-rw-r--r--img/gameloop-console-10.pngbin0 -> 9313 bytes
-rw-r--r--img/gameloop-console.pngbin0 -> 18235 bytes
-rw-r--r--img/gameloop-squares.pngbin0 -> 5034 bytes
-rw-r--r--img/poc-button.pngbin0 -> 4395 bytes
-rw-r--r--img/poc-event-button.pngbin0 -> 4955 bytes
8 files changed, 192 insertions, 48 deletions
diff --git a/design.tex b/design.tex
index a208cd3..be69c8f 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
@@ -258,70 +256,70 @@ 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 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.
-
-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
+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.
+
+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{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}
\begin{figure}
\centering
\includepumldiag{img/gameloop-class.puml}
- \caption{Gameloop Flowchart Diagram}
+ \caption{Gameloop flowchart diagram}
\label{fig:gameloop-class}
\end{figure}
@@ -386,11 +384,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
@@ -731,6 +736,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}
diff --git a/img/custom-event-output.png b/img/custom-event-output.png
new file mode 100644
index 0000000..f818293
--- /dev/null
+++ b/img/custom-event-output.png
Binary files differ
diff --git a/img/event-uml.drawio.png b/img/event-uml.drawio.png
index 9eab458..008dd85 100644
--- a/img/event-uml.drawio.png
+++ b/img/event-uml.drawio.png
Binary files differ
diff --git a/img/gameloop-console-10.png b/img/gameloop-console-10.png
new file mode 100644
index 0000000..af0cd71
--- /dev/null
+++ b/img/gameloop-console-10.png
Binary files differ
diff --git a/img/gameloop-console.png b/img/gameloop-console.png
new file mode 100644
index 0000000..a675fae
--- /dev/null
+++ b/img/gameloop-console.png
Binary files differ
diff --git a/img/gameloop-squares.png b/img/gameloop-squares.png
new file mode 100644
index 0000000..6481c7e
--- /dev/null
+++ b/img/gameloop-squares.png
Binary files differ
diff --git a/img/poc-button.png b/img/poc-button.png
new file mode 100644
index 0000000..633ea99
--- /dev/null
+++ b/img/poc-button.png
Binary files differ
diff --git a/img/poc-event-button.png b/img/poc-event-button.png
new file mode 100644
index 0000000..7d3b546
--- /dev/null
+++ b/img/poc-event-button.png
Binary files differ