377 lines
13 KiB
TeX
377 lines
13 KiB
TeX
\documentclass[aspectratio=169]{beamer}
|
|
|
|
% pdfpc slides.pdf --notes=right
|
|
% comment out to disable notes
|
|
\setbeameroption{show notes on second screen=right}
|
|
|
|
\usetheme{metropolis}
|
|
|
|
\usepackage{outlines}
|
|
\usepackage{graphicx}
|
|
\usepackage{minted}
|
|
\setminted{fontsize=\footnotesize,samepage=true}
|
|
%\usepackage{xcolor}
|
|
%\definecolor{codecolor}{HTML}{FFC300}
|
|
|
|
\usepackage[super]{nth}
|
|
\usepackage{csquotes}
|
|
|
|
\title{VEGO-Engine}
|
|
\subtitle{A student project}
|
|
\author{Benedikt, Nicole}
|
|
|
|
\begin{document}
|
|
|
|
\maketitle
|
|
\note{Tone: light hearted "What did we learn"}
|
|
|
|
\begin{frame}{Outline}
|
|
\tableofcontents
|
|
\end{frame}
|
|
|
|
\section{Context}
|
|
|
|
\subsection{The team}
|
|
\begin{frame}{Who are we?}
|
|
\begin{itemize}
|
|
\item Group of 5 people
|
|
\item 1 Semester of C++ Programming under our belt at the start
|
|
\item Common interest: C++ Programming + game development
|
|
\end{itemize}
|
|
\end{frame}
|
|
\subsection{The project}
|
|
\begin{frame}{The assigment}
|
|
\begin{itemize}
|
|
\item \enquote{Development of a fantasy game console} (similar to Pico8)
|
|
\item For simple Arcade like games (think Pong/Space Invaders)
|
|
\item Goal was to teach how to manage and program within a larger project
|
|
\end{itemize}
|
|
\end{frame}
|
|
\begin{frame}{Our interpretation and goals}
|
|
\begin{itemize}
|
|
\item Started out with a Minigame
|
|
\item Realized we can split it into \enquote{Engine} and \enquote{Game Specific} Content
|
|
\item Goal: make easy to use engine without GUI \Rightarrow essentially a game dev lib
|
|
\end{itemize}
|
|
\Rightarrow We could then build on what we have while simultaneously having an implementation using the engine as proof of concept
|
|
|
|
Smart right :D ?\dots
|
|
\end{frame}
|
|
\begin{frame}
|
|
What awaited us was about the equivalent of trying to neatly sort single spaghetti strands after
|
|
they had previously been vigorously jumbled up and mixed with some *juicy* pasta sauce.
|
|
|
|
In other words, quite a messy endeavor\dots
|
|
\end{frame}
|
|
\begin{frame}{Roadmap}
|
|
\nth{3} Semester - learning SDL and the principles of game development
|
|
|
|
\nth{4} and \nth{5} Semester - making the engine
|
|
|
|
At this point we were certain this would be a walk in the park as it would essentially *just* be some refactoring, right?.
|
|
\end{frame}
|
|
\note[itemize]{
|
|
\item Somewhere around this time we realized there was more to compiling a C++ program than pressing
|
|
the run button in Visual Studio.
|
|
\item (subtle foreshadowing about how using VS set us back many many hours :,))
|
|
\item There was quite the naive optimism at the end of this semester (i.e wheeee we made a whole game on our own we are unstoppable :D)
|
|
\item (about 4th/5th semester): separating the project into the game and the parts of the program that could be reused for other games.
|
|
}
|
|
\begin{frame}
|
|
Realization: doing it right the first time is better than fixing it later.
|
|
|
|
And no, don't "just start programming because the architecture will solve itself later"
|
|
\end{frame}
|
|
|
|
\section{Learnings}
|
|
\begin{frame}[allowframebreaks, fragile]{Spaghetti, an example}
|
|
\begin{itemize}
|
|
\item Original Ticket: \enquote{Attack speed stat needs reimplementation}
|
|
|
|
\framebreak
|
|
|
|
Step one, make it so Stateffects are not hardcoded in Engine
|
|
|
|
\begin{minted}[linenos,autogobble,samepage=false,breaklines]{c++}
|
|
void chickengame::pickupables::movementSpeedEffect(Entity* player)
|
|
{
|
|
player->getComponent<StatEffectsComponent>() .modifyStatDur(Stats::MOVEMENT_SPEED, BUFF_DURATION, BUFF_VALUE);
|
|
}
|
|
\end{minted}
|
|
|
|
\framebreak
|
|
|
|
Step two \dots uh oh how do i manage a stateffects life cycle?
|
|
|
|
\begin{minted}[linenos,autogobble,samepage=false,breaklines]{c++}
|
|
void StatEffectsComponent::update()
|
|
{
|
|
for (int i = 0; i < MAX_STATS; i++)
|
|
{
|
|
if (this->buffs.at(i) == 0) continue;
|
|
if (this->buffs.at(i) - 1 == 0)
|
|
{
|
|
this->resetStatValue((Stats)i);
|
|
}
|
|
this->buffs.at(i) -= 1;
|
|
}
|
|
}
|
|
|
|
void StatEffectsComponent::modifyStatDur(Stats stat, int duration, int value)
|
|
{
|
|
if(this->buffs.at((uint8_t)stat) == 0) this->modifyStatValue(stat, value);
|
|
this->buffs.at((uint8_t)stat) += duration;
|
|
}
|
|
\end{minted}
|
|
|
|
\framebreak
|
|
|
|
Step three \dots oh no why are stat types hardcoded in the engine?
|
|
|
|
\begin{minted}[linenos,autogobble,samepage=false,breaklines]{c++}
|
|
void StatEffectsComponent::modifyStatValue(Stats stat, int modifier)
|
|
enum class Stats
|
|
{
|
|
MOVEMENT_SPEED,
|
|
ATTACK_SPEED
|
|
};
|
|
\end{minted}
|
|
|
|
\framebreak
|
|
|
|
\item Step 3.2 uhhhhhh i should make it so developers can add their own stats
|
|
\item Step 3.2.4 how does one elegantly access those set stats so the engine can also work with them
|
|
\item Step ??? help why is this one commit touching 15 files and still not remotely done???
|
|
\end{itemize}
|
|
\framebreak
|
|
|
|
\begin{minted}[linenos,autogobble,samepage=false,breaklines]{c++}
|
|
void chickengame::pickupables::movementSpeedEffect(Entity* player)
|
|
{
|
|
int basePlayerSpeed = player->getComponent<DataComponent>() .getEntry<int>("speed"). value_or(0);
|
|
player->getComponent<DataComponent>().setEntry("speed", 4);
|
|
player->getComponent<StatEffectsComponent>() .addEffect(BUFF_DURATION, [&]() {
|
|
player->getComponent<DataComponent>() .setEntry("speed", basePlayerSpeed);
|
|
});
|
|
}
|
|
\end{minted}
|
|
\end{frame}
|
|
|
|
\subsection{Project environment}
|
|
|
|
\begin{frame}{Baby's first \texttt{CMakeLists.txt}}
|
|
\begin{itemize}
|
|
\item On UAS: Only ever Make or no project setup
|
|
\end{itemize}
|
|
\end{frame}
|
|
|
|
\begin{frame}{Visual Studio}
|
|
\begin{itemize}
|
|
\item Visual C++ :,)
|
|
\item Extra generated files by IDE
|
|
\item VS Solutions have some filepath quirks if you're not careful
|
|
\end{itemize}
|
|
\end{frame}
|
|
\note[itemize]{
|
|
\item Undefined behaviour first noticed by "huh but it works for me :p"
|
|
\item When trying to access assets things went weird
|
|
}
|
|
|
|
\begin{frame}{How the \_ do I import a library?}
|
|
\begin{itemize}
|
|
\item git-modules
|
|
\end{itemize}
|
|
\end{frame}
|
|
\note[itemize]{
|
|
\item SDL is a library, static compile
|
|
\item git-modules - do not bloat contributions
|
|
}
|
|
|
|
\subsection{ECS}
|
|
% ECS slides, split into 3 sections
|
|
\begin{frame}[fragile]{We made an ECS}
|
|
Why an ECS - Entity Component System?
|
|
\begin{itemize}
|
|
\item Encourages reusable code
|
|
\item "Plug-and-play" to add functionality
|
|
\item We found a video tutorial series for it
|
|
\item Simplified implementation: Entities propagate updates to the components
|
|
\end{itemize}
|
|
\end{frame}
|
|
\note[itemize]{
|
|
\item Reusable code mainly on engine side, but also applies to game dev components
|
|
\item plug and play mostly an advantage for game dev - i.e. "I want physics, here are physics"
|
|
\item \enquote{video series} - tease issue of abruptly ending
|
|
\item Components usually only have data, and are querried by the system (hence ecS)
|
|
\item System part used for rendering
|
|
}
|
|
\begin{frame}[allowframebreaks, fragile]{We made an ECS - Implementation}
|
|
\begin{minted}[linenos,autogobble,samepage=false]{c++}
|
|
class Entity {
|
|
private:
|
|
std::vector<std::unique_ptr<Component>> components;
|
|
ComponentArray componentArray = {};
|
|
ComponentBitSet componentBitSet;
|
|
public:
|
|
void update(uint_fast16_t diffTime) const;
|
|
template <typename T> bool hasComponent() const {
|
|
return componentBitSet[getComponentTypeID<T>()];
|
|
}
|
|
template <typename T> T& getComponent() const {
|
|
auto ptr(componentArray[getComponentTypeID<T>()]);
|
|
return *static_cast<T*>(ptr);
|
|
}
|
|
template <typename T, typename...TArgs> T& addComponent(TArgs&&...mArgs) {
|
|
T* c(new T(std::forward<TArgs>(mArgs)...));
|
|
c->entity = this;
|
|
std::unique_ptr<Component> uPtr{ c };
|
|
this->components.emplace_back(std::move(uPtr));
|
|
|
|
componentArray[getComponentTypeID<T>()] = c;
|
|
componentBitSet[getComponentTypeID<T>()] = true;
|
|
|
|
c->init();
|
|
return *c;
|
|
};
|
|
};
|
|
\end{minted}
|
|
\end{frame}
|
|
\note[itemize]{
|
|
\item appologize for formatting - for the sake of compactness
|
|
\item TypeID: increments through all components - copied from video tutorial
|
|
\item NOT beginner friendly
|
|
}
|
|
\begin{frame}[fragile]{We made an ECS - Usage}
|
|
Very easy to use:
|
|
\begin{minted}[linenos,autogobble]{c++}
|
|
auto& projectile(this->manager->addEntity());
|
|
projectile.addComponent<TransformComponent>(pos.x, pos.y, 32, 32, scale);
|
|
projectile.addComponent<SpriteComponent>(texture, 4); // 4 => zIndex
|
|
projectile.addComponent<ProjectileComponent>(range, speed, velocity, owner);
|
|
projectile.addComponent<ColliderComponent>(0.6f);
|
|
\end{minted}
|
|
\end{frame}
|
|
\note{mention: this ease of use is our goal}
|
|
|
|
\subsection{Memory Management}
|
|
\begin{frame}[allowframebreaks, fragile]{Memory Management}
|
|
\begin{minted}[linenos,autogobble]{c++}
|
|
class Manager
|
|
{
|
|
public:
|
|
Entity& addEntity()
|
|
{
|
|
Entity* e = new Entity(*this);
|
|
std::unique_ptr<Entity> uPtr{ e };
|
|
this->entities.emplace_back(std::move(uPtr));
|
|
return *e;
|
|
}
|
|
private:
|
|
std::vector<std::unique_ptr<Entity>> entities;
|
|
}
|
|
\end{minted}
|
|
\framebreak
|
|
Does that solve memory management? Not quite:
|
|
\begin{itemize}
|
|
\item Missing separation of concern - manager also propagates update call
|
|
\item Does not allow "saving" existing entities
|
|
\end{itemize}
|
|
Architectural issues like this are hard to solve this late in development
|
|
|
|
However it does solve memory management\footnote{For \texttt{Entity} classes only, there are some leaks due to bad usage of the \texttt{SDL\_mixer} library}
|
|
\end{frame}
|
|
\note[itemize]{
|
|
\item \enquote{Perfect example of why our ECS is not ideal}
|
|
\item scene management - would replace manager - however manager is too widely used - break API consistency
|
|
\item classic example of good on surface level only for a beginner; transition to more such examples
|
|
}
|
|
|
|
% Beginner traps
|
|
\subsection{C++ beginner traps}
|
|
\begin{frame}[fragile]{C++ - Friend or Foe?}
|
|
\enquote{I just learned about \underline{\hspace{1cm}}, so I'm going to use it everywhere\dots}
|
|
|
|
Please do not take any of our claims as factually accurate or best practice
|
|
\end{frame}
|
|
\begin{frame}[fragile]{C++ - Friend or Foe? - Ternary operator}
|
|
If used in moderation:
|
|
\begin{minted}[linenos,autogobble,samepage=false,breaklines]{c}
|
|
void TextureManager::draw( /* ... */, bool flipped)
|
|
{
|
|
SDL_FlipMode flip = flipped ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
|
|
// ...
|
|
}
|
|
\end{minted}
|
|
\end{frame}
|
|
\begin{frame}[allowframebreaks, fragile]{C++ - Friend or Foe? - \texttt{std::ranges}}
|
|
\begin{minted}[linenos,autogobble,samepage=false,breaklines]{c++}
|
|
// for each tile set
|
|
auto tileConstructorRange = std::views::iota(0)
|
|
| std::views::take(this->mapData.tileSets->size())
|
|
// return the tile set metadata
|
|
| std::views::transform([&](uint16_t i) { /* ... */ })
|
|
| std::views::transform([=, this](const TileSetData& data) {
|
|
// for each tile on the tile set
|
|
return std::views::iota(0)
|
|
| std::views::take(this->mapData.mapSize->x * this->mapData.mapSize->y)
|
|
// only take tiles that are on the ID range of the tile set
|
|
| std::views::filter([=](uint16_t idx) { /* ... */ })
|
|
// extract tile data
|
|
| std::views::transform([=, this](uint16_t idx) {
|
|
/* ... */
|
|
return std::function<Entity*()>(
|
|
[tilePosX, tilePosY, capture0 = *this->mapData.mapTileSize, u, v, zIndex, capture1 = data.texturePath, collision] {
|
|
return Map::addTile(tilePosX, tilePosY, capture0, u, v, zIndex, capture1, collision);
|
|
}
|
|
);
|
|
});
|
|
})
|
|
// 2D view to 1D vector
|
|
| std::views::join
|
|
| std::ranges::to<std::vector>();
|
|
\end{minted}
|
|
\end{frame}
|
|
\note[itemize]{
|
|
\item context:
|
|
\item readable (sort of), efficient (not really due to to<vector>)
|
|
\item issues with c++23 (requires at least gcc14)
|
|
}
|
|
\begin{frame}[fragile]{C++ - Friend or Foe? - \texttt{std::bitset}}
|
|
Might be efficient, but\dots
|
|
\begin{minted}[linenos,autogobble,samepage=false,breaklines]{c++}
|
|
IntersectionBitSet intersections =
|
|
(CollisionHandler::getIntersectionWithBounds(entity, Vector2D(positionChange.x, 0)) |
|
|
(this->entity->getManager().getGame()->collisionHandler ->getAnyIntersection<IntersectionBitSet>(entity, Vector2D(positionChange.x, 0), colliders)) & IntersectionBitSet("0011")) |
|
|
(CollisionHandler::getIntersectionWithBounds(entity, Vector2D(0, positionChange.y)) |
|
|
(this->entity->getManager().getGame()->collisionHandler ->getAnyIntersection<IntersectionBitSet>(entity, Vector2D(0, positionChange.y), colliders)) & IntersectionBitSet("1100"));
|
|
\end{minted}
|
|
\end{frame}
|
|
\note{\dots not very readable}
|
|
|
|
\begin{frame}{Naming conventions and formatting}
|
|
The project currently uses \texttt{camelCase}, \texttt{snake\_case}, sometimes with \texttt{m\_variablePrefixes} for member variables.
|
|
|
|
The solution? \texttt{clang-format}
|
|
|
|
\dots however, what formatting should be used?
|
|
\begin{figure}
|
|
\includegraphics[width=\linewidth]{formatting.png}
|
|
\end{figure}
|
|
\end{frame}
|
|
\note{transistion to project management}
|
|
|
|
%code example
|
|
\begin{frame}[fragile]{Hello, World!}
|
|
\begin{minted}[linenos,autogobble]{c}
|
|
#include <stdio.h>
|
|
|
|
int main()
|
|
{
|
|
printf("Hello, World!");
|
|
return 0;
|
|
}
|
|
\end{minted}
|
|
\end{frame}
|
|
\note{This is just a slide for testing the minted package}
|
|
|
|
\end{document} |