diff --git a/Thesis/Thesis.tex b/Thesis/Thesis.tex index d33007a..82cd5db 100644 --- a/Thesis/Thesis.tex +++ b/Thesis/Thesis.tex @@ -5,14 +5,11 @@ % Pod rozdzialy do trzech poziomow -> domyslnie zalatwione przez latexa % Rozdzialy poziomu I od nowej strony -> domyslnie przez latexa % Wyliczenia -> tylko kropka lub myslnik -> tylko kropka domylsnie przez latexa -\usepackage{dirtree} -\usepackage{refcount} -\usepackage{longtable} + \usepackage{ulem} \usepackage{fontspec} \usepackage{anyfontsize} \usepackage{graphicx} -\usepackage{subcaption} \usepackage{geometry} \usepackage{leading} \usepackage[nottoc]{tocbibind} @@ -33,16 +30,8 @@ % tytul 1 poziomu 14 % tytul 2 poziomu 13 % tytul 3 poziomu 12 - -\usepackage{tikz} -\usetikzlibrary{shapes.geometric, arrows, positioning} -\tikzstyle{startstop} = [rectangle, rounded corners, minimum width=3cm, minimum height=1cm,text centered, draw=black, fill=red!30] -\tikzstyle{io} = [trapezium, trapezium left angle=70, trapezium right angle=110, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=blue!30] -\tikzstyle{process} = [rectangle, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=orange!30] -\tikzstyle{decision} = [diamond, minimum width=3cm, minimum height=1cm, text centered, draw=black, fill=green!30] -\tikzstyle{arrow} = [thick,->,>=stealth] - \usepackage{sectsty} + \chapterfont{\fontsize{14}{14}\selectfont} \sectionfont{\fontsize{13}{13}\selectfont} @@ -55,7 +44,6 @@ % Ciagła w całej pracy <----- % Kolejna w rozdziałach \usepackage{chngcntr} -\usepackage{tabularx} \counterwithout{figure}{chapter} \counterwithout{table}{chapter} @@ -125,60 +113,19 @@ } {\endtrivlist} -\usepackage{listings} -\usepackage{xcolor} -\lstdefinestyle{C++Style}{ - language=C++, - basicstyle=\footnotesize\ttfamily, - keywordstyle=\color{blue}, - commentstyle=\color{gray}, - stringstyle=\color{red}, - numbers=left, - numberstyle=\tiny, - frame=single, - keepspaces=true, - showspaces=false, - showtabs=false, - tabsize=2, - breaklines=true, - breakatwhitespace=true -} -\lstdefinestyle{mystyle}{ - backgroundcolor=\color{gray!10}, - basicstyle=\ttfamily\footnotesize, - breakatwhitespace=false, - breaklines=true, - captionpos=b, - keepspaces=true, - numbers=left, - numbersep=5pt, - showspaces=false, - showstringspaces=false, - showtabs=false, - tabsize=2, - language=Python, - morekeywords={os, re}, -} -% for blank page after title -\usepackage{afterpage} -\newcommand\blankpage{% - \null - \thispagestyle{empty}% - \addtocounter{page}{-1}% - \newpage} - -\newfontfamily{\adagio}{Adagio_Slab} -\newfontfamily{\adagioLight}{Adagio Slab Light} -\newfontfamily{\helveticaLight}{HelveticaLTStd-Light} -\newfontfamily{\helvetica}{Helvetica} +\fontfamily{helvet}\selectfont +% \newfontfamily{\adagio}{Adagio_Slab} +% \newfontfamily{\adagioLight}{Adagio Slab Light} +% \newfontfamily{\helveticaLight}{HelveticaLTStd-Light} +% \newfontfamily{\helvetica}{Helvetica} \newcommand{\tytul}{ - Tworzenie aplikacji wieloplatformowych na przykładzie silnika gier z gatunku "match 3" + Creating multiplatform match three game engine } \newcommand{\tytulEng}{ - Creating multiplatform applications with creation of match three game engine as an example + Example of creation of multiplatform apps on basis of creating match-three game engine } \begin{document} \begin{titlepage} @@ -208,108 +155,52 @@ {\fontsize{12}{12}\helveticaLight and specialization Computer Systems and Networks \\[50pt]} - {\fontsize{14}{14}\helvetica \tytulEng \\[50pt]} + {\fontsize{14}{14}\helvetica \tytul \\[50pt]} - {\fontsize{12}{12}\helveticaLight thesis number in the Faculty thesis register 103B-ISA-IN/307585/1202985 \\[12pt]} + {\fontsize{12}{12}\helveticaLight numer pracy według wydzia{\fontsize{12}{12}\adagioLight ł}owej ewidencji prac \{liczba\} \\[12pt]} - {\fontsize{21}{21}\helvetica Krzysztof Stefan Rudnicki \\[12pt]} + {\fontsize{21}{21}\helvetica Krzysztof Rudnicki \\[12pt]} - {\fontsize{12}{12}\helveticaLight student record book number 307585 \\[24pt]} + {\fontsize{12}{12}\helveticaLight numer albumu 307585 \\[24pt]} - {\fontsize{12}{12}\helveticaLight thesis supervisor } + {\fontsize{12}{12}\helveticaLight promotor } {\fontsize{12}{12}\helvetica dr hab. inż. Tomasz Martyn \\[12pt]} {\fontsize{12}{12}\helveticaLight konsultacje } - {\fontsize{12}{12}\helvetica Tomasz Martyn \\[40pt]} + {\fontsize{12}{12}\helvetica dr hab. inż. Tomasz Martyn \\[40pt]} - {\fontsize{12}{12}\helveticaLight WARSAW 2023} + {\fontsize{12}{12}\helveticaLight Warszawa 2023} \end{center} -\afterpage{\null\newpage} + + \end{titlepage} -\subsubsection{Thesis abstract} -\fontsize{12}{12}{\tytulEng \\ -With three major operating systems on PC, there exists a need for game engines that are versatile and platform-agnostic. This thesis introduces the design and implementation of a new game engine developed with match-three games in mind, which is a game genre that is both popular and relatively easy to implement, despite this there is still no specialized open-source match three game engine. The engine is built upon the foundations of the OpenGL graphics library and the glfw windowing system, ensuring advanced graphics capabilities while retaining platform compatibility. There are two goals for the engine: simplicity of use for developers, and robustness in performance. The architecture focuses on using good practices, modern C++ code and making the code developer friendly. As a result, game developers can focus on crafting match-three games without need to learn programming, empowering artists without technical background who want to create an unique experience. This work provides motivation, background, documentation and use-case across three major platforms: Linux, Windows, and MacOS. The engine demonstrates usability, adaptability to different platforms, and provides the tools necessary for creation of simple match-three games. This thesis is trying to fill the gap of specialized open-source video game engines. -\{Multiplatform, -Gaming, -Game engines, -Match-three games, -OpenGL, -GLFW, -Graphics, -Platform-agnostic, -Design, -Robustness, -Simplicity, -Linux, -Windows, -MacOS, -C++, -Good coding practices\} -} - -\newpage \subsubsection{Streszczenie pracy} {\fontsize{12}{12} \tytul \\ -Rynek systemów operacyjnych na PC jest podzielony na trzech głównych graczy, tworzy to zapotrzebowanie na silniki do gier, które są wszechstronne i działają tak samo, niezależnie od platformy. Niniejsza praca inżynierska przedstawia projekt i implementację nowego silnika do gier opracowanego z myślą o grach typu "dopasuj 3", gatunku który jest zarówno popularny, jak i stosunkowo łatwy do wdrożenia, mimo to nadal nie ma wyspecjalizowanego silnika open-source pod gry "dopasuj 3". Silnik został zbudowany w oparciu o bibliotekę graficzną OpenGL i system zarządzania oknami glfw, zapewniając zaawansowane możliwości graficzne przy jednoczesnym zachowaniu kompatybilności między platformami. Silnikowi przyświecają dwa cele: prostota obsługi dla deweloperów i solidna wydajność. Architektura skupia się na wykorzystaniu dobrych praktyk, nowoczesnego kodu C++ i sprawieniu że czytanie kodu silnika będzie łatwe i zrozumiałe dla developerów. W rezultacie twórcy gier mogą skupić się na tworzeniu gier typu "dopasuj 3" bez konieczności nauki programowania, umożliwiając artystom bez zaplecza technicznego, którzy chcą stworzyć wyjątkowe doświadczenie. Niniejsza praca opisuje motywacje, tło, dokumentację i przypadki użycia na trzech głównych platformach: Linux, Windows i MacOS. Silnik demonstruje użyteczność, możliwość dostosowania do różnych platform i zapewnia narzędzia niezbędne do tworzenia prostych gier typu "dopasuj 3". Niniejsza praca stara się wypełnić lukę w wyspecjalizowanych silnikach gier wideo open-source. -\{Multiplatformowość, -Gry, -Silniki do gier, -Gry "dopasuj 3" -OpenGL, -GLFW, -Grafika, -Design, -Prostota, -Linux, -Windows, -MacOS, -C++, -Dobre praktyki kodowania\} +\{SLOWA KLUCZOWE\} } - \newpage -\subsubsection{Oświadczenie o samodzielnym pisaniu pracy} -Świadomy(a) odpowiedzialności karnej za składanie fałszywych zeznań oświadczam, że niniejsza praca dyplomowa została napisana przeze mnie samodzielnie, pod opieką kierującego pracą dyplomową. \\ -Jednocześnie oświadczam, że: -\begin{itemize} -\item niniejsza praca dyplomowa nie narusza praw autorskich w rozumieniu ustawy z dnia 4 lutego 1994 roku o prawie autorskim i prawach pokrewnych (Dz.U. z 2021 r., poz. 1062) oraz dóbr osobistych chronionych prawem cywilnym, -\item niniejsza praca dyplomowa nie zawiera danych i informacji, które uzyskałem(am) w sposób niedozwolony, -\item niniejsza praca dyplomowa nie była wcześniej podstawą żadnej innej urzędowej procedury związanej z nadawaniem dyplomów lub tytułów zawodowych, -\item wszystkie informacje umieszczone w niniejszej pracy, uzyskane ze źródeł pisanych i elektronicznych, zostały udokumentowane w wykazie literatury odpowiednimi odnośnikami, -\item znam regulacje prawne Politechniki Warszawskiej w sprawie zarządzania prawami autorskimi i prawami pokrewnymi, prawami własności przemysłowej oraz zasadami komercjalizacji. -\end{itemize} - -\newpage -\subsubsection{Statement about writing diploma thesis on my own} -Under the penalty of perjury, I hereby certify that I wrote my diploma thesis on my own, under the guidance of the thesis supervisor. \\ -I also declare that: -\begin{itemize} -\item this diploma thesis does not constitute infringement of copyright following the act of 4 February 1994 on copyright and related rights (Journal of Acts of 2021, item 1062) or personal rights protected under the civil law, -\item the diploma thesis does not contain data or information acquired in an illegal way, -\item the diploma thesis has never been the basis of any other official proceedings leading to the award of diplomas or professional degrees, -\item all information included in the diploma thesis, derived from printed and electronic sources, has been documented with relevant references in the literature section, -\item I am aware of the regulations at Warsaw University of Technology on management of copyright and related rights, industrial property rights and commercialisation. -\end{itemize} +\subsubsection{Thesis abstract} +\fontsize{12}{12}{\tytulEng \\ +\{KEYWORDS\} +} \tableofcontents \chapter{ Introduction } \section{Background and Motivation} -Among us, computer science students, video games were—and still are—one of the driving forces pushing us into the field of IT. Video games allow people with technical background to express their artistic side and create visually stunning projects. In particular "Match Three" games, characterized by their intuitive mechanics and satisfying gameplay, have attracted millions of players around the globe. They offer statisfying experience of aligning three or more similar game pieces, often resulting in gratyfying chain reactions. This lead to success of titles like "Candy Crush Saga" \cite{candyCrushMarketShare} and "Bejeweled", staple titles for both mobile and desktop market. +Among us, computer science students, video games were—and still are—one of the driving forces pushing us into the field of IT. Video games allow people with technical background to express their artistic side and create visually stunning projects. In particular "Match Three" games, characterized by their intuitive mechanics and satisfying gameplay, have attracted millions of players around the globe. They offer statisfying experience of aligning three or more similar game pieces, often resulting in gratyfying chain reactions. This lead to success of titles like "Candy Crush Saga" and "Bejeweled", staple titles for both mobile and desktop market. + +\begin{figure}[h] -\begin{centering} -\begin{figure}[htp] \includegraphics[scale=0.5]{allgates} -\caption{Minecraft redstone offers introduciton to logical gates \cite{redstoneGates}} -\label{fig:redstoneGatesFig} +\caption{Minecraft redstone offers introduciton to logical gates \cite{raspberrypi2023}} \end{figure} -\end{centering} Match three games are from technological point of view relatively easy to make, despite that there is no dedicated open-source game engine designed just for them. This engineering thesis aims to fill this void, to give back to the world of game developers and open-source projects. @@ -322,16 +213,30 @@ Game development is a broad spectrum, This section aims to define to what extent \subsection{Game Engine Focus: Match Three Genre} -While game engines can be created for multiple game genres, this thesis focuses on Match Three genre. This decision comes from its ease of implementation, combined with relatively high popularity \cite{popularMatchThree} and applicability on all platforms. We will focus on core logic, rendering techniques, and controls focused on Match Three games. +While game engines can be created for multiple game genres, this thesis focuses on Match Three genre. This decision comes from its ease of implementation, combined with relatively high popularity and applicability on all platforms. We will focus on core logic, rendering techniques, and controls focused on Match Three games. \subsection{Multiplatform Development} Another important part of the thesis is multiplatform game development. We will focus on three major desktop operating systems: Windows, Mac, and GNU/Linux. The challenges associated with creating an uniform gaming experience across these platforms, taking into consideration differences in architecture, functionality and graphics. +\begin{figure}[htp] +\centering +\includegraphics[scale=0.50]{platforms.png} +\caption{Logos of platforms we will be focusing on, from left: Windows, Mac and Linux \cite{platforms}} +\label{} +\end{figure} + \subsection{Tool and Library Integration} This thesis will make biggest use of OpenGL API together with libraries: GLFW, GLAD, FreeType, stb\_image, and ft2build. In order used for platform-independed creation of windows, context and handling inputs, loader for OpenGL, library for rendering text, library for loading images and library for building free type sources. Later in the thesis we will dvelfe deeper into those libraries +\begin{figure}[htp] +\centering +\includegraphics[scale=1.00]{images/glfw.png} +\caption{GLFW project logo \cite{glfwLogo} } +\label{} +\end{figure} + \subsection{Theoretical and Practical Exploration} Main portion of this thesis is practical implementation, still we are going to explore theoretical concepts crucial for game engine development. @@ -351,7 +256,7 @@ The foremost objective is to design a core multiplatform engine architecture. Th \subsection{Effective Integration of Libraries} -With numerous libraries at our disposal, an important goal is to combine them into the engine in a cohesive manner. We will make use of GLFW \cite{glfwSite} for window management, GLAD for OpenGL function pointers \cite{gladSite}, FreeType \cite{freeTypeLib} and ft2build for text rendering, and stb\_image for image handling. In addition to integrating them into an engine, these libraries must operate in such a way that they complement each other to improve the engine's overall performance and capability. +With numerous libraries at our disposal, an important goal is to combine them into the engine in a cohesive manner. We will make use of GLFW for window management, GLAD for OpenGL function pointers, FreeType and ft2build for text rendering, and stb\_image for image handling. In addition to integrating them into an engine, these libraries must operate in such a way that they complement each other to improve the engine's overall performance and capability. \subsection{Development of Core Match Three Mechanics} @@ -378,7 +283,7 @@ In 2001 game titled "Bejeweled" came out. Simplifying and refining the mechanics \centering \includegraphics[scale=0.50]{images/bejeweld.jpg} \caption{Bejeweled gameplay \cite{bejeweledGameplay} } -\label{fig:bejeweledGameplay} +\label{} \end{figure} The success of "Bejeweled" lead to several innovation. Developers experimented with various twists on the core mechanics. Games introduced power-ups, barriers, objectives, and narrative elements. "Candy Crush Saga", released in 2012, integrated a level-based progression system, increasing the genre's complexity and depth. @@ -387,7 +292,7 @@ The success of "Bejeweled" lead to several innovation. Developers experimented w The evolution of Match Three games is interlocked with technological improvements in the gaming industry. Initially developed to PCs and consoles, the rise of mobile devices opened a new opportunities for the genre. The touch interface of smartphones and tablets proved to be an ideal medium for the drag-and-swap mechanics of Match Three games. -At the same time, browser-based games, using technologies like Flash, allowed players to engage with Match Three puzzles without heavy downloads or installations. As technology progressed, the need for engines that worked on multiple platforms – mobile, browser, consoles and desktop – became apparent. \cite{crossPlatformRise} +At the same time, browser-based games, using technologies like Flash, allowed players to engage with Match Three puzzles without heavy downloads or installations. As technology progressed, the need for engines that worked on multiple platforms – mobile, browser, consoles and desktop – became apparent. \subsection{Enduring Appeal and Contemporary Significance} @@ -405,7 +310,7 @@ There are three main operating systems, Windows, Mac, and GNU/Linux which togeth \centering \includegraphics[scale=0.50]{chart.png} \caption{Desktop OS market share worldwide \cite{marketShareChart}} -\label{fig:marketShare} +\label{} \end{figure} \subsection{Challenges in Multiplatform Development} @@ -431,7 +336,7 @@ In order to face multiplatform development challenges, we use a set of tools and \subsection{Future Trajectory of Multiplatform Development} -With the cloud gaming on the rise \cite{cloudGamingRise}, ressurection of web-based games, and increasingly powerful mobile devices, multiplatform development's scope is constantly expanding. There will be newer platforms and more varied devices to handle. The goal will remain the same: ensuring that games offer a consistent, engaging, and seamless experience, independent of the platform. +With the cloud gaming on the rise, ressurection of web-based games, and increasingly powerful mobile devices, multiplatform development's scope is constantly expanding. There will be newer platforms and more varied devices to handle. The goal will remain the same: ensuring that games offer a consistent, engaging, and seamless experience, independent of the platform. Multiplatform game development requires technological knowledge and good design. By understanding its challenges and methodologies, developers are better equipped to face-on multiple platforms of modern gaming. This foundation will become crucial later into the development of our multiplatform Match Three game engine. \section{Existing Game Engines and Their Limitations} @@ -439,6 +344,13 @@ The gaming industry has seen rise of various game engines, each offering a uniqu \subsection{Unity Game Engine} +\begin{figure}[htp] +\centering +\includegraphics[scale=0.25]{images/unity.png} +\caption{Unity engine logo \cite{unityLogo}} +\label{} +\end{figure} + Unity is one of the most popular game engines available today. It's known for its versatility, supporting a wide range of platforms, from PCs to consoles to mobile devices. \begin{itemize} @@ -448,6 +360,13 @@ Unity is one of the most popular game engines available today. It's known for it \subsection{Unreal Engine} +\begin{figure}[htp] +\centering +\includegraphics[scale=0.10]{images/unreal.png} +\caption{Unreal engine logo \cite{unrealLogo}} +\label{} +\end{figure} + Epic Games' Unreal Engine stands out for its impressive graphical capabilities, often employed in AAA game titles. Strengths: @@ -464,8 +383,14 @@ Limitations: \subsection{Godot} +\begin{figure}[H] +\centering +\includegraphics[scale=0.05]{images/godot.png} +\caption{Godot game engine logo \cite{godotLogo}} +\label{} +\end{figure} -Godot is the biggest multipurpose game engine that is also free and open-source. \cite{godotEngineProject} +Godot is the biggest multipurpose game engine that is also free and open-source. Strengths: \begin{itemize} @@ -481,6 +406,23 @@ Strengths: \subsection{RPG Maker and Ren'Py} +\begin{figure}[htp] +\centering + +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=0.25]{images/renpy.png} +\caption{Ren'Py engine logo \cite{renpyLogo}} +\label{fig:renpylogo} % You might want to give a label for future referencing +\end{minipage}% +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=0.25]{images/rpgMaker.jpg} +\caption{RPG Maker engine logo \cite{rpgMakerLogo}} +\label{fig:rpgmakerlogo} % You might want to give a label for future referencing +\end{minipage} + +\end{figure} RPG Maker and Ren'Py are examples of game engines focusing on one genre of games. @@ -494,13 +436,13 @@ While existing game engines offer a lot of tools and capabilities, there exists \chapter{Basics of Game Engine Design} \section{Core Components of Game Engines} -Game engines serve as the backbone for game development, providing a selection of tools and features that simplify the process of creating a game from scratch. At the heart of every game engine lie its core components, which define its capabilities, flexibility, and performance. This section explains those elements that we are going to implement in our engine. \cite{gameEngineCharacterics} +Game engines serve as the backbone for game development, providing a selection of tools and features that simplify the process of creating a game from scratch. At the heart of every game engine lie its core components, which define its capabilities, flexibility, and performance. This section explains those elements that we are going to implement in our engine. \begin{figure}[htp] \centering \includegraphics[scale=0.50]{images/gameEngineComponents.drawio.pdf} \caption{Components used in our game engine} -\label{fig:gameEngineComponent} +\label{} \end{figure} @@ -572,7 +514,7 @@ One of OpenGL's strengths lies in its extensible design, allowing to include adv \centering \includegraphics[scale=0.70]{images/openglWikipedia.png} \caption{Connection between Linux and OpenGL-based games \cite{openGLWikipedia}} -\label{fig:oGLWikipedia} +\label{} \end{figure} Gamers are spread across Windows, Mac, GNU/Linux, and other platforms, OpenGL's platform-independent allows for creating games for all platforms. @@ -626,9 +568,9 @@ There are platform-specific nuances in how graphics are presented. \begin{figure}[htp] \centering -\includegraphics[scale=0.5]{images/windowsLinuxSlash.jpg} +\includegraphics[scale=1.00]{images/windowsLinuxSlash.jpg} \caption{Separating folders with backslash and forward slash is one of many differneces between Windows and Linux systems \cite{windowsLinuxSlash} } -\label{fig:foldersWindowsLinux} +\label{} \end{figure} File systems varies between Windows, Mac, and GNU/Linux, influencing how game data is stored, retrieved, and updated. @@ -648,7 +590,7 @@ GLFW, which stands for Graphics Library Framework, was a response to the need fo \subsection{GLFW's Core Competencies} -GLFW serves as a bridge between the game's logic and the operating system, handling tasks that are crucial for interactive applications. \cite{glfwWiki} +GLFW serves as a bridge between the game's logic and the operating system, handling tasks that are crucial for interactive applications. \begin{itemize} \item Window Management: GLFW's manages window creation, querying, and manipulation. It allows developers to focus on their game logic rather than handling platform-specific windowing. @@ -680,7 +622,7 @@ GLFW's is able to work across Windows, Mac, and GNU/Linux. \subsection{Community and Support} -Being open-source, GLFW benefits from a huge community that continually refines, optimizes, and extends the library. \cite{glfwCommunity} +Being open-source, GLFW benefits from a huge community that continually refines, optimizes, and extends the library. \begin{itemize} \item Role: Ensures GLFW remains up-to-date, addreses challenges and evolves with the needs of developers. @@ -692,13 +634,6 @@ GLFW proves useful to many game developers, especially those using OpenGL. Its a \section{Role of GLAD in OpenGL Loading} GLAD is a robust and flexible loader-generator designed for OpenGL. It ensures the dynamic loading of OpenGL functions based on the specific version and extensions a developer chooses. GLAD was developed to automate handling OpenGL extensions. -\begin{figure}[htp] -\centering -\includegraphics[scale=0.50]{images/gladSite.png} -\caption{Glad website allowing for different configurations of OpenGL \cite{gladSite}} -\label{fig:gladSiteImage} -\end{figure} - \subsection{The Need for Loading OpenGL Extensions} OpenGL's is an extensible API. With changes in hardware, new functionalities are introduced through extensions without changing the core API. This creates new challenges on their own. @@ -738,20 +673,13 @@ FreeType is an open-source software font engine, It offers ability to render tex \item Font Metrics and Kerning: Rendering text isn't just about individual glyphs. The spacing between them (kerning) and their metrics are crucial for readability and aesthetics, and FreeType is able to deliver this functionality. \end{itemize} -\begin{figure}[htp] -\centering -\includegraphics[scale=0.50]{images/freetypeInspect.png} -\caption{ftinspect showcasing capabilities of FreeType library \cite{freeTypeSite}} -\label{fig:ftSite} -\end{figure} - \subsection{Understanding ft2build} ft2build serves as an essential interface, allowing for the inclusion and deployment of FreeType headers in development projects. ft2build was created to ease the integration process of FreeType 2. \subsection{Introduction to stb\_image} -While FreeType takes charge of fonts, stb\_image is a solution for image loading and decoding, popular for its simplicity and compactness. It is part of the stb collection of single-file libraries. \cite{stbImageRepo} +While FreeType takes charge of fonts, stb\_image is a solution for image loading and decoding, popular for its simplicity and compactness. It is part of the stb collection of single-file libraries. \subsection{Key Capabilities of stb\_image} @@ -766,16 +694,8 @@ While FreeType takes charge of fonts, stb\_image is a solution for image loading Specialized tools like FreeType and stb\_image elecate the visual experience of games with text rendering and images inclusion into game engine \chapter{Design and Architecture of the Match Three Engine} - \section{Game Loop and State Management} -In the heart of every video game lies the game loop. This cycle dictates the game's pacing, ensuring that events, updates, and rendering processes all appear in a correct order. \cite{gameLoopExplanation} Game loop functions together with state management, which ensures the game behaves consistently in response to player actions and internal events. This section will focus on both the game loop and state management and how they influence our multiplatform Match Three game engine. - -\begin{figure}[htp] -\centering -\includegraphics[scale=0.50]{images/gameLoop} -\caption{Game loop flowchart representation} -\label{fig:gameLoopFlowchart} -\end{figure} +In the heart of every video game lies the game loop. This cycle dictates the game's pacing, ensuring that events, updates, and rendering processes all appear in a correct order. Game loop functions together with state management, which ensures the game behaves consistently in response to player actions and internal events. This section will focus on both the game loop and state management and how they influence our multiplatform Match Three game engine. \subsection{The Anatomy of a Game Loop} @@ -844,13 +764,6 @@ Game development consists of art and logic, where visual assets behave based on \subsection{What are Game Assets?} -\begin{figure}[htp] -\centering -\includegraphics[scale=1.00]{images/gemMatchThree.png} -\caption{Exemplary assets for match three tiles \cite{assetsMatchThree} } -\label{fig:exemplaryAssets} -\end{figure} - In the context of game development, assets refer to: \begin{itemize} @@ -937,13 +850,6 @@ Some of the distinctive features of OS architecture are: \item Kernel Design: Windows uses a hybrid kernel, while Linux is a monolithic kernel, MacOS is based around Unix XNU kernel. \item File Systems: Windows primarily offers NTFS, MacOS stores files using APFS, and GNU/Linux has multiple options like ext4, Btrfs, and many, many more. -\cite{linuxWindowsFileSystems} - \begin{figure}[htp] -\centering -\includegraphics[scale=0.75]{images/fileSystems} -\caption{Windows file system consists of drives, which contain folders which contain subfolders and files, in Linux everything is a file} -\label{fig:fileSystemsFig} -\end{figure} \item APIs \& System Calls: Each OS provides developers with its own set of APIs and system calls, changing how software communicates with the system. \end{itemize} @@ -960,7 +866,7 @@ Differences in GUI of each OS: \subsection{Driver Support} -Certain hardware, sometimes crucial to game engine like graphics card or keyboad, might be optimized for one OS over the others, or might not be supported entirely. \cite{driversWinLinux} +Certain hardware, sometimes crucial to game engine like graphics card or keyboad, might be optimized for one OS over the others, or might not be supported entirely. \subsection{Middleware \& Third-party Libraries} @@ -999,7 +905,7 @@ Across all platforms, multiple hardware components exists: Drivers allow an operating system to interact with hardware: \begin{itemize} - \item Proprietary vs. Open-Source: While NVIDIA provides proprietary drivers, AMD and Intel often offer open-source alternatives for Linux. There are also community driven, universal, open-source drivers. \cite{nvidiaDrivers} + \item Proprietary vs. Open-Source: While NVIDIA provides proprietary drivers, AMD and Intel often offer open-source alternatives for Linux. There are also community driven, universal, open-source drivers. \item Update Frequencies: Hardware vendors release driver updates at different intervals, each update possibly affects game performance and compatibility. @@ -1021,99 +927,6 @@ Drivers allow an operating system to interact with hardware: To address these challenges, our engine adopts abstraction layers: by utilizing existing libraries and solutions (OpenGL, GLAD and GLFW), our game engine is ready for easier adjustments for specific hardware. \chapter{Implementation Details} - -\section{Requirements} - -\subsection{Functional Requirements (FR)} -\begin{enumerate} - \item \textbf{Game Initialization and Setup (FR1)}: - \begin{itemize} - \item The engine shall initialize a game board with a predetermined square size (e.g., 8x8). - \item The game board shall be populated randomly with different colored pieces. - \item The initial game board should not have any matches (three or more of the same colored pieces in a row or column). - \end{itemize} - - \item \textbf{User Interactivity (FR2)}: - \begin{itemize} - \item Players shall be able to select a piece. - \item Players shall be able to swap the selected piece with an adjacent piece (up, down, left, or right) to form a match. - \end{itemize} - - \item \textbf{Matching Logic (FR3)}: - \begin{itemize} - \item When three or more pieces of the same color align vertically or horizontally, they shall be recognized as a match. - \item Matched pieces shall be removed from the board. - \end{itemize} - - \item \textbf{Board Update (FR4)}: - \begin{itemize} - \item After pieces are matched and removed, new pieces shall drop down to fill the vacant spaces. - \item The board shall continuously check for matches after each update. - \end{itemize} - - \item \textbf{Scoring System (FR5)}: - \begin{itemize} - \item Players shall earn points for each match made. - \item Bonus points shall be awarded for matches greater than three pieces. - \end{itemize} - - \item \textbf{Game Over Logic (FR6)}: - \begin{itemize} - \item The game shall end when no moves are left. - \end{itemize} - - \item \textbf{Pause and Resume (FR7)}: - \begin{itemize} - \item Players shall be able to pause and resume the game. - \end{itemize} - - \item \textbf{Game Difficulty Levels (FR8)}: - \begin{itemize} - \item Make the game more difficult or easy by giving player more or less turns to play - \end{itemize} -\end{enumerate} - -\subsection{Non-functional Requirements (NFR)} -\begin{enumerate} - \item \textbf{Performance (NFR1)}: - \begin{itemize} - \item The game engine shall ensure smooth animations without any lag. - \item Match detection should occur within milliseconds to ensure real-time response. - \end{itemize} - - \item \textbf{Usability (NFR2)}: - \begin{itemize} - \item The game interface shall be intuitive. - \end{itemize} - - \item \textbf{Portability (NFR3)}: - \begin{itemize} - \item The game engine should be platform-independent, allowing for easy deployment across different operating systems using C++ and OpenGL. - \end{itemize} - - \item \textbf{Maintainability (NFR4)}: - \begin{itemize} - \item The code shall be well-structured, modular, and commented, allowing for easy updates and maintenance. - \item Utilize design patterns where applicable for cleaner and more understandable code. - \end{itemize} - - \item \textbf{Reliability (NFR5)}: - \begin{itemize} - \item The engine shall be robust enough to handle unexpected inputs without crashing. - \item Memory leaks should be minimized, and efficient memory management practices should be followed. - \end{itemize} - - \item \textbf{Scalability (NFR6)}: - \begin{itemize} - \item The game engine shall support additional features or levels in the future without requiring a complete overhaul. - \end{itemize} - - \item \textbf{Documentation (NFR7)}: - \begin{itemize} - \item A comprehensive documentation detailing the architecture, algorithms, and functionalities should be provided. (For example in this thesis) - \end{itemize} -\end{enumerate} - \section{Core Engine Implementation} For our Match Three game engine, we aimed to create a simple core, capable of ensuring basic gameplay and small codebase allowing developers to extend or modify the engine's capabilities as needed. This section describes the implementation of the Match Three game engine. @@ -1121,13 +934,105 @@ For our Match Three game engine, we aimed to create a simple core, capable of en \begin{itemize} \item Platform Independence: Core engine functionalities independent of any specific platform, allowing for easy porting across Windows, MacOS, and GNU/Linux. - \item Simplicity: There are no more than 1500 lines of code in the entire engine + \item Simplicity: There are no more than 2500 lines of code in the entire engine \item Good coding style: When writing code, popular coding styles of cpplint and clang-tidy were used \item Good coding principles: when writing code, clang-tidy was extensively used to highlight potential errors and eliminate bad practices \end{itemize} +\subsection{Core Modules} + +The engine's architecture is segmented into modules: + +\begin{itemize} + \item Game object: (game\_object.cpp) Defines base class for game objects used by the engine, this class is later used to define tiles + + \item Main Game class: (game.cpp) This class combines all of the other modules and runs the game loop + + \item Game Level: (game\_level.cpp) Loads and saves game levels. + + \item Particle Generator: (particle\_generator.cpp) Generates particle effects for tiles + + \item Post Processor: (post\_processor.cpp) Combines particle and shader effects + + \item Resource Manager: (resource\_manager.cpp) Handles assets and shaders files + + \item Shader: (shader.cpp) calculates shaders and applies their effects + + \item Sprite Renderer: (sprite\_renderer.cpp) Renders sprites from images + + \item Text Renderer: (text\_renderer.cpp) Renders text + + \item Texture Renderer: (texture.cpp) Renders textures +\end{itemize} + +\subsection{Dependency Management} +There are several libraries: OpenGL, GLFW, stb\_image and freetype being integrated into engine, they are simply included at the top of any files that need them. + +\subsection{Constants file} +Game parameters, from graphics settings to gameplay variables, are stored in constants.cpp file, which can be modified, to apply the changes the engine must be compiled again. + +\section{Match Three Logic and Mechanics} +Match Three game challenges players to align identical items in rows or columns of three or more. This section describes how the Match Three logic and mechanics were implemented within our game engine. + +\subsection{The Fundamental Game Grid} + +A Match Three game takes place on a grid, where each cell holds an item (tile): + +\begin{itemize} + \item Dynamic Grid Generation: The engine supports grids of various sizes, ensuring adaptability for different game designs or levels. + + \item Item Diversity: While classic Match Three games primarily used jewels or candies, our engine supports any kind of 2d image assets user wants to use, these assets can later be processed by user created shaders +\end{itemize} + +\subsection{Matching Logic} + +\begin{itemize} + \item Pattern Recognition: The engine monitors the grid, identifies patterns where three or more items align vertically or horizontally. + + \item Combo Chains: Beyond the basic three-item match, the engine recognizes and rewards larger matches and cascading chain reactions. + + \item NICE TO HAVE, TODO, REPLACE ME IN THE FINAL VERSION! \sout{Special Items: Some matches can generate special items with unique properties, such as the ability to clear an entire row or explode surrounding tiles.} +\end{itemize} + +\subsection{Player Interactions and Moves} + +\begin{itemize} + \item Drag and Swap: The primary interaction method, where players drag items to swap positions and create matches. + + \item Move Validation: Not every swap leads to a match. The engine predicts potential matches before finalizing a move, ensuring players don't make invalid swaps. + + \item NICE TO HAVE, TODO, REPLACE ME IN THE FINAL VERSION! \sout{Move Limitations: Some levels might introduce a maximum move count, adding a strategic layer to gameplay.} +\end{itemize} + + +\subsection{Game Progression and Levels} +NICE TO HAVE, TODO, REPLACE ME IN THE FINAL VERSION! +\sout{ +Keeping players engaged: + + Dynamic Difficulty: As players advance, the game introduces larger grids, more item types, or challenging obstacles, ensuring a progressively challenging experience. + + Level Objectives: Beyond mere matching, players might need to achieve specific objectives, such as reaching a certain score or clearing particular grid cells. + + Reward System: Completing levels or achieving special combos can grant players rewards, from in-game currency to new levels or challenges. +} + +\subsection{Challenges and Obstacles} + +NICE TO HAVE, TODO, REPLACE ME IN THE FINAL VERSION! +\sout{ +Introducing variety: + + Static Blocks: Some grid cells might be occupied by immovable blocks, adding a puzzle element to gameplay. + + Movable Obstacles: Items that shift position, grow, or change over time, forcing players to adapt their strategies. + + Timed Challenges: Levels where players need to achieve objectives within a set time frame. +} + +Our engine offers basic match three experience with couple of additional features added on the top. \section{Integration of Libraries and Tools} In this section we will explain rationality for choosing libraries and tools for our engine and how they were implemented @@ -1156,23 +1061,8 @@ spiritual successor of OpenGL with focus on using modern C++ features and fixing issues created by OpenGL 30 years old development time. Out of these three it is recognized as the hardest one as it is both complicated and newest. Similarly as OpenGL -it uses open source license, namely Apache License 2.0. \cite{vulkanDirectXComparison} +it uses open source license, namely Apache License 2.0. \\ -\begin{table}[H] -\centering -\caption{Comparison of Graphics APIs} -\begin{tabularx}{\textwidth}{|l|X|X|X|X|} -\hline -& \textbf{Developer} & \textbf{Platform Focus} & \textbf{Complexity} & \textbf{License} \\ -\hline -\textbf{DirectX} & Microsoft & Windows, Xbox & Harder, requires understanding of underlying mechanisms & Non-free \\ -\hline -\textbf{OpenGL} & Khronos Group & Windows, Linux, Mac OS, Android, iOS, major consoles (especially with OpenGL ES) & Easiest, lacks some advanced features & Open source (similar to BSD) \\ -\hline -\textbf{Vulkan} & Khronos Group & Modern platforms, successor to OpenGL & Hardest, newest & Open source (Apache License 2.0) \\ -\hline -\end{tabularx} -\end{table} Considering all of those characteristics I decided to go with OpenGL API, specifically OpenGL ES subset with its focus on compatibility as making a multiplatform application is one of the focues of this thesis. @@ -1192,7 +1082,7 @@ There are 4 basic OpenGL libraries that I considered: \item SFML \item GLFW \end{itemize} -freeGLUT was created as an open source alternative to GLUT, +freeGLUT was created as opensource alternative to GLUT, is considered to be the worst out of all 4, written in archaic way, using C or very old C++, which in turn results in unexpected "buggy" behaviour, @@ -1231,1240 +1121,36 @@ FreeType and stb\_image were choosen for their simplicity and popularity \item Image Loading: stb\_image simplifies the task of loading and processing various image formats, from PNGs and JPGs to more complex formats, making asset integration much easier. \end{itemize} -\subsection{Core Modules} - -The engine's architecture is segmented into modules: - -\begin{itemize} - \item Game object: (game\_object.cpp) Defines base class for game objects used by the engine, this class is later used to define tiles - - \item Main Game class: (game.cpp) This class combines all of the other modules and runs the game loop - - \item Game Level: (game\_level.cpp) Loads and saves game levels. - - \item Particle Generator: (particle\_generator.cpp) Generates particle effects for tilesz - - \item Resource Manager: (resource\_manager.cpp) Handles assets and shaders files - - \item Shader: (shader.cpp) calculates shaders and applies their effects - - \item Sprite Renderer: (sprite\_renderer.cpp) Renders sprites from images - - \item Text Renderer: (text\_renderer.cpp) Renders text - - \item Texture Renderer: (texture.cpp) Renders textures -\end{itemize} - -\subsection{Main function} -\begin{figure}[H] -\centering -\begin{tikzpicture}[node distance=2cm] -\useasboundingbox (-5,0) rectangle (5, -17); % Set a custom bounding box -\node (start) [startstop] {Enter main function}; -\node (pro1) [process, below of=start] {Initialize GLFW}; -\draw [arrow] (start) -- (pro1); - -\node (pro2) [process, below of=pro1] {Initialize window}; -\draw [arrow] (pro1) -- (pro2); -\node (pro3) [process, below of=pro2] {Load OpenGL}; -\draw [arrow] (pro2) -- (pro3); - -\node (pro4) [process, below of=pro3] {Initialize game}; -\draw [arrow] (pro3) -- (pro4); - -\node (pro5) [process, below of=pro4] {Main Loop}; -\draw [arrow] (pro4) -- (pro5); - -\node (dec1) [decision, below of=pro5] {Closed?}; -\draw [arrow] (pro5) -- (dec1); -% Arrow from 'Closed?' decision node to 'Main Loop' node -\draw [arrow] (dec1.east) -- ++(2,0) |- node[anchor=south] {no} (pro5); - -\node (pro6) [process, below of=dec1] {Free resources}; -\draw [arrow] (dec1) -- node[anchor=east] {yes} (pro6); - -\node (close) [startstop, below of=pro6] {Close}; -\draw [arrow] (pro6) -- (close); -\end{tikzpicture} -\caption{Main function logic} -\label{fig:main_function_logic} -\end{figure} - -\paragraph{GLFW initialization} -Very first operation of our engine is to initliaze GLFW context, window and openGL, we set version of glfw to 3.3, resizable window and the profile depending on whether the engine got initialized on MacOS, or Linux/Windows, we set the windows width and height based on values from constants file - -\subsubsection{Main Loop} -\begin{figure}[H] -\centering -\begin{tikzpicture}[node distance=2cm] -\useasboundingbox (-5,0) rectangle (5, -11); % Set a custom bounding box -\node (start) [startstop] {Enter main loop}; - -\node (pro2) [process, below of=start] {Process input}; -\draw [arrow] (start) -- (pro2); -\node (pro3) [process, below of=pro2] {Update game state}; -\draw [arrow] (pro2) -- (pro3); - -\node (pro4) [process, below of=pro3] {Render}; -\draw [arrow] (pro3) -- (pro4); - -\node (dec1) [decision, below of=pro4] {Closed?}; -\draw [arrow] (pro4) -- (dec1); -% Arrow from 'Closed?' decision node to 'Main Loop' node -\draw [arrow] (dec1.east) -- ++(2,0) |- node[anchor=south] {no} (start); - -\node (close) [startstop, below of=dec1] {Close}; -\draw [arrow] (dec1) -- node[anchor=east] {yes} (close); - -\end{tikzpicture} -\caption{Main loop} -\label{fig:mainLoopLogic} -\end{figure} - -\paragraph{Process inputs} -Reacts to user inputs and sets up game state based on them, for example which tile was chosen by user - -\paragraph{Update game state} -Changes game level, removes blocks if applicable, drops new blocks and checks for win/lose conditions - -\paragraph{Render} -Based on changes from previous steps draws actual game window. - - -\subsection{Constants file} -Game parameters, from graphics settings to gameplay variables, are stored in constants.cpp file, which can be modified, to apply the changes the engine must be compiled again. - - -\subsection*{Game Constants} - -\textbf{constants} namespace: Used for constants that are used over the project, for example colors, colors are hold in open gl vector of 3 values (for RGB colors) - -\begin{lstlisting}[style=C++Style] -namespace constants { - constexpr glm::vec3 LIGHT_BLUE = glm::vec3(0.2F, 0.6F, 1.0F); - ... -} -\end{lstlisting} - -\newpage -\textbf{text} namespace: Parameters concerning text display positions on screen. - -\begin{lstlisting}[style=C++Style] -struct Point { - float x; - float y; -}; - -namespace text { - constexpr Point LIVES_POSITION = {5.0F, 5.0F}; - ... -} -\end{lstlisting} - -\textbf{textures} namespace: Provides paths to texture files and their respective identifiers, in this examples gems are named as: "g" for gems, number of gem and then color of gem - -\begin{lstlisting}[style=C++Style] - -struct TextureInfo { - const char* path; - const char* name; -}; - -namespace textures { - static constexpr TextureInfo textures[] = { - {"resources/textures/background.jpg", "background"}, - {"resources/textures/g1black.png", "block_black"}, - {"resources/textures/g2blue.png", "block_blue"}, - {"resources/textures/g3green.png", "block_green"}, - {"resources/textures/g4purple.png", "block_purple"} - }; -} -\end{lstlisting} - -\section{Game class} -Game class defined in game.h and game.cpp is the main and biggest class in the project, it brings together all other classes and functionalities of the engine. - -\subsubsection{Includes} -\begin{lstlisting}[style=C++Style] -#include "./constants.hpp" -#include "./filesystem.h" -#include "./game_level.h" -#include "./game_object.h" -#include "./resource_manager.h" -#include "./sprite_renderer.h" -#include "./text_renderer.h" -\end{lstlisting} - -\paragraph{Constructor} -Constructor initializes game window size, empties clicked keys, sets game state to game menu, sets initial level map and retrieves max turns from constants file -\begin{lstlisting}[style=C++Style] -Game::Game(ScreenDimensions screen) - : State(GAME_MENU), - Keys(), - KeysProcessed(), - Width(screen.width), - Height(screen.height), - Level(0), - MaxTurns(constants::MAX_TURNS), - Turns(0) -{} -\end{lstlisting} - -\subsection{Game initialization} -\begin{figure}[H] -\centering -\begin{tikzpicture}[node distance=2cm] -\useasboundingbox (-5,0) rectangle (5, -11); % Set a custom bounding box -\node (start) [startstop] {Init game}; - -\node (pro2) [process, below of=start] {Configure Shaders}; -\draw [arrow] (start) -- (pro2); -\node (pro3) [process, below of=pro2] {Load Textures}; -\draw [arrow] (pro2) -- (pro3); - -\node (pro4) [process, below of=pro3] {Render Controls}; -\draw [arrow] (pro3) -- (pro4); - -\node (pro5) [process, below of=pro4] {Load Levels}; -\draw [arrow] (pro4) -- (pro5); - -\node (close) [startstop, below of=pro5] {Finish}; -\draw [arrow] (pro5) -- (close); - -\end{tikzpicture} -\caption{Initialization logic} -\label{fig:initializationLogic} -\end{figure} -We will describe each step below - -\paragraph{Shaders} -Two shaders are used throughout the project, one for rendering sprites (gem tiles) and one for generating post processes (for example shaking the map when tiles get matched) \\ -More on the shaders usage later when we will be discussing postProcessor and Shader clasess - -\begin{lstlisting}[style=C++Style] -void Game::loadShaders() { - ResourceManager::LoadShader("sprite.vs", "sprite.fs", "sprite"); - ResourceManager::LoadShader("post_processing.vs", "post_processing.fs", "postprocessing"); -} -\end{lstlisting} - -\paragraph{Textures} -Textures names are first defined in the constans file, then the game object invokes resource manager to go through texture paths and load them -\begin{lstlisting}[style=C++Style] -void Game::loadTextures() { - const auto *begin = std::begin(textures::textures); - const auto *end = std::end(textures::textures); - - for (auto it = begin; it != end; ++it) { - ResourceManager::LoadTexture(FileSystem::getPath(it->path).c_str(), - it->name); - } -} -\end{lstlisting} - - -\paragraph{Setting controls} -After loading sprite renderers, postprocesses and text, we set instances of classes using loaded resources. -\begin{lstlisting}[style=C++Style] -void Game::renderSpecificControls() { - // set render-specific controls - Renderer = - std::make_unique(ResourceManager::GetShader("sprite")); - Effects = std::make_unique( - ResourceManager::GetShader("postprocessing"), this->Width, this->Height); - Text = - std::make_unique(TextRenderer(this->Width, this->Height)); - Text->Load(FileSystem::getPath("resources/fonts/OCRAEXT.TTF"), text::OCRAEXT_FONT_SIZE); -} -\end{lstlisting} - -\paragraph{Levels} -Levels are loaded automatically from the levels folder, game assumes that every file with extension ".lvl" is a level file and adds it to an array of levels -\begin{lstlisting}[style=C++Style] -void Game::loadLevels() { - const std::string path = "../../resources/levels/"; - for (const auto& entry : std::filesystem::directory_iterator(path)) { - if (entry.is_regular_file() && entry.path().extension() == ".lvl") { - GameLevel level; - level.Load(entry.path().c_str(), this->Width, this->Height); - this->Levels.push_back(level); - } - } - - this->Level = 0; -} -\end{lstlisting} - -\subsection{Game update} -\begin{figure}[H] -\centering -\begin{tikzpicture}[node distance=2cm] -\useasboundingbox (-5,0) rectangle (5, -13); % Set a custom bounding box -\node (start) [startstop] {Update}; - -\node (pro2) [process, below of=start] {Update Tiles}; -\draw [arrow] (start) -- (pro2); -\node (dec1) [decision, below of=pro2, yshift=-1cm] {Points reached?}; -\draw [arrow] (pro2) -- (dec1); - -\node (stopLost) [startstop, left of=dec1, xshift=-2cm] {Game Won}; -\draw [arrow] (dec1) -- node[anchor=north] {yes} (stopLost); - -\node (dec2) [decision, below of=pro4, yshift=-1cm] {Turns exceeded?}; -\draw [arrow] (dec1) -- node[anchor=west]{no} (dec2); -% Arrow from 'Closed?' decision node to 'Main Loop' node -\draw [arrow] (dec2.east) -- ++(2,0) |- node[anchor=south] {no} (start); - -\node (stopWon) [startstop, below of=dec2, yshift=-1cm] {Game Lost}; -\draw [arrow] (dec2) -- node[anchor=west]{yes} (stopWon); - -\end{tikzpicture} -\caption{Game updates logic} -\label{fig:gameUpdatesLogic} -\end{figure} -Game is ended either when the player exceeds maximum move limit (game lost) or when points limit is reached (game won), otherwise the game continues, notice how first we check for win condition then for loss condition, this makes the gameplay a little bit more satisfying and fair. - -\subsection{Controls} -Game allows to move through the grid to highlight a tile, then after clicking "enter" a tile is selected, then by using arrows or WSAD keys user can highlight the tile that will be swapped when again "enter" gets clicked - -\begin{figure}[H] -\centering -\begin{tikzpicture}[node distance=2cm] -\useasboundingbox (-5,0) rectangle (5, -7); % Set a custom bounding box -\node (start) [startstop] {Highight tile (WSAD or arrow keys)}; -\node (pro2) [process, below of=start] {Select tile (ENTER)}; -\draw [arrow] (start) -- (pro2); -\node (pro3) [process, below of=pro2] {Highlight tile to swap (WSAD or arrow keys)}; -\draw [arrow] (pro2) -- (pro3); -\node (stop) [startstop, below of=pro3] {Swap tile (ENTER)}; -\draw [arrow] (pro3) -- (stop); -\end{tikzpicture} -\caption{User controls} -\label{fig:userControls} -\end{figure} - -All of input processing is taken care by game class, and then relied to game level class. -\begin{lstlisting}[style=C++Style] -void Game::handleActiveGame() { - if (this->State == GAME_ACTIVE) { - this->moveLeft(); - this->moveRight(); - this->moveUp(); - this->moveDown(); - this->selectPiece(); - this->unselectPiece(); - } -} -\end{lstlisting} - -\begin{figure}[htp] - \centering - \begin{subfigure}[b]{0.45\textwidth} - \centering - \includegraphics[scale=0.45]{images/controlsSelect.png} - \caption{View of highlighted tile} - \label{fig:highlighted_tile} - \end{subfigure} - \hfill - \begin{subfigure}[b]{0.45\textwidth} - \centering - \includegraphics[scale=0.45]{images/controlsSelectGreen.png} - \caption{View of selected tile} - \label{fig:selected_tile} - \end{subfigure} - - \caption{Tile selection views} - \label{fig:tile_selection} -\end{figure} - -\begin{figure}[H] - \centering - \begin{subfigure}[b]{0.45\textwidth} - \includegraphics[scale=0.45]{images/controlsSwapLeft.png} - \caption{Left tile is set to be swapped when enter key will be hit} - \label{fig:swap_left} - \end{subfigure} - \hfill - \begin{subfigure}[b]{0.45\textwidth} - \includegraphics[scale=0.45]{images/controlsSwapRight.png} - \caption{Right tile is set to be swapped when enter key will be hit} - \label{fig:swap_right} - \end{subfigure} - - \vspace{10pt} - - \begin{subfigure}[b]{0.45\textwidth} - \includegraphics[scale=0.45]{images/controlsSwapUp.png} - \caption{Upper tile is set to be swapped when enter key will be hit} - \label{fig:swap_up} - \end{subfigure} - \hfill - \begin{subfigure}[b]{0.45\textwidth} - \includegraphics[scale=0.45]{images/controlsSwapDown.png} - \caption{Down tile is set to be swapped when enter key will be hit} - \label{fig:swap_down} - \end{subfigure} - - \caption{Tile swap controls} - \label{fig:tile_swaps} -\end{figure} - -\subsection{Rendering} -As mentioned in main loop, the game runs a render method every frame. -\begin{figure}[H] -\centering -\begin{tikzpicture}[node distance=2cm] -\useasboundingbox (-5,0) rectangle (5, -9); % Set a custom bounding box -\node (start) [startstop] {Render}; -\node (dec1) [decision, below of=start] {State}; -\draw [arrow] (start) -- (dec1); -\node (pro1) [process, below of=dec1, xshift=2cm] {Render win view}; -\draw [arrow] (dec1) -- node[anchor=west] {Game won} (pro1); -\node (pro2) [process, below of=dec1, xshift=-2cm] {Render menu view}; -\draw [arrow] (dec1) -- node[anchor=east] {Menu} (pro2); -\node (stop) [startstop, below of=dec1, yshift=-4cm] {Render main view}; -\draw [arrow] (dec1) -- node[anchor=east] {Active} (stop); -\draw [arrow] (pro1) -- (stop); -\draw [arrow] (pro2) -- (stop); -\end{tikzpicture} -\caption{Rendering logic} -\label{fig:renderingLogic} -\end{figure} - -\paragraph{Rendering text} -Rendering menu and win view consists of giving a text class correct coordinates and what text should be displayed - -\begin{lstlisting}[style=C++Style] -From constants.hpp file: -namespace text { - ... - constexpr float WON_X_POSITION = 320.0F; - constexpr float WON_Y_OFFSET = -20.0F; - constexpr float WON_ESCAPE_X_POSITION = 130.0F; - ... -} - -From game.cpp file: -void Game::renderWin() const { - if (this->State == GAME_WIN) { - const float screenMiddleYCoordinate = - static_cast(this->Height) / 2.0F; - Text->RenderText("You WON!", {text::WON_X_POSITION, - screenMiddleYCoordinate + text::WON_Y_OFFSET}, 1.0F, - glm::vec3(0.0F, 1.0F, 0.0F)); - Text->RenderText("Press ENTER to retry or ESC to quit", - {text::WON_ESCAPE_X_POSITION, screenMiddleYCoordinate}, 1.0F, - glm::vec3(1.0F, 1.0F, 0.0F)); - } -} -\end{lstlisting} - -\newpage -\paragraph{Rendering main view} -Rendering main view consists of rendering map and then applying postprocesses and text represending number of turns -\begin{lstlisting}[style=C++Style] -void Game::renderMain() { - if (this->State == GAME_ACTIVE || this->State == GAME_MENU || - this->State == GAME_WIN) { - // begin rendering to postprocessing framebuffer - Effects->BeginRender(); - this->renderDraw(); - // end rendering to postprocessing framebuffer - Effects->EndRender(); - // render postprocessing quad - const auto time = static_cast(glfwGetTime()); - Effects->Render(time); - // render turns (don't include in postprocessing) - this->renderTurns(); - } -} -\end{lstlisting} - -\section{Game level class} -Game level class takes a 2D array of game objects instances (tiles), receives inputs from game class and reacts to them, it handles removing, swaping, adding new tiles on a map and checking for sequences of tiles. It also loads the level file and translates it to in game object - -\paragraph{Reading file} -Game level class goes through each line of level file. \\ -It loads each row to a vector of unsigned int as the level file contains rows of numbers representing tile type -\begin{figure} -\begin{lstlisting} -4 2 3 2 4 3 3 1 3 3 -1 1 2 1 1 2 1 3 1 1 -4 1 4 3 3 4 4 3 2 3 -4 3 3 4 3 3 2 1 2 1 -3 2 3 1 4 4 3 2 4 2 -4 2 1 3 3 2 2 3 1 1 -4 3 2 1 2 2 3 3 4 4 -1 4 3 2 1 4 3 2 1 4 -3 4 3 3 4 4 1 2 3 1 -2 3 2 2 1 2 3 3 4 2 -\end{lstlisting} -\caption{Exemplary level file} -\end{figure} - -\paragraph{Level initialization} -Initializing level takes the loaded vector of ids and creates game objects based on the window dimensions (so that the tiles fill the window evenly), textures are based on the tile id, -we assume that all levels are square so the width and height of the window is always the same. -\newpage1 -\begin{lstlisting}[style=C++Style] -void GameLevel::handleTile(std::vector> tileData, - unsigned int x_coordinate, unsigned int y_coordinate, - float unit_width, float unit_height) { - const unsigned int tileNumber = tileData[y_coordinate][x_coordinate]; - glm::vec3 color = this->assignColor(static_cast(tileNumber)); - - // Directly use the constructor arguments for GameObject with emplace_back - this->Bricks.emplace_back(tileNumber, - glm::vec2(unit_width * static_cast(x_coordinate), unit_height * static_cast(y_coordinate)), - glm::vec2(unit_width, unit_height), - ResourceManager::GetTexture(idTextureMap[tileNumber]), - color - ); -} - -void GameLevel::init(std::vector> tileData, unsigned int levelWidth, unsigned int levelHeight) -{ - // calculate dimensions - unsigned int height = tileData.size(); - unsigned int width = tileData[0].size(); // note we can index vector at [0] since this function is only called if height > 0 - float unit_width = static_cast(levelWidth) / static_cast(width); - // initialize level tiles based on tileData - for (unsigned int y_coordinate = 0; y_coordinate < height; ++y_coordinate) - { - for (unsigned int x_coordinate = 0; x_coordinate < width; ++x_coordinate) - { - this->handleTile(tileData, x_coordinate, y_coordinate, unit_width, unit_width); - } - } -} -\end{lstlisting} -2 -\subsection{Highlighting, selecting and swapping tiles} -There are four steps in order for the tile to be swapped, logic of fulfilling this conditions is handled in game level class - -\paragraph{Highlighting tile} -First the tile must be highlighted, game level makes sure that only one tile is highlighted by keeping the index of highlighted tile and changing it whenever user inputs a change - -\paragraph{Selecting tile} -Then the tile must be selected, again game level has a special parameter just to keep track of which tile is selected -\begin{lstlisting}[style=C++Style] -void GameLevel::selectPiece() { - this->Bricks.at(selectedTile).Selected = true; - ... -} - -void GameLevel::unselectPiece() { - this->Bricks.at(selectedTile).Selected = false; - this->unswapAll(); -} -\end{lstlisting} - -\paragraph{Which tile will be swapped} -Then the user must choose which tile will be swapped, game level does not keep special parameter for that, it just automatically deselects all tiles in the selected tile neighboorhood if user choose any other tile -\begin{lstlisting}[style=C++Style] -void GameLevel::moveRight() -{ - if(!this->Bricks.at(selectedTile).Selected) { - this->Bricks.at(selectedTile).Highlighted = false; - this->selectedTile++; - if(static_cast(this->selectedTile) > this->Bricks.size()) { - this->selectedTile--; - } - this->Bricks.at(selectedTile).Highlighted = true; - return; - } - this->swapRight(); -} -\end{lstlisting} - -\paragraph{Confirming swap} -At the end user must confirm the swap, then the swap is checked if it creates the pattern of at least 3 tiles -\begin{lstlisting}[style=C++Style] -void GameLevel::confirmSwap() -{ - const bool canSwap = this->checkSwap(); - if(this->swappableTile != -1 && canSwap) { - this->Bricks.at(selectedTile).Selected = false; - this->Bricks.at(selectedTile).Highlighted = false; - this->unswapAll(); - const GameObject temp = this->Bricks[this->swappableTile]; - this->Bricks[this->swappableTile].Position = this->Bricks[this->selectedTile].Position; - this->Bricks[this->selectedTile].Position = temp.Position; - std::swap(this->Bricks[this->swappableTile], this->Bricks[this->selectedTile]); - this->selectedTile = 0; - this -> swappableTile = -1; - } -} - -void GameLevel::selectPiece() { - this->Bricks.at(selectedTile).Selected = true; - if(this->Bricks.at(selectedTile).Selected) { - this->confirmSwap(); - } -} -\end{lstlisting} - -\subsection{Matching logic} -When 3, 4 or 5 tiles get match in a row or column, matching function removes those tiles, fills the empty space with blocks from above and fills the final empty space with randomly generated tiles - -\paragraph{Finding matches} -To simplify finding matches first game objects tiles is transformed into vector of ids, then this vector is converted into vectors of columns and rows and finaly those vectors are checked for matches - -\begin{figure}[H] -\centering -\begin{tikzpicture}[node distance=2cm] -\useasboundingbox (-5,0) rectangle (5, -9); % Set a custom bounding box -\node (start) [startstop] {Finding matches logic}; -\node (pro1) [process, below of=start] {Convert to ids vector}; -\draw [arrow] (start) -- (pro1); -\node (pro2) [process, below of=pro1] {Convert to rows and columns ids vectors}; -\draw [arrow] (pro1) -- (pro2); -\node (pro3) [process, below of=pro2] {Find matches}; -\draw [arrow] (pro2) -- (pro3); -\node (stop) [startstop, below of=pro3] {Return matches}; -\draw [arrow] (pro3) -- (stop); -\end{tikzpicture} -\caption{Finding matches logic} -\label{fig:findingMatches} -\end{figure} - -\newpage -\subsection{Processing matches} -\paragraph{Removing blocks} -After finding matches, blocks where matches appear are set to be replaced by replacing their tile ids with -1 -\paragraph{Replacing blocks} -After setting tiles to be replaced, algorithm goes through blocks with ids equal to -1, checks if there are any blocks above, if yes it replaces the block from above with block from below, if there is no block above, it generates one randomly - -\begin{figure}[H] -\centering -\includegraphics{images/processLogicMatches.pdf} -\caption{Logic for processing matches} -\end{figure} - -\subsection{Game objects} -Game object class holds informations about tiles, its position, textures and statuses - -\paragraph{Tile draw} -Based on a status of tile it can be drawn in 4 ways: -\begin{enumerate} -\item No status at all, it is drawn without any color overlayed on it -\item Highlighted, it is drawn with red overlay -\item Selected, it is drawn with green overlay -\item ToSwap, it is drawn with orange overlay -\end{enumerate} - -\begin{lstlisting}[style=C++Style] - -void GameObject::Draw(SpriteRenderer &renderer) -{ - if(ToSwap) { - glm::vec3 color = hexToVec3("#f97316"); - renderer.DrawSprite(this->Sprite, this->Position, this->Size, this->Rotation, color); - return; - } - if(!Highlighted) { - glm::vec3 empty(1.0f, 1.0f, 1.0f); - renderer.DrawSprite(this->Sprite, this->Position, this->Size, this->Rotation, empty); - return; - renderer.DrawSprite(this->Sprite, this->Position, this->Size, this->Rotation, this->Color); - return; - } - if(!Selected) { - glm::vec3 red(1.0f, 0.0f, 0.0f); - renderer.DrawSprite(this->Sprite, this->Position, this->Size, this->Rotation, red); - return; - } - glm::vec3 green(0.0f, 1.0f, 0.0f); - renderer.DrawSprite(this->Sprite, this->Position, this->Size, this->Rotation, green); -} -\end{lstlisting} - -Engine uses two helpful methods to easily translate color hex to glm::vec3 color - -\begin{lstlisting}[style=C++Style] -glm::vec3 hexToVec3(const std::string& hex) { - if (hex.size() != 6 && hex.size() != 7) { - throw std::invalid_argument("Invalid hex string length."); - } - size_t offset = hex[0] == '#' ? 1 : 0; - - int r = std::stoi(hex.substr(offset, 2), nullptr, 16); - int g = std::stoi(hex.substr(offset + 2, 2), nullptr, 16); - int b = std::stoi(hex.substr(offset + 4, 2), nullptr, 16); - - return glm::vec3(r / 255.0f, g / 255.0f, b / 255.0f); -} - -glm::vec3 vectorToVec3(const std::vector& vec) { - if (vec.size() != 3) { - throw std::invalid_argument("Vector must have exactly 3 elements."); - } - - return glm::vec3(vec[0] / 255.0f, vec[1] / 255.0f, vec[2] / 255.0f); -} -\end{lstlisting} - - -\begin{figure}[htp] -\centering -\includegraphics[scale=0.75]{images/statesGameObject.pdf} -\caption{Game object states} -\end{figure} - -\subsection{Text Renderer} -Text renderer class loads the library and single characters of font used to render text, applies text shaders, sets the text at the position requested by engine and removes text after it is not used \\ -We also use a shader for text, we will describe the class and shader in this section - -\paragraph{initialization} -When text render class is initialized, it loads the correct shader and sets it up to work - -\subsubsection{Loading font} -When initializing text render objects, we provide a ttf file from resources/fonts folder. Text render object calls free type library to load a font - -\paragraph{Initializing loading} -First we clear any characters that were loaded previously, initialize freeType library and check if it was correctly loaded - -\begin{lstlisting}[style=C++Style] -void TextRenderer::Load(const std::string &font, unsigned int fontSize) -{ - // first clear the previously loaded Characters - this->Characters.clear(); - // then initialize and load the FreeType library - FT_Library freeTypeLibrary; - // all functions return a value different than 0 whenever an error occurred - if (FT_Init_FreeType(&freeTypeLibrary)) { - std::cout << "ERROR::FREETYPE: Could not init FreeType Library" << std::endl; - } - ... - \end{lstlisting} - -\newpage -\paragraph{Actual loading and configuration} -We load the font using freeType, after checking if it was loaded correctly we set size of a single character, disable byte-alignment in order for the text to be rendered correctly, preload first 128 [ASCII] characters and since we already loaded everything we wanted we destroy FreeType library - -\begin{lstlisting}[style=C++Style] -void TextRenderer::Load(const std::string &font, unsigned int fontSize) -{ - ... - FT_Face face = this -> loadFontAsFace(freeTypeLibrary, font); - // set size to load glyphs as - FT_Set_Pixel_Sizes(face, 0, fontSize); - // disable byte-alignment restriction - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - face = this -> preloadCharacters(face); - this -> destroyFreeType(face, freeTypeLibrary); -} -\end{lstlisting} - -\begin{figure}[htp] -\centering -\begin{tikzpicture}[node distance=1.5cm] -\useasboundingbox (-5,0) rectangle (5, -13cm); % Set a custom bounding box -\node (start) [startstop] {Load font}; -\node (pro1) [process, below of=start] {Clear previous characters}; -\draw [arrow] (start) -- (pro1); - -\node (pro2) [process, below of=pro1] {Initialize FreeType library}; -\draw [arrow] (pro1) -- (pro2); - -\node (pro3) [process, below of=pro2] {Load font}; -\draw [arrow] (pro2) -- (pro3); - -\node (pro4) [process, below of=pro3] {Set letter pixel size}; -\draw [arrow] (pro3) -- (pro4); - -\node (pro5) [process, below of=pro4] {Disable byte allignment}; -\draw [arrow] (pro4) -- (pro5); - -\node (pro6) [process, below of=pro5] {Preload ASCII characters}; -\draw [arrow] (pro5) -- (pro6); - -\node (pro7) [process, below of=pro6] {Destroy FreeType}; -\draw [arrow] (pro6) -- (pro7); - -\node (stop) [startstop, below of=pro7] {Finish loading}; -\draw [arrow] (pro7) -- (stop); - -\end{tikzpicture} -\caption{Loading font logic} -\label{fig:loadingFont} -\end{figure} - -\subsubsection{Loading characters} -In order to preload ASCII characters we use a method loadCharacter from TextRenderer, it uses FreeType library to load character, then generates textures for a character (it needs to allign a texture to character), sets options for texture and adds the character object to characters array - -\begin{figure}[htp] -\centering -\begin{tikzpicture}[node distance=1.5cm] -\useasboundingbox (-5,0) rectangle (5, -10cm); % Set a custom bounding box -\node (start) [startstop] {Load character}; -\node (pro1) [process, below of=start] {Load character using FreeType}; -\draw [arrow] (start) -- (pro1); - -\node (pro2) [process, below of=pro1] {Generate Texture}; -\draw [arrow] (pro1) -- (pro2); - -\node (pro3) [process, below of=pro2] {Setup texture options}; -\draw [arrow] (pro2) -- (pro3); - -\node (pro4) [process, below of=pro3] {Create character instance}; -\draw [arrow] (pro3) -- (pro4); - -\node (pro5) [process, below of=pro4] {Push character to characters array}; -\draw [arrow] (pro4) -- (pro5); - -\node (stop) [startstop, below of=pro5] {Finish loading}; -\draw [arrow] (pro5) -- (stop); - -\end{tikzpicture} -\caption{Loading single character logic} -\label{fig:loadingCharacter} -\end{figure} - -\newpage -\paragraph{Texture options} - -\begin{itemize} - \item The first two lines set the wrapping mode of the texture in the S and T directions (usually the x and y axes) to \texttt{GL\_CLAMP\_TO\_EDGE}. This means if the texture coordinates go beyond [0,1], the texture will not repeat but instead clamp to the edge values. - \item The next two lines set the minification and magnification filter to \texttt{GL\_LINEAR}. This means when the texture is scaled down or up, it will use linear interpolation between the texture coordinates, resulting in a smoother texture appearance. -\end{itemize} - -\begin{lstlisting}[style=C++Style] -void TextRenderer::setTextureOptions() const { - // set texture options - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); -} -\end{lstlisting} - -\newpage -\subsubsection{Rendering text} -We are given text as a C++ string and a position, method is supposed to automatically render text so that it fits in a position that was provided \\ -We achieve that by iterating through each character of a string and putting it next to each other at a gap equal to a previous character width and height - -\begin{figure}[htp] -\centering -\includegraphics[scale=1.00]{images/glyph_offset.png} -\caption{Glyph metrics from learnopengl} -\label{fig:glyphMetrics} -\end{figure} - -Calculating x and y positions of each character \\ -$x_{pos}$ and $y_{pos}$ is the final x/y position of character \\ -$x_{start}$ and $y_{start}$ is the position inputed to the method render text \\ -$x_{bearing}$ and $y_{bearing}$ is a parameter shown in image above \\ -$s$ is scale \\ -We calculate final bearing of $y_{pos}$ by subtracting bearing of "H" character from the bearing of actual character, we do it this way because "y" bearing of "H" character is highest \\ -\[ -x_{pos} = x_{start} + x_{bearing} * s -\] -\[ -y_{pos} = y_{start} + (H_{bearing} - y_{bearing}) * s -\] - -\subsection{Resource Manager} -Resource manager class loads textures and shaders from files. - -\paragraph{Loading textures} -After giving a name of texture to a method it is loaded (converted to raw byte data) using stb image library, we receive texture width, height and number of channels and later use it to generate texture using texture class. At the end we free image data - -\begin{lstlisting}[style=C++Style] -// RGB for Red Green Blue, RGBA for alpha channel -GLenum getTextureFormat(int nrChannels) { - switch (nrChannels) { - case 1: return GL_RED; - case 3: return GL_RGB; - case 4: return GL_RGBA; - default: - throw std::runtime_error("Unsupported number of channels in the image."); - } -} - -Texture2D ResourceManager::loadTextureFromFile(const char *file) -{ - // create texture object - Texture2D texture; - // load image - int width; - int height; - int nrChannels; - unsigned char* data = stbi_load(file, &width, &height, &nrChannels, 0); - GLenum format = getTextureFormat(nrChannels); - texture.Internal_Format = format; - texture.Image_Format = format; - // now generate texture - texture.Generate(width, height, data); - // and finally free image data - stbi_image_free(data); - return texture; -} -\end{lstlisting} - -\subsubsection{Loading shaders} -In order to load shaders we load up to three different source codes for vertex, fragment and geometry shaders and compile them in shader class. - -\paragraph{Shader types} -\begin{itemize} - \item \textbf{Vertex Shader:} This is the first stage in the graphics pipeline that processes each vertex and is responsible for transforming vertex positions from object space (local coordinates) to clip space. Additionally, it can manipulate vertex attributes such as colors, normals, and texture coordinates. - \item \textbf{Fragment Shader:} Often referred to as a pixel shader, this stage operates on each fragment (potential pixel) generated by rasterization. It determines the final color of the pixels on the screen. Fragment shaders can apply textures, compute lighting, and handle other surface details. - \item \textbf{Geometry Shader:} This is an optional shader stage that sits between the vertex and fragment shaders. It can generate new graphics primitives (like points, lines, and triangles) from input primitives. It offers more flexibility in processing than the vertex shader but can be computationally intensive. -\end{itemize} - -\subsection{Shader class} -Shader object compiles shader from shader string code and sets its input parameters - -\subsubsection{Compiling shader} -When compiling shader we provide shader source code, create shader from shader type (vertex, fragment or geometry), compile it, attach to program and link program so that it can be used by engine, at the end we remove the shader code as it is already in our engine in order to free resources. - -\begin{lstlisting}[style=C++Style] -unsigned int Shader::compileShader(const char *shaderSource, const std::string& type, const GLenum shaderType) { - unsigned int shaderPointer = 0; - if(shaderSource != nullptr) { - shaderPointer = glCreateShader(shaderType); - glShaderSource(shaderPointer, 1, &shaderSource, nullptr); - glCompileShader(shaderPointer); - checkCompileErrors(shaderPointer, type); - } - return shaderPointer; -} - -void Shader::Compile(const char* vertexSource, const char* fragmentSource, const char* geometrySource) -{ - unsigned int sVertex = this->compileShader(vertexSource, "VERTEX", GL_VERTEX_SHADER); - // shader program - this->ID = glCreateProgram(); - glAttachShader(this->ID, sVertex); - ... - glLinkProgram(this->ID); - checkCompileErrors(this->ID, "PROGRAM"); - // delete the shaders as they're linked into our program now and no longer necessary - glDeleteShader(sVertex); - ... -} -\end{lstlisting} - -\begin{figure}[htp] -\centering -\begin{tikzpicture}[node distance=1.5cm] -\useasboundingbox (-5,0) rectangle (5, -13cm); % Set a custom bounding box -\node (start) [startstop] {Processing shader}; -\node (pro1) [process, below of=start] {Create shader}; -\draw [arrow] (start) -- (pro1); - -\node (pro2) [process, below of=pro1] {Get shader source}; -\draw [arrow] (pro1) -- (pro2); - -\node (pro3) [process, below of=pro2] {Compile shader}; -\draw [arrow] (pro2) -- (pro3); - -\node (pro4) [process, below of=pro3] {Create shader program}; -\draw [arrow] (pro3) -- (pro4); - -\node (pro5) [process, below of=pro4] {Attach shader to program}; -\draw [arrow] (pro4) -- (pro5); - -\node (pro6) [process, below of=pro5] {Link program to engine}; -\draw [arrow] (pro5) -- (pro6); - -\node (pro7) [process, below of=pro6] {Delete shaders}; -\draw [arrow] (pro6) -- (pro7); - -\node (stop) [startstop, below of=pro7] {Finish processing}; -\draw [arrow] (pro7) -- (stop); - -\end{tikzpicture} -\caption{Processing shader} -\label{fig:processShader} -\end{figure} - -\subsection{Dependency Management} -There are several libraries: OpenGL, GLFW, stb\_image and freetype being integrated into engine, they are simply included at the top of any files that need them. - -\chapter{Practical Use of Engine} -In the previous chapters, we explained on the architecture, design, and construction of our match-three game engine This chapter focuses on practical application of this engine by illustrating the creation of a functional match-three game from scratch. - -\section{Initial Setup and Configuration} -Before we start creating the game, We need to set up the development environment to utilize our game engine's capabilities fully. Using a modern IDE (Integrated Development Environment) like Visual Studio or Code::Blocks can ease the process. - -\paragraph{Importing the engine} Initiate a new C++ project and import our game engine's header and source files. Ensure that the necessary libraries related to OpenGL are linked. - -\paragraph{Configuration of OpenGL} Validate that the OpenGL version compatible with our engine is installed. This often involves setting up GLEW (OpenGL Extension Wrangler Library) or similar middleware. - -\subsection{Choosing assets} -Choosing assets is the most important part of our match three engine design \\ -We are going to use site \href{https://opengameart.org/}{https://opengameart.org/} which offers free game assets with permissive license, wcansFilterede are going to search for assets with the most permisible CC0 license which is an equivalent to public license - -\begin{figure}[H] -\centering -\includegraphics[scale=1.00]{images/openGameArt.png} -\caption{Searching for 2D art on OpenGameArt} -\label{fig:2dArtFig} -\end{figure} - -\begin{figure}[H] -\centering -\includegraphics[scale=1.00]{images/OGASearch.png} -\caption{Searching for CC0 licensed assets} -\label{fig:searchingCC0} -\end{figure} - -\paragraph{Filtering non tiles assets} -Our first step is to filter all assets that refer to graphics that cannot be used as tiles, for example UI elements, icons or buttons - -\subsubsection{Filtering tile assets} -We end up with 4 assets to choose from, based on 2 parameters we filtered 3 of them - -\paragraph{Using mechanics not available in engine} -First we filter asset pack that uses dynamic falling mechanic which is not present in our engine - -\begin{figure}[htp] -\centering -\includegraphics[scale=0.4]{images/cans.png} -\caption{Cans have different sprite depending on if they are falling or not, we do not have such mechanic in our engine \cite{cansFiltered}} -\label{fig:cansFiltered} -\end{figure} - -\paragraph{Too much details} -Two more tile assets use too much details inside the tiles, this would require our game to react to those details to make sure that the gameplay experience is compatible with the tiles look. - -\begin{figure}[htp] -\centering - -\begin{minipage}{0.45\textwidth} -\centering -\includegraphics[scale=0.50]{images/tooMuchDetailsOne.png} -\caption{First assets with too much details inside the detail \cite{tooMuchOne}} -\label{fig:tooMuchDetailsOne} -\end{minipage}\hfill -\begin{minipage}{0.45\textwidth} -\centering -\includegraphics[scale=1.00]{images/tooMuchDetailsTwo.png} -\caption{Second assets with too much details inside the detail \cite{tooMuchTwo}} -\label{fig:tooMuchDetailsTwo} -\end{minipage} - -\end{figure} - -\paragraph{Chosen assets} -Finally we settled on these tiles, they look simple, work well with our shaders and engine gameplay and offer high quality non-pixelated files - -\begin{figure}[htp] -\centering -\includegraphics[scale=1.00]{images/gemMatchThree.png} -\caption{Chosen assets \cite{assetsMatchThree}} -\label{fig:matchThreeAssets} -\end{figure} - -\subsection{Loading assets} -In order to use our assets in the game engine we need to define their names in the engine code and put the files into correct folder - -\paragraph{Setting files} -All non-code files used in our engine are stored in resources older \\ -Textures in particular are stored in "textures" folder \\ -That is where we unpack our tiles, files in the unzipped folder are named with a pattern: "Gem Type[number] [color].png" \\ -In order to simplify importing assets name we created a short python script that renames all tiles to "g[number][color].png" - -\begin{lstlisting}[style=mystyle][caption=Python code for renaming files] -import os -import re - -def rename_files_in_directory(directory_path): - # List all files in the directory - for filename in os.listdir(directory_path): - # Match filenames using regex - match = re.match(r"Gem Type(\d+) (\w+)\.png", filename) - if match: - number = match.group(1) - color = match.group(2).lower() # Convert to lowercase - new_filename = f"g{number}{color}.png" - # Rename files - os.rename( - os.path.join(directory_path, filename), - os.path.join(directory_path, new_filename) - ) - -# Call the function with the path to your directory -rename_files_in_directory("./") -\end{lstlisting} - -\paragraph{Setting tiles names in constants file} -After choosing tiles we are going to use we modify constants.hpp file, namespace textures and add our tiles tot textures array - -\begin{lstlisting}[style=C++Style] -namespace textures { - static constexpr TextureInfo textures[] = { - {"resources/textures/background.jpg", "background"}, - {"resources/textures/g1black.png", "block_black"}, - {"resources/textures/g2blue.png", "block_blue"}, - {"resources/textures/g3green.png", "block_green"}, - {"resources/textures/g4purple.png", "block_purple"} - }; -} -\end{lstlisting} - +\chapter{Case Study: Development of a Prototype Match Three Game} \section{Game Concept and Design} +In order to gauge the usability of our game engine, we decided to create a sample match three game using royalty free assets. First part of this process was game design -\subsection{Game Concept} -A game concept encapsulates the core idea or essence of a game. It's the core vision that drives the entire game development process, giving direction to the gameplay and user experience. In match-three games, the primary concept is to align three or more similar game elements in a row or column to achieve points. +\subsection{Game Theme and Narrative} -\paragraph{Our concept} -In order to benchmark our game engine we are going to focus on simplicity and rudimentary features of match-three games. We are going to implement a game that while offers only elementary features can still be called a match-three game and can be won or lost - -\subsection{Game Design} -Game design describes how a game should be structured and played. It's the process of deciding game rules, creating levels, setting challenges, and determining how players interact with the game. While the game concept is the overarching idea, game design provides the detailed blueprint. In match-three games factors such as how new elements are introduced, the complexity of levels, the scoring system and win/fail condistions are all vital components of the design. - -\subsubsection{Our design} - -\paragraph{Scoring system} -Let's devise a scoring system for a match-three game with increasing difficulty levels. We will also differentiate the scores based on matching 3, 4, or 5 tiles. \\ - -First, let's determine the scores for each matching set: -\begin{enumerate} -\item Match 3 tiles = 10 points -\item Match 4 tiles = 20 points -\item Match 5 tiles = 40 points -\end{enumerate} - -When devising the point system for matching sets in the game, there are several considerations we kept in mind: - -\newpage -\paragraph{Progressive Increase} -We wanted the points to represent a clear progression, making matches of larger sets more valuable, incentivizing players to aim for them. \\ -3-tile matches are the baseline, so they were assigned a modest 10 points. \\ -4-tile matches are rarer and more difficult to create than 3-tile matches, so they're assigned 20 points, doubling the value from the 3-tile matches. \\ -5-tile matches are even rarer, so they were given a point value that's a bit more than the sum of 3-tile and 4-tile matches, settling at 40 points. \\ - -\paragraph{Simple numbers} -Using round numbers like 10, 20, and 35 makes it easy for players to quickly understand and calculate their scores. Complexity can deter casual gamers, and match-three games are typically aimed at a wide audience, including those who may prefer simpler mechanics. - - -\paragraph{Winning} -Given these scores, let's decide the target scores for each difficulty level. We'll set these target scores based on an estimated number of matches a player might make in a given game, we will create Easy, Normal and Hard levels - -\begin{itemize} - - \item Easy Level: - \begin{itemize} - - \item Target: 400 points - \item This might involve roughly 40 matches 3 tiles, or a combination of fewer 4 and 5 tile matches. This offers the player plenty of room for errors and learning the game mechanics. - - \end{itemize} - \item Normal Level: - \begin{itemize} - - \item Target: 600 points - \item For this target, the player might need to make 60 matches of 3 tiles, which will not be possible since we set up the limit of matches to 40 or a combination of fewer 4 and 5 tile matches, which will force the player to use bigger matches. This requires the player to make more strategic matches and utilize power-ups or special moves (if any). - - \end{itemize} - \item Hard Level: - \begin{itemize} - \item Target: 800 points - \item Achieving this score might involve 80 matches of 3 tiles, or a combination of fewer 4 and 5 tile matches. With our limit of matches set up to 40 will force the player to keep the average matching to at least 20 points per match. This level is challenging and will likely require the player to plan moves carefully and maximize opportunities to match 4 or 5 tiles for higher scores. - \end{itemize} - -\end{itemize} - -\paragraph{Losing} -The game is lost whenever player reaches 40 turns, this forces the player to act under limited moveset and ensures that the game offers a challenge \\ -Number 40 was chosen based on the similar choice in professional match-three games, it is the same number used for matching 5 tiles, it offers gameplay that is not too short neither too long and it is a "nice" number, even, round and with many divisors - -\newpage -\paragraph{Setting up values in constants file} -In order to implement this design in our game engine we need to again modify constants.hpp file in following way: - -\begin{lstlisting}[style=C++Style] -struct DifficultySettings { - const std::string name; - const int points; - const int limit; - const std::vector matchScores; - - DifficultySettings(const std::string& n, int p, int l, const std::vector& scores) - : name(n), points(p), limit(l), matchScores(scores) {} -}; - -namespace difficulty { - std::vector settings = { - DifficultySettings("easy", 400, 40, {10, 20, 40}), - DifficultySettings("normal", 600, 40, {10, 20, 40}), - DifficultySettings("hard", 80, 40, {10, 20, 40}) - }; -} - -\end{lstlisting} +We decided to create a classic gem filled match three game, comparable to old bejeweled games. \subsection{Core Mechanics and Gameplay} Game utilizes full functionality of our engine, player needs to match three or more gems, chaining matches or achieving higher combos grants the player higher score -\paragraph{Generating levels} -We used python script to first generate levels user can choose from in order to simplify our work so we do not need to create the levels on our own \\ -Later in a course of our game the game engine handles generating new random blocks when old ones are replaced - \subsection{User Interface and User Experience} -The Heads-Up Display is minimalistic, showcasing only essential elements, score and moves left. -\paragraph{Choosing level and difficulty} -User can choose both level and difficulty on the menu screen using WSAD keys +\begin{itemize} + \item Game HUD: The Heads-Up Display is minimalistic, showcasing only essential elements like score and moves left. -\begin{figure}[htp] -\centering + \item, Feedback Mechanics: Animations, particles and shaders provide feedback for successful matches and combos. -\begin{minipage}{0.32\textwidth} -\centering -\includegraphics[scale=0.40]{images/easy.png} -\caption{UI when choosing easy level} -\label{fig:easy} -\end{minipage} -\hfill -\begin{minipage}{0.32\textwidth} -\centering -\includegraphics[scale=0.40]{images/normal.png} -\caption{UI when choosing normal level} % Adjusted the caption -\label{fig:normal} -\end{minipage} -\hfill -\begin{minipage}{0.32\textwidth} -\centering -\includegraphics[scale=0.40]{images/hard.png} -\caption{UI when choosing hard level} % Adjusted the caption -\label{fig:hard} -\end{minipage} +\end{itemize} -\end{figure} +\section{Application of the Engine in Real-world Development} +After designing the game, we started creating it using our game engine. -\begin{figure}[H] -\centering -\includegraphics[scale=1.00]{images/scoreMoves.png} -\caption{Score and moves left text} -\label{fig:scoreMovesLeft} -\end{figure} +\subsection{Seamless Integration with Game Design} +Since the engine was created exactly with this type of game in mind, brining design decisions to life was very easy -\begin{figure}[H] -\centering -\includegraphics[scale=1.00]{images/redMoves.png} -\caption{Score and moves left text, when there are less than 10 moves left} -\label{fig:scoreMovesLeft10} -\end{figure} +\subsection{Challenges in Implementation} -\paragraph{Losing and winning} -Whenever player loses (reaches max number of turns) or wins (reaches target points), game stops, clears the level and displays a message -\begin{figure}[htp] -\centering -\includegraphics[scale=0.50]{images/losing.png} -\caption{Game message when lost} -\label{fig:losingScreen} -\end{figure} - -\begin{figure}[htp] -\centering -\includegraphics[scale=0.50]{images/win.png} -\caption{Game message when won} -\label{fig:winingScreen} -\end{figure} +Biggest challenge was our toolset, we had to either modify the existing engine solutions or create new one from scratch. \subsection{Advantages of a Custom-built Engine} @@ -2550,104 +1236,14 @@ In order to overcome differences for different operating system, a lot of time w Using multiple different libraries and integrating them with our game engine was a challenge, thanks to existing solutions like learnopengl repository which already overcame those difficulties and KDevelop easy approach to external libraries it was possible to implement all of libraries within our game engine. \subsection{Time management} -Lastly, probably the biggest challenge was managing time for this thesis. Finishing it required a lot of determination, setting barriers and working every day to ensure that the thesis is finished on time. Thankfully past experiences helped in guiding through this task that could be compared to colossus. +Lastly, probably the biggest challenge was managing time for this thesis. Finishing it required a lot of determination, setting barriers and working every day to ensure that the thesis is finished on time. Thankfully past experiences helped in guiding through this task that could be compared to colos\textbf{sus}. +\chapter{References} + +\chapter{Appendices} \bibliography{references} -\chapter{List of Illustrations} -\begin{longtable}{|p{10cm}|c|} - \hline - Image Number Title & Page \\ - \hline - \endhead - Fig. \ref{fig:redstoneGatesFig} Minecraft redstone offers introduciton to logical gates \cite{redstoneGates} & \pageref{fig:redstoneGatesFig} \\ - \hline - Fig. \ref{fig:bejeweledGameplay} Bejeweled gameplay \cite{bejeweledGameplay} & \pageref{fig:bejeweledGameplay} \\ - \hline - Fig. \ref{fig:marketShare} Desktop OS market share worldwide \cite{marketShareChart} & \pageref{fig:marketShare} \\ - \hline - Fig. \ref{fig:gameEngineComponent} Components used in our game engine & \pageref{fig:gameEngineComponent} \\ - \hline - Fig. \ref{fig:oGLWikipedia} Connection between Linux and OpenGL-based games \cite{openGLWikipedia} & \pageref{fig:oGLWikipedia} \\ - \hline - Fig. \ref{fig:foldersWindowsLinux} Separating folders with backslash and forward slash is one of many differneces between Windows and Linux systems \cite{windowsLinuxSlash} & \pageref{fig:foldersWindowsLinux} \\ - \hline - Fig. \ref{fig:gladSiteImage} Glad website allowing for different configurations of OpenGL \cite{gladSite} & \pageref{fig:gladSiteImage} \\ - \hline - Fig. \ref{fig:ftSite} ftinspect showcasing capabilities of FreeType library \cite{freeTypeSite} & \pageref{fig:ftSite} \\ - \hline - Fig. \ref{fig:fileSystemsFig} Windows file system consists of drives, which contain folders which contain subfolders and files, in Linux everything is a file & \pageref{fig:fileSystemsFig} \\ - \hline - Fig. \ref{fig:gameLoopFlowchart} Game loop flowchart representation & \pageref{fig:gameLoopFlowchart} \\ - \hline - Fig. \ref{fig:exemplaryAssets} Exemplary assets for match three tiles \cite{assetsMatchThree} & \pageref{fig:exemplaryAssets} \\ - \hline - Fig. \ref{fig:main_function_logic} Main function logic & \pageref{fig:main_function_logic} \\ - \hline - Fig. \ref{fig:mainLoopLogic} Main loop & \pageref{fig:mainLoopLogic} \\ - \hline - Fig. \ref{fig:initializationLogic} Initialization logic & \pageref{fig:initializationLogic} \\ - \hline - Fig. \ref{fig:gameUpdatesLogic} Game updates logic & \pageref{fig:gameUpdatesLogic} \\ - \hline - Fig. \ref{fig:userControls} User controls & \pageref{fig:userControls} \\ - \hline - Fig. \ref{fig:highlighted_tile} View of highlighted tile & \pageref{fig:highlighted_tile} \\ - \hline - Fig. \ref{fig:selected_tile} View of selected tile & \pageref{fig:selected_tile} \\ - \hline - Fig. \ref{fig:tile_selection} Tile selection views & \pageref{fig:tile_selection} \\ - \hline - Fig. \ref{fig:swap_left} Left tile is set to be swapped when enter key will be hit & \pageref{fig:swap_left} \\ - \hline - Fig. \ref{fig:swap_right} Right tile is set to be swapped when enter key will be hit & \pageref{fig:swap_right} \\ - \hline - Fig. \ref{fig:swap_up} Upper tile is set to be swapped when enter key will be hit & \pageref{fig:swap_up} \\ - \hline - Fig. \ref{fig:swap_down} Down tile is set to be swapped when enter key will be hit & \pageref{fig:swap_down} \\ - \hline - Fig. \ref{fig:tile_swaps} Tile swap controls & \pageref{fig:tile_swaps} \\ - \hline - Fig. \ref{fig:renderingLogic} Rendering logic & \pageref{fig:renderingLogic} \\ - \hline - Fig. \ref{fig:findingMatches} Finding matches logic & \pageref{fig:findingMatches} \\ - \hline - Fig. \ref{fig:loadingFont} Loading font logic & \pageref{fig:loadingFont} \\ - \hline - Fig. \ref{fig:loadingCharacter} Loading single character logic & \pageref{fig:loadingCharacter} \\ - \hline - Fig. \ref{fig:glyphMetrics} Glyph metrics from learnopengl & \pageref{fig:glyphMetrics} \\ - \hline - Fig. \ref{fig:processShader} Processing shader & \pageref{fig:processShader} \\ - \hline - Fig. \ref{fig:2dArtFig} Searching for 2D art on OpenGameArt & \pageref{fig:2dArtFig} \\ - \hline - Fig. \ref{fig:searchingCC0} Searching for CC0 licensed assets & \pageref{fig:searchingCC0} \\ - \hline - Fig. \ref{fig:cansFiltered} Cans have different sprite depending on if they are falling or not, we do not have such mechanic in our engine \cite{cansFiltered} & \pageref{fig:cansFiltered} \\ - \hline - Fig. \ref{fig:tooMuchDetailsOne} First assets with too much details inside the detail \cite{tooMuchOne} & \pageref{fig:tooMuchDetailsOne} \\ - \hline - Fig. \ref{fig:tooMuchDetailsTwo} Second assets with too much details inside the detail \cite{tooMuchTwo} & \pageref{fig:tooMuchDetailsTwo} \\ - \hline - Fig. \ref{fig:matchThreeAssets} Chosen assets \cite{assetsMatchThree} & \pageref{fig:matchThreeAssets} \\ - \hline - Fig. \ref{fig:easy} UI when choosing easy level & \pageref{fig:easy} \\ - \hline - Fig. \ref{fig:normal} UI when choosing normal level & \pageref{fig:normal} \\ - \hline - Fig. \ref{fig:hard} UI when choosing hard level & \pageref{fig:hard} \\ - \hline - Fig. \ref{fig:scoreMovesLeft} Score and moves left text & \pageref{fig:scoreMovesLeft} \\ - \hline - Fig. \ref{fig:scoreMovesLeft10} Score and moves left text, when there are less than 10 moves left & \pageref{fig:scoreMovesLeft10} \\ - \hline - Fig. \ref{fig:losingScreen} Game message when lost & \pageref{fig:losingScreen} \\ - \hline - Fig. \ref{fig:winingScreen} Game message when won & \pageref{fig:winingScreen} \\ - \hline -\end{longtable} \end{document}