嗨,我是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);
}
}
}
提前致谢!
答案 0 :(得分: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)));
}
}
}
最后,我们想要更新并绘制我们之前创建的列表中的每个小行星。这是循环遍历小行星列表的简单问题,在每个实例上调用Draw
或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
字段默认为private,不能在Asteroid
类之外访问。但我们可以创建一个我们可以从外部访问的公共财产,提供职位:
class Asteroid
{
public Vector2 Position { get { return position; } }
}
(您可能希望完全摆脱position
字段,并使用auto-implemented property私有设置器。)
当您希望它与小行星交互时,您将需要使用相同的方法来访问船舶对象的属性。对于一个简单的游戏,可以在Game1.Update
(here 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添加新的小行星。 希望这很有用