Search code examples
c#xnaartificial-intelligencepath-finding

I need to make an AI character that follows the player when they reach a certain distance in C# XNA


Here is the code that I have so far for my game. I am wanting to create an AI character (BlackBall) that will follow the player (WhiteBall) when they are a certain distance away. I have no idea where to start to get this working but it will be a main part of my game so it is essential.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace PickUpTheCrew
{
/// <summary>
/// This is the main type for your game
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    SpriteFont TitleFont;


    private Vector2 playerPos = Vector2.Zero;
    Vector2 BlackBallPos;
    Vector2 position, velocity;
    Vector2 scorePos;
    Vector2 saved;
    private KeyboardState keyboardState;
    private KeyboardState prevKeyboardState;
    private bool canMove = true;

    int score;

    //Textures for background, player and sharks.
    Texture2D BlackBallTexture;
    Texture2D BlackBallTexture2;
    Texture2D BlueBallTexture;
    Texture2D GreenBallTexture;
    Texture2D OrangeBallTexture;
    Texture2D PinkBallTexture;
    Texture2D RedBallTexture;
    Texture2D WhiteBallTexture;
    Texture2D YellowBallTexture;

    Rectangle BlackBallRectangle;
    Rectangle BlackBallRectangle2;
    Rectangle BlueBallRectangle;
    Rectangle GreenBallRectangle;
    Rectangle OrangeBallRectangle;
    Rectangle PinkBallRectangle;
    Rectangle RedBallRectangle;
    Rectangle WhiteBallRectangle;
    Rectangle YellowBallRectangle;

    Sprite mainPlayer;

    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    /// <summary>
    /// Allows the game to perform any initialization it needs to before starting to run.
    /// This is where it can query for any required services and load any non-graphic
    /// related content.  Calling base.Initialize will enumerate through any components
    /// and initialize them as well.
    /// </summary>
    protected override void Initialize()
    {

        score = 0;

        playerPos = new Vector2(this.GraphicsDevice.Viewport.Width / 2,
                       this.GraphicsDevice.Viewport.Height * 0.25f);
        BlackBallPos = new Vector2(this.GraphicsDevice.Viewport.Width / 2,
                       this.GraphicsDevice.Viewport.Height * 0.75f);

        base.Initialize();
    }

    /// <summary>
    /// LoadContent will be called once per game and is the place to load
    /// all of your content.
    /// </summary>

    private Texture2D BackgroundTexture;
    protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(Gra`enter code here`phicsDevice);

        TitleFont = Content.Load<SpriteFont>("TitleFont");
        WhiteBallTexture = Content.Load<Texture2D>("WhiteBall");

        //mainPlayer = new Sprite(Content.Load<Texture2D>("WhiteBall"), new Rectangle((int)(playerPos.X - WhiteBallTexture.Width / 2),
        //(int)(playerPos.Y - WhiteBallTexture.Height / 2), WhiteBallTexture.Width, WhiteBallTexture.Height));

        BackgroundTexture = Content.Load<Texture2D>("Background");
        BlackBallTexture = Content.Load<Texture2D>("BlackBall");
        BlackBallTexture2 = Content.Load<Texture2D>("BlackBall");
        BlueBallTexture = Content.Load<Texture2D>("BlueBall");
        GreenBallTexture = Content.Load<Texture2D>("GreenBall");
        OrangeBallTexture = Content.Load<Texture2D>("OrangeBall");
        PinkBallTexture = Content.Load<Texture2D>("PinkBall");
        RedBallTexture = Content.Load<Texture2D>("RedBall");
        YellowBallTexture = Content.Load<Texture2D>("YellowBall");

        WhiteBallRectangle = new Rectangle(100, 100, 25,25);
        BlackBallRectangle = new Rectangle(150, 300, 25,25);
        BlackBallRectangle2 = new Rectangle(500, 400, 25, 25);
        BlueBallRectangle = new Rectangle(500, 150, 25, 25);
        GreenBallRectangle = new Rectangle(100, 500, 25, 25);
        OrangeBallRectangle = new Rectangle(180, 200, 25, 25);
        PinkBallRectangle = new Rectangle(260, 260, 25, 25);
        RedBallRectangle = new Rectangle(300, 450, 25, 25);
        YellowBallRectangle = new Rectangle(550, 300, 25, 25);

        scorePos.X = 575;
        scorePos.Y = 450;
        saved.X = 0;
        saved.Y = 50;
        /*
        WhiteBallRectangle = new Rectangle((int)(playerPos.X - WhiteBallTexture.Width / 2),
        (int)(playerPos.Y - WhiteBallTexture.Height / 2), WhiteBallTexture.Width, WhiteBallTexture.Height);
        BlackBallRectangle = new Rectangle((int)(BlackBallPos.X - BlackBallTexture.Width / 2),
        (int)(BlackBallPos.Y - BlackBallTexture.Height / 2), BlackBallTexture.Width, BlackBallTexture.Height);
        */
        /*
        WhiteBallRectangle; = new Rectangle((int)(playerPos.X - WhiteBallTexture.Width / 2),
        (int)(playerPos.Y - WhiteBallTexture.Height / 2), WhiteBallTexture.Width, WhiteBallTexture.Height);
        BlackBallRectangle; = new Rectangle ((int)(BlackBallPos.X - BlackBallTexture.Width / 2),
        (int)(BlackBallPos.Y - BlackBallTexture.Height / 2), BlackBallTexture.Width, BlackBallTexture.Height);
         */

    }

    /// <summary>
    /// UnloadContent will be called once per game and is the place to unload
    /// all content.
    /// </summary>
    protected override void UnloadContent()
    {
        // TODO: Unload any non ContentManager content here
    }

    /// <summary>
    /// Allows the game to run logic such as updating the world,
    /// checking for collisions, gathering input, and playing audio.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    protected override void Update(GameTime gameTime)
    {
        //mainPlayer.Update();

        prevKeyboardState = Keyboard.GetState();
        keyboardState = Keyboard.GetState();

        if (canMove)
        {
            if (keyboardState.IsKeyDown(Keys.Up) && prevKeyboardState.IsKeyDown(Keys.Up))
                WhiteBallRectangle.Y = WhiteBallRectangle.Y - 1;
           //playerPos -= new Vector2(0, 4);
            else if (keyboardState.IsKeyDown(Keys.Left) && prevKeyboardState.IsKeyDown(Keys.Left))
                WhiteBallRectangle.X = WhiteBallRectangle.X - 1;
            //playerPos -= new Vector2(4, 0);
            else if (keyboardState.IsKeyDown(Keys.Down) && prevKeyboardState.IsKeyDown(Keys.Down))
                WhiteBallRectangle.Y = WhiteBallRectangle.Y + 1;
            //playerPos += new Vector2(0, 4);
            else if (keyboardState.IsKeyDown(Keys.Right) && prevKeyboardState.IsKeyDown(Keys.Right))
                WhiteBallRectangle.X = WhiteBallRectangle.X + 1;
               //playerPos += new Vector2(4, 0);

                if (keyboardState.IsKeyDown(Keys.Up) && keyboardState.IsKeyDown(Keys.Left))
                {

                    WhiteBallRectangle.X = WhiteBallRectangle.X -  1;
                    WhiteBallRectangle.Y = WhiteBallRectangle.Y - 1;
                    //playerPos -= new Vector2(4, 4);
                }

                else if (keyboardState.IsKeyDown(Keys.Up) && keyboardState.IsKeyDown(Keys.Right))
                {
                    WhiteBallRectangle.Y = WhiteBallRectangle.Y - 1;
                    WhiteBallRectangle.X = WhiteBallRectangle.X + 1;
                    //playerPos -= new Vector2(0, 4);
                    //playerPos += new Vector2(4, 0);
                }
                else if (keyboardState.IsKeyDown(Keys.Down) && keyboardState.IsKeyDown(Keys.Left))
                {
                    WhiteBallRectangle.Y = WhiteBallRectangle.Y + 1;
                    WhiteBallRectangle.X = WhiteBallRectangle.X - 1;
                    //playerPos += new Vector2(0, 4);
                    //playerPos -= new Vector2(4, 0);
                }
                else if (keyboardState.IsKeyDown(Keys.Down) && keyboardState.IsKeyDown(Keys.Right))
                {
                    WhiteBallRectangle.Y = WhiteBallRectangle.Y + 1;
                    WhiteBallRectangle.X = WhiteBallRectangle.X + 1;
                }
                    //playerPos += new Vector2(4, 4);


        }


        CheckBounds();

        //Collision
       // Rectangle WhiteBallRectangle = new Rectangle((int)playerPos.X, (int)playerPos.Y, 10, 100);
        //Rectangle BlackBallRectangle = new Rectangle((int)playerPos.X, (int)playerPos.Y, 10, 100);
        if (WhiteBallRectangle.Intersects(BlueBallRectangle))
        {
            score = score + 20;
        }
        if (WhiteBallRectangle.Intersects(GreenBallRectangle))
        {

            score = score + 10;
        }
        if (WhiteBallRectangle.Intersects(OrangeBallRectangle))
        {

            score = score + 40;
        }
        if (WhiteBallRectangle.Intersects(PinkBallRectangle))
        {

            score = score + 25;
        }
        if (WhiteBallRectangle.Intersects(RedBallRectangle))
        {

            score = score + 10;
        }
        if (WhiteBallRectangle.Intersects(YellowBallRectangle))
        {

            score = score + 50;
        }
        if (WhiteBallRectangle.Intersects(BlackBallRectangle))
        {
            Exit();
        }
        if (WhiteBallRectangle.Intersects(BlackBallRectangle2))
        {
            Exit();
        }


        base.Update(gameTime);
    }

    private void CheckBounds()
    {
        if (WhiteBallRectangle.Y <= 0)
        {
            WhiteBallRectangle.Y = 1;
            canMove = false;
        }
        else if (WhiteBallRectangle.Y >= 452)
        {
            WhiteBallRectangle.Y = 451;
            canMove = false;
        }
        else
            canMove = true;

        if (WhiteBallRectangle.X <= 0)
        {
            WhiteBallRectangle.X = 1;
            canMove = false;
        }
        else if (playerPos.X >= 772)
        {
            WhiteBallRectangle.X = 771;
            canMove = false;
        }
        else
            canMove = true;
    }

    /// <summary>
    /// This is called when the game should draw itself.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);
        Vector2 text = new Vector2(10, 0);
        spriteBatch.Begin();


        spriteBatch.Draw(BackgroundTexture, position, Color.White);
        spriteBatch.DrawString(TitleFont, "Pick Up The Crew", text, Color.Black);
        spriteBatch.DrawString(TitleFont, "Score: " + score, scorePos, Color.Black);

       // spriteBatch.Draw(WhiteBallTexture, playerPos, null, Color.White, 0.0f, new Vector2(0, 0),
        //0.3f, SpriteEffects.None, 0.0f);
        //mainPlayer.Draw(spriteBatch);

        spriteBatch.Draw(WhiteBallTexture, WhiteBallRectangle, Color.White);
        spriteBatch.Draw(BlackBallTexture, BlackBallRectangle, Color.White);
        spriteBatch.Draw(BlackBallTexture2, BlackBallRectangle2, Color.White);
        spriteBatch.Draw(BlueBallTexture, BlueBallRectangle, Color.White);
        spriteBatch.Draw(GreenBallTexture, GreenBallRectangle, Color.White);
        spriteBatch.Draw(OrangeBallTexture, OrangeBallRectangle, Color.White);
        spriteBatch.Draw(PinkBallTexture, PinkBallRectangle, Color.White);
        spriteBatch.Draw(RedBallTexture, RedBallRectangle, Color.White);
        spriteBatch.Draw(YellowBallTexture, YellowBallRectangle, Color.White);

        /*spriteBatch.Draw(BlueBallTexture, new Vector2(500, (380 + (BlueBallTexture.Height / 2))), null, Color.White, 0.0f, new Vector2(0, 0),
        0.3f, SpriteEffects.None, 0.0f);
        spriteBatch.Draw(GreenBallTexture, new Vector2(230, (180 + (GreenBallTexture.Height / 2))), null, Color.White, 0.0f, new Vector2(0, 0),
        0.3f, SpriteEffects.None, 0.0f);
        spriteBatch.Draw(OrangeBallTexture, new Vector2(700, (200 + (OrangeBallTexture.Height / 2))), null, Color.White, 0.0f, new Vector2(0, 0),
        0.3f, SpriteEffects.None, 0.0f);
        spriteBatch.Draw(PinkBallTexture, new Vector2(600, (20 + (PinkBallTexture.Height / 2))), null, Color.White, 0.0f, new Vector2(0, 0),
        0.3f, SpriteEffects.None, 0.0f);
        spriteBatch.Draw(BlackBallTexture, new Vector2(100, (80 + (BlackBallTexture.Height / 2))), nul`enter code here`l, Color.White, 0.0f, new Vector2(0, 0),
        0.3f, SpriteEffects.None, 0.0f);
        spriteBatch.Draw(BlackBallTexture, new Vector2(300, (100 + (BlackBallTexture.Height / 2))), null, Color.White, 0.0f, new Vector2(0, 0),
        0.3f, SpriteEffects.None, 0.0f);
        spriteBatch.Draw(BlackBallTexture, new Vector2(400, (400 + (BlackBallTexture.Height / 2))), null, Color.White, 0.0f, new Vector2(0, 0),
        0.3f, SpriteEffects.None, 0.0f);
         */
        if (WhiteBallRectangle.Intersects(BlueBallRectangle))
        {
            spriteBatch.DrawString(TitleFont, "You Rescued Liuetenant Sky for 20 points!", saved, Color.Black);
        }
        spriteBatch.End();

        base.Draw(gameTime);
    }
}

}


Solution

  • Not realy Q&A question, but hey...

    First of all, start with reorganizating your game to components (ist realy big topic to discuss here)

    Second, you can find distance with simple Pythagorean theorem, something like

    var x = WhiteBallRectangle.X - BlackBallRectangle.X;
    var y = WhiteBallRectangle.Y - BlackBallRectangle.Y;
    var distance = (decimal)Math.Sqrt((Math.Pow(x, 2) + Math.Pow(y, 2));
    if(distance < f)
    {
      // do something
    }
    

    And what is that something? It depends what you want... Go full speed at your white? Then read about delta timing (for understanding acceleration in game etc). Direction is very simple Vector math:

    var x = WhiteBallRectangle.X - BlackBallRectangle.X;
    var y = WhiteBallRectangle.Y - BlackBallRectangle.Y;
    var direction = new Vector2(x, y);
    direction.Normalize();
    

    note that x and y are same, so:

    var x = WhiteBallRectangle.X - BlackBallRectangle.X;
    var y = WhiteBallRectangle.Y - BlackBallRectangle.Y;
    var distance = (decimal)Math.Sqrt((Math.Pow(x, 2) + Math.Pow(y, 2));
    var direction = new Vector2(x, y); // from Black to White
    direction.Normalize();
    if(distance < f)
    {
      // do something
    }
    

    I am pretty sure there are some fancy Vector.xxx methods which do lots of this for you, but understanding problem is always better ;)

    As Micky Duncan pointed out in comment - i totally miss that "ai" part:

    So, now about AI. AI is complex system (look for UCT / minimax), which is overpower for your small project. Becouse you did not described what you want, i will make up my own example:

    if the distance is less then "far"
      go slowly
    if the distance is less then "medium"
      go fast
    if the distance is less then "near" OR greater then "far"
      stop
    

    Distance and where to go we just computed, so its simple updating position (in your project just stick it in Update() method)

    decimal far = 50.0m, medium = 30.0m, near = 10.0m; // tweak this
    decimal slow = 10.0m, fast = 20.0m; // tweak this
    var x = WhiteBallRectangle.X - BlackBallRectangle.X;
    var y = WhiteBallRectangle.Y - BlackBallRectangle.Y;
    var distance = (decimal)Math.Sqrt((x * x) + (y * y));
    var direction = new Vector2(x, y); // from Black to White
    direction.Normalize();
    Vector2 move = new Vector2(0, 0);
    float delta = (float)gameTime.ElapsedGameTime.TotalSeconds;
    if(distance < far)
    {
       move = direction * slow * delta; // delta timing, explained at *
    } else if (distance < medium && distance > near)
    {
      move = direction * fast * delta; // delta timing, explained at *
    }
    
    BlackBallRectangle.X += move.X;
    BlackBallRectangle.Y += move.Y;
    
    • delta timing:

      I used delta timing here, so your speed (slow and fast) are "per second", not "per frame" - except for physics (things like tunneling can occur at one PC and not at another) you really should be using this technique.

      I will give you one bonus example for delta timing:

      if (keyboardState.IsKeyDown(Keys.Up) && prevKeyboardState.IsKeyDown(Keys.Up)) WhiteBallRectangle.Y = WhiteBallRectangle.Y - (10 * delta);

      now it is 10 units (px?) per second, not "per-random-i-dont-really-know-how-fast-other-folk-pc-is-rendering-and-only-hope-it-is-60-frame-per-sec"

    • why normalized vector:

      We want only direction, without speed factor - we will multiple with speed in condition "if near" / "if far"

    Its far from real AI, but its good enought for simple follow around on 2D without obstacles. You can smooth acceleration using (lets say) Lerp method and much more, this is really basic example.