Let’s Create A Game – 2D Side-Scrolling Shooter

In my new series „Let’s Create A Game” I’m developing a full-scale game, producing everything from code to graphics on my own. In parallel to my YouTube videos I will release a written and more in-depth version on my blog after each episode.

The first game I am creating is a 2D side-scrolling shooter I’ve named “Intergalax”. In this little drawing I’ve outlined the game design and the main features which are planned to be implemented.

sketch

The basic graphics

Graphically the game will use a rather simplistic art style so I’m going with vector graphics which I create using the open-source software Inkscape. Using very few shapes and only a handful of different colors I start “drawing” the player’s character: a basic jet. I’m mostly using rectangles which I transform into all different shapes by adding and moving nodes.
After the player I’m creating some layers of background mountains. The different layers will later be exported as separate PNG files and then used in game with parallax scrolling.
Last, I am drawing a first enemy which is going to be another more fiercely looking type of military jet.

The code

I am writing the game in C# using the game framework MonoGame. Beside the MonoGame installation, the only tool I need is the free Visual Studio 2015.
First I create a new MonoGame Windows project. Visual Studio automatically sets up everything that is required to run the game. It creates a basic game class (containing the game loop) and it includes the content pipeline with which I will add assets to my game.
Let’s start with the coding. First I implement a basic sprite class which I am going to use a lot later. This class will be abstract and provides the properties any sprite will need. It also requires the subclasses to implement the game loop methods “Update” and “Draw”.

public abstract class Sprite
    {
        protected Texture2D texture;

        public Vector2 Position
        {
            get { return position; }
            set { position = value; }
        }
        protected Vector2 position;

        protected int width, height;

        public Rectangle Rect { get { return new Rectangle((int)position.X, (int)position.Y, width, height); } }


        public Sprite(Vector2 position, int width, int height)
        {
            this.position = position;
            this.width = width;
            this.height = height;
        }

        public abstract void Update(GameTime gameTime);

        public abstract void Draw(SpriteBatch spriteBatch);
    }

Next I create the Player class. This class loads the player jet’s texture and provides basic input handling and movement.

public class Player : Sprite
    {
        public int Lifes { get; set; }

        private float speed = 500;
        private Vector2 velocity;

        public Player(ShooterGame game) : base(Vector2.Zero, 300, 100)
        {
            texture = game.Content.Load<Texture2D>("Textures/player");

            Lifes = 5;
        }

        public override void Update(GameTime gameTime)
        {
            KeyboardState kbState = Keyboard.GetState();

            if (kbState.IsKeyDown(Keys.Up))
            {
                velocity.Y = -speed;
            }
            else if (kbState.IsKeyDown(Keys.Down))
            {
                velocity.Y = speed;
            }
            else
                velocity.Y = 0;
            if (kbState.IsKeyDown(Keys.Left))
            {
                velocity.X = -speed;
            }
            else if (kbState.IsKeyDown(Keys.Right))
            {
                velocity.X = speed;
            }
            else
                velocity.X = 0;

            float elapsedSeconds = (float)gameTime.ElapsedGameTime.TotalSeconds;

            position += velocity * elapsedSeconds;

        }

        public override void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(texture, Rect, Color.White);
        }
    }

The next step is now the actual Level class where most of the game’s magic will happen. Here I collect the references to my player and to all enemies. The “Update” and “Draw” methods are also implemented of course. Before I continue in this class I need a class for my different background layer data as well as a class that handles our 2D camera.
The BackgroundLayer class is just a collection of background properties. I will create an array of those in the level class later.

public class BackgroundLayer
    {
        public Texture2D Texture { get; set; }

        public float ScrollingSpeed { get; set; }

        public float OffsetX { get; set; }
        public float OffsetY { get; set; }
    }

The camera class is pretty simple as well. As our sprite it also has a position vector. Furthermore it’s got a scrolling speed value which defines the speed of our auto-scrolling background. In the Update method I’m moving the camera horizontally and after that I adjust background positions by iterating through the backgrounds array of the level and modifying the layer’s x-offset value. By having different scrolling speed values for each layer, I can create the parallax scrolling effect which creates somewhat of a 3D illusion. Last I create a method which returns the translation matrix based on the camera’s position.

public class Camera
    {
        public Vector2 Position
        {
            get { return position; }
            set { position = value; }
        }
        private Vector2 position;

        public float ScrollingSpeed { get; set; }

        private Level level;

        
        public Camera(Level level)
        {
            this.level = level;
            ScrollingSpeed = 100;
        }

        public void Update(GameTime gameTime)
        {
            float elapsedSeconds = (float)gameTime.ElapsedGameTime.TotalSeconds;
            float previousPosX = position.X;

            position.X -= ScrollingSpeed * elapsedSeconds;

            for (int i = 0; i < level.backgroundLayers.Length; i++)
            {
                BackgroundLayer layer = level.backgroundLayers[i];
                level.backgroundLayers[i].OffsetX -= layer.ScrollingSpeed;

                if (level.backgroundLayers[i].OffsetX < layer.Texture.Width)
                {
                    level.backgroundLayers[i].OffsetX = 0;
                } 
            }
        }

        public Matrix GetMatrix()
        {
            return Matrix.CreateTranslation(new Vector3(position, 0));
        }
    }

Now that I have my BackgroundLayer and Camera classes I add these to the level class. I initialize the background layer array with four hardcoded entries for now. Each entry has got a different texture, scrolling speed and eventually a vertical offset if the textures have different heights. In the level’s Draw method I iterate through the background layers and draw each of them repeatedly. Then I simply call the player’s Draw method.

public class Level
    {
        private Player player;

        public BackgroundLayer[] backgroundLayers;

        public Camera Camera { get; set; }

        private ShooterGame game;

        public Level(ShooterGame game)
        {
            this.game = game;

            player = new Player(game);

            backgroundLayers = new BackgroundLayer[4];
            backgroundLayers[0] = new BackgroundLayer()
            {
                Texture = game.Content.Load<Texture2D>("Textures/bg04"),
                ScrollingSpeed = 0.25f,
                OffsetY = 300
            };
            backgroundLayers[1] = new BackgroundLayer()
            {
                Texture = game.Content.Load<Texture2D>("Textures/bg03"),
                ScrollingSpeed = 0.5f,
                OffsetY = 700
            }; ;
            backgroundLayers[2] = new BackgroundLayer()
            {
                Texture = game.Content.Load<Texture2D>("Textures/bg02"),
                ScrollingSpeed = 0.75f,
                OffsetY = 800
            };
            backgroundLayers[3] = new BackgroundLayer()
            {
                Texture = game.Content.Load<Texture2D>("Textures/bg01"),
                ScrollingSpeed = 1f,
                OffsetY = 900
            };


            Camera = new Camera(this);
        }

        public void Update(GameTime gameTime)
        {
            player.Update(gameTime);

            Camera.Update(gameTime);
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            int cameraX = (int)Camera.Position.X;
            int cameraY = (int)Camera.Position.Y;
            foreach (BackgroundLayer layer in backgroundLayers)
            {
                if (layer.Texture != null)
                {
                    int bgRepeatsX = ShooterGame.Width / layer.Texture.Width + 1;
                    int bgRepeatsY = ShooterGame.Height / layer.Texture.Height + 1;
                    int bgStartX = cameraX / layer.Texture.Width;

                    for (int j=-1;j<bgStartX + bgRepeatsX; j++)
                    {
                        spriteBatch.Draw(layer.Texture, new Vector2(cameraX + layer.OffsetX + j * layer.Texture.Width, cameraY + layer.OffsetY), null, Color.White);
                    }
                }
            }

            player.Draw(spriteBatch);
        }
    }

Finally I can put all the parts I’ve just created together in the base game class. I renamed it from Game1 to ShooterGame for convenience by the way. In this class I first define two constants for our viewport width and height. I set those to 1920×1080 (Full HD). Then in the Initialize method I set the back buffer size of the graphics device to my constant values. This will set the size of the game window. After that I apply the changes to the graphics device. The rest is easy: in the LoadContent I create a new object of my Level class, in the Update method I just call the level’s Update and in the Draw method I also call the level’s Draw. However here I have to first start the sprite batch which is used to draw the textures. For that I just call spriteBatch.Begin(). Similarly at the end of my drawing process I call spriteBatch.End().

Now after a whole lot of coding, I have deserved to take a look and my current progress.

screenshot_e01

My achievements so far: A basic game world with a scrolling background and a character which the player can move around the screen using the keyboard.
Make sure to also watch my video on this episode and join me next time when I’m doing more progress on the game.



3 comments on “Let’s Create A Game – 2D Side-Scrolling Shooter

  • This is the first Monogame tutorial that has actually worked for me! I’ve always wanted to create a shmup style game WITH parallax. So thank you!
    I’ve run into a snag after episode two, though. When shooting, after 5 or more shots, the bullets no longer draw until I move to the right side of the screen, and then eventually, stop all together. It’s as if there is an invisible rectangle moving the opposite way of my game that places the bullets out of bounds as soon as the draw method is called. Does this make sense? I can’t seem to defeat it. Any ideas?

    Reply

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>