how to show lots of sprites from one texture and have them move at intervals XNA 4.0

五迷三道 提交于 2019-12-23 01:46:39

问题


Hi I'm new to xna and I'm trying to make a simple game where your a little ship that moves around and avoids asteroids that fall down from the top to the bottom. I've got the ship moving and one asteroid falling but I don't know how to get lots of asteroids falling from the same texture and how to get them falling at intervals. This is my asteroids class so far:

namespace Asteroids
{
class Asteroids
{
    Texture2D AsteroidTexture;
    Vector2 Position;
    Random random = new Random();
    float AsteroidSpeed = 5;

    public void Initialize()
    {
        Position.Y = 0;
        Position.X = random.Next(0, 1000);
    }

    public void Update()
    {
        Position.Y += AsteroidSpeed;
        if (Position.Y > 600)
        {
            Position.Y = 0;
            Position.X = random.Next(0, 1000);
        }
    }

    public void Load_Content(ContentManager Content)
    {
        AsteroidTexture = Content.Load<Texture2D>("asteroid");
    }

    public void Draw(SpriteBatch SpriteBatch)
    {
        SpriteBatch.Draw(AsteroidTexture, Position, Color.White);
    }
}
}

And this is my Game1 class:

namespace Asteroids
{
public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    KeyboardState keyboardState;

    Ship ship;
    Asteroids asteroids;

    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()
    {
        ship = new Ship();
        asteroids = new Asteroids();

        asteroids.Initialize();

        this.graphics.PreferredBackBufferWidth = 1000;
        this.graphics.PreferredBackBufferHeight = 600;
        //this.graphics.IsFullScreen = true;
        this.graphics.ApplyChanges();

        base.Initialize();
    }

    /// <summary>
    /// LoadContent will be called once per game and is the place to load
    /// all of your content.
    /// </summary>
    protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);

        ship.Load_Content(this.Content);
        asteroids.Load_Content(this.Content);
    }

    /// <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)
    {
        // Allows the game to exit
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
            this.Exit();
        keyboardState = Keyboard.GetState();
        if (keyboardState.IsKeyDown(Keys.Escape))
            this.Exit();

        ship.Update();
        asteroids.Update();

        base.Update(gameTime);
    }

    /// <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.Black);

        spriteBatch.Begin();
        asteroids.Draw(this.spriteBatch);
        ship.Draw(this.spriteBatch);
        spriteBatch.End();

        base.Draw(gameTime);
    }
}
}

Thanks in advance!


回答1:


Important: pay close attention to the capitalisation and pluralisation in my code. It is important.


First you need to determine what data should exist per-asteroid instance, and what data should be shared between all asteroids. And what data should be shared for your entire program.

  • The asteroid position should exist for every single asteroid
  • The asteroid texture can be shared between every instance of Asteroid
  • An instance of the Random class should exist once per program (technically once per thread - but let's not worry about that for now)

So here is what that looks like in code. (Please note that I'm spreading the contents of classes out throughout my answer for sake of readability - you will have to merge each section's code yourself.)

class Asteroid
{
    // Static variables are shared between all instances of a class
    static Texture2D asteroidTexture;

    // Non-static variables exist once for each instance of the class
    Vector2 position;

    // Constants are fixed at compile time and cannot be modified
    const float asteroidSpeed = 50; // units per second
}

// A static class can only contain static variables (and constants)
// (You can't create an instance of it, so you can't have variables.)
static class Shared
{
    // "readonly" prevents anyone from writing to a field after it is initialised
    public static readonly Random Random = new Random();
}

Next you need to decide how that data will be initialised and modified:

Notice (above) how we have already initialised Shared.Random to a new instance of the Random class. (The actual initialisation will be done by the runtime automatically before it is first used.)

First let's look at loading the texture:

class Asteroid
{
    // Static methods can only act on static data
    public static void LoadContent(ContentManager content)
    {
        asteroidTexture = content.Load<Texture2D>("asteroid");
    }
}

public class Game1 : Microsoft.Xna.Framework.Game
{
    protected override void LoadContent()
    {
        Asteroid.LoadContent(Content);
    }
}

Because we know that Game1.LoadContent is called once at the start of our program, it is an appropriate place to call Asteroid.LoadContent.


Now let's look at the per-instance data for each asteroid. Each asteroid needs to have its position set when it is first created. We do this by giving the Asteroid class a constructor, which we then call whenever we wish to create an asteroid.

class Asteroid
{
    public Asteroid(Vector2 position)
    {
        this.position = position;
    }
}

Now we want to create and store multiple instances of our asteroid class:

We use a loop to create multiple instances - each with a random X position inside the width of the screen. And we use a list to store them as they are created:

public class Game1 : Microsoft.Xna.Framework.Game
{
    List<Asteroid> asteroids = new List<Asteroid>();

    protected override void Initialize()
    {
        int screenWidth = GraphicsDevice.Viewport.Width;

        // Create 15 asteroids:
        for(int i = 0; i < 15; i++)
        {
            float xPosition = Shared.Random.Next(0, screenWidth);
            asteroids.Add(new Asteroid(new Vector2(xPosition, 0)));
        }
    }
}

Finally, we want to update and draw every asteroid in the list that we created earlier. This is a simple matter of looping through the list of asteroids, calling the Draw or Update method on each instance.

class Asteroid
{
    public void Update(float elapsedSeconds)
    {
        position.Y += asteroidSpeed * elapsedSeconds;
    }

    public void Draw(SpriteBatch spriteBatch)
    {
        spriteBatch.Draw(asteroidTexture, position, Color.White);
    }
}

public class Game1 : Microsoft.Xna.Framework.Game
{
    protected override void Update(GameTime gameTime)
    {
        foreach(Asteroid asteroid in asteroids)
        {
            asteroid.Update((float)gameTime.ElapsedGameTime.TotalSeconds);
        }
    }

    protected override void Draw(GameTime gameTime)
    {
        foreach(Asteroid asteroid in asteroids)
        {
            asteroid.Draw(spriteBatch);
        }
    }
}

Note the way that I take into consideration the elapsed time in the Update method. This is why I put the comment "units per second" on the asteroid speed earlier.

This is good practice because it makes your game logic independent of the frame rate that your game runs at. (XNA, by default, runs at a fixed frame rate - but it's good practice to write frame-rate independent code anyway.)


By this point you should have complete code for your Asteroid class and for using it. Let's make some additions:

You wanted to know how to get asteroids to fall at intervals. To do this you need to accumulate time as it elapses, and whenever it reaches some threshold, create a new asteroid and reset the timer.

public class Game1 : Microsoft.Xna.Framework.Game
{
    float asteroidSpawnTimer;
    const float asteroidSpawnDelay = 5; // seconds

    void CreateAsteroid()
    {
        // This is the same code as I used in Initialize().
        // Duplicate code is extremely bad practice. So you should now modify 
        // Initialize() so that it calls this method instead.

        int screenWidth = GraphicsDevice.Viewport.Width;
        float xPosition = Shared.Random.Next(0, screenWidth);
        asteroids.Add(new Asteroid(new Vector2(xPosition, 0)));
    }

    protected override void Update(GameTime gameTime)
    {
        // ... other stuff ...

        asteroidSpawnTimer += (float)gameTime.ElapsedGameTime.TotalSeconds;
        if(asteroidSpawnTimer >= asteroidSpawnDelay)
        {
            asteroidSpawnTimer -= asteroidSpawnDelay; // subtract "used" time
            CreateAsteroid();
        }
    }
}

While you are adding asteroids - it would be a good idea to remove the old ones you no longer need - such as when they reach the bottom of the screen.

First of all, you need some way to access the asteroid's position externally. Because we didn't specify an access modifier, our position field defaults to private and can't be accessed outside of the Asteroid class. But we can create a public property that we can access from outside, that provides the position:

class Asteroid
{
    public Vector2 Position { get { return position; } }
}

(You might want to look into getting rid of the position field entirely and using an auto-implemented property with a private setter.)

You will want to use this same method for accessing the properties of your ship object when you want it to interact with asteroids. For a simple game it is ok to do do this kind of inter-object logic in Game1.Update (here is an in-depth discussion).

Anyway, now we have a way to access Asteroid.Position, we can remove the asteroids that fall off the screen.

public class Game1 : Microsoft.Xna.Framework.Game
{
    protected override void Update(GameTime gameTime)
    {
        // ... other stuff ...

        int screenHeight = GraphicsDevice.Viewport.Height;

        // Loop backwards through all asteroids.
        //
        // Note that you must iterate backwards when removing items from a list 
        // by index, as removing an item will change the indices of all items in
        // the list following (in a forward order) the item that you remove.
        for(int i = asteroids.Count - 1; i >= 0; i--)
        {
            if(asteroids[i].Position.Y > screenHeight)
                asteroids.RemoveAt(i);
        }
    }
}

Now, this was an amazingly long answer - I've covered a lot of topics (at the beginner level). So there is plenty of detail that I've not gone into. So if you're confused about something - look for the key words that I've used to describe that thing and go searching. Microsoft's documentation on MSDN is a particularly good place to look (here are the XNA docs).




回答2:


You may define them in an array and on fixed interval from Update method you can add a new asteroid based on the gameTime. Hope this was useful



来源:https://stackoverflow.com/questions/11382101/how-to-show-lots-of-sprites-from-one-texture-and-have-them-move-at-intervals-xna

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!