Simple Tower

From TRCCompSci - AQA Computer Science
Revision as of 10:35, 15 November 2017 by Admin (talk | contribs) (Adding Tower to Game1)
Jump to: navigation, search

This tutorial will create a simple tower defence style game. In order not to give too much away, it will only pre-create a single tower and only have a single enemy. For your actual project you will need to position towers, have swarms of enemies.

The Enemy

You will need to create a new class, so click on project & select new class. Obviously give the class the name enemy. You will need to add the MonoGame references at the top of the code (in the using section).

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

No to create a moving enemy you will need to declare some variables within the class. The enemy is going to need a texture to display, but also a path to follow. The best way to represent a path would be to have a Queue of vectors, and each vector would be a waypoint for the enemy:

public Texture2D texture;
Queue<Vector2> path = new Queue<Vector2>();

In order to move the the enemy we will also need to know the current position, the destination, and how to move between the two. We also need a way to start the enemy, so declare a bool called active and set it to false:

Vector2 position;
Vector2 movement;
Vector2 destination;

bool active = false;

When you create an enemy we can write an Initialise method to setup the enemy. This will allow us to set the Texture and Position of the Enemy. This method can also be used to set the path of the enemy. The path.Enqueue lines will add each waypoint into the queue, you will need to set the coordinates for each waypoint:

        public void Initialize(Texture2D text, Vector2 pos)
        {
            texture = text;
            position = pos;
            movement = new Vector2(0, 0);
            path.Enqueue(new Vector2(100, 100));
            path.Enqueue(new Vector2(100, 100));
            path.Enqueue(new Vector2(100, 100));
            path.Enqueue(new Vector2(100, 100));
            path.Enqueue(new Vector2(100, 100));
        }

Now we need a way of starting the enemy, the easiest way is to create a property to get & set the Active boolean. This will also allow us to run additional code when active is set. So add the property below:

        public bool Active {
            get{return active;}
            set {
                active = value;
            }
        }

So if the value of active is set, we want to see if we have a vector in the path queue. If we do have a vector we want to set that as our destination. Now we have a destination we can work out the movement required to get there:

        public bool Active {
            get{return active;}
            set {
                active = value;
                if (path.Count() > 0)
                    destination = path.FirstOrDefault<Vector2>();
                Vector2 difference = (destination - position);
                movement = difference / Vector2.Distance(destination, position);
            }
        }

The vector called difference can be calculated by subtracting the current position from the destination vector. If we used this for movement we would instantly jump to that position, so instead we can divide the difference vector by the distance. This will give us a movement vector which we can keep applying to make the enemy move.

Now for the update method for your enemy, create the following method:

        public void Update(GameTime gameTime)
        {
            if (Active)
            {
                position += movement;
            }
        }

This will allow your enemy to move to the first destination, however we will need to check when it arrives at its destination. So add the following:

        public void Update(GameTime gameTime)
        {
            if (Active)
            {
                Vector2 difference = (destination - position);
                if (difference.X > -1 && difference.X < 1 && difference.Y > -1 && difference.Y < 1)
                {
                    Console.WriteLine(position + " " + destination);
                    path.Dequeue();
                }

                position += movement;
            }
        }

We can check we have arrived by check the difference between the position and the destination. If the X & Y are between 1 & -1 we should be at the destination. At the moment the destination is removed from the path, but we also need to get a new destination so we can use the Active property to get a new destination:

        public void Update(GameTime gameTime)
        {
            if (Active)
            {
                Vector2 difference = (destination - position);
                if (difference.X > -1 && difference.X < 1 && difference.Y > -1 && difference.Y < 1)
                {
                    Console.WriteLine(position + " " + destination);
                    path.Dequeue();
                    if (path.Count == 0)
                        Active = false;
                    else
                        Active = true;
                }

                position += movement;
            }
        }

Finally we need to create the Draw method:

        public void Draw(SpriteBatch spriteBatch)
        {
            if (active)
                spriteBatch.Draw(texture, position);
        }

You should now have an enemy which will move from waypoint to waypoint.

Creating Enemy in Game1

In the Game1.cs, add the following in the declaration section (look for SpriteBatch spriteBatch;):

        Texture2D enemyTexture;
        Enemy enemy;

In LoadContent, we need to add the following to create the enemy and set its texture & starting postion:

            enemy = new Enemy();
            enemyTexture = Content.Load<Texture2D>("enemy");
            enemy.Initialize(enemyTexture, new Vector2(300, 300));

In Update, add the following to update the enemy and also to set active on a key press:

            if (Keyboard.GetState().IsKeyDown(Keys.H))
            {
                enemy.Active = true;
            }

            enemy.Update(gameTime);

Finally we need to add the enemy to the draw method:

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

            spriteBatch.Begin();
            enemy.Draw(spriteBatch);
            spriteBatch.End();

            base.Draw(gameTime);
        }

If you test it and assuming you set each coordinate to a different vector, your enemy should move from waypoint to waypoint. It will disappear when it reaches its final destination.

The Bullet

Create a new class called Bullet, remember click project and select new class.

You will need to add the MonoGame references to the using section of the new class.

A bullet will require the following variables to be declared. The texture is the sprite for the bullet, the position is its current position, the movement will be how it gets to the target. The boolean will be used to kill the bullet on collision (or when it leaves the screen):

        public Texture2D texture;
        public Vector2 position;
        public Vector2 movement;
        public bool active = false;
        public bool dead = false;

Now when a button is created we will need to initialise it to provide the bullet with its position, movement and texture. Create the Init method below:

public void Init(Texture2D text, Vector2 pos, Vector2 move)
        {
            texture = text;
            position = pos;
            movement = move;
        }

If the bullet is still alive we need to update its position using the movement vector, so create the update method below:

        public void Update(GameTime gameTime)
        {
            if (!dead)
            {
                position += movement;
            }
        }

Finally if the bullet is still alive we need to draw it on the screen, so create the draw method below:

        public void Draw(SpriteBatch spriteBatch)
        {
            if (!dead)
                spriteBatch.Draw(texture, position);
        }

The Tower

You will need to create a new class for the tower (click project and select new class). You will also need to add the MonoGame references into the using section of your new class.

The tower will obviously be stationary, so we will need to know its position, but also to fire bullets from the center of the tower we will also need to calculate the center vector. The tower will need to create bullets so we also need a texture for the bullet as well as the tower itself. The list of bullets will be used to update and draw each bullet fired from the tower. The radius will be used to see if an enemy is in range, and the active bool will be used to start the tower.

So add the following variables into your tower class:

        public Texture2D texture;
        public Texture2D bulletTexture;
        public Vector2 position;
        public Vector2 center;

        public List<Bullet> bullets = new List<Bullet>();

        private int radius;

        private bool active;

Next we need to create an Init method for the tower. This will need to accept the tower texture, the bullet texture and the tower position. We will also need to calculate the center of the tower and also set the radius:

        public void Init(Texture2D text, Texture2D bullet, Vector2 pos)
        {
            texture = text;
            bulletTexture = bullet;
            position = pos;                
            center = new Vector2((pos.X + texture.Width/2),(pos.Y + texture.Height/2));
            radius = 250;
            active = true;
        }

The tower will need a way of checking what is in range, so create the method below. We know the current position of the tower so we only need to accept the position we are checking. We calculate the distance between the tower and the pos vector. If this is within our radius the tower will run the fire method. Create the following method:

        public void InRange(Vector2 pos)
        {
            float distance = Vector2.Distance(pos, position);
            if (distance <= radius)
            {
                FireOnTarget(pos, distance);
            }
        }

Now a target is aquired, we can calculate the movement required to fire a bullet from our center to the target vector. We can then create a new bullet and run the Init method, passing the texture to use, its starting position and its movement vector. We then add the new bullet to our list of bullets. Create the following method:

        public void FireOnTarget(Vector2 pos, float distance)
        {
            if (active)
            {
                Vector2 movement = ((pos - center) / distance); ;
                Bullet newBullet = new Bullet();
                newBullet.Init(bulletTexture, position, movement);
                bullets.Add(newBullet);
            }
        }

Create an update method for the tower class. The tower will need to update each bullet within the list of bullets, bullets which have died are ignored:

        public void Update(GameTime gameTime)
        {
            foreach (Bullet b in bullets)
            {
                if (!b.dead)
                    b.Update(gameTime);
            }
        }

Finally we need to create a draw method, we need to draw the tower itself and then any bullet it has fired:

        public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(texture, position);
            foreach (Bullet b in bullets)
                if (!b.dead)
                    b.Draw(spriteBatch);
        }

Adding Tower to Game1

       Texture2D towerTexture;
       Texture2D bullet;
       Tower tower;

LoadContent

           towerTexture = Content.Load<Texture2D>("turret");
           bullet = Content.Load<Texture2D>("bullet");
           tower = new Tower();
           tower.Init(towerTexture, bullet, new Vector2(300,300));

Update

           MouseState mousePos = Mouse.GetState();
           tower.InRange(new Vector2(mousePos.Position.X, mousePos.Position.Y));
           tower.Update(gameTime);

Draw

           spriteBatch.Begin();
           tower.Draw(spriteBatch);
           spriteBatch.End();