Search code examples
c#monogame

Monogame and C#, loading multiple objects with same image into a list, all get same properties


i'm just starting out with MonoGame and C#, and I've run into a issue trying to use 1 image for a list of objects.

I'm trying to make 20 balloons with 1 image, but they should all have different position and timer. They are added into a List in a loop 0-19, at each iteration i create a new Balloon(image), the constructor gives it random starting coords and timer, and the image is taken from the parameter.

The issue i'm having is that all the balloons in the list get the values of the last one added after the loop is finished, which would be so if they were all the same object, but i am creating a new one each time so no idea whats going on here??

Balloon class :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Game1
{
    public class Balloon
{
    private const int SCREEN_WIDTH = 640;
    private const int SCREEN_HEIGHT = 480;
    private const int MIN_VELOCITY = 30;

    //picture from contents
    private Texture2D image;
    //balloons current position
    private Vector2 position;
    //how fast its moving
    private Vector2 velocity;
    private bool moving;
    //timer, so they can pop up at a diferent time, number 1-9
    private int timer;


    public Balloon(Texture2D image)
    {
        this.image = image;
        setRandomPosition();
        setRandomTimer();
        //no balloon is moving when initialsied
        moving = false;
        //start with 0 speed
        velocity = new Vector2(0.0f, 0.0f);
    }

    //sets random X for the balloon, with Y just out of screen visible area
    public void setRandomPosition()
    {
        position = new Vector2(new Random().Next(SCREEN_WIDTH), SCREEN_HEIGHT + 1);
    }

    //number 1-9 , balloon starts moving when seconds elapsed % timer is 0
    public void setRandomTimer()
    {
        timer = new Random().Next(1, 10);
    }

    private void calculateRandomVelocity()
    {
        velocity = new Vector2(velocity.X, new Random().Next(50) + MIN_VELOCITY);
    }

    //starts moving the balloon by subtracting the Y
    public void go(GameTime gameTime)
    {
        moving = true;
        calculateRandomVelocity();
        //subtract Y so balloon goes up
        position -= velocity * (float)gameTime.ElapsedGameTime.TotalSeconds;
    }

    //stops the balloon and sets random X with Y outside visible area
    public void stop()
    {
        moving = false;
        setRandomPosition();
    }

    //--------Getters and Setters------------//

    public Vector2 getPosition()
    {
        return position;
    }

    public void setMoving(bool moving)
    {
        this.moving = moving;
    }

    public bool isMoving()
    {
        return moving;
    }

    public Texture2D getImage()
    {
        return image;
    }

    public void setImage(Texture2D image)
    {
        this.image = image;
    }

    public int getTimer()
    {
        return timer;
    }
}

}

Game class :

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Game1
{

public class Game1 : Game
{
    const int NUM_BALLOONS = 20;

    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    List<Balloon> balloons;

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

    protected override void Initialize()
    {
        base.Initialize();
    }

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

        loadBalloonsIntoList();           
    }

    public void loadBalloonsIntoList()
    {
        balloons = new List<Balloon>();

        Texture2D image = Content.Load<Texture2D>("balloon");
        //load 20 balloons into the list  
        //after loading here it ends up with same properties - exact same object for all the elements
        //they all take the values from the last one added
        for (int i = 0; i < NUM_BALLOONS; i++)
        {
            Balloon b = new Balloon(image);
            balloons.Add(b);
        }   
    }

    public void moveBalloons(GameTime gameTime)
    {
        foreach(Balloon b in balloons)
        {
            //cant be 0 because all balloons would start on first iteration, start moving a balloon that is not moving already
            //and its time has come
            if (gameTime.ElapsedGameTime.Seconds > 0 && gameTime.ElapsedGameTime.Seconds % b.getTimer() == 0 && !b.isMoving())
            {
                b.go(gameTime);
            }
            //stop the balloon when it goes out of visible area
            if (b.getPosition().Y <= 0)
            {
                b.stop();
            }
        }
    }

    protected override void UnloadContent()
    {

    }

    protected override void Update(GameTime gameTime)
    {
        moveBalloons(gameTime);
    }

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

        spriteBatch.Begin();

        foreach(Balloon b in balloons)
        {
            spriteBatch.Draw(b.getImage(), b.getPosition(), Color.White);    
        }

        spriteBatch.End();

    }
}
}

Solution

  • Your setRandomPosition method creates a new instance of the Random class with each call. Since the 20 balloons are being instantiated in rapid succession (i.e. in a loop), each of those new Random class instances is likely ending up with the same seed value and is therefore outputting the same "random" values. Hence, all balloon objects end up with the same coordinates. Try creating a single static instance of Random at the class-level instead.

    From https://msdn.microsoft.com/en-us/library/h343ddh9(v=vs.110).aspx

    The default seed value is derived from the system clock and has finite resolution. As a result, different Random objects that are created in close succession by a call to the default constructor will have identical default seed values and, therefore, will produce identical sets of random numbers.