- SpriteBatch - a SpriteBatch object lets you draw things to the screen in 2D. XNA handles 2D like it's a special case of 3D -- the things you draw are just a bunch of rectangles that face the camera, using a special camera mode that doesn't create perspective shift. SpriteBatch does all the 3D graphics card wrangling for you. You tell it to Begin(), you give it a bunch of things to Draw(), and then you tell it to End(). As soon as you End() it processes all the draw commands, maybe sorts and orders them for optimal rendering, uploads everything to the graphics card and makes the magic happen.
- Texture2D - represents a bitmap or texture, with lots of customizability and optional features. You create the object, usually tell it to load its contents from a file, and then you use it like a stamp, to draw with.
- Vector2 - special object that holds two floats, x and y. Some functions ask for this object by name.
- Rectangle - special object that holds four ints, x, y, width, and height. Some functions ask for this object by name as well.
- KeyboardState - object that holds a snapshot of the state of ALL keys on the keyboard. Usually you'll grab one of these objects and store it, and then take your time checking the keys you care about.
- MouseState - object that holds a snapshot of the mouse, including up to five mouse buttons (left, mid, right, x1, x2), the mouse wheel, and the x and y position of the pointer.
- Mouse - this is a static class that represents the actual mouse. The mouse is unique in that you can SetPosition() to move the pointer to wherever you want. This is how you implement mouselook controls like first person shooters have: every time you read the mouse you move it back to the center of the screen, so the player can never move the mouse outside the window (unless the window is really small and the mouse really fast).
- SpriteFont - this is another graphical asset you draw with, but it represents a prepared library of pictures of text, letters and whatnot, in a previously-named font and size.
- Color - this stores four bytes, one for red, green, blue, and alpha (transparency). Some functions ask for a color object by name.
- Game - The Big One. Game execution begins and ends with a Game object. A Game object inherits some important things from the operating system. Essentially you define one of these game objects. When your game runs, the system instantiates ONE of these. When your game object is destroyed, execution stops.
Flow of execution:
You have six functions you must fill with code, and they each have different roles.
- Game Constructor - this is where you set things that must be known BEFORE the graphics card gets touched, register for optional XNA services you might need, and otherwise create and register major structural things.
- Register a GameComponent or DrawableGameComponent - YES
- Create a huge array for game state information - NO
- Initialize() - this is where your game logic gets initialized. Allocate memory, prepare to show a main menu, position the player on the starting square, whatever. This is for NON-GRAPHICAL initialization only.
- LoadContent() - this is where your graphical objects get initialized. Allocate and Load() Texture2D objects, SpriteFonts, anything that will be fed to the graphics card.
- UnloadContent() - this is where graphical objects get destroyed. Personally I have never used this, as I'm too much of a noob I guess.
- Update() - This is part of your main game loop, and is where you change the game state, but don't touch the screen. Read input, do some math, change some variables, maybe Exit() if appropriate. By default XNA tries to call this function 60 times per second, but you can change this if you want.
- Draw() - This is the other part of your main game loop, and is where you draw things to the screen, but don't read input or change the game state. XNA will try to call this as often as possible between Update calls, but if XNA is trying to reach a target rate of Update() calls and can't quite make it, it will skip calling Draw() every now and then to help keep your game from lagging too badly.
One gap in my understanding remains: I don't actually use UnloadContent(), and I don't really know the functional difference between what happens in the constructor and what happens in Initialize(). I've just kinda learned by watching how other programs have used those.
So, ahem, your humble instructor's ignorance aside . . . .
When your XNA game is run, here's roughly what happens:
- Your Game object is created, and that creation causes all of the following:
- Now you have an execution thread and some memory, but no access to the screen, network, GamerServices, or really anything. So lonely . . .
- Your Game object's constructor is called, in which you set some flags for how you want your drawing and updating to be handled, what your screen device should look like once it's born, etc.
- Your Initialize() function is called. You still only have no access to the screen or any components you've added, but this is the proper place to do all your game state initialization. Make arrays and fill them, etc.
- At this point the graphics hardware gets initialized. You have a video card now, and XNA is ready to be told to load graphical things like textures.
- Your LoadContent() function is called. You load graphical things like textures.
- Now it's time for your main game loop. XNA does these things until it's time to stop:
- Get a new timestamp from the system clock
- Call your Update() function, and pass it this timestamp. Wait until you're done.
- Did the Update() function call Exit()? If so then we'd better break out of this loop.
- Check the time. Are we running slowly? Can we spare some time to draw to the screen?
- If so, call your Draw() function, and pass it this last timestamp. Wait until you're done.
- Return to the beginning of the loop and do it all again.
- The game is exiting now! Call UnloadContent()
- Destroy the Game object and finish execution
Here's some quick and dirty recipes for how to do things in XNA.
Prepare to draw sprites to the screen:
- In your class definition -- above the constructor -- declare some Texture2D objects but do not initialize them yet.
- Also declare a Rectangle to hold the current screen dimensions, and another Rectangle for a work variable for calculating where you're next going to draw something.
- In your LoadContent() function, assign each Texture2D object variable to the output of Content.Load<Texture2D>() with the name of your bitmap.
- Also inside LoadContent() copy graphics.GraphicsDevice.Viewport into your Rectangle for the screen dimensions.
Actually draw to the screen in your Draw() method:
- Call spriteBatch.Begin() to signify the start of your drawing batch job
- For each sprite you need to draw to the screen, do the following:
- Use your screen rectangle object and whatever local variables you have, do some math, and come up with a new Rectangle that holds the position on the screen you would like to stamp your texture onto.
- call spriteBatch.Draw() with your texture, your rectangle, and Color.White (no tint - paint your texture as-is)
- Call spriteBatch.End() to signify the end of your job
- Do NOT create more spriteBatches or call begin or end more than once. Powerful and video-bus-bandwidth-intensive mojo occurs with each begin and end, so inside your Draw() function begin ONCE, draw everything, and end ONCE.
- For example, call begin, call a bunch of functions that each call draw as many times as they need to, and then call end.
Read the keyboard so you can see what keys are down or up:
- In your class definition create a KeyboardState object.
- In your Update() method, first copy the output of Keyboard.GetState() into your KeyboardState object.
- Still in your Update() method, next call your keyboardstate object's IsKeyDown() and IsKeyUp() method (like mykeystate.IsKeyDown(Keys.Space)) to see if, true or false, is that key down or up.
Read the keyboard, but detect CHANGES in key state, so you act once per tap of the key, and the player has to keep tapping to make something happen more than once:
- In your class definition create TWO KeyboardState objects, one for old and one for new.
- In your Initialize() method, assign the output of Keyboard.GetState() to your old state variable.
- In your Update() method, first assign the output of Keyboard.GetState() to your new state variable.
- Still in your Update() method, check for the exact moment a key is pressed by checking if the new state's IsKeyDown() is true AND the old state's IsKeyUp() is true.
- Or, still in your Update method, check for the exact moment a key is released by checking if the new state's IsKeyUp() is true AND the old state's IsKeyDown() is true.
- Or, still in your Update method, you can still check only the new state's IsKeyDown() or IsKeyUp() if you don't need to care about individual keystrokes.
- When you're done checking IsKeyDown() and IsKeyUp(), copy your new key state into your old key state.
Add in GamerServices, so you get Games For Windows Live functionality added "for free" -- but which won't do anything with Xbox Live unless you have a premium creator's club account:
- In your class constructor, add: Components.Add(new GamerServicesComponent(this));