diff options
Diffstat (limited to 'design.tex')
-rw-r--r-- | design.tex | 240 |
1 files changed, 192 insertions, 48 deletions
@@ -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} |