如何从一个纹理中显示许多精灵,让它们以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("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"; } ///  /// 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. ///  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(); } ///  /// LoadContent will be called once per game and is the place to load /// all of your content. ///  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); } ///  /// UnloadContent will be called once per game and is the place to unload /// all content. ///  protected override void UnloadContent() { // TODO: Unload any non ContentManager content here } ///  /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. ///  /// Provides a snapshot of timing values. 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); } ///  /// This is called when the game should draw itself. ///  /// Provides a snapshot of timing values. 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); } } } 

提前致谢!

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


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

  • 每个小行星都应该存在小行星位置
  • 小行星纹理可以在每个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("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 asteroids = new List(); 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字段并使用具有私有setter的自动实现的属性 。)

当您希望它与小行星交互时,您将需要使用相同的方法来访问船舶对象的属性。 对于一个简单的游戏,可以在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文档 )。

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