如何从一个纹理中显示许多精灵,让它们以XNA 4.0的间隔移动

时间:2012-07-08 09:35:41

标签: c# xna xna-4.0

嗨,我是xna的新手,我正在尝试制作一个简单的游戏,你的小船可以四处移动,避免从顶部到底部坠落的小行星。我有船移动和一颗小行星坠落,但我不知道如何让大量的小行星从相同的纹理中掉落,以及如何让它们每隔一段时间掉落一次。 到目前为止,这是我的小行星类:

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);
    }
}
}

这是我的Game1课程:

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);
    }
}
}

提前致谢!

2 个答案:

答案 0 :(得分:6)

重要提示:密切关注我的代码中的大写和多元化。这很重要。


首先,您需要确定每个小行星实例应该存在哪些数据,以及应该在所有小行星之间共享哪些数据。应该为整个计划分享哪些数据。

  • 每个小行星都应该存在小行星位置
  • 小行星纹理可以在每个Asteroid实例之间共享
  • Random类的一个实例应该每个程序存在一次(技术上每个线程一次 - 但现在不要担心)

所以这就是代码中的样子。 (请注意我为了便于阅读而在整个答案中传播课程内容 - 您必须自己合并每个部分的代码。)

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();
}

接下来,您需要确定如何初始化和修改数据:

请注意(上文)我们如何将Shared.Random初始化为Random类的新实例。 (实际初始化将在运行时首次使用之前自动完成。)

首先让我们看看加载纹理:

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);
    }
}

因为我们知道在我们的计划开始时调用Game1.LoadContent一次,所以它是调用Asteroid.LoadContent的合适位置。


现在让我们看看每个小行星的每个实例数据。每个小行星在首次创建时都需要设置其位置。我们通过给Asteroid类提供一个构造函数来做到这一点,然后我们在想要创建一个小行星时调用它。

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

现在我们要创建和存储我们的小行星类的多个实例:

我们使用循环创建多个实例 - 每个实例在屏幕宽度内具有随机X位置。我们使用列表来存储它们:

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)));
        }
    }
}

最后,我们想要更新并绘制我们之前创建的列表中的每个小行星。这是循环遍历小行星列表的简单问题,在每个实例上调用DrawUpdate方法。

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);
        }
    }
}

请注意我在Update方法中考虑已用时间的方式。这就是为什么我早些时候将评论“每秒单位”放在小行星速度上。

这是一种很好的做法,因为它可以使您的游戏逻辑独立于游戏运行的帧速率。 (默认情况下,XNA以固定的帧速率运行 - 但最好还是编写与帧速率无关的代码。)


到目前为止,您应该拥有Asteroid课程的完整代码并使用它。让我们做一些补充:

你想知道如何让小行星间隔下降。要做到这一点,你需要累积时间,当它达到某个阈值时,创建一个新的小行星并重置计时器。

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();
        }
    }
}

在添加小行星时 - 最好删除不再需要的旧小行星 - 例如当它们到达屏幕底部时。

首先,您需要一些方法来从外部访问小行星的位置。因为我们没有指定访问修饰符,所以我们的position字段默认为private,不能在Asteroid类之外访问。但我们可以创建一个我们可以从外部访问的公共财产,提供职位:

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

(您可能希望完全摆脱position字段,并使用auto-implemented property私有设置器。)

当您希望它与小行星交互时,您将需要使用相同的方法来访问船舶对象的属性。对于一个简单的游戏,可以在Game1.Updatehere is an in-depth discussion)中进行这种对象间逻辑。

无论如何,现在我们有办法访问Asteroid.Position,我们可以移除掉在屏幕上的小行星。

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);
        }
    }
}

现在,这是一个非常长的答案 - 我已经涵盖了很多主题(在初级阶段)。所以我有很多细节没有进入。因此,如果您对某些事情感到困惑 - 请查找我用来描述该事物的关键词并进行搜索。 Microsoft在MSDN上的文档是一个特别好看的地方(here are the XNA docs)。

答案 1 :(得分:0)

您可以在数组中定义它们,并且在Update方法的固定间隔内,您可以根据gameTime添加新的小行星。 希望这很有用