1

嗨,我是 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);
    }
}
}

提前致谢!

4

2 回答 2

6

重要提示:密切注意我的代码中的大写和复数。这很重要。


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

  • 每个小行星都应该存在小行星位置
  • 小行星纹理可以在小行星的每个实例之间共享
  • 每个程序应该存在一个类的实例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)));
        }
    }
}

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

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字段默认为私有,不能在Asteroid类外访问。但是我们可以创建一个可以从外部访问的公共属性,它提供了以下位置:

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

(您可能希望考虑完全摆脱该position字段并使用带有私有设置器的自动实现的属性。)

当您希望它与小行星交互时,您将希望使用相同的方法来访问您的船对象的属性。对于一个简单的游戏来说,做这种对象间逻辑是可以的Game1.Update这里有一个深入的讨论)。

无论如何,现在我们有了访问的方法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 上的文档是一个特别好的地方(这里是 XNA 文档)。

于 2012-07-08T13:19:34.200 回答
0

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

于 2012-07-08T09:57:05.053 回答