<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Brittany Blair</title>
    <description>The latest articles on DEV Community by Brittany Blair (@brittanyblairdesign).</description>
    <link>https://dev.to/brittanyblairdesign</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1440800%2F29ca0c44-6a9f-45d2-bac7-3ead71389de0.png</url>
      <title>DEV Community: Brittany Blair</title>
      <link>https://dev.to/brittanyblairdesign</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/brittanyblairdesign"/>
    <language>en</language>
    <item>
      <title>The 20 Game Challenge - Game 2</title>
      <dc:creator>Brittany Blair</dc:creator>
      <pubDate>Mon, 03 Jun 2024 04:13:20 +0000</pubDate>
      <link>https://dev.to/brittanyblairdesign/the-20-game-challenge-game-2-41oh</link>
      <guid>https://dev.to/brittanyblairdesign/the-20-game-challenge-game-2-41oh</guid>
      <description>&lt;p&gt;This is a continuation of my last blog post found &lt;a href="https://dev.to/brittanyblairdesign/the-20-game-challenge-game-1-42jp"&gt;Here&lt;/a&gt;. If this post has caught your interest the first one goes over all the basics of the challenge, why I'm doing it, etc.&lt;/p&gt;

&lt;p&gt;This took me longer than expected. Making your own game engine is hard, and structuring the engine to be scalable &amp;amp; modular is even harder. Most of my time spent on game #2 was dedicated to improving the game engine to support a ton of new features. Even though the game itself is fairly simple, the additions and changes made to the engine itself were quite an undertaking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Game 2 Requirements
&lt;/h2&gt;

&lt;p&gt;So our first game was a flappy bird style game, and the challenge helps you out by building upon the previous challenges. So if you chose pong last time, you could do Breakout this time are re-use a lot of your previously written code. &lt;/p&gt;

&lt;p&gt;I have the option of choosing either Breakout or Jetpack Joyride for game number 2. I am going to do the jetpack joyride, this time around which adds a few more stretch goals that we can use to expand upon our MonoGame Engine logic, but more on that later.&lt;/p&gt;

&lt;p&gt;The requirements for this game are a half step more difficult than the last game. Here are the main goals:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a game world with a floor. The world will scroll from right to left endlessly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a player character that falls when no input is held, but rises when the input is held.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add obstacles that move from right to left. Feel free to make more than one type of obstacle.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Obstacles can be placed in the world using a script so the level can be truly endless.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Obstacles should either be deleted or recycled when they leave the screen.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The score increases with distance. The goal is to beat your previous score, so the high score should be displayed alongside the current score.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Optional Goals:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Save the high score between play sessions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The jetpack is a machine gun! Add bullet objects that spew from your character when the input is held.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Particle effects are a fun way to add game juice. Mess around with some here, making explosions or sparks when things get destroyed!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So that's quite a bit more than the first game, and of course same as the last time. I will be doing my best to reach every goal, including the stretch goals.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Plan
&lt;/h2&gt;

&lt;p&gt;Same as before, I decided to make my game plan on paper as seen above. I couldn't tell you exactly why, but when I first read the requirements and the description of Jetpack Joyride the first thing I thought of was a squid. So I ran with it, and in my game, you play as a little Squid running endlessly to escape a fish market.&lt;/p&gt;

&lt;p&gt;The more I wrote down in my plan the more I loved the idea so This time around we are going to put more effort into the art &amp;amp; animation of the game to make something befitting our little squid. &lt;/p&gt;

&lt;p&gt;Right out of the gate, I knew that I needed to do some refactors on the game engine systems I designed during game 1. So I also wrote down a note about what systems I don't have, that I will need to reach all the goals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Engine Systems Update
&lt;/h2&gt;

&lt;p&gt;The very first thing I did was work on refactoring what I already had. The worst thing I could do to myself between games was to trust the code I had written previously to be bulletproof. So I reworked a lot. &lt;/p&gt;

&lt;p&gt;I worked on Improving collisions as a large part of this project. Before I was using the MonoGame Rectangle class and just calling &lt;code&gt;_collierRect.IsIntersecting(otherRectangle)&lt;/code&gt; and that worked fine and all. But this time we are going to have bullets that will move at various speeds and we could risk bullets not colliding if the framerate and speed aren't correct. &lt;/p&gt;

&lt;p&gt;So I decided to implement both  AABB collision detection ( similar to the rectangle class ) and the Segment AABB collision detection which is a more accurate collision detection ( at a performance cost. ).&lt;/p&gt;

&lt;p&gt;I made some other miscellaneous improvements to  UI buttons, object classes, etc. Check out my &lt;a href="https://github.com/BrittanyBlairDesign/JetSquid"&gt;GitHub repository&lt;/a&gt; if you're interested in seeing what's new.&lt;/p&gt;

&lt;h2&gt;
  
  
  Animation and VFX, &amp;amp; Window Scaling Systems for the Engine
&lt;/h2&gt;

&lt;p&gt;Listed in the requirements of this game there is a stretch goal of having some visual effects for particles and destruction. So that means working on creating a system to handle particles. But also, I want to have an animated character so we needed to implement an Animation state system. &lt;/p&gt;

&lt;p&gt;Starting with particles, I made a few new classes: Particle, Emitter, EmitterParticleState, an Interface for Emitter types, and a ConeEmitterType. The Emitter class utilizes Object Pooling, using a linked list of active particles and a linked list of inactive particles. Here's how it works&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The emitter has a maximum number of particles and when it updates, it will spawn particles if the linked list length has not reached the maximum.&lt;/li&gt;
&lt;li&gt;When a particle gets to the end of its lifespan it no longer gets drawn to the screen. Instead, it is moved from the active list over to the inactive list where it will wait to be re-spawned.&lt;/li&gt;
&lt;li&gt;When the Emitter reaches its maximum number of spawned particles, I will stop making new particles and instead re-spawn the existing particle objects from the inactive list, moving them over to the active list once more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the animation system, I created a custom importer for sprite sheet animations. &lt;a href="https://dev.to/brittanyblairdesign/20-game-challenge-making-a-custom-importer-52b"&gt;A full post about that importer is here.&lt;/a&gt; This was super helpful in the process of making the game. The importer takes in a PNG and a bunch of import settings for the sprite sheet and then creates custom animation assets that I can load and unload from the game.&lt;/p&gt;

&lt;p&gt;The last major update I made the the engine files was the addition of a Viewport scaler. I wanted to add the ability for players to scale up or down the game's viewport and maintain my desired aspect ratio. &lt;/p&gt;

&lt;p&gt;This was pretty difficult because of how mouse input is captured. If the window itself tracks the location of the mouse and when you get the mouse position it returns the pixel position of the mouse. &lt;/p&gt;

&lt;p&gt;Here was my problem If your game is designed for 1920 x 1080 but your window size was changed by the user to be larger or smaller than that, the mouse position wouldn't account for this new scale. So, if I have a button placed at x = 400, y = 400, and my window is scaled down by half the designed size. when I click on the image of the button the mouse's position is x = 200, y = 200. Meaning that it looks like you clicked the button but mathematically you did not click it.&lt;/p&gt;

&lt;p&gt;Unfortunately, the only solution to this is to use a matrix to calculate scale and invert the matrix to find the mouse position. I never learned any matrix math so I went into this blind. Here is my solution to this problem&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace Engine.Viewports
{
    public class ScalingViewport
    {
        private readonly GameWindow _Window;
        public GraphicsDeviceManager _Graphics { get; }
        public Viewport _Viewport =&amp;gt; _Graphics.GraphicsDevice.Viewport;

        int _virtualWidth;
        int _virtualHeight;

        bool useBlackBars = true;
        int _barWidth;
        int _barHeight;

        bool isResizing;

        int DESIGNED_RESOLUTION_WIDTH;
        int DESIGNED_RESOLUTION_HEIGHT;
        float DESIGNED_RESOLUTION_ASPECT_RATIO;

        public ScalingViewport(GameWindow window, GraphicsDeviceManager graphics, int DesignedWidth, int DesignedHeight, float DesignedRatio)
        {
            _Window = window;
            _Graphics = graphics;

            DESIGNED_RESOLUTION_WIDTH = DesignedWidth;
            DESIGNED_RESOLUTION_HEIGHT = DesignedHeight;
            DESIGNED_RESOLUTION_ASPECT_RATIO = DesignedRatio;

            isResizing = false;
            window.ClientSizeChanged += OnClientSizeChanged;

            _Graphics.HardwareModeSwitch = true;
            _Graphics.IsFullScreen = false;
            _Graphics.ApplyChanges();
        }

        public Rectangle GetDestinationRectangle()
        {
            return new Rectangle(0, 0, _virtualWidth, _virtualHeight);
        }

        private void OnClientSizeChanged(object sender, EventArgs e)
        {
            if (!isResizing &amp;amp;&amp;amp; _Window.ClientBounds.Width &amp;gt; 0 &amp;amp;&amp;amp; _Window.ClientBounds.Height &amp;gt; 0)
            {
                isResizing = true;
                RefreshViewport();
                isResizing = false;
            }
        }

        public virtual void RefreshViewport()
        {
            _Graphics.GraphicsDevice.Viewport = GetViewportScale();

        }

        protected virtual Viewport GetViewportScale()
        {
            var variance = 0.5;
            int windowWidth = _Graphics.GraphicsDevice.PresentationParameters.BackBufferWidth;
            int windowHeight = _Graphics.GraphicsDevice.PresentationParameters.BackBufferHeight;
            var actualAspectRatio = (float)windowWidth / windowHeight;
            _barHeight = 0;
            _barWidth = 0;

            if (actualAspectRatio &amp;lt;= DESIGNED_RESOLUTION_ASPECT_RATIO)
            {
                var presentHeight = (int)(windowWidth / DESIGNED_RESOLUTION_ASPECT_RATIO + variance);
                _barHeight = (int)(windowHeight - presentHeight) / 2;

                _virtualWidth = windowWidth;
                _virtualHeight = presentHeight;

            }
            else
            {
                var presentWidth = (int)(windowHeight * DESIGNED_RESOLUTION_ASPECT_RATIO + variance);
                _barWidth = (int)(windowWidth - presentWidth) / 2;

                _virtualWidth = presentWidth;
                _virtualHeight = windowHeight;

            }


            int x = _barWidth;
            int y = _barHeight;

            if(!useBlackBars)
            {
                _Graphics.PreferredBackBufferWidth = _virtualWidth;
                _Graphics.PreferredBackBufferHeight = _virtualHeight;
                _Graphics.ApplyChanges();

                x = 0;
                y = 0;
            }

            return new Viewport
            {
                X = x ,
                Y = y ,
                Width = _virtualWidth,
                Height = _virtualHeight,
                MinDepth = 0,
                MaxDepth = 1,
            };

        }

        public virtual Matrix GetScaleMatrix()
        {
            float Scale = (float)_virtualWidth / (float)DESIGNED_RESOLUTION_WIDTH;
            return Matrix.CreateScale(Scale);
        }

        public Point PointToScreen(Point point)
        {
            return PointToScreen(point.X, point.Y);
        }

        public virtual Point PointToScreen(int x, int y)
        {
            Matrix matrix = Matrix.Invert(GetScaleMatrix());


            return Vector2.Transform(new Vector2(x - _barWidth, y - _barHeight), matrix).ToPoint();
        }

        public Point GetScaledMousePosition()
        {
            return PointToScreen(Mouse.GetState().Position);
        }

    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this solution worked for me, and while I don't like the black bars on the side of the screen. I tried to adjust this so that the window would automatically adjust the scale after a user made a change to fill the space without black bars. However, even though it worked and there were no bars, I found a bug with the mono game framework where if you directly set the width or height of the window it locks the game window to your main monitor. I have a dual monitor setup so only being able to test it on my main monitor and not being able to move it outside of that would not be ideal&lt;/p&gt;

&lt;h2&gt;
  
  
  The Art
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fha7n8sy8ijwkg3x9noyt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fha7n8sy8ijwkg3x9noyt.png" alt="All art assets and source art files" width="610" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just as I did for the first game I am making my art for this game as well. But this time I am also animating the character I haven't animated something in like 7 years and I was a 3D animator so this step was kind of scary for me. I wanted to take more time on the other assets In the level but time was not on my side so I wasn't able to achieve a cohesive look. But it's okay, I can always go back and revisit this in the future.&lt;/p&gt;

&lt;p&gt;So, I started with images. The cover image for this blog post is the start menu for the game. Going with our game's theme I pulled up some references to fish markets and saw that there are tons of blue bins with ice and fish usually stacked in front of or around various stalls. Then I looked up some common types of squid you can purchase at a fish market and chose these red squids to be the type of squid our character will be. Then I worked on making some unique UI buttons &amp;amp; a logo. I decided to make the UI look like price ticket signs and I made the logo in a graphic style.&lt;/p&gt;

&lt;p&gt;After a few sketches and trying to figure out how I wanted the characters to move when they were on the ground, I came up with the perfect design. Squids have 10 tentacles and to keep the character's silhouette clear I decided to do 8 tentacles, and let's just say they lost 2 of them at some point.&lt;/p&gt;

&lt;p&gt;Here is a look into my animation process.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2q0zqg82m5hm6n9f2f8w.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2q0zqg82m5hm6n9f2f8w.gif" alt="Initial animation sketch. Rough version of what I wanted the animation to look Like." width="436" height="338"&gt;&lt;/a&gt; &lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frg857dbegwmdlh0wrhtk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frg857dbegwmdlh0wrhtk.gif" alt="Motion passes to better define the movements of each part of the character." width="436" height="338"&gt;&lt;/a&gt; &lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frq8zhsc8oedfjgc2m9zh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frq8zhsc8oedfjgc2m9zh.gif" alt="Base color pass. animating the various spots and shading of just the base color of the character." width="436" height="338"&gt;&lt;/a&gt; &lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fovn90vbp7i0dlqrzw8mn.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fovn90vbp7i0dlqrzw8mn.gif" alt="Shading pass, adding light and shadow to the animation to give the character a more 3-dimensional look." width="436" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the first animation I made for this character, a simple walking animation to better understand who the character is and how they move around the world. I love how they use their tiny tentacles to run and their big arms are being dragged behind them it's so cute. All the animated elements of the game were made in Marmoset Hexels 3, this program is game art friendly and will let me export my animation frames as a sprite sheet.&lt;/p&gt;

&lt;p&gt;by the time I got done with the player animation, I had to speed up the other assets so I used a voxel art program called Magica Voxel to model out some obstacles for the game and render them as PNG images. Here is a list of the assets I made as obstacles &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A blue bin filled with ice and fish filets&lt;/li&gt;
&lt;li&gt;A simple wooden crate&lt;/li&gt;
&lt;li&gt;hanging ceiling fan that is animated&lt;/li&gt;
&lt;li&gt;hanging ceiling light&lt;/li&gt;
&lt;li&gt;Air ducts&lt;/li&gt;
&lt;li&gt;Tiling brick wall background &lt;/li&gt;
&lt;li&gt;Tiling Floor Background&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Game
&lt;/h2&gt;

&lt;p&gt;Ok now that all the systems are designed, Art is drawn, and animations are exported. I moved on to programming the game itself. &lt;/p&gt;

&lt;p&gt;This game is pretty similar to the Flappy Duck game, but there are a few key differences when it comes to the obstacles you're avoiding. I had to make a spawning system that would spawn obstacles on the ground and stack them if an obstacle already exists rather than pile them on top of each other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2e85jq950bku09xycejm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2e85jq950bku09xycejm.png" alt="Collisions Visible Screenshot" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;for there are lots of collision checks and items being spawned so I had to be very careful about memory leaks. I had to make some automated functionality that would garbage-collect the spawned obstacles and items once they exited the main viewport.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fajtx8il6i5nond3prred.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fajtx8il6i5nond3prred.png" alt="Player Hovering" width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I had to take a bit of time working out how I wanted animations to change / transition by making a kind of state machine for the player. The player's animation state could be walking, jumping, falling, or hovering in the air. each of these had its transition requirements and actions that would be performed once the transition was complete. &lt;/p&gt;

&lt;p&gt;For example, Hovering until you run out of ink and running out of ink transitions you into falling. Walking over the edge of an obstacle also lets you fall, or getting Hit by an overhead obstacle would force you to fall. So there were lots of conditions to check for when switching animation states and that was time-consuming but gave the best visual outcome for the game.&lt;/p&gt;

&lt;p&gt;The game has 3 scenes: the Start Menu, the Main Game, and the End Menu. The loop for the game has the player playing until the player either dies or quits the game. When the player destroys obstacles by hovering over them and shooting them with their ink, they get points which adds up to a high score. When the game ends, the player's session score and their Highest Score are listed in the End Menu. This is the same as how we did scores in the Flappy Duck game.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This second game took far longer than anticipated but I learned a TON about game engine architecture. Even though this is game 2 of a 20-game challenge I already feel way more confident in my coding abilities than when I started. &lt;/p&gt;

&lt;p&gt;There was a lot I wanted to do that I just couldn't spare the time to do for this game. It was hard to just get the game done because It was really easy to fall into a rabbit hole just working to add new things to the engine or improve existing features. Toward the end here, I just had to draw a line for myself and say "Just finish the game". At times It's hard for me to truly accept that This 20-game project is not about making perfect games. But rather it's about getting a game over the finish line, Something that I frequently struggle to do while working on personal projects.&lt;/p&gt;

&lt;p&gt;I am going to be taking a break from the 20-game challenge for a bit as I need to do some research and figure out if there is a way to publish a Mono Game project for the Web. I've seen others talk about doing it but have no clue how they achieved it and I would like to have Web builds for games I make with the Engine so I can post them on Itch.io. Also, many game jams only accept web platform builds because of people handing over viruses in build projects and I want to use my game engine to do some game jams as a part of this 20-game challenge.&lt;/p&gt;

&lt;p&gt;There is a possibility that I won't be able to figure out web publishing for Mono Game, and if that happens then Mono Game might not be the framework for me. If that is the case I'll have to find a new Framework and continue the challenge using whatever new framework I find.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>gamedev</category>
      <category>challenge</category>
      <category>learning</category>
    </item>
    <item>
      <title>20 Game Challenge: Making a Custom Importer</title>
      <dc:creator>Brittany Blair</dc:creator>
      <pubDate>Mon, 13 May 2024 23:15:12 +0000</pubDate>
      <link>https://dev.to/brittanyblairdesign/20-game-challenge-making-a-custom-importer-52b</link>
      <guid>https://dev.to/brittanyblairdesign/20-game-challenge-making-a-custom-importer-52b</guid>
      <description>&lt;p&gt;If you haven't seen My first 20 game challenge post you can check out the first post &lt;a href="https://dev.to/brittanyblairdesign/the-20-game-challenge-game-1-42jp"&gt;here&lt;/a&gt; It goes over the progress I made thus far in building my game engine using the MonoGame framework.&lt;/p&gt;

&lt;p&gt;This post focuses on creating a custom Importer for the MonoGame Content Pipeline. I have made a bunch of improvements on my game engine in preparation for game 2 that I am super excited to talk about. However, due to the lack of documentation and guides around making a custom pipeline extension, I decided to dedicate a post to this task.&lt;/p&gt;

&lt;p&gt;If you're looking for specifically a game 2 of the 20-game challenge update, no worries I am still working on game 2 and will be posting all about it very soon!&lt;/p&gt;

&lt;p&gt;I am approaching this tutorial in the same way I made my sprite sheet animation importer but you can follow these steps to make ANY kind of importer you want. Just know that you may have to use different libraries or create different functions depending on what you're importing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;For game 2 I made some 2D sprite animations in Marmoset Hexels 3, which allows me to export sprite sheet animations. But once I started trying to make an animation system in my game engine I ran into some quality-of-life trouble. &lt;/p&gt;

&lt;p&gt;MonoGame's standard content pipeline handles these sprite sheets in the same way they would any 2D image, but there is so much more information in our sprite sheet. So I would have to hard code a lot of the animation information such as how many frames, the frame rate, the size of the sprite being animated, etc. and make a new object for every animation in a sprite sheet. Multiply that by the number of sprite sheet animations imported and that becomes a ton of work.&lt;/p&gt;

&lt;p&gt;So I needed to find a way to import .png files in a way that would let me fill out that information, save it, and load it in the game when it's needed.&lt;/p&gt;

&lt;p&gt;For someone whose first career was art, and who is an entirely self-taught programmer this was not an easy task for me. Yeah, I could search GitHub and find someone else's importer or use MonoGame Extended and add that to my project. But that would defeat the purpose of all of this. I want to learn all I can about MonoGame so, I want to make it myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;This is a seriously undocumented task and It took me weeks to get what I'm about to show you working. But also It would be a disservice to the community not to go ahead and mention &lt;a href="https://www.youtube.com/@aristurtledev"&gt;Aristurtle&lt;/a&gt; and their channel. They have a video all about making a simple importer that I used to get started on my importer. They consistently upload MonoGame content and help to moderate the MonoGame Discord server, 10/10 resource and awesome human being ( or turtle? )&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 - Make a MonoGame Pipeline Extension Project.
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0fb5bz9g0y6950kt3od3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0fb5bz9g0y6950kt3od3.png" alt="Image description" width="800" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First step, use the MonoGame Pipeline Extension Template, name it whatever you want to name it. I named mine "&lt;em&gt;SpriteSheetAnimationPipelineExtension&lt;/em&gt;". I know it's long-winded but what's done is done. Save it wherever you want to save it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 - Take a Look around.
&lt;/h2&gt;

&lt;p&gt;There are 2 files autogenerated for you. the content Processor, and the Content Importer. both with generic names. &lt;/p&gt;

&lt;p&gt;Importer1 looks something like this :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Microsoft.Xna.Framework.Content.Pipeline;

using TImport = System.String;

namespace SpriteSheetAnimationPipelineExtension
{
    [ContentImporter(".txt", DisplayName = "Importer1", DefaultProcessor = "Processor1")]
    public class Importer1 : ContentImporter&amp;lt;TImport&amp;gt;
    {
        public override TImport Import(string filename, ContentImporterContext context)
        {
            return default(TImport);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When The MonoGame content Pipeline starts the process of building the project this is the first class that is called. Rename the Importer 1 file and class, I name mine "&lt;em&gt;SpriteSheetAnimationImporter&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;Processor 1 looks something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Microsoft.Xna.Framework.Content.Pipeline;

using TInput = System.String;
using TOutput = System.String;

namespace SpriteSheetAnimationPipelineExtension
{
    [ContentProcessor(DisplayName = "Processor1")]
    internal class Processor1 : ContentProcessor&amp;lt;TInput, TOutput&amp;gt;
    {
        public override TOutput Process(TInput input, ContentProcessorContext context)
        {
            return default(TOutput);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The processor class 'Process' function takes in the output of the Importer's 'Import' function. Rename the Processor 1 file and class. I named mine "&lt;em&gt;SpriteSheetAnimationProcessor&lt;/em&gt;"&lt;/p&gt;

&lt;p&gt;2 other classes will be super important that aren't loaded into the project for us. We are going to make one of them right now. It's a Writer class that will take the output of the Processor classes 'Process' method and write the information to an XML file for us. &lt;/p&gt;

&lt;p&gt;Go ahead and create a new class and name it. I named my class "&lt;em&gt;SpriteSheetAnimationWriter&lt;/em&gt;". In that class copy down the code I have here. Make sure to change the namespace to match your project's namespace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler;


using TInput = System.String;

namespace SpriteSheetAnimationPipelineExtension
{

    [ContentTypeWriter]
    internal class SpriteSheetAnimationWriter : ContentTypeWriter&amp;lt;TInput&amp;gt;
    {
        string _runtimeIdentifier;
        protected override void Write(ContentWriter output, TInput value)
        {

        }

        public override string GetRuntimeReader(TargetPlatform targetPlatform)
        { 
           return default (string);
        }

        public override string GetRuntimeType(TargetPlatform targetPlatform)
        {
            return default(string);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3 - What Information Do We Need Saved?
&lt;/h2&gt;

&lt;p&gt;We need to know what data is important for us to save and some kind of asset type to hold that data. Using our Sprite Sheet animation as an example, let's write down everything we know about a Sprite Sheet animation.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sprite sheet animations have columns, one for each frame of an animation that is the same width as the sprite&lt;/li&gt;
&lt;li&gt;Sprite sheet animations rows, one for each animation and the same height as the sprite.&lt;/li&gt;
&lt;li&gt;sprite sheet animations have a width that represents the width of the image in pixels.&lt;/li&gt;
&lt;li&gt;Sprite sheet animations have a Height that represents the height of the image in pixels.&lt;/li&gt;
&lt;li&gt;In terms of MonoGame a sprite sheet animation has a Texture2D that is referenced to load the image and draw it to the screen.&lt;/li&gt;
&lt;li&gt;Sprite sheet animations can have a list of multiple animations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now let's write down what we know about animations.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Each animation has a name to describe the animation.&lt;/li&gt;
&lt;li&gt;Each animation is located at a specific row on the sprite sheet.&lt;/li&gt;
&lt;li&gt;Each animation has a start frame that represents which frame the animation will start at. &lt;/li&gt;
&lt;li&gt;Each animation has a frame count that represents the number of frames in the animation.&lt;/li&gt;
&lt;li&gt;Each animation has a frame rate, which is used to calculate how many frames are displayed per second.&lt;/li&gt;
&lt;li&gt;Each animation has a width that represents the width of the sprite for every frame of the animation.&lt;/li&gt;
&lt;li&gt;Each animation has a height that represents the height of the sprite for every frame of the animation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's a good amount of info that we can integrate into some classes and class variables. Let's start with the animations by making a new class. I am naming mine "&lt;em&gt;SpriteAnimationAsset&lt;/em&gt;".&lt;/p&gt;

&lt;p&gt;Then I'm designing the class to have variables for each of the points of info I wrote above. I'm also going to write a constructor.&lt;/p&gt;

&lt;p&gt;Also a quick note, you can write whatever information you want in your class to have and make a class in the same way. No need to follow this exactly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace SpriteSheetAnimationPipelineExtension
{
    public class SpriteAnimationAsset
    {
        public string Name
        { get; set; }

        public int Row
        { get; set; }

        public int StartFrame
        { get; set; }

        public int FrameCount
        { get; set; }

        public int FrameRate
        { get; set; }

        public int SpriteWidth
        { get; set; }

        public int SpriteHeight
        { get; set; }

        public SpriteAnimationAsset(string name, int row, int startFrame, int frameCount, int frameRate, int spriteWidth, int spriteHeight)
        {
            Name = name;
            Row = row; 
            StartFrame = startFrame;
            FrameCount = frameCount;
            FrameRate = frameRate;
            SpriteWidth = spriteWidth;
            SpriteHeight = spriteHeight;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can make a class for the Sprite sheet itself. Make a new class, mine is named "&lt;em&gt;SpriteSheetAnimationAsset&lt;/em&gt;". Fill the class with some variables that match what we wrote about sprite sheets. You can write a constructor here too if you would like. I decided not to.&lt;/p&gt;

&lt;p&gt;Two important notes are that instead of a Texture2D variable, we want to make a Texture2DContent variable. We have to import and load the texture ourselves and this will help us do it. Also, I added a string variable called runtime Identifier, I will explain this later but for now, add one to your class too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using System.Collections.Generic;

namespace SpriteSheetAnimation
{
    public class SpriteSheetAnimationAsset
    {
        public int Rows
        { get; set; }

        public int Columns
        {  get; set; }

        public int Width
        { get; set; }

        public int Height 
        { get; set; }

        public Texture2DContent TextureSheet 
        { get; set; }

        public IReadOnlyCollection&amp;lt;SpriteAnimationAsset&amp;gt; Animations { get; set; } = new List&amp;lt;SpriteAnimationAsset&amp;gt;();

        public string RuntimeIdentifier
        { get; set; }

    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These two new classes are going to be used exclusively as a part of our pipeline extension. So we don't need to add any fancy methods to them. We will get this information in our games using different classes that we will write later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 - The Importer class
&lt;/h2&gt;

&lt;p&gt;As I said earlier, this is the very first class that is called upon by the MonoGame content pipeline. Let's start at the top of the script.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;using TImport = System.String;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This line of code creates a type alias for the type of object we want to import. The default type is a string, but what we want to import is a &lt;em&gt;SpriteSheetAnimationAsset&lt;/em&gt;. To do that we need to set TImport equal to [namespace.className]. Here's what it looks like after I implement this change.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;using TImport = SpriteSheetAnimationPipelineExtension.SpriteSheetAnimationAsset&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next let's look at our class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace SpriteSheetAnimationPipelineExtension
{
    [ContentImporter(".txt", DisplayName = "Importer1", DefaultProcessor = "Processor1")]
    public class SpriteSheetAnimationImporter : ContentImporter&amp;lt;TImport&amp;gt;
    {
        public override TImport Import(string filename, ContentImporterContext context)
        {
            return default(TImport);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The line &lt;code&gt;[ContentImporter(".txt", DisplayName = "Importer1, DefaultProcessor = "Processor1")]&lt;/code&gt; Is super important, this tells the MonoGame content Pipeline, what file type is compatible, the name of the importer to show in a dropdown list of all importer types, and the name of the processor to show in a dropdown list of all processors types. The image below is an example of where these display names are shown in the MonoGame Content pipeline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9tc594f141qn3gdbg51.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9tc594f141qn3gdbg51.png" alt="Image description" width="559" height="230"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's customize this line to fit our needs. I'm Importing .png files, and I'm going to name the display name the same as my class but with spaces. The Default processor I'm going to name is the same as what I will write in the Processor classes display name.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[ContentImporter(".png", DisplayName = "Sprite Sheet Animation Importer", DefaultProcessor = "Sprite Sheet Animation Processor")]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For the class declaration, it should look something like this &lt;code&gt;public class SpriteSheetAnimationImporter : ContentImporter&amp;lt;TImport&amp;gt;&lt;/code&gt; That just means that it is a content importer made to import the content type of our TImport alias.&lt;/p&gt;

&lt;p&gt;Now this is the bulk of the processor, overriding the Import method. So just by looking at it. we know that it needs to return the same Type as our TImport alias, and it will be passed in a string for the file name and a ContentImportercontext which we can use for debugging and logging messages.&lt;/p&gt;

&lt;p&gt;In this method, we want to create a new SpriteSheetAnimationAsset local variable. This is what we will be returning and passing on to our processor class.&lt;br&gt;
&lt;code&gt;// Make a sprite sheet animation asset.&lt;br&gt;
            SpriteSheetAnimationAsset sheetAsset = new SpriteSheetAnimationAsset();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next, we need to import the .png file as Texture2DContent. otherwise, we won't be able to properly use our sprite sheet animation image in the same ways we would a Texture2D. To do that we need to make a new TextureImporter, find the file path, and import the file as Texture2DContent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            //Use the texture Importer to get the texture content for the sheet asset.
            TextureImporter textureImporter = new TextureImporter();
            var filePath = Path.Combine(Path.GetDirectoryName(filename), filename);

            context.Logger.LogMessage("importing sprite sheet file : " + filePath); // Log the progress

            Texture2DContent content = (Texture2DContent)textureImporter.Import(filePath, context); // Import the file as Texture2DContent
            content.Name = Path.GetFileNameWithoutExtension(filePath); // Make sure the texture name is correct so it can be loaded with the content.Load&amp;lt;&amp;gt; function in the game.

            // assign the texture content to the texture sheet var.
            sheetAsset.TextureSheet = content;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, Return the SpritesheetAnimation variable we made at the start of the Import Method.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;return sheetAsset;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 - The Processor
&lt;/h2&gt;

&lt;p&gt;Similar to the importer, the processor has a TInput but it also has a TOutput. if you're importing a Text file, CSV, JSON, or any other kind of file. You might be importing a string or system.Json object. Make sure the TInput matches what we used in our Importer because the result of the Input method gets passed straight to the processor.&lt;/p&gt;

&lt;p&gt;For the TOutput you can output whatever you want your final output to be. For me, I'm going to have the same TInput and TOutput type, but yours might be different if you aren't following along.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;using TInput = SpriteSheetAnimation.SpriteSheetAnimationAsset;&lt;br&gt;
using TOutput = SpriteSheetAnimation.SpriteSheetAnimationAsset;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next above the class declaration, we have &lt;code&gt;[ContentProcessor(DisplayName = "Processor1")]&lt;/code&gt; we need to change the display name of the processor to match the DefaultProcessor we wrote in our Importer. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;[ContentProcessor(DisplayName = "Sprite Sheet Animation Processor")]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now something you might have noticed if you have imported an asset into MonoGame is that in the properties window, there are some options you can edit and adjust. Take a look at the properties shown for a texture importer in the image below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F866vkentb6f3whilqpyn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F866vkentb6f3whilqpyn.png" alt="Processor parameters of the MGCB Editor" width="560" height="586"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These are Processor Parameters and we are going to make some simple parameters for ourselves too. These parameters are written like variables inside of our processor class, with some component model tags. Here's the basic structure.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[DisplayName("Parameter Name here")]&lt;br&gt;
[DefaultValue(static value here)] // optional, you don't need a default value.&lt;br&gt;
[Description("Description of the parameter here")] // optional, you don't need a description.&lt;br&gt;
public string parameter {get;set;} // Define the parameter. So far I know that integers, strings, and booleans can be parameters. There are probably other types too.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can add as many parameters as you need or want to for your specific asset class needs. But make sure to add a Runtime Identifier we will need it later. Here is what I added to my processor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        [DisplayName("Sprite Sheet Animations")]
        [DefaultValue(1)]
        [Description("How many animations are in the sprite sheet. 1 row is 1 animation.")]
        public int _rows { get; set; }

        [DisplayName("Sprite Sheet Max Frames")]
        [DefaultValue(2)]
        [Description("What is the maximum number of frames per animation")]
        public int _columns { get; set; }

        [DisplayName("Texture Width")]
        [Description("The total Width in pixels of the sprite sheet")]
        public int _width { get; set; }

        [DisplayName("Texture Height")]
        [Description("The total Height in pixels of the sprite sheet")]
        public int _height { get; set; }

        [DisplayName("Animation Frame Rate")]
        [Description("List a common frame rate for the animation.")]
        public int _frameRate{ get; set; }

        [DisplayName("Animation Names")]
        [Description("Add a list of Names for the animations in order from the top of the sheet to the bottom of the sheet." +
             "Please put a comma between each animation name")]
        public string _AnimationNames { get; set; }

        [DisplayName("Frame Counts")]
        [Description("Enter in the frame count for the animations in order from top to bottom." +
            "Please put a comma between each Frame count.")]
        public string frameCount { get; set; }

        [DisplayName("Sprite Width")]
        [Description("In pixels, write down the width of the sprite for each frame ")]
        public int _spriteWidth { get; set; }

        [DisplayName("Sprite Height")]
        [Description("In pixels, write down the Height of the sprite for each frame")]
        public int _spriteHeight { get; set; }

        [DisplayName("Runtime Identifier")]
        public string identifier { get; set; }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I have all my processor parameters defined, we can move on to the Process Method. It outputs our TOutput type alias and takes in our TInput from the Importer, and a ContentProsessorContext we can use to debug and write logs.&lt;/p&gt;

&lt;p&gt;First, we want to make sure that the Runtime Identifier (identifier) is not null. It's required for us to move forward.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;if(identifier == string.Empty)&lt;br&gt;
            {&lt;br&gt;
                throw new Exception("No Runtime Type was specified for the content.");&lt;br&gt;
            }&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next, if you noticed in my processor parameters, I ask the user to give me lists of items separated with a comma rather than asking for an array or asking for a List. This is because I couldn't get a list or array to show up properly in the MGCB Editor, So I grabbed this string of names and frame counts then I split the strings using the ',' comma as the separator. For the frame count, I need to parse the string version of the int into an actual integer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                // Split all values that are contained in a string list.
                string[] names = _AnimationNames.Split(',');
                string[] framesStr = frameCount.Split(",");
                int[] frames = new int[framesStr.Length];

                for( int i = 0; i &amp;lt; framesStr.Length; i++)
                {
                    f
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I need to take all the information about animations and make a loop that creates a new SpriteAnimationAsset for each row of the sprite sheet and fills out all the necessary information.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                // Create all the sprite animation assets
                List&amp;lt;SpriteAnimationAsset&amp;gt; animations = new List&amp;lt;SpriteAnimationAsset&amp;gt;();
                for(int i = 0; i &amp;lt; _rows; i++)
                {
                    SpriteAnimationAsset anim = new SpriteAnimationAsset(names[i], i, 0, frames[i], _frameRate, _spriteWidth, _spriteHeight);
                    animations.Add(anim);
                }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we need to run the Texture2DContent we set in our Importer through the TextureProcessor and update the Texture2D content of our SpriteSheetAnimationAsset (TInput)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`                // Process the Texture file and assign values to the input variable.
                TextureProcessor TexProcessor = new TextureProcessor();
                var textureContent = TexProcessor.Process(input.TextureSheet, context);
                input.TextureSheet = textureContent as Texture2DContent;`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, We set the variables of our SpriteSheetAnimationAsset (input) and return it as the result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             input.Rows = _rows;
             input.Columns = _columns;
             input.Width = _width;
             input.Height = _height;
             input.Animations = animations;

             input.RuntimeIdentifier = identifier;

             // Return a Sprite Sheet animation Asset.
             SpriteSheetAnimationAsset result = input;
             return result;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6 The Type Writer
&lt;/h2&gt;

&lt;p&gt;When you hit the Build button in the MCGB Editor, it calls the Process method of your Processer class and passes the output to the Write method of your Type Writer class to compress the information into an XML file.&lt;/p&gt;

&lt;p&gt;For this step, it's very important that you write the XML file in an intuitive order because, in the next few steps, we will have to read this XML file and turn it into an asset our game can use while it's running.&lt;/p&gt;

&lt;p&gt;Starting at the top, TInput for this class Must be the same as the TOutput of your processor class. otherwise, your 'value' variable will be null and you will get errors when you try to build. Here is mine. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;using TInput = SpriteSheetAnimationPipelineExtension.SpriteSheetAnimationAsset;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;[ContentWriter]&lt;/code&gt; doesn't need any adjustment so we can move on to the class itself. Add a private string variable called _runtimeIdentifier to the content writer class. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;private string _runtimeIdentifier;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next, we can move on to the Write Method. It has no output and takes in a ContentWriter and our TInput alias type passed to us from the processor. We use the ContentWriter (output) to write the XML version of our SpriteSheetAnimationAsset. I recommend writing the file in the same order in which your variables are laid out in your class definition. Here's how I started.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;// Write the file in a specific order because it will be read in this same order.&lt;br&gt;
            output.Write(value.Rows);&lt;br&gt;
            output.Write(value.Columns);&lt;br&gt;
            output.Write(value.Width);&lt;br&gt;
            output.Write(value.Height);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then, because we have a list of SpriteAnimationAssets as a variable for our SpriteSheeteAnimationAsset, we need to loop through all the animations, and write them to the XML doc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            // Write each animation contained in the sprite.
            foreach(SpriteAnimationAsset anim in value.Animations)
            {
                output.Write(anim.Name);
                output.Write(anim.Row);
                output.Write(anim.StartFrame);
                output.Write(anim.FrameCount);
                output.Write(anim.FrameRate);
                output.Write(anim.SpriteWidth);
                output.Write(anim.SpriteHeight);
            }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we have to write the Texture2DContent in our asset class. we will use the WriteRawObject method, as it will automatically find and use the TextureTypeWriter for us. the TextureTypeWriter is an internal class so we cannot access it directly.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;// Write the texture Object.&lt;br&gt;
            output.WriteRawObject((Texture2DContent)value.TextureSheet);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We also set the _runtimeIdentifier to the asset classes runtime identifier variable, but we don't write it to the XML file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;// Save the RuntimeIdentifier&lt;br&gt;
            _runtimeIdentifier = value.RuntimeIdentifier;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Moving on to the GetRuntimeReader method, we will have it return the _runtimeIdentifier string variable. To finally explain this. We will have a class in the next step that is used to Read the XML we wrote here and use it in the game. &lt;/p&gt;

&lt;p&gt;Basically, if you know what reader you are using you can avoid all the runtime identifier stuff by typing out a fully qualified type path and returning it here in this method.  That would look something like "[libraryName.Namespace.Classname],[AssemblyName]". It's notoriously a line of code that will cause you lots of problems. So we are going to be putting the burden of this onto the user to figure out. if you don't want to do it this way, I recommend following along anyway till you find the correct fully qualified path name and then refactor to remove all traces of the RuntimeIdentifier from your code. Otherwise, you have to build this project, reference it in the MCGB Editor, and test it, if there's an error remove the reference, edit the code, build the project again, wash, and repeat till you figure it out.&lt;/p&gt;

&lt;p&gt;Onto the final method, the GetRuntimeType. This one is easy, we just return this: &lt;br&gt;
&lt;code&gt;return typeof(SpriteSheetAnimationAsset).AssemblyQualifiedName;&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 7 - What Do We Use In The Game?
&lt;/h2&gt;

&lt;p&gt;OK, we got the Importer done, we got the Processor done and we got the Writer Done. Now we just need to find a way to read it. First, we are going to make a new project using the MonoGame Library template. We do this, because all this pipeline extension jazz doesn't need to be packaged into our game build.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa2z18uzdis2um46m49sc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa2z18uzdis2um46m49sc.png" alt="Image description" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Name the project, I'm naming mine "&lt;em&gt;SpriteSheetAnimationContentPipeline&lt;/em&gt;", and under 'solution' make sure you change the dropdown to the 'Add to solution' option. Then you should see it in your solution explorer. Inside of this new project, we are going to make 3 new classes. &lt;/p&gt;

&lt;p&gt;A SpriteAnimation class, feel free to add some methods for yourself such as an Update or Draw method. It will look pretty similar to our SpriteAnimationAsset class from the Pipeline Extension Project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace SpriteSheetAnimationContentPipeline
{
    public class SpriteAnimation
    {
        public string Name
        { get; private set; }

        public int StartFrame
        { get; private set; }

        public int FrameCount
        { get; private set; }

        public int FrameRate
        {  get; private set; }

        public int SpriteWidth
        { get; private set; }

        public int SpriteHeight
        { get; private set; }

        public SpriteAnimation(string name, int startFrame, int frameCount, int frameRate, int spriteWidth, int spriteHeight)
        {
            Name = name;
            StartFrame = startFrame;
            FrameCount = frameCount;
            FrameRate = frameRate;
            SpriteWidth = spriteWidth;
            SpriteHeight = spriteHeight;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a SpriteSheetAnimation class, again you can add any additional methods you would like here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;

namespace SpriteSheetAnimationContentPipeline
{
    public class SpriteSheetAnimation
    {
        public int Rows
        { get; private set; }

        public int Columns
        { get; private set; }

        public int Width
        { get { return TextureSheet.Width; } }

        public int Height
        { get { return TextureSheet.Height; } }

        public Texture2D TextureSheet
        { get; private set; }

        public List&amp;lt;SpriteAnimation&amp;gt; Animations { get; private set; }

        public int AnimationIndex
        { get; set; } = 0;

        public SpriteSheetAnimation()
        {
            Rows = 0;
            Columns = 0;
            TextureSheet = null;
            Animations = new List&amp;lt;SpriteAnimation&amp;gt;();
        }
        public SpriteSheetAnimation(int rows, int columns, Texture2D textureSheet, List&amp;lt;SpriteAnimation&amp;gt; animations)
        {
            Rows = rows;
            Columns = columns;
            TextureSheet = textureSheet;
            Animations = animations;
        }

        public SpriteAnimation GetAnimation(int index)
        {
            return Animations[index];
        }

        public List&amp;lt;SpriteAnimation&amp;gt; GetAllAnimations()
        {
            return Animations;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a SpritesheetAnimationTypeReader class. In this one we will fill out the details together, but go ahead and write the basic framework for it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;


namespace SpriteSheetAnimationContentPipeline
{

    public class SpriteSheetAnimationTypeReader : ContentTypeReader&amp;lt;SpriteSheetAnimation&amp;gt;
    {
        protected override SpriteSheetAnimation Read(ContentReader input, SpriteSheetAnimation existingInstance)
        {

        }

        private Texture2D ReadTexture2D(ContentReader input, Texture2D existingInstance)
        {

        }

}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 8 - The Type Reader
&lt;/h2&gt;

&lt;p&gt;Lets fill out the type reader class together. This class inherits the ContentTypeReader, we replaced T with our SpriteSheetAnimation class meaning that we are outputting a SpriteSheetAnimation from our Read method. The Read method takes in a ContentReader (input) which is our XML file written by our Type Writer class in the Pipeline Extension project, and an existing instance of the SpriteSheetAnimation class.&lt;/p&gt;

&lt;p&gt;In the Read method, let's start by creating local variables for rows, columns, width, and height. Mirroring the order in which we wrote to the XML file by reading the lines in the same order.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;// Set the result of reading the first 4 lines as int32 to some local variables.&lt;br&gt;
            int Rows = input.ReadInt32();&lt;br&gt;
            int Columns = input.ReadInt32();&lt;br&gt;
            int Width = input.ReadInt32();&lt;br&gt;
            int Height = input.ReadInt32();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next, we can create a loop to go through the number of rows and create one SpriteAnimation per row, adding it to a list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            List&amp;lt;SpriteAnimation&amp;gt; anims = new List&amp;lt;SpriteAnimation&amp;gt;();
            for (int r = 0; r &amp;lt; Rows; r++)
            {
                string aName = input.ReadString();
                int aRow = input.ReadInt32();
                int aStartFrame = input.ReadInt32();
                int aFrameCount = input.ReadInt32();
                int aFrameRate = input.ReadInt32();
                int aSpriteWidth = input.ReadInt32();
                int aSpriteHeight = input.ReadInt32();

                anims.Add(new SpriteAnimation(aName, aStartFrame, aFrameCount, aFrameRate, aSpriteWidth, aSpriteHeight));
            }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we need to read the Texture2DContent as a Texture 2D asset.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;IGraphicsDeviceService graphicsDeviceService = (IGraphicsDeviceService)input.ContentManager.ServiceProvider.GetService(typeof(IGraphicsDeviceService));&lt;br&gt;
            var device = graphicsDeviceService.GraphicsDevice;&lt;br&gt;
            Texture2D sheetTexture = new Texture2D(device, Width, Height);&lt;br&gt;
            Texture2D texture = ReadTexture2D(input, sheetTexture);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Finally, we create a SpriteSheetAnimation and return it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SpriteSheetAnimation AnimSheet = new SpriteSheetAnimation(Rows, Columns, texture, anims);&lt;br&gt;
            return AnimSheet;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Moving on to the ReadTexture2D method, we are going to create a  local Texture2D variable called output and set it to be null;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Texture2D output = null;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next we will Try to set the output variable by reading a raw object of type Texture2D. if it throws a not supported exception we will have to create a Texture2D reader and use a different read raw object method overload.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            Texture2D output = null;
            try
            {
                output = input.ReadRawObject&amp;lt;Texture2D&amp;gt;(existingInstance);
            }
            catch (NotSupportedException)
            {
                var assembly = typeof(Microsoft.Xna.Framework.Content.ContentTypeReader).Assembly;
                var texture2DReaderType = assembly.GetType("Microsoft.Xna.Framework.Content.Texture2DReader");
                var texture2DReader = (ContentTypeReader)Activator.CreateInstance(texture2DReaderType, true);
                output = input.ReadRawObject&amp;lt;Texture2D&amp;gt;(texture2DReader, existingInstance);
            }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then outside of the catch, &lt;code&gt;return output;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 9 References &amp;amp; Building
&lt;/h2&gt;

&lt;p&gt;Now that the content pipeline library is done right-click your pipeline Extension project ( the first project we made at the beginning) and build it.&lt;/p&gt;

&lt;p&gt;Then, you can test things out by making a new MonoGame Desktop GL project and adding it to the solution. Right-click on the new game project go to Add, project reference, and add the ContentPipeline (the second project we made) as a reference.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F683yizlp3jk1vivflona.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F683yizlp3jk1vivflona.png" alt="Image description" width="758" height="795"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiweadca29zx2fvmg0w88.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiweadca29zx2fvmg0w88.png" alt="Image description" width="777" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open up the MGCB Editor and click on 'Content' in the project window and go to the bottom of the properties list. You will see a parameter called references. Click on the empty space next to the name references and it should open up a window to let you add or remove references.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnd7k2m3e6bhycnwsx4hg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnd7k2m3e6bhycnwsx4hg.png" alt="Image description" width="572" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;in the pop-up window click add, and find the pipeline extension project's folder in your file browser. If you built your project you should be able to navigate to &lt;code&gt;pipelineExtenstionProjectName/bin/debug/net6.0/&lt;/code&gt; and see a .dll file select it and hit add. then hit Ok in the reference editing window.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 10 Using Your Pipeline Extension
&lt;/h2&gt;

&lt;p&gt;add a new class file to your game call it AnimationReader or whatever you want. It should inherit from your Type Readere class and look like this.&lt;/p&gt;

&lt;p&gt;`namespace [your game namespace here]&lt;br&gt;
{&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class AnimationReader : SpriteSheetAnimationTypeReader { }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;}`&lt;/p&gt;

&lt;p&gt;Open the MGCB Editor and import a Sprite Sheet Animation png or whatever type of file your Importer supports. if it doesn't automatically set the importer and processor to your custom importer and processor, then make sure to set them in the properties window along with all the processor parameters you need.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flx3n5a4e6pdspmkwr8kz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flx3n5a4e6pdspmkwr8kz.png" alt="Image description" width="564" height="724"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the Runtime Identifier it's going to be "[gameProjectNamespace.WhatYouNamedYourInGameReader],[gameProjectNamespace]"&lt;/p&gt;

&lt;p&gt;To load it go back to your game1 file and in the Load method load your SpriteSheetAnimation like this &lt;/p&gt;

&lt;p&gt;&lt;code&gt;SpriteSheetAnimation animation = Content.Load&amp;lt;SpriteSheetAnimation&amp;gt;("Squid Walk_V3.spriteanim");&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I went a step further and added some logic to animate the sprite for debugging. here's what my Game1 class looks like.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    public class Game1 : Game
    {
        private GraphicsDeviceManager _graphics;
        private SpriteBatch _spriteBatch;

        SpriteAnimation currentAnim;
        private int startFrame = 0;
        private int frameIndex = -1;
        private int EndFrame;
        private float FrameTimer = 0.0f;
        private float FrameDurration;

        private SpriteSheetAnimation animation;
        public Game1()
        {
            _graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            IsMouseVisible = true;
        }

        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }

        protected override void LoadContent()
        {
            _spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here

            SpriteSheetAnimation animation = Content.Load&amp;lt;SpriteSheetAnimation&amp;gt;("Squid Walk_V3.spriteanim");
            currentAnim = animation.GetAnimation(0);
            EndFrame = currentAnim.FrameCount -1;

            FrameDurration = 1.0f / currentAnim.FrameRate;
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            // TODO: Add your update logic here

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here

            _spriteBatch.Begin();

            animateSprite(gameTime);


            _spriteBatch.End();

            base.Draw(gameTime);
        }

        public void animateSprite(GameTime gt)
        {
            float deltaTime = (float)gt.ElapsedGameTime.TotalSeconds;

            if(FrameTimer &amp;lt;= 0.0f)
            {
                if (frameIndex + 1 &amp;gt; EndFrame)
                {
                    frameIndex = startFrame;
                }
                else
                {
                    frameIndex++;
                }

                FrameTimer = FrameDurration;
            }
            else
            {
                FrameTimer -= deltaTime;
            }

            Rectangle rect = new Rectangle(currentAnim.SpriteWidth * frameIndex, 0, currentAnim.SpriteWidth, currentAnim.SpriteHeight);
            _spriteBatch.Draw(animation.TextureSheet, Vector2.One, rect, Color.White);
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I play the game my character animation plays! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7txddx6dg8lhgpv69gk7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7txddx6dg8lhgpv69gk7.gif" alt="Image description" width="436" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Best of luck to anyone wanting to make a custom pipeline extension. It can be a real headache but I encourage you to check out the resources section above and see Aristurtle's YouTube if you find yourself stuck you can find a link to the MonoGame discord on all of their videos and join it to ask any questions.&lt;/p&gt;

&lt;p&gt;I hope that in the future someone else who is just as confused as I was can find this to be a useful guide. I am already planning to improve upon this to be more functional for game 2 in the 20-game challenge as I am working on a whole animation state system.&lt;/p&gt;

&lt;p&gt;The Final code for all of this will be published with the Game 2 update that is coming up very soon!&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>csharp</category>
      <category>learning</category>
      <category>challenge</category>
    </item>
    <item>
      <title>The 20 Game Challenge : Game 1</title>
      <dc:creator>Brittany Blair</dc:creator>
      <pubDate>Wed, 01 May 2024 03:59:42 +0000</pubDate>
      <link>https://dev.to/brittanyblairdesign/the-20-game-challenge-game-1-42jp</link>
      <guid>https://dev.to/brittanyblairdesign/the-20-game-challenge-game-1-42jp</guid>
      <description>&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://20_games_challenge.gitlab.io/how/" rel="noopener noreferrer"&gt;20 game challenge&lt;/a&gt; is a website that, you guessed it, challenges you to make 20 games. This challenge is an entirely self-imposed set of rules to follow while making 20 small games on your own.&lt;/p&gt;

&lt;p&gt;To summarize how this works, you decide on a game engine ( or engines you wish to use ) and watch tutorials before starting the challenge if you need them. Then you can begin making up to 20 games on your own. Start with the first 10 recommended games as they will teach the fundamentals. The next 10  games you choose to design yourself based on what you have learned. After 20 games are done, end the challenge with a capstone project that is either larger than the other games or more difficult to complete than the previous 20 games. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why do this?
&lt;/h2&gt;

&lt;p&gt;Ok, so why am I doing this challenge when I already have experience making games? Well, there are 3 simple answers I have for you.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;You can never do enough practice. Making small games regularly helps keep me in practice, and as long as I'm not practicing bad habits It will only help me out.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I want to code games from scratch outside of a game engine (i.e. using a framework )for the first time. I have made games in engines like Unity and Unreal for most of my career, but I don't want to feel limited to their platforms. So I want to expand my horizons and use this 20-game challenge to learn how to use the MonoGame framework to make games.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I'm trying to develop better habits and be more present on social media. I am an extremely introverted person, and for me, this is also an excuse to post about this experience online. It's like a small rung on my ladder to socialization. Right now I am a feral and easily spooked introvert. I'm hoping to create a habit of online interaction by posting about my progress on this challenge, thus getting used to putting myself out there and feeling less self-conscious about it. This is important to me as a game dev because I want to be able to interact with and cultivate a community for those who like my games.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;Real quick before I dive into the code, or the game itself, I want to link to some helpful resources I've used so far to get started with MonoGame.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/@codingwithsphere" rel="noopener noreferrer"&gt;Coding with Sphere&lt;/a&gt; is a small YouTube channel, and right now he is making a MonoGame tutorial series that is very well-paced and easy to understand. His tutorials were very helpful to me, and I highly recommend you check him out. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://link.springer.com/book/10.1007/978-1-4842-6309-9" rel="noopener noreferrer"&gt;MonoGame Mastery&lt;/a&gt; is a book you can download or grab a physical copy of. It was published back in 2020 and is a great reference for getting started and understanding the core concepts of MonoGame.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gameprogrammingpatterns.com/" rel="noopener noreferrer"&gt;Game Programming Patterns&lt;/a&gt; is an opensource free to read book. It's great to use if your looking for more efficient or effective ways to make re-usable code or if your learning Object Oriented Programming and want to learn it from a game prospective.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The first Game Requirements
&lt;/h2&gt;

&lt;p&gt;Alright with all the explanation out of the way Let's get into the first game challenge. I get to pick between a 'Pong' style game, or a 'Flappy Bird' style game. I've made pong-style games a lot when I started learning, so I'll opt for the 'Flappy Bird' style game. &lt;/p&gt;

&lt;p&gt;The goals are as follows &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a game world with a floor&lt;/li&gt;
&lt;li&gt;Add obstacles on the left of the game area. They should have a vertical gap between them, and move across the screen.&lt;/li&gt;
&lt;li&gt;Detect character collisions with the floor or obstacles, and reset the game when a collision occurs&lt;/li&gt;
&lt;li&gt;accumulate one point for each obstacle that the player passes. Display the score.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Optional goals&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add sounds that ply each time a point is gained, and when the player loses the game.&lt;/li&gt;
&lt;li&gt;add a basic game over the screen to display the player's score&lt;/li&gt;
&lt;li&gt;Track the score between sessions and display the high score alongside the current score&lt;/li&gt;
&lt;li&gt;Add some background art, Try layering and scrolling at different rates ( parallax scrolling )&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Planning
&lt;/h2&gt;

&lt;p&gt;So, another habit I'm trying to break is just jumping right into programming things without writing out any kind of Design doc, draft, or plan. I used to plan everything and as I got more experienced I started falling out of this habit. So for each game no matter how simple, I'm going to be doing a planning phase. It doesn't need to be a full-blown GDD but simply writing down what I need to make the game work, and sketching some of the main frames of the game is enough. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fshreavmnqj2s1qb2cima.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fshreavmnqj2s1qb2cima.jpg" alt="Design Planning"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Above is my design/planning sketch. I always do this on paper and usually have journals full of these kinds of notes. It's a good way to demonstrate thought processes and even if I don't actualize a game right away it's like a catalogue of ideas I can pull from in the future.&lt;/p&gt;

&lt;p&gt;So Flappy Bird is pretty straightforward, there's a reason why it was a game 1 option so I wrote down some of the most important features. Everything on the screen needs movement, the player, obstacles, the foreground, and background elements. So I plan to make a movement component to add to all moving elements. &lt;/p&gt;

&lt;p&gt;I know that input is required for gameplay and for the other screens of the game. So I'll need to make an input handler. Finally, I know that I'll need classes for the obstacles and the player because they will collide and interact with each other to drive the game forward and add to the score.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/BrittanyBlairDesign/20-Game-Challenge-Game1" rel="noopener noreferrer"&gt;You can take a look at the full source code here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So long story short I had to make a lot of systems that are normally designed for you in a traditional game engine such as an input management system, collision detection, event systems, and more. All of that stuff is inside the 'Engine' folder of the GitHub repository. It is the beginning of a foundational set of tools I will be using and continue to expand upon as I complete the rest of these upcoming game challenges.&lt;/p&gt;

&lt;p&gt;While this project took me roughly 4 days to complete, most of that time was put towards making those game engine systems and the game itself was almost like a way to test how they were all working.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuiqti4ik5kfjj49sy0o5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuiqti4ik5kfjj49sy0o5.png" alt="Screenshot of my solution file tree."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The image above is a screenshot of all the .cs files I made for not only the engine classes but also what I made for the game itself. The game has 3 game states, 1 player, 1 obstacle class, and one terrain class. All of these classes were inherited from the base classes I made in the engine section.&lt;/p&gt;

&lt;p&gt;The game states spawn the scene actors, make the music play, update all the actors for each update loop, and handle the actions for each type of input. FlappyGameplayState is the main play state, FlappyStartSplash is the main menu, and FlappyLooseSplash is the retry menu.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Art
&lt;/h2&gt;

&lt;p&gt;I made all the art in the game myself using Marmoset Hexels3. It's a program that does pixel art very well, and I purchased my license on Steam while it was on sale.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgevs48t7euml96w730ka.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgevs48t7euml96w730ka.png" alt="Flappy Duck Player Sprite"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As I didn't have a ton of time in the world I went with a more whimsical approach and didn't think too hard about it all. I make a player, some obstacles, and the UI elements. Everything turned out pretty well and my favorite piece for this project is a tie between the title and the player duck.&lt;/p&gt;

&lt;p&gt;I got the Music and audio from &lt;a href="https://opengameart.org/" rel="noopener noreferrer"&gt;OpenGameArt.org&lt;/a&gt;. If you're interested in using any of it yourself, check out the credits document in my GitHub repository. I have the assets I use linked in there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Conclusion
&lt;/h2&gt;

&lt;p&gt;This was a challenging but fun start to my MonoGame journey. I don't know if I'll be able to finish the 20-game challenge in the long run. But I feel far better about moving forward after finishing this first game.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>csharp</category>
      <category>challenge</category>
      <category>learning</category>
    </item>
    <item>
      <title>Path to CS Certificaiton : Python Terminal Game</title>
      <dc:creator>Brittany Blair</dc:creator>
      <pubDate>Mon, 22 Apr 2024 05:18:13 +0000</pubDate>
      <link>https://dev.to/brittanyblairdesign/path-to-cs-certificaiton-python-terminal-game-4fo5</link>
      <guid>https://dev.to/brittanyblairdesign/path-to-cs-certificaiton-python-terminal-game-4fo5</guid>
      <description>&lt;p&gt;For the past few weeks, I've been working on finishing the Codecademy Computer Science career path certification. (and no, this is not at all sponsored, the opposite actually, they took my money. :&amp;lt; )&lt;/p&gt;

&lt;p&gt;I have some coding experience in C++ and C# so I'm not a total beginner. I did however put off learning Python for quite a while. It was something I knew I was going to learn eventually. But I always felt internal resistance to actually doing it.&lt;/p&gt;

&lt;p&gt;So, here I am finally getting it done! I had to make a portfolio project for the CS cert, and the prompt was to make a Python terminal game. Examples were tic-tac-toe and making calculators. I wanted to make something that would draw an ASCII art map and let you move a 'character' around the map. I decided to name this 'Dungeon Crawler' because I'm original and full of creativity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Game
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9vbdu3xux238iw7nig8g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9vbdu3xux238iw7nig8g.png" alt="'Dungeon Crawler' Map example" width="578" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is a sequence of events that happens once the program is run.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The main menu is printed on the terminal and shows you options for Starting the game, Exiting, or reading the Rules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When you start the game you're asked to choose a keyboard character to represent yourself on the map. in the image above, I choose my first initial but any key will work as long as it is not a key already in use.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After you select a key on the keyboard to represent yourself, the game map is randomly generated. It can be anywhere between a 5x5 map to a 20x20 map and will generate a random amount of items represented as (*) and a random amount of enemies represented as (&amp;amp;). Everything including your character symbol and a goal represented by (#) is randomly placed on the map&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Player information is printed below the game map. It shows you what symbol you picked to represent yourself, what directions you can move in, and where you're currently located on the map.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzlis9v3o0aztdybxihq5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzlis9v3o0aztdybxihq5.png" alt="rules screenshot" width="643" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can type in 'rules' to print out how to play the game, and what the goals of the game are.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;Under the hood, this game consists of 5 files; characters.py, components.py, map.py, vectors.py, and game.py. There are a total of 7 classes in this project: Player, Enemy, Item, Map, Movement, Stat, and Vector2. &lt;/p&gt;

&lt;p&gt;Game.py there are 5 functions defined: Intro, Start, Rules, PlayGame, and ValidateResponse.&lt;/p&gt;

&lt;p&gt;the game starts by calling intro, which prints the start menu. When the player types 'Start' it runs the Start function, which handles spawning items, enemies, the map, and asks the player what symbol will represent them. It calls the  PlaceItems method of the Map to randomly place everything and make sure it gets printed on the screen.&lt;/p&gt;

&lt;p&gt;The Start function calls the PlayGame function, passing it the Player, Enemies, and Map. The PlayGame function preforms a while loop, that will continue as long as the game is being played, and the player hasn't won, lost, or exited.&lt;/p&gt;

&lt;p&gt;The PlayGame loop takes in an input from the player, passes it to the ValidateResponse function, and assures that the player typed in a valid input. If the player is moving, it uses the Maps UpdatePlayerLocation method to check if a move in the requested direction is within the map boundaries, if the player hits an enemy, or picks up an item. If the player runs into an enemy the player's health status decreases. Each enemy has a random damage  stat, so we find the correct enemy by using the location the player tried to move in to find them. If the player runs into an item, their health stat will heal based on the item's value&lt;/p&gt;

&lt;p&gt;After the player moves, if the game is not won or lost we iterate through the list of enemies, and let each enemy move. Enemies can only move if the space in the direction they randomly choose to move in is considered a 'floor' space. they cannot overlap items, players, other enemies, or the goal space. If an enemy hits a player during their turn, the player's health stat will take damage.&lt;/p&gt;

&lt;p&gt;As long as the player has not won or lost the game, the loop will continue. Once the player has won or lost the game, a thank you message is printed and the game is exited.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This took me about 3 days of work to put this together. It's a cute little project and while I still struggle with some Python concepts as a primarily C user. this project goes down in my book as a Win. &lt;/p&gt;

&lt;p&gt;Now a short disclaimer: I am a beginner at using git. I have always used Perforce professionally as a game designer. So It's my first time using git to version control to publish a repository onto GitHub. I didn't add a .ignore file because I didn't know what to ignore yet. So please look past that beginner mistake. &lt;/p&gt;

&lt;p&gt;If you want to check out this small little game, you can grab it here: &lt;a href="https://github.com/BrittanyBlairDesign/python_terminal_game"&gt;https://github.com/BrittanyBlairDesign/python_terminal_game&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>gamedev</category>
      <category>beginners</category>
      <category>terminal</category>
    </item>
  </channel>
</rss>
