mirror of
https://github.com/kuhyx/engineer-thesis-WUT.git
synced 2026-07-04 13:23:09 +02:00
2208 lines
102 KiB
TeX
2208 lines
102 KiB
TeX
\documentclass[a4paper,11pt,twoside]{report}
|
||
% From specification:
|
||
% kroj bezszeryfowy rozmiar 11
|
||
% Druk dwustronny A4
|
||
% 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{ulem}
|
||
\usepackage{fontspec}
|
||
\usepackage{anyfontsize}
|
||
\usepackage{graphicx}
|
||
\usepackage{subcaption}
|
||
\usepackage{geometry}
|
||
\usepackage{leading}
|
||
\usepackage[nottoc]{tocbibind}
|
||
\usepackage{titleps}
|
||
\usepackage{float}
|
||
% From specification:
|
||
% Tytuł tabeli
|
||
% Umieszczony nad tabelą (manually by setting caption above table)
|
||
% Justowany do lewej -> raggedright
|
||
% Czcionka bezszeryfowa rozmiar 9 -> footnotesize (see: https://www.sascha-frank.com/latex-font-size.html)
|
||
% Podpis rysunku -> pod rysunkiem (manually by setting caption below table)
|
||
% Źródło rysunku/tabeli -> Pod rysunkiem lub tabelą (manually by setting caption below table)
|
||
% Przypis dolny -> numeracja ciagla w calej pracy, kroj bezszeryfowy analogiczy jak w tekscie, rozmiar 9
|
||
\usepackage[font=footnotesize, justification=raggedright]{caption}
|
||
\usepackage{svg}
|
||
% Kroj pisma tytulow glownych rozdzialow
|
||
% pogrubiony bezszeryfowy -> wyzej
|
||
% tytul 1 poziomu 14
|
||
% tytul 2 poziomu 13
|
||
% tytul 3 poziomu 12
|
||
\usepackage{sectsty}
|
||
\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]
|
||
|
||
|
||
\chapterfont{\fontsize{14}{14}\selectfont}
|
||
|
||
\sectionfont{\fontsize{13}{13}\selectfont}
|
||
|
||
\subsectionfont{\fontsize{12}{12}\selectfont}
|
||
|
||
|
||
% From specification:
|
||
% Numeracja tabel i rysunków:
|
||
% Ciagła w całej pracy <-----
|
||
% Kolejna w rozdziałach
|
||
\usepackage{chngcntr}
|
||
\usepackage{tabularx}
|
||
\counterwithout{figure}{chapter}
|
||
\counterwithout{table}{chapter}
|
||
|
||
% From specification:
|
||
% Odwołania do źródeł
|
||
% Styl numeracyjny wg normy PW
|
||
% OR Styl Harwardzki <-----
|
||
% Bibliografia w układzie alfabetycznych wg nazwisk autorów (has to be ensured manually)
|
||
\usepackage{natbib}
|
||
\usepackage{hyperref}
|
||
\hypersetup{
|
||
colorlinks=true,
|
||
linkcolor=blue,
|
||
filecolor=magenta,
|
||
urlcolor=cyan,
|
||
pdftitle={Overleaf Example},
|
||
pdfpagemode=FullScreen,
|
||
}
|
||
\bibliographystyle{agsm}
|
||
|
||
% From specification:
|
||
% Numeracja stron:
|
||
% U dolu
|
||
% Po zewnetrznej stronie
|
||
% Odbicie lustrzane na stronach parzystych i nie parzystych
|
||
\renewpagestyle{plain}{%
|
||
\sethead{}{}{}
|
||
\setfoot[\thepage][][]{}{}{\thepage}
|
||
}%
|
||
\pagestyle{plain}
|
||
|
||
% From specification:
|
||
% kroj bezszeryfowy rozmiar 11
|
||
\renewcommand{\familydefault}{\sfdefault}
|
||
|
||
% From specification:
|
||
% Akapit wciecie 0.5 cm <-----
|
||
% OR bez wciecia z odstepem 4 przed akapitem
|
||
\setlength{\parindent}{0.5cm}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
% From specification:
|
||
% Marginesy:
|
||
% wewnetrzny - 30 mm
|
||
% Zewnetrzny - 20 mm
|
||
% Gorny/dolny - 25 mm
|
||
\geometry{
|
||
inner=30mm,
|
||
outer=20mm,
|
||
top=25mm,
|
||
bottom=25mm,
|
||
}
|
||
\graphicspath{ {./images/} }
|
||
|
||
|
||
\newenvironment{centereq}[1][0.90]
|
||
{%
|
||
\trivlist
|
||
\centering
|
||
\setlength{\leftskip}{\dimexpr(\columnwidth-#1\columnwidth)/2\relax plus 1em}%
|
||
\setlength{\rightskip}{\leftskip}%
|
||
\item\relax
|
||
}
|
||
{\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
|
||
}
|
||
|
||
\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"
|
||
}
|
||
\newcommand{\tytulEng}{
|
||
Creating multiplatform applications with creation of match three game engine as an example
|
||
}
|
||
\begin{document}
|
||
\begin{titlepage}
|
||
\begin{minipage}{0.9\textwidth}%
|
||
\begin{center}
|
||
{\fontsize{24}{24}\adagio Warsaw University of Technology }
|
||
|
||
|
||
\end{center}
|
||
\begin{center}
|
||
{\fontsize{12}{12}\adagio \addfontfeature{LetterSpace=41.0} FACULTY OF ELECTRONICS AND INFORMATION TECHNOLOGY}
|
||
|
||
\end{center}
|
||
\end{minipage}
|
||
\begin{minipage}{0.1\textwidth}%
|
||
\includegraphics[width=25mm, height=25mm]{logo.jpg}%
|
||
\end{minipage}
|
||
\\[100pt]
|
||
|
||
|
||
|
||
\begin{center}
|
||
{\fontsize{43}{43}\adagioLight Bachelor's diploma thesis}
|
||
\\[24pt]
|
||
{\fontsize{12}{12}\helveticaLight in the field of study Computer Science}
|
||
|
||
{\fontsize{12}{12}\helveticaLight and specialization Computer Systems and Networks \\[50pt]}
|
||
|
||
|
||
{\fontsize{14}{14}\helvetica \tytulEng \\[50pt]}
|
||
|
||
{\fontsize{12}{12}\helveticaLight thesis number in the Faculty thesis register 103B-ISA-IN/307585/1202985 \\[12pt]}
|
||
|
||
{\fontsize{21}{21}\helvetica Krzysztof Stefan Rudnicki \\[12pt]}
|
||
|
||
{\fontsize{12}{12}\helveticaLight student record book number 307585 \\[24pt]}
|
||
|
||
{\fontsize{12}{12}\helveticaLight thesis supervisor }
|
||
|
||
{\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}\helveticaLight WARSAW 2023}
|
||
\end{center}
|
||
|
||
|
||
|
||
\end{titlepage}
|
||
|
||
|
||
\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łem dla develiperó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\}
|
||
}
|
||
\newpage
|
||
\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\}
|
||
}
|
||
|
||
\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" and "Bejeweled", staple titles for both mobile and desktop market.
|
||
|
||
\begin{figure}[h]
|
||
|
||
\includegraphics[scale=0.5]{allgates}
|
||
\caption{Minecraft redstone offers introduciton to logical gates \cite{raspberrypi2023}}
|
||
\end{figure}
|
||
|
||
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.
|
||
|
||
Another motivation is a chance to learn one of the most popular graphical API - OpenGL, It is a graphical API which proved useful again and again with video game engines, rendering engines or for desktop applications.
|
||
|
||
|
||
|
||
\section{Scope of the Thesis}
|
||
Game development is a broad spectrum, This section aims to define to what extent the game engine will be developed
|
||
|
||
\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 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.
|
||
|
||
\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
|
||
|
||
\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.
|
||
|
||
\subsection{Limitations}
|
||
|
||
Since the engine aims to be simple and robust, it will not contain every possible feature or optimization like in commercial-grade game engines. The primary intent is to create a solid structure for which further advancements can be made.
|
||
|
||
In conclusion, this thesis goal is to provide an understanding of multiplatform Match Three game engine development. By setting managable boundaries, it aims to dive deep into specific challenges, tools, and solutions, providing valuable insights for aspiring engine developers. Next sections will further describe the objectives, methodologies, and findings of this exploration into the world of game development.
|
||
|
||
\section{Objectives of the Thesis}
|
||
Clear objectives allow for accomplishments that can be measured. Therefore it is important to define goals that underpin this thesis. In this section, we describe the primary goals of this thesis.
|
||
|
||
\subsection{Design of a Cross-Platform Core Architecture}
|
||
|
||
The foremost objective is to design a core multiplatform engine architecture. This demands a good understanding of the operating systems differences, from their system calls to their graphics pipelines. The engine should be modular and scalable, in order to make sure that the core mechanics and logic can be implemented across Windows, Mac, and GNU/Linux platforms.
|
||
|
||
\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 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}
|
||
|
||
Architecture and libraries used form the skeleton, the Match Three mechanics are the most important part of the engine. The aim is to create algorithms and techniques to take care of typical Match Three functionalities – from matching detection to gravity-induced piece drops and chain reactions.
|
||
|
||
\subsection{Providing a Blueprint for Future Development}
|
||
|
||
While the first objective is the engine's development, a broader goal is to expand upon created engine. By addressing challenges and sharing solutions, this thesis hopes to offer an example for engine developers. Whether they wish to enhance this engine or create new game engines. This thesis tries to improve their understanding of the topic
|
||
|
||
\section{Match Three Games and Engines}
|
||
The world of video games is vast and varied, Offering anything from massive multiplayer role playing games to small mobile puzzles. Match Three games offer players a blend of strategy, pattern recognition, and instant gratification. To better understand the significance of our task in creating a multiplatform Match Three game engine, it's important to trace how the genre evolved, understanding its roots, its development, and its appeal.
|
||
|
||
\subsection{Origins and Early Forerunners}
|
||
|
||
Match Three games owe their existence to the broader genre of tile-matching video games. The earliest entrants in this category were games like "Tetris" in the 1980s, where players manipulated falling blocks to create complete lines. "Tetris" kickstarted games focused on spatial reasoning and pattern recognition.
|
||
|
||
Expandin on this formula, 1980s and 1990s saw games like "Columns" and "Dr. Mario". These games introduced the mechanic of matching three or more identical items, but they were still using the falling-block paradigm. The true prototype of modern Match Three mechanics can be credited to "Shariki," a 1994 game where players swapped adjacent pieces to form chains of three or more identical items.
|
||
|
||
\subsection{Mainstream Adoption and Innovations}
|
||
|
||
In 2001 game titled "Bejeweled" came out. Simplifying and refining the mechanics of "Shariki", "Bejeweled" was the first mainstream success of Match Three genre. It provided intuitive gameplay, visually appealing graphics and set the benchmark for games that followed.
|
||
|
||
\begin{figure}[htp]
|
||
\centering
|
||
\includegraphics[scale=0.50]{images/bejeweld.jpg}
|
||
\caption{Bejeweled gameplay \cite{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.
|
||
|
||
\subsection{Technological Influences and Platform Diversification}
|
||
|
||
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.
|
||
|
||
\subsection{Enduring Appeal and Contemporary Significance}
|
||
|
||
Despite multiple competing genres, Match Three games remain popular. Their success can be attributed to their 'easy to learn, hard to master' trait. The games offer immediate rewards – the satisfaction of creating a match, beauty of pieces disappearing, and the strategic depth of planning several moves ahead. Additionally, the modular nature of their design allows for easy adaptation, whether it's incorporating a narrative, integrating with popular cultures, or tailoring to specific demographics.
|
||
|
||
In conclusion, the history of Match Three games is a proof to their adaptability and universal charm. They established themselves as one of the most recognizable genres in mobile and desktop gaming, they continue to gather players. This perspective marks significance of engines tailored to Match Three games specificaly.
|
||
\section{Overview of Multiplatform Game Development}
|
||
Game developres try to reach wider audiences by ensuring that their creations are accessible across a variety of operating systems. Multiplatform game development aims to deliver the same experience to different user bases. This section describes challenges and methodologies connected with multiplatform game development.
|
||
|
||
\subsection{The Need for Multiplatform Development}
|
||
|
||
There are three main operating systems, Windows, Mac, and GNU/Linux which together are responsible for over 90\% of desktop market share \cite{marketShareChart}, the potential to reach a bigger audience increases when a game is made available across all of these platforms.
|
||
|
||
\begin{figure}[H]
|
||
\centering
|
||
\includegraphics[scale=0.50]{chart.png}
|
||
\caption{Desktop OS market share worldwide \cite{marketShareChart}}
|
||
\label{}
|
||
\end{figure}
|
||
|
||
\subsection{Challenges in Multiplatform Development}
|
||
|
||
Idea of developing once and deploying everywhere comes with its set of challenges:
|
||
\begin{itemize}
|
||
\item Hardware Disparity: Different platforms might have varying hardware specifications, which can influence the game's performance.
|
||
\item OS Specific Limitations: Each operating system operates under its own rules, from system calls to user interface.
|
||
\item Toolchain Variability: Development tools and libraries might have platform-specific versions or may not be available for all platforms.
|
||
\end{itemize}
|
||
|
||
\subsection{Tools and Libraries for Cross-Platform Development}
|
||
|
||
In order to face multiplatform development challenges, we use a set of tools and libraries. Libraries like OpenGL offer an unified graphics rendering solution, while GLFW enforces consistent window management and input handling. stb\_image, and ft2build are other libraries that offer consistent behavior across platforms for handling images and font rendering.
|
||
|
||
\subsection{Strategies for Effective Multiplatform Development}
|
||
|
||
\begin{itemize}
|
||
\item Modular Design: By dividing the game engine into distinct modules, developers can isolate platform-specific code, ensuring that core logic remains unchanged by platform dependencies.
|
||
\item Continuous Integration and Testing: Automated testing across platforms can identify inconsistencies, ensuring that the game offers an uniform experience.
|
||
\item Community Engagement: Using the community that uses the game engine across multiple platforms offers developers insights into existing bugs and potential optimizations
|
||
\end{itemize}
|
||
|
||
\subsection{Future Trajectory of Multiplatform Development}
|
||
|
||
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}
|
||
The gaming industry has seen rise of various game engines, each offering a unique set of tools and functionalities for different needs. While these engines have facilitated the creation of iconic games, they also come with certain limitations, especially concerning specific genres or platforms. This section will describe prominent game engines and analyze their shortcomings, providing a reference for our multiplatform Match Three game engine.
|
||
|
||
\subsection{Unity Game Engine}
|
||
|
||
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}
|
||
\item Strengths: Plenty of community resources, a vast asset store, and an intuitive interface make Unity a popular choice among both beginners and professional developers.
|
||
\item Limitations: Unity's all-purpose nature might introduce unnecessary load for simpler games, like Match Three titles, potentially impacting performance.
|
||
\end{itemize}
|
||
|
||
\subsection{Unreal Engine}
|
||
|
||
Epic Games' Unreal Engine stands out for its impressive graphical capabilities, often employed in AAA game titles.
|
||
|
||
Strengths:
|
||
\begin{itemize}
|
||
\item Blueprints: Blueprint visual scripting system allows for easier game development especialy for non-technical users
|
||
\item Graphics: Unreal Engine offers top-tier rendering capabilities, which makes it ideal for creating visually stunning games.
|
||
\end{itemize}
|
||
|
||
Limitations:
|
||
\begin{itemize}
|
||
\item Learning Curve: Its advanced features can be overwhelming for beginners.
|
||
\item Simpler Genres: As with Unity, for a simple game like Match Three game, the engine might be too sophisticated, leading to longer load times and increased resource consumption compared to custom solution.
|
||
\end{itemize}
|
||
|
||
\subsection{Godot}
|
||
|
||
|
||
Godot is the biggest multipurpose game engine that is also free and open-source.
|
||
|
||
Strengths:
|
||
\begin{itemize}
|
||
\item No licensing: Godot is free to use
|
||
\item Open: Godot source code can be accessed and modified by anyone
|
||
\item Community: As with many open source projects community offers huge support for potential developers
|
||
\end{itemize}
|
||
Limitations:
|
||
\begin{itemize}
|
||
\item Limited professional presence: While unity and unreal dominate professional market, Godot is a less popular alternative
|
||
\item Scalability: Godot game engine is not famous for building big game projects
|
||
\end{itemize}
|
||
|
||
\subsection{RPG Maker and Ren'Py}
|
||
|
||
|
||
|
||
RPG Maker and Ren'Py are examples of game engines focusing on one genre of games.
|
||
RPG Maker focues on top-down rpg games, while ren'py aims to ease the process of making visual novel games. Both of those engines were used with commercial and critical success, with creation of games like "To The Moon" and "Doki Doki Literature Club". Their limitations are both their strenght and weaknesses, allowing to speedup development process but blocking the developer for leaving the area of interest for those engines.
|
||
|
||
\subsection{Limitations}
|
||
|
||
While each game engine has its unique strengths and weaknesses, some common limitations come apparent when considering the development of a specialized game like a Match Three title, those engines either do not allow for match three development (RPG Maker or Ren'Py) or deliver match three titles with too much technological bloat (Unity/Unreal/Godot)
|
||
|
||
While existing game engines offer a lot of tools and capabilities, there exists a space for specialized engines made for specific genres. Recognizing the limitations of mainstream engines explains development of our multiplatform Match Three game engine, which tries to fix these gaps while providing an easy development.
|
||
\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.
|
||
|
||
\begin{figure}[htp]
|
||
\centering
|
||
\includegraphics[scale=0.50]{images/gameEngineComponents.drawio.pdf}
|
||
\caption{Components used in our game engine}
|
||
\label{}
|
||
\end{figure}
|
||
|
||
|
||
\subsection{Rendering Engine}
|
||
|
||
|
||
The rendering engine is responsible for all visual aspects within a game. It takes the game's data – including textures and shaders – and converts them into pixels on the screen.
|
||
|
||
\begin{itemize}
|
||
\item Role: Ensures that all visual elements, from static backgrounds to animations, are displayed with clarity, smoothness, and consistency.
|
||
\item Key Features: Supports various rendering techniques such as ray tracing, rasterization, and shadow mapping to enhance visual quality.
|
||
\end{itemize}
|
||
|
||
\subsection{Input Handling}
|
||
|
||
Games are interactive by nature, and this interaction is delivered by the engine's input handling system.
|
||
|
||
\begin{itemize}
|
||
\item Role: Processes user inputs from various sources, like a keyboard, mouse, gamepad, or touch screen. Translates these inputs into in-game actions or commands.
|
||
\item Key Features: Detects multiple simultaneous key presses, supports touch gestures, and allows for input remapping.
|
||
\end{itemize}
|
||
|
||
\subsection{Transformations}
|
||
In order for object in game to move, game engine must handle its position on screen, this is a job of transformation system
|
||
\begin{itemize}
|
||
\item Role: Move objects based on user input, delete, add and modify existing coordinates
|
||
\item Key Features: Matrices and vectors calculation
|
||
\end{itemize}
|
||
|
||
Each component of game engine plays a different role, in combination they ensure that the game runs smoothly, offers an enjoyable experience, and responds to user interactions. These core parts form the foundation upon which our multiplatform Match Three game engine will be built and updated.
|
||
\section{The Role of OpenGL in Game Development}
|
||
Game engines need graphics libraries, those libraries provide the necessary tools to bring visual elements to the screen. Among these libraries, OpenGL (Open Graphics Library) has established itself as one of the most popular choices for graphics rendering. This section explores the role of OpenGL in game development, focusing on library functionalities and how they can be used.
|
||
|
||
\subsection{Introduction to OpenGL}
|
||
|
||
OpenGL is a cross-language, cross-platform application programming interface (API) designed for rendering vector graphics. Initially developed by Silicon Graphics in the 1990s, it is now taken care by the non-profit technology organization, the Khronos Group. It is platform-agnostic which makes experience consistent across different platforms.
|
||
|
||
\subsection{Rendering Mechanism}
|
||
|
||
At its core, OpenGL is about rendering. It provides developers with the tools needed to draw complex 3D and 2D graphics.
|
||
|
||
\begin{itemize}
|
||
\item Role: OpenGL communicates directly with a system's GPU (Graphics Processing Unit) and translates the game's data into visual elements displayed on screen.
|
||
\item Key Features: Supports techniques such as texture mapping, anti-aliasing, and shader programming.
|
||
\end{itemize}
|
||
|
||
\subsection{Shader Programming}
|
||
|
||
Shaders allow for creating stunning visuals programatticaly, and OpenGL provides extensive support for shader programming.
|
||
|
||
\begin{itemize}
|
||
\item Role: Shaders allow developers to program the GPU directly, which gives massive control over how individual pixels or vertices are processed.
|
||
\item Key Features: OpenGL's GLSL (OpenGL Shading Language) lets developers create custom shaders, enabling dynamic lighting, shadows, and other advanced visual effects.
|
||
\end{itemize}
|
||
|
||
|
||
\subsection{Extensibility}
|
||
|
||
One of OpenGL's strengths lies in its extensible design, allowing to include advancements in hardware and software.
|
||
|
||
\begin{itemize}
|
||
\item Role: As hardware evolves, new extensions can be added to OpenGL without changing the entire API. This makes OpenGL relevant, independent of technological improvements.
|
||
\item Key Features: These extensions can be used to utilize latest graphics hardware capabilities, making games look and perform their best.
|
||
\end{itemize}
|
||
|
||
\subsection{Cross-Platform Development}
|
||
|
||
\begin{figure}[htp]
|
||
\centering
|
||
\includegraphics[scale=0.70]{images/openglWikipedia.png}
|
||
\caption{Connection between Linux and OpenGL-based games \cite{openGLWikipedia}}
|
||
\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.
|
||
|
||
\begin{itemize}
|
||
\item Role: OpenGL ensures that games look consistent across different platforms, easing the process of multiplatform game development.
|
||
\item Key Features: It abstracts underlying platform-specific differences, allowing developers to focus on creating the game without need to adapt to platform-specific constraints.
|
||
\end{itemize}
|
||
|
||
\subsection{Integration with Other Libraries and Tools}
|
||
|
||
OpenGL is compatible with many libraries and development tools.
|
||
|
||
\begin{itemize}
|
||
\item Role: Enhances OpenGL capabilities by working with libraries like GLFW for window management, GLAD for handling extensions, and more.
|
||
\item Key Features: Provides an unified development environment, where various tools and libraries work together under OpenGL, again simplifying the process of game development.
|
||
\end{itemize}
|
||
|
||
In summary, OpenGL combines software and hardware, enabling developers to create experiences with enough precision and speed. Understanding impact of OpenGL will make explaining our own game engine inner workings much easier.
|
||
\section{Multiplatform Considerations}
|
||
In order to reach as wide an audience as possible, developers try to develop a game for multiple platforms – mostly Windows, Mac, and GNU/Linux. Creating multiple platforms game engine is filled with challenges and considerations. This section explores difficulties of multiplatform game development, describing what developers must be conscious of when targeting multiple operating systems.
|
||
|
||
\subsection{Platform-Specific Hardware and Software}
|
||
|
||
Every platform has its unique hardware and software configurations, which influence a game's performance and appearance.
|
||
|
||
\begin{itemize}
|
||
\item Role: Ensuring the game runs smoothly across different hardware setups, from different GPU architectures to memory configurations.
|
||
\item Key Features: Developers need tools to abstract these hardware-software differences, allowing the game engine to react dynamically based on the platform.
|
||
\end{itemize}
|
||
|
||
\subsection{User Input Variations}
|
||
|
||
Different platforms often have different input methods, from touchscreens to gamepads to keyboard-mouse setups.
|
||
|
||
\begin{itemize}
|
||
\item Role: Making sure that the game responds accurately to various input methods.
|
||
\item Key Features: Input handling system that can detect and adapt to different input devices.
|
||
\end{itemize}
|
||
|
||
\subsection{Graphical Rendering Nuances}
|
||
|
||
There are platform-specific nuances in how graphics are presented.
|
||
|
||
\begin{itemize}
|
||
\item Role: Making engine work under different display resolutions, aspect ratios, and screen sizes.
|
||
\item Key Features: Dynamic resolution scaling and responsive UI layouts to ensure the game looks similar across platforms.
|
||
\end{itemize}
|
||
|
||
\subsection{File System and Data Management}
|
||
|
||
\begin{figure}[htp]
|
||
\centering
|
||
\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{}
|
||
\end{figure}
|
||
|
||
File systems varies between Windows, Mac, and GNU/Linux, influencing how game data is stored, retrieved, and updated.
|
||
|
||
\begin{itemize}
|
||
\item Role: Managing game saves, configurations, and other data across platforms.
|
||
\item Key Features: Data management system that behaves the same for platform-specific directory structures and access permissions.
|
||
\end{itemize}
|
||
|
||
\chapter{Toolchain and Libraries Overview}
|
||
\section{Introduction to GLFW}
|
||
In the arena of game development, libraries related to window management and input handling play crucial role. GLFW offers an interface for developers to create interactive applications. This section offers an introduction to GLFW, explaining into its origins, functionalities, and relevance in game development.
|
||
|
||
\subsection{Historical Context}
|
||
|
||
GLFW, which stands for Graphics Library Framework, was a response to the need for an open-source library that simplifies the challenges of window management and input handling, especially for OpenGL applications. Initially developed to provide a more straightforward alternative to existing solutions, GLFW has grown in popularity due to its simplicity, efficiency, and cross-platform capabilities.
|
||
|
||
\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.
|
||
|
||
\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.
|
||
|
||
\item Input Handling: GLFW provides a system to capture and process user inputs. It forms for keyboard strokes, mouse movements, or even joystick inputs, GLFW makes sure that user interactions are detected and communicated to the application efficiently.
|
||
|
||
\item Context Management: Especially relevant for OpenGL applications, GLFW helps create contexts, manage profiles, and handle extensions, for easier development experience.
|
||
\end{itemize}
|
||
|
||
\subsection{Integration with OpenGL}
|
||
|
||
While GLFW is platform-agnostic and can support multiple rendering systems, its mostly used with OpenGL.
|
||
|
||
\begin{itemize}
|
||
\item Role: It functions as the companion to OpenGL, offering an environment where OpenGL can work more efficiently by addressing system-specific challenges that are outside OpenGL's specification.
|
||
|
||
\item Key Features: Context creation for OpenGL.
|
||
\end{itemize}
|
||
|
||
\subsection{Cross-Platform Capabilities}
|
||
|
||
GLFW's is able to work across Windows, Mac, and GNU/Linux.
|
||
|
||
\begin{itemize}
|
||
\item Role: GLFW is a consistent interface for developers, regardless of the target platform. This ensures uniformity in window creation, input handling, and other functionalities across different operating systems.
|
||
|
||
\item Key Features: An architecture that ensures platform-specific norms while offering a consistent API for developers.
|
||
\end{itemize}
|
||
|
||
\subsection{Community and Support}
|
||
|
||
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.
|
||
|
||
\item Key Features: Tutorials and forums, assisting both new and trained developers in using GLFW
|
||
\end{itemize}
|
||
|
||
GLFW proves useful to many game developers, especially those using OpenGL. Its ability to handle window management, input processing, and other tasks ensures that developers can focus on crafting game mechanics and narratives.
|
||
\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{}
|
||
\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.
|
||
|
||
\begin{itemize}
|
||
\item Role: Extensions allow developers to use the latest graphics capabilities, which are not always part of the core OpenGL library.
|
||
|
||
\item Key Features: Handling these extensions requires dynamic loading at runtime, so that the game can access and utilize these advanced functionalities.
|
||
\end{itemize}
|
||
|
||
\subsection{GLAD's Functionality in Extension Loading}
|
||
|
||
GLAD simplifies working with OpenGL extensions through its automated approach.
|
||
|
||
\begin{itemize}
|
||
\item Role: Provided with the OpenGL version and extensions, GLAD generates C/C++ code to load these extensions dynamically at runtime. This removes the need to manually address each extension, reducing errors and inefficiencies.
|
||
|
||
\item Key Features: GLAD supports multiple languages and specifications. It is also up-to-date with the latest OpenGL specifications, making it compatible with newest graphics capabilities.
|
||
\end{itemize}
|
||
|
||
\subsection{Integration with GLFW and OpenGL}
|
||
|
||
GLAD interacts with both OpenGL and GLFW, creating an unified development environment. Once GLFW creates the OpenGL context, GLAD is utilized to load the necessary functions. This collaboration ensures that GLFW handles the platform-specific instructions, while GLAD focuses on OpenGL's extensibility.
|
||
|
||
\section{Handling text and images with FreeType, ft2build and stb\_image}
|
||
\subsection{Introduction to FreeType}
|
||
|
||
FreeType is an open-source software font engine, It offers ability to render text onto bitmaps and other font-related functionalities.
|
||
|
||
\subsection{Key Capabilities of FreeType}
|
||
|
||
\begin{itemize}
|
||
\item Font Parsing and Loading: Before rendering, fonts need to be parsed and loaded. FreeType supports many font formats, ensuring broad compatibility.
|
||
|
||
\item Glyph Rendering: FreeType is good at converting individual glyphs into bitmaps, handling anti-aliasing and hinting to create clear, sharp text rendering.
|
||
|
||
\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{}
|
||
\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.
|
||
|
||
\subsection{Key Capabilities of stb\_image}
|
||
|
||
\begin{itemize}
|
||
\item Image Loading: It is capable of handling popular formats like JPEG, PNG, BMP, and more, this way developers can incorporate multiple types of graphical assets into their game
|
||
|
||
\item Image Decoding: stb\_image decodes images into a format that can be directly used with OpenGL or other rendering systems.
|
||
|
||
\item Simplicity and Efficiency: stb\_image has minimalistic design. With just a single header file, developers can use its capabilities without employing complex dependencies or configurations.
|
||
\end{itemize}
|
||
|
||
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. 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{}
|
||
\end{figure}
|
||
|
||
\subsection{The Anatomy of a Game Loop}
|
||
|
||
The game loop is a repetitive process that continues for the lifespan of the game session. It tries to:
|
||
|
||
\begin{itemize}
|
||
\item Poll Input: Gather and process player inputs.
|
||
|
||
\item Update Game State: Based on the inputs and game rules, modify game entities and overall state.
|
||
|
||
\item Render: Draw the current state of the game onto the screen.
|
||
|
||
\item Sleep (Optional): Introduce a short delay, if necessary, to control the loop's frequency and match the target framerate.
|
||
\end{itemize}
|
||
|
||
\subsection{Importance of State Management}
|
||
|
||
As games grow complex, they may contain numerous states, including:
|
||
|
||
\begin{itemize}
|
||
\item Menu Screens: Where players can start, configure settings, or view credits.
|
||
|
||
\item Gameplay Modes: Different modes or levels of gameplay, each with its unique rules and environments.
|
||
|
||
\item Pause/Resume States: Allowing players to stop the game and later resume it.
|
||
|
||
\item Endgame Scenarios: Winning, losing, or drawing conditions.
|
||
\end{itemize}
|
||
|
||
State management ensures the game recognizes and responds appropriately to these states.
|
||
|
||
\subsection{Interlinking Game Loop and State Management}
|
||
|
||
Game Loop and State Management must work together:
|
||
|
||
\begin{itemize}
|
||
\item State-Driven Rendering: What the game displays during each loop iteration depends on its current state. For instance, the game may render a main menu, gameplay screen, or a 'game over' message based on its active state.
|
||
|
||
\item State Transitions: Player inputs or internal events can trigger state changes. The game loop continually checks for such triggers and initiates the necessary transitions.
|
||
\end{itemize}
|
||
|
||
\subsection{Challenges and Considerations}
|
||
Managing a game loop and states is filled with challenges:
|
||
|
||
\begin{itemize}
|
||
\item Performance: The game loop must run efficiently to maintain smooth gameplay, especially crucial for real-time games where fast decisions matter.
|
||
|
||
\item State Persistence: Some states may require data persistence, like saving a game, which requires storing and retriving game data.
|
||
|
||
\item Synchronization: Ensuring that state transitions and game loop iterations are in sync is essential to avoid unexpected behaviors.
|
||
\end{itemize}
|
||
|
||
\subsection{Role in the Multiplatform Match Three Game Engine}
|
||
|
||
For our game engine, the game loop and state management fullfill following functions:
|
||
|
||
\begin{itemize}
|
||
\item Cross-Platform Consistency: The game's behavior, pacing, and responses should be consistent across Windows, Mac, and GNU/Linux platforms.
|
||
|
||
\item Match-Three Logic: The game engine will manage states specific to match-three mechanics, like checking for matches, handling cascades, and introducing new game pieces.
|
||
\end{itemize}
|
||
|
||
In summary, game loop maintains the rhythm of the game, while state management ensures logic in the game's behavior.
|
||
\section{Asset Management and Rendering}
|
||
Game development consists of art and logic, where visual assets behave based on game code. Ensuring these assets are well-organized, efficiently loaded, and rendered is vital for any gaming experience. This section explores asset management and rendering.
|
||
|
||
\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{}
|
||
\end{figure}
|
||
|
||
In the context of game development, assets refer to:
|
||
|
||
\begin{itemize}
|
||
\item Graphics: These include textures, sprites, animations, and UI elements.
|
||
|
||
\item Data: Configurations, level designs, scripts, and other data structures that define gameplay mechanics.
|
||
\end{itemize}
|
||
|
||
\subsection{Rendering: Bringing Assets to Life}
|
||
|
||
Once assets are organized and loaded, rendering puts them onto the screen:
|
||
|
||
\begin{itemize}
|
||
\item Displaying Graphics: Textures and sprites are mapped onto game entities.
|
||
|
||
\item Shaders \& Effects: Advanced graphical effects, such as lighting, shadows, and particle effects, are layered on to assets to elevate visuals.
|
||
\end{itemize}
|
||
|
||
\subsection{Cross-Platform Considerations in Asset Management}
|
||
|
||
Given our engine's multiplatform nature, certain challenges arise:
|
||
|
||
\begin{itemize}
|
||
\item Format Compatibility: Not all asset formats are supported uniformly across platforms. Ensuring assets are in universally compatible formats and using libraries like stb\_image that unify asset formats among different operating systems is crucial.
|
||
|
||
\item Optimization: Different platforms may have varying memory capacities and processing power. Assets might need to be optimized, resized, or compressed to respond to those limitations.
|
||
\end{itemize}
|
||
|
||
\subsection{Role in the Multiplatform Match Three Game Engine}
|
||
Our game engine’s being multiplatform complicates assets rendering:
|
||
|
||
\begin{itemize}
|
||
\item Uniformity Across Platforms: Visual experience should remain consistent across Windows, Mac, and GNU/Linux.
|
||
|
||
\item Match-Three Specifics: Given the genre, the engine must effectively manage assets like gems, power-ups, grid structures, and cascade animations and then render them in a visually pleasing way.
|
||
\end{itemize}
|
||
|
||
\section{Event Handling and User Input}
|
||
Main trait of video game is interactivity. Game must have the ability to capture, interpret, and respond to user input, whether it is the click of a mouse, the press of a keyboard key, or the swipe on a touchscreen. Managing those interactions is called event handling. In this section we will describe event handling and user input, and their role in our game engine.
|
||
|
||
\subsection{Understanding User Input and Events}
|
||
|
||
Every action by the user generates an event. This event contains specific data, such as:
|
||
|
||
\begin{itemize}
|
||
\item Type of Input: Whether it's a key press, mouse movement, click, or touch.
|
||
|
||
\item Coordinates: In case of mouse or touch, the precise location of the interaction.
|
||
|
||
\item Duration: The length of time an input is maintained, like a long press.
|
||
\end{itemize}
|
||
|
||
\subsection{Event Polling vs. Event Callbacks}
|
||
|
||
There are two primary ways to handle these events:
|
||
|
||
\begin{itemize}
|
||
\item Event Polling: The game loop consistently checks (or "polls") for user inputs at fixed intervals, processing any detected events.
|
||
|
||
\item Event Callbacks: The system is set up to notify (or "call back") the game engine immediately when an event occurs.
|
||
\end{itemize}
|
||
|
||
\subsection{Role in the Multiplatform Match Three Game Engine}
|
||
|
||
Our game engine should provide following features connected with user input:
|
||
|
||
\begin{itemize}
|
||
\item Intuitive Matching: Selecting and swaping game pieces should be easy and convenient, with the engine accurately capturing and responding to these interactions.
|
||
|
||
\item Feedback Loop: Upon receiving an input, the game engine must also provide immediate feedback, such as highlighting a selected piece, initiating a combo move or playing animation.
|
||
\end{itemize}
|
||
|
||
Event handling and user input are ways for players to communicate with the game. It is crucial to make them intuitive and smooth.
|
||
|
||
\chapter{Cross-Platform Development Challenges and Solutions}
|
||
\section{Operating System Variabilities}
|
||
Operating systems (OS) are a middle ground between human interaction and hardware functionality. With Windows, Mac, and GNU/Linux operating systems as the most popular desktop environments, a multiplatform game engine must work under each of them. This section describes differences between these operating systems and how our engine can handle them.
|
||
|
||
\subsection{Core Architectural Differences}
|
||
|
||
Some of the distinctive features of OS architecture are:
|
||
|
||
\begin{itemize}
|
||
\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.
|
||
|
||
\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{}
|
||
\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}
|
||
|
||
\subsection{Graphical User Interface (GUI) Divergence}
|
||
|
||
Differences in GUI of each OS:
|
||
|
||
\begin{itemize}
|
||
\item Window Management: Windows uses a floating window system, MacOS has its unique mission control, and Linux distributions can use anything from floating, fixed to no graphical interface at all.
|
||
|
||
\item Input Methodologies: Multi-touch gestures work different under MacOS than under Windows. Linux, depending on its distribution and desktop environment, may have a the same or different approach altogether.
|
||
\end{itemize}
|
||
|
||
\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.
|
||
|
||
\subsection{Middleware \& Third-party Libraries}
|
||
|
||
The accessibility, performance, and support of middleware and third-party libraries can differ, while some libraries are universally compatible, others might be OS-specific, another problem might be with the update cycle, the frequency of libraries updates can vary, impacting game engine compatibility and performance.
|
||
|
||
\subsection{Implications for the Multiplatform Match Three Game Engine}
|
||
|
||
Operating system variances impact directly on our game engine:
|
||
|
||
\begin{itemize}
|
||
\item Performance Optimizations: Depending on the OS, certain code paths might be more efficient. The engine must be adaptable.
|
||
|
||
\item Consistent Gameplay Experience: Players on all platforms should have a similar gameplay experience.
|
||
|
||
\item Updates \& Maintenance: As OSs changes, the game engine must be ready for regular updates to ensure compatibility.
|
||
\end{itemize}
|
||
|
||
Multitude of operating systems poses challenges for game engine developers. By understanding these differences, developers can make sure that the game functions across different platforms.
|
||
\section{Addressing Hardware and Driver Differences}
|
||
Hardware and its associated drivers act as the final execution devices for any game engine. When creating a game engine, developer must keep in mind a variety of hardware configurations and their driver implementations. This section focuses on understanding these differences and how we can challenge them.
|
||
\subsection{A Landscape of Diverse Hardware}
|
||
|
||
Across all platforms, multiple hardware components exists:
|
||
|
||
\begin{itemize}
|
||
\item Processors: Mostly Intels and AMD's x86 architecture (32 and 64 bits), together with ever more popular ARM processors
|
||
\item Graphics Cards: NVIDIA, AMD, and Intel dominate the market, each with their architectures and feature sets.
|
||
|
||
\item Memory Configurations: RAM specifications, speeds, and type of storage devices (SSDs and HDDs) can influence performance.
|
||
|
||
\item Input Devices: Various keyboards, mice, touchpads, touchscreens, and game controllers, each have unique specs and capabilities.
|
||
\end{itemize}
|
||
|
||
\subsection{Drivers: The Link to Hardware}
|
||
|
||
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.
|
||
|
||
\item Update Frequencies: Hardware vendors release driver updates at different intervals, each update possibly affects game performance and compatibility.
|
||
|
||
\item Feature Support: New hardware features may be enabled in driver updates.
|
||
\end{itemize}
|
||
|
||
\subsection{Challenges Posed to Game Engines}
|
||
|
||
\begin{itemize}
|
||
\item Optimization Issues: What's optimized for one graphics card might not be for another.
|
||
|
||
\item Feature Inconsistencies: Some hardware may support specific graphical features, while others might not.
|
||
|
||
\item Input Latency Variations: Different input devices and their drivers can lead to varied response times and incosistent responses.
|
||
\end{itemize}
|
||
|
||
\subsection{Abstraction Layers in Match Three Game Engine}
|
||
|
||
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{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.
|
||
|
||
\subsection{Foundational Principles}
|
||
\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 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}
|
||
|
||
\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
|
||
|
||
\subsection{ Choice of graphic renderning API }
|
||
There are 3 main APIs for graphical rendering
|
||
\begin{itemize}
|
||
\item DirectX
|
||
\item OpenGL
|
||
\item Vulkan
|
||
\end{itemize}
|
||
DirectX developed by Microsoft focues on Windows operating systems and
|
||
Microsoft line of consoles Xbox, it is deemed as being harder with more
|
||
low level programming and requiring better understanding of how underlying
|
||
mechanisms work but in turn offers functionalities and better performance.
|
||
It does not have free license. \\
|
||
OpenGL is developed by Khronos Group and offers good compatibility,
|
||
especially if using OpenGL ES subset which works on Windows, Linux,
|
||
Mac OS, Android, iOS and all major consoles.
|
||
It is widely recognized as easiest of APIs and most popular choice
|
||
for writing first game engine. On the other hand it lacks some of
|
||
more advanced features which have to be written manually.
|
||
It uses open source license similar to BSD
|
||
\\
|
||
Vulkan is also developed by Khronos Group and as such is deemed as a
|
||
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.
|
||
\\
|
||
\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.
|
||
I decided that since this will be my first attempt at game engine development
|
||
I need something that is relatively easy and has a lot of resources online.
|
||
I would most likely not use advanced features of Vulkan and DirectX and
|
||
therefore finish my thesis before approaching problems where OpenGL
|
||
does not deliver more complicated architecture.
|
||
From my own private preferences I also prefer software with
|
||
open source license.
|
||
|
||
\subsection{Choice of OpenGL Library}
|
||
There are 4 basic OpenGL libraries that I considered:
|
||
\begin{itemize}
|
||
\item freeGLUT
|
||
\item SDL
|
||
\item SFML
|
||
\item GLFW
|
||
\end{itemize}
|
||
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,
|
||
it is also not really popular with lack of online guides \\
|
||
SDL - Simple DirectMedia Layer has big userbase,
|
||
it is not designed to by used as a standalone
|
||
library and requires additional libraries to do networking
|
||
or to create more complex applications. \\
|
||
SFML is the library with most features out of all 4,
|
||
it supports networking, audio and has system features by
|
||
default. It uses modern object oriented C++.
|
||
Main problem with SFML is that it is not very popular API,
|
||
therefore troubleshooting problems with SFML is quite hard and
|
||
it has only few use guides online \\
|
||
GLFW is an library that is both the most popular and with fewest features by default.
|
||
It forces users to use additional libraries for networking,
|
||
sound, physic calculations and so on but in turn is also
|
||
quite small and flexible. It has biggest community and a
|
||
lot of guides, like one hosted at
|
||
\href{learnopengl.com}{learnopengl.com} or one created by
|
||
programming youtuber Cherno \\
|
||
I decided to use GLFW library. I wanted something that is
|
||
relatively easy to troubleshoot and has abundance of
|
||
learning materials online.
|
||
|
||
\subsection{GLAD and OpenGL Extension Handling}
|
||
GLAD enabled the dynamic loading of OpenGL functions, this way engine can use the latest OpenGL features.
|
||
|
||
\subsection{Text and Image Rendering with FreeType, stb\_image, and ft2build}
|
||
|
||
FreeType and stb\_image were choosen for their simplicity and popularity
|
||
|
||
\begin{itemize}
|
||
\item Font Rendering: With FreeType and ft2build, the engine can render text, crucial for messages, scores, and menus.
|
||
|
||
\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{File structure}
|
||
|
||
\dirtree{%
|
||
.1 engine\DTcomment{Main project folder}.
|
||
.2 build\DTcomment{Builded binary files of engine stored here}.
|
||
.2 includes\DTcomment{Used libraries}.
|
||
.3 freetype\DTcomment{Used to render text}.
|
||
.3 glad\DTcomment{Makes loading openGL easier}.
|
||
.3 ft2build.h\DTcomment{For building free type}.
|
||
.3 stb\_image.h\DTcomment{For loading images}.
|
||
.2 src\DTcomment{All of code is stored here}.
|
||
.3 constants.hpp.
|
||
.3 filesystem.h.
|
||
.3 game.cpp\DTcomment{Main game loop}.
|
||
.3 game.h.
|
||
.3 game\_level.cpp\DTcomment{Converts text files to game level}.
|
||
.3 game\_level.h.
|
||
.3 game\_object.cpp\DTcomment{Used for tiles}.
|
||
.3 game\_object.h.
|
||
.3 glad.c.
|
||
.3 resource\_manager.cpp\DTcomment{For loading assets}.
|
||
.3 resource\_manager.h.
|
||
.3 shader.cpp.
|
||
.3 shader.h.
|
||
.3 sprite.fs.
|
||
.3 sprite.vs.
|
||
.3 sprite\_renderer.cpp\DTcomment{Transforms assets into sprites}.
|
||
.3 sprite\_renderer.h.
|
||
.3 stb\_image.cpp.
|
||
.3 text\_2d.fs.
|
||
.3 text\_2d.vs.
|
||
.3 text\_renderer.cpp.
|
||
.3 text\_renderer.h.
|
||
.3 text\_rendering.cpp.
|
||
.3 texture.cpp\DTcomment{Transforms sprites into textures}.
|
||
.3 texture.h.
|
||
.2 glad.c.
|
||
.2 stb\_image.cpp.
|
||
.2 configuration\DTcomment{Holds reference to root directory of project}.
|
||
.2 resources\DTcomment{Holds assets}.
|
||
.2 bin\DTcomment{Holds binary engine files}.
|
||
.2 cmake\DTcomment{Holds build artifacts}.
|
||
.2 CmakLists.txt\DTcomment{Holds instructions for how to build the engine}.
|
||
}
|
||
|
||
\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:main_function_logic}
|
||
\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:main_function_logic}
|
||
\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<SpriteRenderer>(ResourceManager::GetShader("sprite"));
|
||
Effects = std::make_unique<PostProcessor>(
|
||
ResourceManager::GetShader("postprocessing"), this->Width, this->Height);
|
||
Text =
|
||
std::make_unique<TextRenderer>(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:main_function_logic}
|
||
\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:main_function_logic}
|
||
\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:main_function_logic}
|
||
\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<float>(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<float>(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<std::vector<unsigned int>> 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<int>(tileNumber));
|
||
|
||
// Directly use the constructor arguments for GameObject with emplace_back
|
||
this->Bricks.emplace_back(tileNumber,
|
||
glm::vec2(unit_width * static_cast<float>(x_coordinate), unit_height * static_cast<float>(y_coordinate)),
|
||
glm::vec2(unit_width, unit_height),
|
||
ResourceManager::GetTexture(idTextureMap[tileNumber]),
|
||
color
|
||
);
|
||
}
|
||
|
||
void GameLevel::init(std::vector<std::vector<unsigned int>> 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<float>(levelWidth) / static_cast<float>(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<size_t>(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<int>& 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{}
|
||
\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{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 Theme and Narrative}
|
||
|
||
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
|
||
|
||
\subsection{User Interface and User Experience}
|
||
|
||
\begin{itemize}
|
||
\item Game HUD: The Heads-Up Display is minimalistic, showcasing only essential elements like score and moves left.
|
||
|
||
\item, Feedback Mechanics: Animations, particles and shaders provide feedback for successful matches and combos.
|
||
|
||
\end{itemize}
|
||
|
||
\section{Application of the Engine in Real-world Development}
|
||
After designing the game, we started creating it using our game engine.
|
||
|
||
\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
|
||
|
||
\subsection{Challenges in Implementation}
|
||
|
||
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}
|
||
|
||
\begin{itemize}
|
||
\item Optimized Performance: Tailored specifically for a Match Three game, the engine could be highly optimized for this genre, reducing unnecessary overheads. Using OpenGL and plain C++ also added to engine speed
|
||
|
||
\item Direct Control: Having complete control over engine we could introduce, modify, or remove features as needed.
|
||
\end{itemize}
|
||
|
||
We were able to create a game using our engine, which means that the goal of this entire thesis was reached.
|
||
|
||
\chapter{Future Work and Enhancements}
|
||
\section{Potential Extensions to the Engine}
|
||
While we were able to produce a game using our engine, it still has a potential to grow more and introduce many new features.
|
||
|
||
\subsection{Enhanced Graphics and Physics}
|
||
|
||
Enhancing the visual and interactive experience:
|
||
|
||
\begin{itemize}
|
||
\item Ray Tracing: Using ray tracing can provide more realistic lighting, shadows, and reflections, increasing visual depth with little cost to an engine user.
|
||
|
||
\item Advanced Particle Systems: Enhancing particle effects can create more immersive environments, from weather effects to more dynamic game elements.
|
||
|
||
\item Physics Enhancements: More advanced physics simulations can improve feel to game interactions, from simple collisions to complex motion dynamics.
|
||
\end{itemize}
|
||
|
||
\subsection{Adaptive Difficulty}
|
||
Machine learning can analyze player performance and adjust game difficulty in real-time, ensuring a balanced challenge.
|
||
|
||
\subsection{Real-time Multiplayer}
|
||
Adding capabilities for online multiplayer modes can increase engagement, replayability and competetivnes.
|
||
|
||
\subsection{More platforms}
|
||
Engine could be adapted to work under mobile systems (Android and IOs) and consoles (Xbox, PlayStation and Switch)
|
||
|
||
As can be seen there are still a lot of features that can be added to our engine to make it even more useful
|
||
|
||
\chapter{Conclusion}
|
||
\section{Summary of Achievements}
|
||
In this section we will look back at our project and describe our achievments.
|
||
\subsection{Multiplatform Support}
|
||
|
||
\begin{itemize}
|
||
\item OS Versality: We successfully developed a game engine that operates across Windows, Mac, and GNU/Linux platforms, addressing the title of this thesis.
|
||
|
||
\item Addressed Platform-specific Challenges: Overcame technical challenges associated with different operating systems, ensuring a consistent user experience.
|
||
\end{itemize}
|
||
|
||
\subsection{Library Integrations}
|
||
We managed to itegrate libraries such as GLFW, glad, freetype, stb\_image, and ft2build, all within one game engine
|
||
|
||
\begin{itemize}
|
||
\item GLFW/glad: easying the process of using OpenGL
|
||
\item freetype/ft2build: allowing for rendering text within game
|
||
\item stb\_image: for ease of importing image assets into game engine
|
||
\end{itemize}
|
||
|
||
\subsection{Robust Game Mechanics}
|
||
|
||
\begin{itemize}
|
||
\item Match Three Logic: Core match three logic was implemented, matching 3 or more tiles, chain reactions and limited moves
|
||
|
||
\item Event Management: The game responds successfuly to user interactions
|
||
\end{itemize}
|
||
|
||
\subsection{Practical usage}
|
||
We managed to create a fully functional match-three game, using only our game engine and royalty free assets.
|
||
|
||
\subsection{Contributions to the Field of Game Development}
|
||
Our engine allows aspiring game developers to create their own match three games, and engine developers to learn from it and expand it.
|
||
|
||
In conclusion, we managed to accomplish all of the goals we set for this thesis, our engine successfuly produces a game for all three operating systems we were aiming for.
|
||
|
||
|
||
\section{Reflection on the Development Process}
|
||
Creating this game engine was a long process, it took almost a year to bring it to the state it is now in, during this time several reflections came about.
|
||
|
||
\subsection{Embracing the Multiplatform Challenge}
|
||
In order to overcome differences for different operating system, a lot of time was put into choosing correct graphical api and libraries that could work on Windows, GNU/Linux and MacOS. This proved crucial when later developing the engine as this issue did not came back and I could focus on creating the engine itself.
|
||
|
||
\subsection{Library Integrations}
|
||
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.
|
||
|
||
\chapter{Appendices}
|
||
|
||
|
||
\bibliography{references}
|
||
|
||
|
||
|
||
\end{document}
|
||
|