I hope this isn't getting annoying, but I've asked a lot of questions about improving my game lately:
- How can I improve my game project design?
- Using the observer pattern with collision detection
- Is this a good way to cascade a property?
- How should I implement my domain model?
- Setting up keyboard bindings using JSON and reflection
- Setting up keyboard bindings using JSON (no reflection!)
- Get array of pressed buttons using extension method
No, you don't have to read all of those. I'm starting to think all my questions stem from problems I have with XNA's default project template. After creating a new Windows game project in Visual Studio, this is what it gives you:
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main(string[] args)
{
using (Game1 game = new Game1())
{
game.Run();
}
}
}
/// <summary>
/// This is the main type for your game
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
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()
{
// 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);
// TODO: use this.Content to load your game content here
}
/// <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();
// TODO: Add your update logic here
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);
// TODO: Add your drawing code here
base.Draw(gameTime);
}
}
I feel like I've been fighting the framework all along because I don't like how it is structured. There are several tutorials around the net, and most (if not all) of them show you how to add your code to the Game
class directly - loading sprites, animating sprites, drawing sprites, adding sound effects, responding to input, etc. I've tried several times to abstract this as much as possible, but my results haven't been great because I'm not the best designer. This is my latest iteration:
public static class Program
{
private static void Main( string[] args )
{
IFileHandler fileHandler = new JsonHandler();
var settings = fileHandler.Read<Settings>( "settings.json" );
var level = fileHandler.Read<Level>( "level.json" );
var player = fileHandler.Read<Entity>( "player.json" );
using ( var game = new MyGame( new Graphics( settings.DisplaySettings, level, player ),
new Input( settings.GamePadSettings, settings.KeyboardSettings, level, player ) ) )
{
game.Run();
fileHandler.Write( "player.json", player );
fileHandler.Write( "level.json", level );
}
}
}
public class MyGame : Game
{
private readonly IGraphics graphics;
private readonly IInput input;
public MyGame( IGraphics graphics, IInput input )
{
if ( graphics == null || input == null )
{
throw new ArgumentNullException();
}
this.graphics = graphics;
this.input = input;
graphics.Setup( this );
input.Setup( this );
}
protected override void LoadContent()
{
Content.RootDirectory = "Content";
graphics.Load( GraphicsDevice, Content );
}
protected override void Update( GameTime gameTime )
{
input.Process();
base.Update( gameTime );
}
protected override void Draw( GameTime gameTime )
{
graphics.Draw();
base.Draw( gameTime );
}
}
Not only are there several problems with my code, but in a way I feel like it doesn't make sense for me to try and do this. One of the biggest problems I have is GraphicsDeviceManager
is, by its very definition, tightly coupled to the Game
class - you can't instantiate it without giving it an instance of Game
. Furthermore, Game
already has a GraphicsDevice
property (separate from GraphicsDeviceManager
) that's needed to load sprites into the content pipeline. I really wanted to get this stuff - loading, drawing, input - out of my base class, but I can't figure out how to create a class that does this without tightly coupling it to Game
. See:
public class Graphics : IGraphics
{
private readonly DisplaySettings displaySettings;
private readonly Level level;
private readonly Entity player;
private GraphicsDeviceManager graphicsDeviceManager;
private SpriteBatch spriteBatch;
public Graphics( DisplaySettings displaySettings, Level level, Entity player )
{
if ( displaySettings == null || level == null || player == null )
{
throw new ArgumentNullException();
}
this.displaySettings = displaySettings;
this.level = level;
this.player = player;
}
public void Setup( Game game )
{
if ( game == null )
{
throw new ArgumentNullException();
}
graphicsDeviceManager = new GraphicsDeviceManager( game )
{
IsFullScreen = displaySettings.IsFullScreen,
PreferredBackBufferWidth = displaySettings.PreferredBackBufferWidth,
PreferredBackBufferHeight = displaySettings.PreferredBackBufferHeight
};
}
public void Load( GraphicsDevice graphicsDevice, ContentManager contentManager )
{
if ( graphicsDevice == null || contentManager == null )
{
throw new ArgumentNullException();
}
spriteBatch = new SpriteBatch( graphicsDevice );
level.Load( contentManager );
player.Load( contentManager );
}
public void Draw()
{
level.Clear( graphicsDeviceManager.GraphicsDevice );
spriteBatch.Begin( SpriteSortMode.BackToFront, BlendState.AlphaBlend, SamplerState.LinearWrap, DepthStencilState.Default,
RasterizerState.CullNone );
level.Draw( spriteBatch );
player.Draw( spriteBatch );
spriteBatch.End();
}
}
Like I said, I feel like I'm fighting the framework. I'm not convinced that what I've come up with is any better, and it might even be worse than if I simply stuck everything in the Game
class. At least that seems to be the way the XNA team intended for us to work with it.
TLDR: How can I use XNA without shooting myself in the foot? How would you work with the template to make it more SOLID?
SpriteBatch
constructor gets aGraphicsDevice
argument, later in draw you get aGraphicsDevice
fromGraphicsDeviceManager
. How does that work, are these two required to be same, how doesspritebatch
know current device? \$\endgroup\$SpriteBatch
constructor gets aGraphicsDevice
argument, later in draw you get aGraphicsDevice
fromGraphicsDeviceManager
... are these two required to be same" I assume so - in my "improved" version I tried to get theGraphicsDeviceManager
andGraphicsDevice
stuff out of theGame
class but in reality they are in both now. "how does spritebatch know current device?" I don't know - I assume it holds a reference to the device but it's all very magical. \$\endgroup\$