\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} \begin{frame}[allowframebreaks, fragile]{Spaghetti, an example} \begin{itemize} \item Original Ticket:\enquote{attackspeed 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() .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() .getEntry("speed"). value_or(0); player->getComponent().setEntry("speed", 4); player->getComponent() .addEffect(BUFF_DURATION, [&]() { player->getComponent() .setEntry("speed", basePlayerSpeed); }); } \end{minted} \end{frame} \section{Learnings} \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> components; ComponentArray componentArray = {}; ComponentBitSet componentBitSet; public: void update(uint_fast16_t diffTime) const; template bool hasComponent() const { return componentBitSet[getComponentTypeID()]; } template T& getComponent() const { auto ptr(componentArray[getComponentTypeID()]); return *static_cast(ptr); } template T& addComponent(TArgs&&...mArgs) { T* c(new T(std::forward(mArgs)...)); c->entity = this; std::unique_ptr uPtr{ c }; this->components.emplace_back(std::move(uPtr)); componentArray[getComponentTypeID()] = c; componentBitSet[getComponentTypeID()] = 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(pos.x, pos.y, 32, 32, scale); projectile.addComponent(texture, 4); // 4 => zIndex projectile.addComponent(range, speed, velocity, owner); projectile.addComponent(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 uPtr{ e }; this->entities.emplace_back(std::move(uPtr)); return *e; } private: std::vector> 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( [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(); \end{minted} \end{frame} \note[itemize]{ \item context: \item readable (sort of), efficient (not really due to to) \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(entity, Vector2D(positionChange.x, 0), colliders)) & IntersectionBitSet("0011")) | (CollisionHandler::getIntersectionWithBounds(entity, Vector2D(0, positionChange.y)) | (this->entity->getManager().getGame()->collisionHandler ->getAnyIntersection(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 int main() { printf("Hello, World!"); return 0; } \end{minted} \end{frame} \note{This is just a slide for testing the minted package} \end{document}