Objects
- 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));
- ???
- Profit!
Posts
A lot of what you'll be doing in XNA is working with specialty classes that implement functionality or system logic that you need. When you create a variable that represents a class, conceptually it works something like a pointer in C, except XNA doesn't let you work with memory addresses.
When I do this: all I've created is a variable that points to a KeyboardState object instance -- but the instance of the object doesn't exist, like a null pointer. I haven't actually created an instance of that object yet. If I try to read the value of mykeystate I get an exception.
Now suppose I do this: I happen to know that Keyboard.GetState() creates its own KeyboardState object and returns a reference to it. Now mykeystate points to a KeyboardState object. If I have a second KeyboardState object and I do this: I haven't actually created a second KeyboardState object instance, or made a copy of the one I had. I just have two reference variables that both point to the same KeyboardState instance.
In C# there are several kinds of classes, but the two most important kinds are static and not static. A static class doesn't get instantiated -- there's one global copy of the class with one set of storage. Keyboard is a static class. When I call Keyboard.GetState() I'm referring to one universal Keyboard object. KeyboardState is not a static class. I can't just refer to KeyboardState's methods -- there could be many, so which one do I mean? I have to create an instance of a KeyboardState object, and call the methods on that instance.
Nearly every variable you create will be an instance of a class, and they behave that same way. Just declaring one doesn't create the object -- you have to say "new ClassNameGoesHere()" and assign that to your variable, or call a function that creates one of those objects and assign THAT to your variable. Collectively these are called "reference types." There are a few "value types" that behave more like what you're used to in C: bool, char, sbyte, short, int, long, byte, ushort, uint, ulong, float, double, and decimal. Declare one of these and it's just a variable with its own storage.
// int i = new int(); //Not really necessary -- it just initializes i to 0. // int i = new int(5); //Don't do this either. Not necessary. It's wrong and won't compile. int i = 5; //that's more like it. Color col1; //Color is a reference type, so you can't actually use this yet. It points nowhere. // col1.R = 0; //error -- col1 doesn't point anywhere, so there's no object instance to receive your data. col1 = new Color(); //now it points somewhere and you can use it. col1.R = 0; col1.G = 127; col1.B = 255; col1.A = 255; //now this works. Color col2 = new Color(0, 127, 255, 255); //but this is so much faster. Color has an overloaded constructor //that lets you create a new object that starts already set to the values you want.Notice there's lots of "new" but not so much deleting and freeing memory. C# has a garbage collector, and the language expects you to use objects and then just toss them aside without freeing them. Remove the last reference to an object and it's eligible for garbage collection.
There's a problem though. Garbage is inevitable, but creating too much garbage causes performance problems. To avoid an out of memory situation, the runtime will trigger a collection pretty much any time it feels like it, and when this happens it can cause a frame rate dip. If you're creating TONS of garbage, you might see a significant impact on your frame rate.
How do you avoid creating unnecessary garbage? Watch for things that allocate memory.
For example: I have this book, O'Reilly Learning XNA 3.0 by Aaron Reed. It contains code examples like this:
spriteBatch.Draw(skullTexture, new Vector2(100, 100), new Rectangle(skullCurrentFrame.X * skullFrameSize.X, skullCurrentFrame.Y * skullFrameSize.Y, skullFrameSize.X, skullFrameSize.Y), Color.White, 0, Vector2.Zero, 1, SpriteEffects.None, 0);I call a garbage collector foul on that. Every time you draw that texture with that line of code, you're allocating a new Vector2 object and a new Rectangle object that both get used once by Draw() and then they become garbage.Sure, I hear you say, both Vector2's and Rectangles are tiny. What harm could they do?
Well, how many of these things might you have on the screen at once? 10? 50? What's your frame rate again? Sure maybe they're only 4 or 8 bytes each (probably larger), but 8 bytes * 50 sprites * 60 fps = 24 KB per second, 1.4 MB per minute. From just one errant line of garbage-producing code.
For that reason I recommend you create copies of each of these work variables you might need. Create new copies in your Initialize() function, and every time you use one, just set its values to what you need and pass it into the function.
I should point out that references to static class members, like Color.White, Vector2.Zero, and SpriteEffects.None don't allocate any memory and are perfectly garbage-free.
Every string variable is a reference variable. You won't be passing in any buffers of characters. Unfortunately, every objecct of type String is untouchable once created. You can't go in and monkey with the characters. To get around this there's another class called StringBuilder, but more on that in a second.
You would be right to expect that, if you want to create a String object, you have to instantiate one with stringVariable = new String("blah blah");. You'd be wrong -- the language has made the syntax for strings different, as a kind of special case to make C programmers feel at home.
"The quick brown fox" -- that is a constructor for an object of type String, and it's equal to a reference that points to a string object containing the text. It's not equal to the text itself.
Because string is a class, each string object you create has methods built in. It also supports binary operands like + for string concatenation.
Unfortunately, every operation you do with a String creates garbage. That's why you sometimes use StringBuilder, which I will describe shortly.
What's the solution? Class StringBuilder. First an aside: To use Class StringBuilder in your program, you need to add another using statement to the top of your .cs file.
Create a StringBuilder object, and most of the operations you're used to will stop creating garbage. In most cases you can use a StringBuilder just like you use a String. Don't use StringBuilder unnecessarily though.
using System.Text; . . . StringBuilder sb = new StringBuilder(); . . . sb.Length = 0; //blanks out the current string without creating garbage sb.Append("The "); sb.Append("quick "); sb.Append("brown "); sb.Append("fox");If you have text that never changes, or changes only occasionally, allocate a String object and reuse that object every time. If you have text that changes every frame, consider a StringBuilder object.
Some examples, that both show how strings work AND shows how to avoid garbage: Figure 1: This code works but it creates lots of garbage.
int CurrentScore; int LastScore = -1; //assuming you can never really have a negative score String strScore = "Score: "; String strFullScoreText; . . . if (CurrentScore != LastScore) { strFullScoreText = strScore + CurrentScore; LastScore = CurrentScore; } . . . spriteBatch.DrawString(myScoreFont, strFullScoreText, scorePosition, Color.White); //Figure 2Figure 2: This is a little more work but it avoids creating so much garbage every frame. The only thing it does that creates garbage only happens once each time the score changes, not once every frame.using System.Text; . . . int CurrentScore; int LastScore = -1; String strScore = "Score: "; String strFullScoreText; StringBuilder sbFullScore = new StringBuilder(30); //pre-allocate internal storage for 30 characters // (but you can fill it with more than 30, paying a performance penalty) . . . if (CurrentScore != LastScore) { sbFullScore.Length = 0; sbFullScore.Append(strScore); sbFullScore.Append(CurrentScore); strFullScoreText = sbFullScore.ToString(); LastScore = CurrentScore; } . . . spriteBatch.DrawString(myScoreFont, strFullScoreText, scorePosition, Color.White); //Figure 3Figure 3: OK maybe that's a little overboard, since you're only adding two strings together. If you were instead doing a huge bunch of string processing, a StringBuilder could save you some memory.Finally, strings don't accept < and > for alphabetic comparison. Use the .CompareTo() method instead. Like, if (string1.CompareTo(string2) > 0) then string1 > string2.
Arrays are different in C#, but not extremely so. Arrays are reference variables as well, so there's a separation between the type that describes the object and the instance that holds the storage space in memory. This creates a reference variable that is supposed to point to a one-dimensional array of ints. Right now it points to nothing. No actual array exists. OK now some memory has been allocated. myIntArray[0] means something now.
int i; for (i = 0; i < 100; i++) { myIntArray = new int[262144]; }WAIT WHAT ARE YOU I DONT EVENYes this does what you might be afraid it does. One hundred times it creates a massive new array, and chucks the old one into the garbage collector. (If you're just doing this to get even for the previous two sections . . . OK I guess I had it coming. )
So needless to say, don't allocate a new array every time through your Update() or Draw() loop. Create one, initialize it in Initialize(), and reuse it.
So it's best to do this:
You can also populate an array as you create it.
int[] myIntArray = new int[] {0, 1, 2, 3, 4, 5};You can do multidimensional arrays, but don't think you can be all cavalier about how the array is structured like in C.
int[,] myArray2 = new int[32,32]; myArray2[0,0] = 0; // myArray2[32,32] = 0; //array out of bounds int[][] myJaggedArray = new int[32][]; //this is something different and WEIRD. // Essentially this is an array of 32 one-dimensional-array reference variables. // myJaggedArray[0][0] = 0; //Not so fast there! myJaggedArray[0] is itself an uninitialized array. myJaggedArray[0] = new int[3]; //now myJaggedArray[0] is an array, but myJaggedArray[1] is uninitialized. myJaggedArray[0][0] = 0; //now this is fine. myJaggedArray[0][1] = 0; //this is fine too. // myJaggedArray[1][0] = 0; //Not so fast! myJaggedArray[1] = new int[32768]; //now myJaggedArray[0][n] works for values of n from n=0 to n=3. //but myJaggedArray[1][n] works for values of n from n=0 to n=32767. //Weird huh?An Array is a type of class called a collection. There are other collection classes out there, like List and Queue and Stack and Dictionary. I'll cover these later if there's interest.
Just so the syntax isn't foreign, I'll show how List<> works.
List is one of these classes that contains all kinds of logic for handling data, but no preconceived notion of what data you're going to be storing. All of List's functions are defined in terms of some type, which you have to supply inside <> brackets.
List<int> is a list of ints. List<string> is a list of strings. Etc.
List<int> myints = new List<int>(); myints.Add(3); myints.Add(91); // myints.Add("c-c-c-combo breaker!"); // compilation will fail because the types don't match. if (myints.Count == 2) { /*true*/ } myints.Clear(); // removes all elements from the list . . . //suppose I now add a ton of ints to the list. if (myints.Contains(42)) { /* true if there's a 42 anywhere in the list */ } foreach (int i in myints) { //if myints is empty, this section never gets executed //otherwise this is called once, with my iterator (i in this case) set to each value in the List. }That <> syntax is important, as it's used anywhere a class just defines some logic for handling data in a certain way, but doesn't necessarily care what kind of data you want to use it for.
Sure a book on C# will teach you ALL about EventHandler objects, how to create your own events, dispatch events when they happen, and let programs register their functions to be called when those events happen. You don't need to know all that -- yet.
A class might have an Event object. For example the GameWindow object has an event called ClientSizeChanged and an event called ScreenDeviceNameChanged. Each of those events represents a kind of internal list of functions XNA should call when that event happens. You can build your own function and add it to that event, so your function gets executed when that event happens.
Events don't just call your function when they happen. They might also pass in some data of a certain format. When no such data is needed, like with ClientSizeChanged, you just create a new EventHandler(). When there IS data to pass back, you need to create an EventHandler for a specific type in <> brackets.
Here's some examples. Suppose you have a windowed application on PC, and you want the user to be able to resize it whenever they like.
GraphicsDeviceManager graphics; Rectangle myWindow = new Rectangle(); . . . void MyWindowResizingHandler (object sender, EventArgs e) { myWindow.X = graphics.GraphicsDevice.Viewport.X; myWindow.Y = graphics.GraphicsDevice.Viewport.Y; myWindow.Width = graphics.GraphicsDevice.Viewport.Width; myWindow.Height = graphics.GraphicsDevice.Viewport.Height; } . . . public Game1() { . . . this.Window.AllowUserResizing = true; this.Window.ClientSizeChanged += new EventHandler(MyWindowResizingHandler); }+= is a weird operator to see here. Usually you use it to increment integers, like PlayerScore += 100; to add 100 to whatever the score is. With events, you use += to "add" your event handler to the list of handlers for that event.That EventArgs data structure is important. Sometimes. This particular event doesn't really try to send any event data back to the handler. "The window was resized" is all it's really saying. So because it's an EventHandler it MUST contain an EventArgs argument, but EventArgs only has one member, called Empty. You can guess why they named it Empty.
Some events DO have to send some data back. These require some different syntax, so the EventHandler knows it's supposed to be dealing with this different kind of SomethingEventArgs.
In this example we're handling a multiplayer event where a gamer just joined the session. Our EventHandler won't be getting back the useless and empty EventArgs structure, it'll be getting a GamerJoinedEventArgs structure that actually has some data in it.
// adapted from XNA online help, "How To: Manage Players Joining and Leaving the Game" void MyFunctionForWhenGamersJoin(object sender, GamerJoinedEventArgs e) { e.Gamer.Tag = //this is not the gamertag -- it's for you to store custom data in, to keep track of things new Tank(Content, GraphicsDevice.PresentationParameters); } . . . session.GamerJoined += new EventHandler<GamerJoinedEventArgs>(MyFunctionForWhenGamersJoin);Now whenever a new gamer joines your game session, your MyFunctionForWhenGamersJoin() function (or whatever you want to name it) gets called, and it gets a reference to a GamerJoinedEventArgs object, which you can read to get data about the event that just happened.To summarize, if you want to handle an event, do this:
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; using Microsoft.Xna.Framework.Net; using Microsoft.Xna.Framework.Storage; namespace PAForumsExampleOne { public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; // **** 1 **** public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; // **** 2 **** } protected override void Initialize() { // **** 3 **** base.Initialize(); } protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); // **** 4 **** } protected override void UnloadContent() { // **** 5 **** } protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); // **** 6 **** base.Update(gameTime); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); // **** 7 **** base.Draw(gameTime); } } }I put a **** numbered **** comment in each place where you're going to want to insert some code. Just as in the concept section, I'll explain what each part will do.- This is where class-level global variables go. Your CS professor isn't watching -- as long as you keep the project simple, feel free to implement most of your game logic with globals. Two very important globals are created for you: a GraphicsDeviceManager, which lets you read properties of the graphics device, and a SpriteBatch, which helps you draw sprites to the screen in 2D.
- This is your class constructor, where you will create and register other game components and services. For example, if you add Components.Add(new GamerServicesComponent(this)); to the end of this function, you get Games For Windows Live and/or XBOX Live functionality added to your game. By default this creates a GraphicsDeviceManager instance, and sets the current directory for all content loads to the "Content" folder you see in the solution explorer.
- This is half of where you do most of your game initialization -- specifically the half that doesn't touch the graphics card or use graphical resources. When you're done, leave the call to base.Initialize() in there so any other game components also get their chance to initialize.
- This is the other half of your initialization -- the graphical half. By default your lone SpriteBatch object gets created, and then you get to load game content from your Content folder in solution explorer into various content-like class variables you previously created.
- I guess you unload content here, but I never use this. LOL NOOB I guess. Sorry.
- Here's the non-graphical half of the main game loop, where most of your custom code will go! You get a GameTime object which you can read to find out how long the game has been running, and you can read input and do math and move things and check for collisions and otherwise make the game work. By default it also checks for the Back button on a GamePad and exits when Back is pressed, but this code does no harm if you have no GamePad. When you're done call base.Update(gameTime); so any other game components (that are enabled) get to update themselves too.
- Here's the graphical half of the main game loop. You can leave the Clear() function in there or remove it, either way. Most likely you'll be adding calls to spriteBatch.Begin(), lots of spriteBatch.Draw(), and then spriteBatch.End(). Then when you're done call base.Draw(gameTime); so any other enabled game components (that are visible) get to draw themselves too.
When adding variables for your game logic, be careful about where you put them.If you add them to just the functions that need them, you'll create a lot of garbage and anger the garbage collector gods, who will consume your frame rate.
For example:
. . . in section 6 . . . (replacing the entire function actually) protected override void Update(GameTime gameTime) { int i = 0; int[] testArray = new int[1000]; . . . for (i = 0; i < 1000; i++) { // do stuff with testArray } Array.Sort(testArray); . . . base.Update(gameTime); } //Figure 1Figure 1: Please don't do that, as it creates a new 1000-int array every time Update is called. Unless you told it to do something different, XNA is probably going to try to call your Update function 60 times a second. That means you are creating and throwing away 60,000 ints every second. Poor garbage collector.. . . in section 1 . . . int i = 0; int[] testArray = new int[1000]; . . . in section 6 . . . for (i = 0; i < 1000; i++) { // do stuff with testArray } Array.Sort(testArray);Even once you understand much of the above, it can still be confusing remembering where to put things once you stop following examples and start striking out on your own. So here's those same XNA recipes from the concept overview again, but this time with code.
Prepare to draw sprites to the screen:
Actually draw to the screen in your Draw method:
- SpriteBatch.Draw() is a heavily-overloaded, super-flexible function. You can use a simple version that just makes you give a texture, a screen coordinate for the upper left corner, and a color. We'll be using a slightly more complex version that has you fill in a rectangle with width and height as well. Note that we still have to specify the upper left corner. If we specify that corner so it's touching the right edge of the screen, pretty much the entire texture will be off the screen and invisible. So plan some math for figuring out where on the screen you want this texture to go.
- For this example, I'll be drawing an old-school life counter that takes the number of lives left, LivesLeft, and draws that many Player textures on the bottom right of the screen.
- All of this code goes in your Draw() method. Somewhere before base.Draw(gameTime), tell your spriteBatch to begin.
- spriteBatch.Begin();
- For each texture you're going to draw, do some math for where the texture is supposed to go on the screen, as well as how wide and how tall it should be. Store the results in your work variable. (Mine is called drawpos, like draw position.)
- for (i = 0; i < LivesLeft; i++)
- {
- drawpos.Y = screenSize.Y + screenSize.Height * 29 / 30;
- drawpos.X = screenSize.X + screenSize.Width * (29 - i) / 30;
- drawpos.Width = screenSize.Width / 30;
- drawpos.Height = screenSize.Height / 30;
- // DRAW STATEMENT GOES HERE
- }
- Go ahead and call Draw() with your texture, your destination rectangle, and Color.White.
- spriteBatch.Draw(PlayerTexture, drawpos, Color.White);
- When you're done with ALL draw operations EVERYWHERE (for this current pass of the Draw() function that is) go ahead and call SpriteBatch.End()
- spriteBatch.End();
Source up to this point: http://mspencer.net/PA_XNA/PAForumsExampleOne_v1.zipRead the keyboard so you can see which keys are 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 the space above the class constructor, add TWO KeyboardState objects.
- KeyboardState newKeyState, oldKeyState;
- In the Initialize function, store a dummy keyboard state in oldKeyState.
- oldKeyState = Keyboard.GetState();
- In the Update function, get a copy of the current keyboard state and place in newKeyState.
- newKeyState = Keyboard.GetState();
- Next, still in the Update function, start testing if keys are pressed and DO THINGS. The logic is: if a key is DOWN in the new state but UP in the old state, it was pressed JUST NOW. Likewise if a key is UP in the new state but DOWN in the old state, it was released JUST NOW.
- if (newKeyState.IsKeyDown(Keys.OemPlus) && oldKeyState.IsKeyUp(Keys.OemPlus)) LivesLeft += 1;
- if (newKeyState.IsKeyDown(Keys.OemMinus) && oldKeyState.IsKeyUp(Keys.OemMinus)) LivesLeft -= 1;
- if (LivesLeft < 0) LivesLeft = 0;
- Finally, still in the Update function, we're done with all of our keys testing, so copy the newKeyState into the oldKeyState. Yes this creates a garbage KeyboardState object, but there's no way around that unfortunately. This way the current keyboard state gets used as the old keyboard state next time, so we can compare the next update against the current update.
- oldKeyState = newKeyState;
Source up to this point: http://mspencer.net/PA_XNA/PAForumsExampleOne_v2.zipXBL Michael Spencer || Wii 6007 6812 1605 7315 || PSN MichaelSpencerJr || Steam Michael_Spencer || Ham NOØK
QRZ || My last known GPS coordinates: FindU or APRS.fi (Car antenna feed line busted -- no ham radio for me X__X )
I'd love to get some feedback though, as I'm scared I'm jumping all around different skill levels. I might be in danger of confusing and driving away the very people I'm trying to help.
Maybe I'm harping on the garbage collector a little too much. I figure that whole lesson does double duty, helping people better understand how objects are created and used. Maybe I'm just confusing people though.
I don't really have the real POINT of the thread -- here's some recipes for adding your own code to a blank project -- done yet, but so far what do people think?
XBL Michael Spencer || Wii 6007 6812 1605 7315 || PSN MichaelSpencerJr || Steam Michael_Spencer || Ham NOØK
QRZ || My last known GPS coordinates: FindU or APRS.fi (Car antenna feed line busted -- no ham radio for me X__X )
Robots Will Be Our Superiors (Blog)
http://michaelhermes.com
I've never done anything with managed directx, but I'll look into it.
XBL Michael Spencer || Wii 6007 6812 1605 7315 || PSN MichaelSpencerJr || Steam Michael_Spencer || Ham NOØK
QRZ || My last known GPS coordinates: FindU or APRS.fi (Car antenna feed line busted -- no ham radio for me X__X )
:^: that'll take your op from the "best" I've read so far, to "fucking amazing" in terms of XNA dev.
No idea when at the moment though as I have a lot of work to catch up on. :lol:
The PhalLounge :: Chat board for Phalla discussion and Secret Santas :: PhallAX 2013
Critical Failures IRC! :: #CriticalFailures and #mafia on irc.slashnet.org
I just took a break to start reading about MDX. Wikipedia says MDX 2.0 was cancelled while still in development, superceded by XNA. Maybe there could be some info about MDX 1.1, but I don't know if it makes sense.
Or did I find the wrong thing? Were you referring to something else?
XBL Michael Spencer || Wii 6007 6812 1605 7315 || PSN MichaelSpencerJr || Steam Michael_Spencer || Ham NOØK
QRZ || My last known GPS coordinates: FindU or APRS.fi (Car antenna feed line busted -- no ham radio for me X__X )
Let me look it up.
Edit: Nevermind, yeah XNA is MDX 2.0 with some more fluff added on top of it. I didn't realize that all the classes got rewritten into the c# and I thought there was a lot of extra steps to do anything other than "behind the scenes" game logic with XNA.
Man I wish I had any skill at art. Blender was so fun to mess around with. Even bought me the Blender book a few years back.
*Though I understand calling it such as for the purpose of learning simple XNA stuff you won't really need any of it's other features.
OK, I didn't expect it to take a DAY AND A HALF but both sections of the OP are done.
I think I already said this, but my hope is that this isn't just a document plus comments, but a living resource. People who are NOT ME can say "I want to play with making a game do THIS" and start tinkering and zipping up daily builds and posting about them.
I really don't want to be the ONLY person doing this, so I'll start. I'm curious about how to make bullet hell games, or at least aiming algorithms for slow-moving bullets or missiles. So I want a game where the player moves around and dodges a stream of bullets, so I can play with bullet aiming algorithms.
So I think I'll start by making a player that slides around the screen, make a little stationary ball at the upper right corner that shoots things at a fixed 45 degree angle with no aiming intelligence, make a little bullet thing, and see if I can make everything animate at once. And then detect collisions.
XBL Michael Spencer || Wii 6007 6812 1605 7315 || PSN MichaelSpencerJr || Steam Michael_Spencer || Ham NOØK
QRZ || My last known GPS coordinates: FindU or APRS.fi (Car antenna feed line busted -- no ham radio for me X__X )
This is what I was going to work on, a tone-downed version of a shooter game concept I have. :lol:
The PhalLounge :: Chat board for Phalla discussion and Secret Santas :: PhallAX 2013
Critical Failures IRC! :: #CriticalFailures and #mafia on irc.slashnet.org
You can often create a basic one just by having a bullet spread from several different sources that are moving and/or rotating
The PhalLounge :: Chat board for Phalla discussion and Secret Santas :: PhallAX 2013
Critical Failures IRC! :: #CriticalFailures and #mafia on irc.slashnet.org
Should... do... work... first...
The PhalLounge :: Chat board for Phalla discussion and Secret Santas :: PhallAX 2013
Critical Failures IRC! :: #CriticalFailures and #mafia on irc.slashnet.org
Fix'd for the game my artless self would make.
Quit looking down on us circle folk.
The PhalLounge :: Chat board for Phalla discussion and Secret Santas :: PhallAX 2013
Critical Failures IRC! :: #CriticalFailures and #mafia on irc.slashnet.org
How can you have too much math?
Using the maths = good.
Hand calculating the maths = oh god shoot me.
The PhalLounge :: Chat board for Phalla discussion and Secret Santas :: PhallAX 2013
Critical Failures IRC! :: #CriticalFailures and #mafia on irc.slashnet.org
"Man, if only there was some way to compute all of these values... some kind of processor unit, perhaps centrally located, that could do all this for me, as if I had a program of some kind. Oh well."
Robots Will Be Our Superiors (Blog)
http://michaelhermes.com
I'm going to see what I can whip up this weekend.
And yeah, it's probably because I just got my graphic calculator for my calculus class, for my senior year. Good ol' Basic.
One of the more serious features of a BulletHell game is LOTS of bullets. So I'm going to need a bullet manager, so I can just "fire a bullet" by adding an object to a list that someone else is managing, and then for my Update call I let the bullet manager update the position of all its bullets; and for my Draw call I get the list of bullets and draw them all.
I create a new project called BulletHell.
I'm going to need a GameComponent, so I'll right-click "BulletHell" in Solution Explorer and choose Add -> Component. In the dialog that appears I'll choose XNA Game Studio 3.1 from the list on the left, choose the icon for GameComponent on the right, and name it BulletManager.cs.
Slow-moving bullets that deal damage on collision are a kind of universal standard -- there won't be any innovation in how these bullets fly, so I'll get this sorted first.
Above the class constructor for BulletManager I'll add a data structure for holding each bullet, and an empty LinkedList structure that contains my bullets.
public struct bullet { public Texture2D tex; public Vector2 center; public float size; public float direction; //radians public float speed; public bool active; public bullet(Texture2D tex, Vector2 center, float size, float direction, float speed, bool active) { this.tex = tex; this.center = center; this.size = size; this.direction = direction; this.speed = speed; this.active = active; } public bullet(Texture2D tex, Vector2 center, float direction) { this.tex = tex; this.center = center; this.size = 4.0f; this.direction = direction; this.speed = 20.0f; this.active = true; } } public LinkedList<bullet> BulletList = new LinkedList<bullet>(); public LinkedListNode<bullet> bulletNode;Yes, in C# structs can have constructor functions. Weird huh? I don't know if I'll even need this, I just thought it was cool to share.Also instead of tracking speed as a number of X and Y coordinates to move per update, I'm using radians and a speed, to force myself to use MathHelper and trig functions.
Anyway, I'll need a function so someone can add a new bullet, I'll need a function for collision testing, and I'll need a function for Update simulating the passage of time, making each bullet fly a little farther and deleting the ones that leave the screen completely.
public void FireBullet(Texture2D tex, Vector2 center, float size, float direction, float speed) { BulletList.Add(new bullet(tex, center, size, direction, speed, true)); }That was easy.For hit detection I need some new variables up above the class constructor:
Vector2 positionOffset; int HitCounter; bullet bill; //yes I'm a dork bool listDone = false;And then the function.
public int TestCollision(Vector2 targetPosition, float targetSize) { HitCounter = 0; if (BulletList.Count > 0) { listDone = false; bulletNode = BulletList.First; while (!listDone) { bill = bulletNode.Value; if (bulletNode == BulletList.Last) listDone = true; positionOffset.X = MathHelper.Distance(bill.center.X, targetPosition.X); positionOffset.Y = MathHelper.Distance(bill.center.Y, targetPosition.Y); if ((positionOffset.X * positionOffset.X + positionOffset.Y * positionOffset.Y) < bill.size + targetSize) { //HIT! HitCounter++; //increment the hit counter BulletList.Remove(bulletNode); } if (!listDone) bulletNode = bulletNode.Next; } } return HitCounter; }Last I'll need my update function to move each bullet, and then test to see if it's off the screen, and remove it if it is.
I need one more class-level variable, public so it can be initialized later: And then my new Update function:
public override void Update(GameTime gameTime) { // TODO: Add your update code here if (BulletList.Count > 0) { listDone = false; bulletNode = BulletList.First; while (!listDone) { bill = bulletNode.Value; if (bulletNode == BulletList.Last) listDone = true; if (bill.active) { bill.center.X += bill.speed * (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000f * (float)Math.Sin(bill.direction); bill.center.Y -= bill.speed * (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000f * (float)Math.Cos(bill.direction); bulletNode.Value = bill; } if ((bill.center.X + bill.size < screen.X) || (bill.center.Y + bill.size < screen.Y) || (bill.center.X - bill.size > screen.X + screen.Width) || (bill.center.Y - bill.size > screen.Y + screen.Height)) { if (!listDone) { bulletNode = bulletNode.Next; BulletList.Remove(bulletNode.Previous); } else BulletList.Remove(bulletNode); } else if (!listDone) bulletNode = bulletNode.Next; } } base.Update(gameTime); }Now I think my BulletManager is done, so I'll go make a basic game that just fires some bullets.
I right-clicked Content and chose new bitmap, named Bullet1.bmp. I set the background color to purple (which is transparent by default) and created a white circle that fills up the whole square. OK I guess I can make it fancy. I'll divide it into quarters and make the top left and bottom right quarters red.
I'll follow my recipe above for drawing sprites, so I'll need a Texture2D and two Rectangles. I need an instance of my BulletManager as well, and I'll need some values to hold where my cannon is, what direction I'm firing, how often I'm firing, etc. So above the class constructor:
Texture2D BulletTexture; Vector2 cannon = new Vector2(); float fireDirection = 0f - MathHelper.PiOver2 - MathHelper.PiOver4; //0 is north. positive pi over 2 is east, negative pi over 2 is west. double LastFireMS = 0; int fireDelay = 200; Rectangle screen = new Rectangle(); Rectangle drawpos = new Rectangle(); BulletManager myBulletManager;In my class constructor I need to register my BulletManager as a game component. So after the Content.RootDirectory() thing in my constructor I add:
public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; myBulletManager = new BulletManager(this); Components.Add(myBulletManager); }Initialize can be left alone.
In LoadContent I need to load my one and only sprite, read the screen size, tell my BulletManager about the screen size (so it knows when a bullet has left the screen and can be deleted), and then position my cannon wherever the top right of the screen is.
protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); BulletTexture = Content.Load<Texture2D>("Bullet1"); screen.X = graphics.GraphicsDevice.Viewport.X; screen.Y = graphics.GraphicsDevice.Viewport.Y; screen.Width = graphics.GraphicsDevice.Viewport.Width; screen.Height = graphics.GraphicsDevice.Viewport.Height; myBulletManager.screen = screen; cannon.X = screen.X + screen.Width; cannon.Y = screen.Y; // TODO: use this.Content to load your game content here }UnloadContent is still blank -- nothing to unload that isn't already destroyed when the game exits.
For Update, all I'm doing is checking the system clock, and remembering a timestamp for the last time I fired a bullet. Have enough milliseconds passed since my last bullet that I'm due for another one? (Current time minus last-fired time is greater than my fireDelay?) Then adjust my last-fired time to current, and fire another bullet!
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 if (gameTime.TotalGameTime.TotalMilliseconds - LastFireMS > fireDelay) { LastFireMS += fireDelay; myBulletManager.FireBullet(BulletTexture, cannon, 10f, fireDirection, 200f); } base.Update(gameTime); }Last my Draw method. I'll reach into the bullet manager's bullet list and count the bullets myself, drawing each one.
protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.Black); //easier to see if the background is black? Maybe? // TODO: Add your drawing code here spriteBatch.Begin(); foreach (BulletManager.bullet bill in myBulletManager.BulletList) { if (bill.active) { drawpos.X = (int)(bill.center.X - bill.size); drawpos.Y = (int)(bill.center.Y - bill.size); drawpos.Width = (int)(2 * bill.size); drawpos.Height = (int)(2 * bill.size); spriteBatch.Draw(bill.tex, drawpos, Color.White); } } spriteBatch.End(); base.Draw(gameTime); }Code so far: http://mspencer.net/PA_XNA/BulletHell_v1.zip
There's no game to it, just a stream of bullets. But the framework I built should let me use any bullet size and speed, and even different textures. Since they're balls maybe I should make them spin. Maybe I should add some extra textures -- a sawblade, that 4chan smiley, a soccer ball, a baseball, etc.
XBL Michael Spencer || Wii 6007 6812 1605 7315 || PSN MichaelSpencerJr || Steam Michael_Spencer || Ham NOØK
QRZ || My last known GPS coordinates: FindU or APRS.fi (Car antenna feed line busted -- no ham radio for me X__X )
public int TestCollision(Vector2 targetPosition, float targetSize) { HitCounter = 0; if (BulletList.Count > 0) { listDone = false; bulletNode = BulletList.First; while (!listDone) { bill = bulletNode.Value; if (bulletNode == BulletList.Last) listDone = true; positionOffset.X = MathHelper.Distance(bill.center.X, targetPosition.X); positionOffset.Y = MathHelper.Distance(bill.center.Y, targetPosition.Y); if ((positionOffset.X * positionOffset.X + positionOffset.Y * positionOffset.Y) < (bill.size + targetSize) * (bill.size + targetSize)) { //HIT! HitCounter++; //increment the hit counter if (!listDone) { bulletNode = bulletNode.Next; BulletList.Remove(bulletNode.Previous); } else BulletList.Remove(bulletNode); } else if (!listDone) bulletNode = bulletNode.Next; } } return HitCounter; }Alright I want to add a player and make the player controllable, and successfully detect hits and make bullets disappear when there's a hit. I think I can do all this just in Game1.cs.
I need another Texture2D for the player, and I imported the stupid tomato-looking minotaur bmp from Minotaur and renamed it Player.bmp. Also created another 48x48 bmp called Damage.bmp, colored all red. I'll need another Texture2D for the damage meter. I also need a Vector2 for the player's position, and let's say a float for the player damage amount, and another for the player's movement speed. Plus two keyboardstates.
Texture2D PlayerTexture; Texture2D DamageTexture; Vector2 playerPosition; Vector2 playerMove; float playerDamage = 0f; float playerSpeed = 5f; float playerSize = 24f; KeyboardState newKeyState, oldKeyState;Also I'll load the player texture, and position the player on the left edge of the screen, we'll say halfway down the left edge.
protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); BulletTexture = Content.Load<Texture2D>("Bullet1"); PlayerTexture = Content.Load<Texture2D>("Player"); DamageTexture = Content.Load<Texture2D>("Damage"); screen.X = graphics.GraphicsDevice.Viewport.X; screen.Y = graphics.GraphicsDevice.Viewport.Y; screen.Width = graphics.GraphicsDevice.Viewport.Width; screen.Height = graphics.GraphicsDevice.Viewport.Height; myBulletManager.screen = screen; cannon.X = screen.X + screen.Width; cannon.Y = screen.Y; playerPosition.X = screen.X + (int)playerSize; playerPosition.Y = screen.Y + screen.Height / 2; // TODO: use this.Content to load your game content here }oh and I'd better add oldKeyState = Keyboard.GetState(); to my initialize function.For moving, I'll get the intent of the player, check if we're trying to pass through a wall and zero out the X or Y movement as required to keep us out of the wall, check if we're moving diagonally and divide by sqrt(2), and then apply the movement.
All of this is in the Update method, just after the TODO part.
newKeyState = Keyboard.GetState(); playerMove = Vector2.Zero; if (newKeyState.IsKeyDown(Keys.W) || newKeyState.IsKeyDown(Keys.Up)) playerMove.Y = 0 - playerSpeed; if (newKeyState.IsKeyDown(Keys.A) || newKeyState.IsKeyDown(Keys.Left)) playerMove.X = 0 - playerSpeed; if (newKeyState.IsKeyDown(Keys.S) || newKeyState.IsKeyDown(Keys.Down)) playerMove.Y = playerSpeed; if (newKeyState.IsKeyDown(Keys.D) || newKeyState.IsKeyDown(Keys.Right)) playerMove.X = playerSpeed; if ((playerMove.X < 0 && playerPosition.X + playerMove.X < screen.X + playerSize) || (playerMove.X > 0 && playerPosition.X + playerMove.X > screen.X - playerSize + screen.Width)) playerMove.X = 0; if ((playerMove.Y < 0 && playerPosition.Y + playerMove.Y < screen.Y + playerSize) || (playerMove.Y > 0 && playerPosition.Y + playerMove.Y > screen.Y - playerSize + screen.Height)) playerMove.Y = 0; if (playerMove.X != 0 && playerMove.Y != 0) { playerMove.X /= (float)Math.Sqrt(2); playerMove.Y /= (float)Math.Sqrt(2); } playerPosition.X += playerMove.X; playerPosition.Y += playerMove.Y; oldKeyState = newKeyState;Then after my existing firing code I'll check for collisions. I want a damage meter that punishes for lots of constant damage, like a refilling health bar except with no no-damage-taken-in-N-seconds delay before the healing starts. Except it's a damage bar so it shrinks instead of growing. Remember that TestCollision returns the number of bullets that hit, if more than one at the same time.
playerDamage *= 0.99f; playerDamage += (float)myBulletManager.TestCollision(playerPosition, playerSize);Finally in Draw I want to draw the player, and draw the damage meter.
drawpos.X = (int)(playerPosition.X - playerSize); drawpos.Y = (int)(playerPosition.Y - playerSize); drawpos.Width = (int)(2 * playerSize); drawpos.Height = (int)(2 * playerSize); spriteBatch.Draw(PlayerTexture, drawpos, Color.White); drawpos.X = screen.X; drawpos.Y = screen.Y + 29 * screen.Height / 30; drawpos.Width = (int)((float)screen.Width * playerDamage / 11f); drawpos.Height = screen.Height / 30; spriteBatch.Draw(DamageTexture, drawpos, Color.White);Last I want to give the cannon a LITTLE intelligence. Have it aim precisely for the current location of the player, without leading the target at all.
I'll need some class-global variables above the constructor:
Vector2 aimVector = new Vector2(); float aimDistance; int aimCalcMethod; float aimAngle;Then I'll replace the if clause that shoots bullets with the following:
if (gameTime.TotalGameTime.TotalMilliseconds - LastFireMS > fireDelay) { aimVector.X = playerPosition.X - cannon.X; aimVector.Y = playerPosition.Y - cannon.Y; aimDistance = (float)Math.Sqrt((double)(aimVector.X * aimVector.X + aimVector.Y * aimVector.Y)); if (aimDistance > playerSize * 2) { //don't shoot if the player is too close. //real reason is the trig calculations might explode and throw an exception. //fake reason is the splash damage from a round hitting the target that close might blah blah blah. if (Math.Abs(aimVector.X) > Math.Abs(aimVector.Y)) //firing angle has shallowest angle along X axis, and we want to avoid arctan returning values > 1. aimCalcMethod = 0; else aimCalcMethod = 1; if (aimCalcMethod == 0) { if (aimVector.X > 0) //firing to the right, need angle above or below positive X axis aimAngle = 0 - (float)Math.Atan((double)(aimVector.X / aimVector.Y)); else aimAngle = 0 - (float)Math.Atan((double)(aimVector.X / aimVector.Y)) - MathHelper.Pi; } else { if (aimVector.Y > 0) //firing up, need angle right or left of positive Y axis aimAngle = (float)Math.Atan((double)(aimVector.Y / aimVector.X)) - MathHelper.PiOver2; else aimAngle = (float)Math.Atan((double)(aimVector.Y / aimVector.X)) + MathHelper.PiOver2; } if ((aimVector.X + 0.01) * (aimVector.Y + 0.01) > 0) aimAngle += MathHelper.Pi; fireDirection = aimAngle; LastFireMS = gameTime.TotalGameTime.TotalMilliseconds; myBulletManager.FireBullet(BulletTexture, cannon, 10f, fireDirection, 200f); } }First, to avoid the math exploding if the player is right on top of the turret, I don't fire if the player is too close.
If I can still fire, I get a vector from the cannon to the player. I find which axis the player seems to be closest to (positive or negative X, positive or negative Y) and take the arctangent of the slope of the line relative to that axis. (I have to admit I apparently don't know what I'm going with trig -- I had to start patching things up via trial and error, which is embarrassing.)
Also I updated the firing delay so it starts counting as of the last clock tick a bullet was actually shot. Otherwise if you hover over the cannon for too long and then move away, you get a huge stream of bullets fired as fast as possible.
So it kinda works now. What I really want to do, though, is play with leading the target -- and then play with variable-speed shots and see if I can box the player in, do time-on-target barrages, that sort of thing.
XBL Michael Spencer || Wii 6007 6812 1605 7315 || PSN MichaelSpencerJr || Steam Michael_Spencer || Ham NOØK
QRZ || My last known GPS coordinates: FindU or APRS.fi (Car antenna feed line busted -- no ham radio for me X__X )
Robots Will Be Our Superiors (Blog)
http://michaelhermes.com
XBL Michael Spencer || Wii 6007 6812 1605 7315 || PSN MichaelSpencerJr || Steam Michael_Spencer || Ham NOØK
QRZ || My last known GPS coordinates: FindU or APRS.fi (Car antenna feed line busted -- no ham radio for me X__X )
Robots Will Be Our Superiors (Blog)
http://michaelhermes.com