我对XNA相对较新且缺乏经验,我一直在遵循Jamie McMahon的指南,在C#中创建一个基本的突破游戏(http://xnagpa.net/xna4beginner.php)。最近,我偶然发现了微软的游戏状态管理代码示例(http://xbox.create.msdn.com/en-US/education/catalog/sample/game_state_management)并且一直试图移动我的Breakout代码,这样我就可以使用代码示例提供给我的菜单。然而,每当我尝试启动游戏时,我都会在spriteBatch中收到NullReferenceException。从以下代码开始:
#region File Description
//-----------------------------------------------------------------------------
// GameplayScreen.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#region Using Statements
using System;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using GameStateManagement;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Media;
#endregion
namespace Rebound
{
/// <summary>
/// This screen implements the actual game logic. It is just a
/// placeholder to get the idea across: you'll probably want to
/// put some more interesting gameplay in here!
/// </summary>
public class GameplayScreen : GameScreen
{
#region Fields
ContentManager content;
SpriteFont gameFont;
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Ball ball;
Paddle paddle;
Rectangle screenRectangle;
int bricksWide = 10;
int bricksHigh = 5;
Texture2D brickImage;
Brick[,] bricks;
float pauseAlpha;
InputAction pauseAction;
#endregion
#region Initialization
/// <summary>
/// Constructor.
/// </summary>
public GameplayScreen()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.PreferredBackBufferWidth = 800;
graphics.PreferredBackBufferHeight = 480;
screenRectangle = new Rectangle(
0,
0,
graphics.PreferredBackBufferWidth,
graphics.PreferredBackBufferHeight);
TransitionOnTime = TimeSpan.FromSeconds(1.5);
TransitionOffTime = TimeSpan.FromSeconds(0.5);
pauseAction = new InputAction(
new Buttons[] { Buttons.Start, Buttons.Back },
new Keys[] { Keys.Escape },
true);
}
protected override void Initialize()
{
// TODO: Add your initialization logic here
base.Initialize();
}
/// <summary>
/// Load graphics content for the game.
/// </summary>
///
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
gameFont = Content.Load<SpriteFont>("gamefont");
Texture2D tempTexture = Content.Load<Texture2D>("paddle");
paddle = new Paddle(tempTexture, screenRectangle);
tempTexture = Content.Load<Texture2D>("ball");
ball = new Ball(tempTexture, screenRectangle);
brickImage = Content.Load<Texture2D>("brick");
StartGame();
// once the load has finished, we use ResetElapsedTime to tell the game's
// timing mechanism that we have just finished a very long frame, and that
// it should not try to catch up.
ScreenManager.Game.ResetElapsedTime();
base.LoadContent();
}
private void StartGame()
{
paddle.SetInStartPosition();
ball.SetInStartPosition(paddle.GetBounds());
bricks = new Brick[bricksWide, bricksHigh];
for (int y = 0; y < bricksHigh; y++)
{
Color tint = Color.White;
switch (y)
{
case 0:
tint = Color.Blue;
break;
case 1:
tint = Color.Red;
break;
case 2:
tint = Color.Green;
break;
case 3:
tint = Color.Yellow;
break;
case 4:
tint = Color.Purple;
break;
}
for (int x = 0; x < bricksWide; x++)
{
bricks[x, y] = new Brick(
brickImage,
new Rectangle(
x * brickImage.Width,
y * brickImage.Height,
brickImage.Width,
brickImage.Height),
tint);
}
}
}
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>
#endregion
#region Update and Draw
/// <summary>
/// Updates the state of the game. This method checks the GameScreen.IsActive
/// property, so the game will stop updating when the pause menu is active,
/// or if you tab away to a different application.
/// </summary>
public override void Update(GameTime gameTime, bool otherScreenHasFocus,
bool coveredByOtherScreen)
{
base.Update(gameTime, otherScreenHasFocus, false);
// Gradually fade in or out depending on whether we are covered by the pause screen.
if (coveredByOtherScreen)
pauseAlpha = Math.Min(pauseAlpha + 1f / 32, 1);
else
pauseAlpha = Math.Max(pauseAlpha - 1f / 32, 0);
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
paddle.Update();
ball.Update();
foreach (Brick brick in bricks)
{
brick.CheckCollision(ball);
}
ball.PaddleCollision(paddle.GetBounds());
if (ball.OffBottom())
StartGame();
base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
}
/// <summary>
/// Draws the gameplay screen.
/// </summary>
public override void Draw(GameTime gameTime)
{
// This game has a blue background. Why? Because!
ScreenManager.GraphicsDevice.Clear(ClearOptions.Target,
Color.CornflowerBlue, 0, 0);
spriteBatch.Begin();
foreach (Brick brick in bricks)
brick.Draw(spriteBatch);
paddle.Draw(spriteBatch);
ball.Draw(spriteBatch);
spriteBatch.End();
base.Draw(gameTime);
// If the game is transitioning on or off, fade it out to black.
if (TransitionPosition > 0 || pauseAlpha > 0)
{
float alpha = MathHelper.Lerp(1f - TransitionAlpha, 1f, pauseAlpha / 2);
ScreenManager.FadeBackBufferToBlack(alpha);
}
}
#endregion
}
}
那么我哪里错了?我对这个错误做了一些研究,但没有一个解决方案能帮我解决问题。请随意指出我代码中的任何其他冗余/错误,因为我对这类事情非常缺乏经验。谢谢
编辑:添加微软GameStateManagement代码示例中的原始GameplayScreen.cs和Breakout游戏代码,我正试图与之合并。如果有人知道一个简单的方法,我很乐意听取他们的建议。
突围游戏代码:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
namespace Rebound
{
/// <summary>
/// This is the main type for your game
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Ball ball;
Paddle paddle;
Rectangle screenRectangle;
int bricksWide = 10;
int bricksHigh = 5;
Texture2D brickImage;
Brick[,] bricks;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.PreferredBackBufferWidth = 750;
graphics.PreferredBackBufferHeight = 600;
screenRectangle = new Rectangle(
0,
0,
graphics.PreferredBackBufferWidth,
graphics.PreferredBackBufferHeight);
}
/// <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()
{
// TODO: Add your initialization logic here
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);
Texture2D tempTexture = Content.Load<Texture2D>("paddle");
paddle = new Paddle(tempTexture, screenRectangle);
tempTexture = Content.Load<Texture2D>("ball");
ball = new Ball(tempTexture, screenRectangle);
brickImage = Content.Load<Texture2D>("brick");
StartGame();
}
private void StartGame()
{
paddle.SetInStartPosition();
ball.SetInStartPosition(paddle.GetBounds());
bricks = new Brick[bricksWide, bricksHigh];
for (int y = 0; y < bricksHigh; y++)
{
Color tint = Color.White;
switch (y)
{
case 0:
tint = Color.Blue;
break;
case 1:
tint = Color.Red;
break;
case 2:
tint = Color.Green;
break;
case 3:
tint = Color.Yellow;
break;
case 4:
tint = Color.Purple;
break;
}
for (int x = 0; x < bricksWide; x++)
{
bricks[x, y] = new Brick(
brickImage,
new Rectangle(
x * brickImage.Width,
y * brickImage.Height,
brickImage.Width,
brickImage.Height),
tint);
}
}
}
/// <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();
paddle.Update();
ball.Update();
foreach (Brick brick in bricks)
{
brick.CheckCollision(ball);
}
ball.PaddleCollision(paddle.GetBounds());
if (ball.OffBottom())
StartGame();
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.CornflowerBlue);
spriteBatch.Begin();
foreach (Brick brick in bricks)
brick.Draw(spriteBatch);
paddle.Draw(spriteBatch);
ball.Draw(spriteBatch);
spriteBatch.End();
base.Draw(gameTime);
}
}
}
来自微软的原始GameplayScreen.cs代码示例:
#region File Description
//-----------------------------------------------------------------------------
// GameplayScreen.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion
#region Using Statements
using System;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using GameStateManagement;
#endregion
namespace GameStateManagementSample
{
/// <summary>
/// This screen implements the actual game logic. It is just a
/// placeholder to get the idea across: you'll probably want to
/// put some more interesting gameplay in here!
/// </summary>
class GameplayScreen : GameScreen
{
#region Fields
ContentManager content;
SpriteFont gameFont;
Vector2 playerPosition = new Vector2(100, 100);
Vector2 enemyPosition = new Vector2(100, 100);
Random random = new Random();
float pauseAlpha;
InputAction pauseAction;
#endregion
#region Initialization
/// <summary>
/// Constructor.
/// </summary>
public GameplayScreen()
{
TransitionOnTime = TimeSpan.FromSeconds(1.5);
TransitionOffTime = TimeSpan.FromSeconds(0.5);
pauseAction = new InputAction(
new Buttons[] { Buttons.Start, Buttons.Back },
new Keys[] { Keys.Escape },
true);
}
/// <summary>
/// Load graphics content for the game.
/// </summary>
public override void Activate(bool instancePreserved)
{
if (!instancePreserved)
{
if (content == null)
content = new ContentManager(ScreenManager.Game.Services, "Content");
gameFont = content.Load<SpriteFont>("gamefont");
// A real game would probably have more content than this sample, so
// it would take longer to load. We simulate that by delaying for a
// while, giving you a chance to admire the beautiful loading screen.
Thread.Sleep(1000);
// once the load has finished, we use ResetElapsedTime to tell the game's
// timing mechanism that we have just finished a very long frame, and that
// it should not try to catch up.
ScreenManager.Game.ResetElapsedTime();
}
#if WINDOWS_PHONE
if (Microsoft.Phone.Shell.PhoneApplicationService.Current.State.ContainsKey("PlayerPosition"))
{
playerPosition = (Vector2)Microsoft.Phone.Shell.PhoneApplicationService.Current.State["PlayerPosition"];
enemyPosition = (Vector2)Microsoft.Phone.Shell.PhoneApplicationService.Current.State["EnemyPosition"];
}
#endif
}
public override void Deactivate()
{
#if WINDOWS_PHONE
Microsoft.Phone.Shell.PhoneApplicationService.Current.State["PlayerPosition"] = playerPosition;
Microsoft.Phone.Shell.PhoneApplicationService.Current.State["EnemyPosition"] = enemyPosition;
#endif
base.Deactivate();
}
/// <summary>
/// Unload graphics content used by the game.
/// </summary>
public override void Unload()
{
content.Unload();
#if WINDOWS_PHONE
Microsoft.Phone.Shell.PhoneApplicationService.Current.State.Remove("PlayerPosition");
Microsoft.Phone.Shell.PhoneApplicationService.Current.State.Remove("EnemyPosition");
#endif
}
#endregion
#region Update and Draw
/// <summary>
/// Updates the state of the game. This method checks the GameScreen.IsActive
/// property, so the game will stop updating when the pause menu is active,
/// or if you tab away to a different application.
/// </summary>
public override void Update(GameTime gameTime, bool otherScreenHasFocus,
bool coveredByOtherScreen)
{
base.Update(gameTime, otherScreenHasFocus, false);
// Gradually fade in or out depending on whether we are covered by the pause screen.
if (coveredByOtherScreen)
pauseAlpha = Math.Min(pauseAlpha + 1f / 32, 1);
else
pauseAlpha = Math.Max(pauseAlpha - 1f / 32, 0);
if (IsActive)
{
// Apply some random jitter to make the enemy move around.
const float randomization = 10;
enemyPosition.X += (float)(random.NextDouble() - 0.5) * randomization;
enemyPosition.Y += (float)(random.NextDouble() - 0.5) * randomization;
// Apply a stabilizing force to stop the enemy moving off the screen.
Vector2 targetPosition = new Vector2(
ScreenManager.GraphicsDevice.Viewport.Width / 2 - gameFont.MeasureString("Insert Gameplay Here").X / 2,
200);
enemyPosition = Vector2.Lerp(enemyPosition, targetPosition, 0.05f);
// TODO: this game isn't very fun! You could probably improve
// it by inserting something more interesting in this space :-)
}
}
/// <summary>
/// Lets the game respond to player input. Unlike the Update method,
/// this will only be called when the gameplay screen is active.
/// </summary>
public override void HandleInput(GameTime gameTime, InputState input)
{
if (input == null)
throw new ArgumentNullException("input");
// Look up inputs for the active player profile.
int playerIndex = (int)ControllingPlayer.Value;
KeyboardState keyboardState = input.CurrentKeyboardStates[playerIndex];
GamePadState gamePadState = input.CurrentGamePadStates[playerIndex];
// The game pauses either if the user presses the pause button, or if
// they unplug the active gamepad. This requires us to keep track of
// whether a gamepad was ever plugged in, because we don't want to pause
// on PC if they are playing with a keyboard and have no gamepad at all!
bool gamePadDisconnected = !gamePadState.IsConnected &&
input.GamePadWasConnected[playerIndex];
PlayerIndex player;
if (pauseAction.Evaluate(input, ControllingPlayer, out player) || gamePadDisconnected)
{
#if WINDOWS_PHONE
ScreenManager.AddScreen(new PhonePauseScreen(), ControllingPlayer);
#else
ScreenManager.AddScreen(new PauseMenuScreen(), ControllingPlayer);
#endif
}
else
{
// Otherwise move the player position.
Vector2 movement = Vector2.Zero;
if (keyboardState.IsKeyDown(Keys.Left))
movement.X--;
if (keyboardState.IsKeyDown(Keys.Right))
movement.X++;
if (keyboardState.IsKeyDown(Keys.Up))
movement.Y--;
if (keyboardState.IsKeyDown(Keys.Down))
movement.Y++;
Vector2 thumbstick = gamePadState.ThumbSticks.Left;
movement.X += thumbstick.X;
movement.Y -= thumbstick.Y;
if (input.TouchState.Count > 0)
{
Vector2 touchPosition = input.TouchState[0].Position;
Vector2 direction = touchPosition - playerPosition;
direction.Normalize();
movement += direction;
}
if (movement.Length() > 1)
movement.Normalize();
playerPosition += movement * 8f;
}
}
/// <summary>
/// Draws the gameplay screen.
/// </summary>
public override void Draw(GameTime gameTime)
{
// This game has a blue background. Why? Because!
ScreenManager.GraphicsDevice.Clear(ClearOptions.Target,
Color.CornflowerBlue, 0, 0);
// Our player and enemy are both actually just text strings.
SpriteBatch spriteBatch = ScreenManager.SpriteBatch;
spriteBatch.Begin();
spriteBatch.DrawString(gameFont, "// TODO", playerPosition, Color.Green);
spriteBatch.DrawString(gameFont, "Insert Gameplay Here",
enemyPosition, Color.DarkRed);
spriteBatch.End();
// If the game is transitioning on or off, fade it out to black.
if (TransitionPosition > 0 || pauseAlpha > 0)
{
float alpha = MathHelper.Lerp(1f - TransitionAlpha, 1f, pauseAlpha / 2);
ScreenManager.FadeBackBufferToBlack(alpha);
}
}
#endregion
}
}
您正在LoadContent
中初始化spriteBatch
。在对GamePlayScreen.Loadcontent()
进行任何操作之前,你确定你正在运行它吗?也许您可以向我们展示使用这个GameScreen
对象的类。另一方面,您可以通过将主类中的SpriteBatch
对象传递给GameScreen
的draw方法来使用它。
public void Draw(GameTime gameTime, SpriteBatch spritebatch)
{
}
//In the main class (Game1) you can use draw like this:
gameScreen.Draw(gameTime, spritebatch);
此外,我从未在我的抽签方法中使用过GameTime。GameTime afaik用于更新Update方法中的逻辑。我感觉你在制造一点混乱。重新开始,也许可以从一个更简单的教程开始。gameScreen需要继承的基本知识。当你想了解更多关于游戏屏幕管理的信息时,我建议你先了解更多关于继承的信息。
您的代码中似乎没有activate方法(除非我错过了它)。从内存中,这是从屏幕管理器中实际调用的方法。覆盖此方法,然后从那里调用loadcontent方法。此外,您不需要重新创建您的spritebatch。我很确定屏幕管理器有对它的引用,所以你应该有类似this.spritebatch=ScreenManager.spritebatch;
再次,这是我的记忆,我可以在回家后再次更新,并可以参考我自己的项目。
是否先添加纹理文件?因为如果没有,那就是为什么它会给出一个null异常。。。因为它无法找到和加载纹理文件。