Wednesday, June 8, 2011

XNA Game Design: ContentManager and Audio

Unlike the SpriteBatch, the ContentManager is actually a lot easier to spawn instances of, and just requires a variable we can make a static in our game1 class. In our Game1 properties, add the following;

public static GameServiceContainer services;
and in the constructor, initialize it with

services = Services;
What is a GameServiceContainer? It seems like one of those generally random variables we seem to be needing here and there (and it is). A ContentManager needs a particular service that can import and read the files it's loading.*  GameServiceContainer objects simply contain a collection of all Game.Services so that we can pass it in lieu of picking out each service from each service provider and passing that. It's like passing a box of different cookies to children in a classroom instead of asking each child what they want, running to the factory and getting it for them. Nifty huh?


Our game will have a significant number of services, most if not all of whom are default initialized in the Microsoft.Xna.Framework.Game class. These are contained in the private Services object, which is not static and we would like public static access to. With this done, from here on out we can make ContentManagers anywhere using Game1.services .
*(Contentmanagers really only need a service for loading texture files, so this is the equivalent of giving a lumberjack an ax to trim a bonsai tree. He just needs the ax in case he's suddenly asked to chop a tree, which shouldn't happen. God these metaphors are getting weird...)


Let's create a class to play audio now! This audio playing class will contain methods designed to do the following:
  • In a single line of code, take in a String that will correspond to an asset name in our "Content/Audio" folder, and play that sound at default settings (full volume, normal pitch, no pan).
  • Allow us to change the volume, pitch or pan of the sound in a single line of code.
  • Allow us to pause the sound in a single line of code.
  • Allow us to resume the sound in a single line of code.

static class Audio
{
    public static SoundEffect s_bgm;
    public static SoundEffectInstance BGM;
    //Our Audio ContentManager will be called Player
    public static ContentManager Player = 
    new ContentManager(Game1.services, "Content/Audio");
}

We see our ContentManager is initialized, but hold on a second; there's a class... called SoundEffectInstance? What's the difference between a SoundEffect and a SoundEffectInstance? Is an instance of a SoundEffect a SoundEffectInstance? What is this I don't even-


Chillax. A SoundEffect is the direct output from the ContentManager. SoundEffect objects contain mostly metadata and no methods actually related to playing the sound. This metadata includes length, channels of audio, kbps, and other things related to sound we wouldn't want to touch with a ten foot pole. SoundEffectInstance, on the other hand, contains all our fun methods for playing, pausing, resuming, looping, paning, pitching and general tinkering with our sound. SoundEffect has a method called CreateInstance() which, as you can imagine, returns a SoundEffectInstance object. With our content manager, we can load the SoundEffect, and then use CreateInstance() to create our playable SoundEffectInstance. It's just a two step process.


So, here is our method to take in the string and play the audio:



public static void bgm_play(string name)
{
    if(BGM != null) BGM.Stop(); 
    //Without this the current sound would not stop playing
    s_bgm = Player.Load<SoundEffect>(@"BGM\" + name);
    //The @ before the string indicates that escape codes 
    //should be ignored. It should be noted that both / and \
    //would be read as a sign to look into that folder. 
    //Therefore, ("BGM/" + name) would work just as well.
    BGM = s_bgm.CreateInstance();
    BGM.IsLooped = true;
    BGM.Play();
}

That's all there is to it. From here on out, we just have to say Audio.BGM.Pause();,  Audio.BGM.Stop()Audio.BGM.Volume = x, etc. just to change things. Everything from one line of code- that's how to design a game.


Still, there is a slight problem; with this design, we only get one channel to play sound on. I named our SoundEffect s_bgm and our SoundEffectInstance BGM. BGM is designed to be background music that will loop upon completion (notice the BGM.IsLooped = true;). But because this is our only channel of audio, we would not be able to play other noises, such as spoken voices, running water, explosions off in the distance and so forth. As such, this is the homework:


Homework: In your Audio folder, create four new subfolders named BGM, BGS, SE, and ME. Initialize and create the variables for these four audio channels (BGM is already there), and create methods so that following occurs:
  • Play_BGM() is looped (Background Music)
  • Play_BGS() is looped and will play at 80% volume. (Background Sound)
  • Play_SE() is not looped, will play at 80% volume (Sound Effect)
  • Play_ME() is not looped, and when called will pause BGM and BGS. (Music Effect)
  • Each method will look for it's audio file in the respective audio subfolder 

Tuesday, June 7, 2011

Wii want to play with u


Nintendo's new console is a controller with a touch screen, designed specifically not to compete with the Apple Ipad, and cannot be taken out of the living room. Still no love for indie and homebrew developers, and the 3DS is still freakin spectacular.



Of course it will make a boatload of money, and it's far better than Microsofts conference (don't look it up. It was just shameful. Their big announcement was Halo 4, and without bungie it's all up in the air)


But back to Nintendo: I have a love/hate relationship with this company. Their mentality regarding their own profound success is something along the lines of "Gosh! where did all this money come from?" and yet they push aside general purpose ad open development for their own proprietary hardware and software. You want to make a game for Nintendo? Tough stuff. You don't work for a game development company. You're just a curious kid. Nintendo would rather you play their game's then look into them yourself. It's the kind of evil that I'd expect from Apple or Disney.


This thing will make boatloads of money.

Sunday, June 5, 2011

XNA Game Design: Explaining the Spritebatch

Note: to make reading this easier, remember SpriteBatch refers to the SpriteBatch class and spriteBatch refers to our spriteBatch object located in our Game1 class.

In XNA programming, we use the spriteBatch object used to draw items on-screen. Commands to draw items require that they already been loaded into the game through a ContentManager object. This is a fair request.

By default, we already have a ContentManager and a SpriteBatch object ready for use in our Game1 class, respectively named Content and spriteBatch. These are good and ready to use right off the bat, but we are not really given any instructions on how to use them in the first place.

Think of the spriteBatch as a go-between that communicates with the system's GPU to draw our 2D images onscreen. We really only should need one, but because the SpriteBatch instance we have lives in the Game1 class, it would make sense to create another one to work with in our other classes.

Yeah, my art is crap. And in case you wondering, The Don is the GPU, and he'd probably be confined to a wheelchair and painting all over the place. Go figure.

Here are where the problems start coming up. In order to create an instance of a SpriteBatch, we need a graphicsdevice object. A graphicsdevice object contains most of the metadata used to interact with the system's GPU, and cannot be created from scratch. By default, Microsoft.Xna.Framework.Game contains the only initialized graphicsdevice we can use (which is not static), meaning all spritebatch objects must be spawned from the game1 class.

On top of that, all commands to draw sprites must be done between a spriteBatch.begin() and a spriteBatch.end() command. These commands wake up the spritebatch and give them the orders we want sent to the GPU. Because of this, all Draw commands for our entire game must originate from the Game1.Draw() method, and therefore our spriteBatch simply cannot just be made static and called with Game1.spriteBatch .

In a sense, the SpriteBatch is much like Sander Cohen's muse. Calling our cursor.Draw() command is easy only because the cursor is a constant part of any game. Instances are not made of it, and it's implementation is organized and straightforward. For every other aspect of a video game, this is not practical or efficient.

This post has mostly been me complaining about how awful the SpriteBatch is, but what can we do? We will engineer around this mess when the time comes, but for now we just want to display our freakin mouse cursor.


Assuming you did the homework (you are supposed to the homework!) you should have found out on your own how to import assets into the game. Now we are going to load them into our game. I'm using this image to represent my cursor, and I have named it Arrow.



Classy.


In the Game1.LoadContent() method, we will use the generic method 
Content
.Load<>() method to load our assets into our game. This method is generic, so we will be using it to import images, audio, and most other content into our game.


Just to refresh your memory, a generic method returns the type specified in carrot brackets. In this case, our mouse cursor image is represented with Texture2D. Our code will therefore look like this: 



// TODO: use this.Content to load your game content here
Cursor.Texture = this.Content.Load<Texture2D>("Graphics/Arrow");

 Not too shabby, no? Our Cursor.Texture no longer equals null, and we can now finalize our Cursor.Draw() method. Go into your Cursor class and add this sole line of code into the method (remember the Draw method takes in a spritebatch argument)


spriteBatch.Draw(Texture, Pos, Color.White);


You see why we took in the spriteBatch as a parameter now? Technically, the draw command is within the confines of .begin() and .end() because Cursor.Draw is called between those two lines in Game1. Passing the spriteBatch by parameter is actually the best solution to comply by it's picky standards. The reason we had to use a Vector2 for Pos is because the spriteBatch.Draw() method only takes in coordinate parameters as Vector2 values. This makes sense for everything... except mouse cursor coordinates. Ah well, no harm done in casting a few integers to floats.


Hey. Do yourself a favor and run your game. Correct any bugs or errors it throws you, and get a finished build to run.


A hells yeah. If your windows mouse shows up alongside the game mouse, make sure IsMouseVisible = false in your Game1 constructor.


Give yourself a pat on the back. With this simple layout and knowledge, changing the mouse cursor graphic should be easy. Just remember to make things static and to load all the cursor textures at the games start. They're small images, and we don't want the game to reload /  unload every time we move over an area that would change the icon


For this tremendous feat, no homework. Next week, we will be going into (simple API) audio.

Saturday, June 4, 2011

BS Zelda - Four lost Zelda Games?!


It's not often I'll try link to anything so videogame related, but goddamn is this fascinating. A media giant like Nintendo would probably try to milk everything it had developed, yet it seems there are four games (well, technically three, but two of those are remakes, so it's only one... you'll see) of the Zelda series that have since been lost to the winds. But as we all know, nothing is ever lost thanks to the internet!



And no, this has nothing to do with the CD-i Zelda games. Just give it a watch- it's very well-made and the commentary is painfully funny.

Friday, June 3, 2011

XNA Game Design: Mouse Input (part 2)

I told you Mouse Input would be a bit more difficult.


In order to create the aforementioned method necessary to determine whether our mouse cursor is outside the confines of the game window, we need to know the dimensions of the game window. At a later point in time we will be setting these values to tangible variables so different game resolutions can be offered. In the meantime however, we actually need a value to work with.


In the Game1 class, create two new static variables. Name them whatever you want, as we will be doing away with them later. Recognize however that they will be representing our game window's width and height.
        public static readonly int wWIDTH = 640;
        public static readonly int wHEIGHT = 480;
Now, at the end of the constructor, add the following two lines.

        graphics.PreferredBackBufferHeight = wHEIGHT;
        graphics.PreferredBackBufferWidth = wWIDTH;
This essentially sets the game window to the specified dimensions. Again, the reason we want to use variables (for now) is to ensure that we have a mutable constant to refer to. Many aspects of the game will need to get the game window's width and height, and only a few will ever actually change it. An in-game change of the game window dimensions is a complex process, and for now we don't intend to ever change these dimensions (especially considering the variables we are using are only temporary). That is why we are setting their protection level to readonly.


Because wWIDTH and wHEIGHT now contain the maximum window dimensions, we can create the method for our cursor class to fix the game window mouse coordinates. This method should do the following:
  • If the Mouse.X coordinate is less than zero, the Cursor.X coordinate is set to zero.
  • If the Mouse.Y coordinate is less than zero, the Cursor.Y coordinate is set to zero.
  • If the Mouse.X coordinate is greater than the window width, the Cursor.X coordinate is set to the window width.
  • If the Mouse.Y coordinate is greater than the window height, the Cursor.Y coordinate is set to the window height.
Alright, let's get through this awful pitfall.


        private static void fix_bounds()
        {
            if (Pos.X < 0)
                Pos.X = 0;
            if (Pos.Y < 0)
                Pos.Y = 0;
            if (Pos.X > Game1.wWIDTH)
                Pos.X = Game1.wWIDTH;
            if (Pos.Y > Game1.wHEIGHT)
                Pos.Y = Game1.wHEIGHT;
        }
It's taking longer for me to explain why we need the code than to actually show it.

Stick the fix_bounds()method just at the end of our Cursor.Update()Then let's return to our Game1class to start implementing our code.

At the end of the Game1.Update() method, add the following two lines.
            Input.Update();
            Cursor.Update();
And where the Game1.Draw() method designates code be added, append the following:
            // TODO: Add your drawing code here
            spriteBatch.Begin();
            Cursor.Draw(spriteBatch);
            spriteBatch.End();
Effectively, our game now has a practical understanding of keyboard and mouse input that updates whenever the game updates. Still, we don't have a custom mouse graphic, and the call to Cursor.Draw is effectively moot considering it currently contains no code. Our Input class is now effectively operational.

And just what is going on with the spritebatch? Why did we add those two extra lines of code? What are they meant to do?

In our next lesson, these questions will all be answered, and you'll learn why the SpriteBatch and ContentManager classes are two of the most frustrating aspects of XNA 4.0 to work with, and we will begin to segue into working around them.


Yesterdays Homework Solution: There are two effective ways of determining whether our cursor is within the bounds of a rectangle object. The first involves comparing the X and Y properties of Cursor.Pos to the bounds of the rectangle, and the second involves creating a Point object and using the Rectangle.Contains() method.


//Comparing the float coordinates to the rectangle coordinates
public static bool in_rectangle(Rectangle r)
{
    if ((Pos.X > r.X) && (Pos.X < r.X + r.Width) 
    && (Pos.Y > r.Y) && (Pos.Y < r.Y + r.Height))
    {
        return true;
    }
    else return false;
}


//Using Rectangle.Contains()
public static bool in_rectangle(Rectangle r)
{
    Point p = new Point(State.X, State.Y);
    return r.Contains(p);
}

I would recommended casting to a point, as it is effectively less lines of code, but both methods get the job done.

Homework: Find out how to import assets into your game. Add three folders into your Content folder labeled Audio, Graphics and Data.

Tuesday, May 31, 2011

XNA Game Design: Mouse Input (part 1)


Implementation for cursor input is a bit more complex, but still very similar to the keyboard's. If the keyboard input is just a case of checking boolean values of keyboard keys, mouse input is more like Cartesian graphing. Just like the keyboard, the mouse has it's own status property called MouseStateMouseState contains X and Y coordinates that are relative to the upper-left origin of the game window.

If you've played with the empty game window before, you might have noticed the mouse cursor disappears when moved onto the game window. This is a default property of XNA Windows games, and can be changed in the Game1 class with the line IsMouseVisible = true;.still, this does not provide us with a custom cursor graphic, and the mouse will still not be able to interact with our game.

Like the Keyboard class, XNA provides a Mouse class which has it's own GetState(method. However, the X and Y integers contained in the mousestate returned by Mouse.GetState() will contain negative values if they are above or to the left of the origin, and will return values greater than the width and height of the game window if they exceed the Game Window's boundaries.

So why is this bad?

Lets say you have a game where the character is the mouse cursor, and is being chased by angry monsters that bounce around the screen, and the goal of the game is to last as long as possible. If this problem isn't fixed, the player could move their cursor off the screen, and walk away. Effectively the player's coordinates would be outside the confines the game window, and the enemies would never reach the player.
Your game should not be broken by the player moving the cursor off the screen! As this cheesy photoshop shows, negative cursor coordinates (as well as coordinate beyond the confines of the game window) beat the game's logic, and the player can successfully cheat. This is bad!

The Cursor class we will be creating is different from the XNA Mouse class for two reasons. Of course the XNA Mouse class is protected and immutable, but more importantly it contains all the information about the actual onscreen mouse relative to our game window. We need this metadata to handle certain interactions with the Windows shell.

As such, the update and draw methods we will be implementing will focus more on the following two aspects:
  • Coordinates that are conducive to our game window.
  • A custom cursor texture that can be easily changed depending on the desired scenario.

Alright, lets get this general framework going.
static class Cursor
{
    //Current state of the Mouse
    public static MouseState State = Mouse.GetState();
    //Previous state of the Mouse
    public static MouseState Prevstate;
    //Graphic used to represent the cursor
    public static Texture2D Texture;
    //value representing mouse coordinates
    public static Vector2 Pos;


    public static void Update()
    {
        PrevState = State;
        State = Mouse.GetState();
        Pos = new Vector2((float) state.X, (float) state.Y);
    }

    public static void Draw(Spritebatch spritebatch)
    {
        //We'll write this code later
    }
}

Look familiar? It is essentially the same method as the keyboard. Still, this doesn't address the aforementioned problem, nor will it allow us to draw our special cursor graphic.

As you can see, we also added two new properties to the class These are Pos and Texture.
.


Again, these properties are public and static because we will not be making instances of Cursor, and because we want their access to be commonplace throughout the game. Those two types however, Texture2D and Vector2, are XNA framework types. Texture2D represents a two-dimensional image (of which we can assign a raster graphic, which we will be do in the next lesson) and Vector2; a set of two float values that can be used to identify a vector, or in our case, the Cartesian coordinates of the mouse. The reason we will be using Vector2 rather than Point (similar to a vector2, but composed of two integers instead of two floats) to represent our mouse cursor will be explained later, but you are right in thinking that such a datatype would be more conducive to representing the Cartesian coordinates of our cursor. You were thinking that, right?

Texture and Pos will both be implemented in the next lesson, and we will also tie-in our completed Input class into our main loop, giving our game the input it needs.

Yesterday's Homework Solution: In order to create a method that can tell the difference between holding a button down and pushing a button, we would need to check the previous state of the keyboard using our PrevState  property. Like before, we want the method to be static so it's implementation is ubiquitous.


    public static bool IsKeyTrigger(Keys k) //returns true if key is triggered at one moment. returns false if held.
    {
        if ((State.IsKeyDown(k)) && !(PrevState.IsKeyDown(k))) return true;
        else return false;
    }

Because State would return true so long as the key is pressed down, PrevState would only return false at the first moment the key is pressed. The next time Input.Update() is run, this method will begin returning false until the key is released and PrevState reset to false.

Homework: This lesson introduced three new XNA classes; Texture2DVector2 and Point. All three are quite useful, and we will be seeing them many more times in the future. Another class we will see frequently is the Rectangle class. Rectangle object, as you can imagine, represents a 2D space with an (X, Y) origin, a width and height.

Given a Rectangle r, return a boolean value indicating whether the cursor is within the confines of r. For reference, here are the members of rectangle, vector2 and point.