Showing posts with label XNA game. Show all posts
Showing posts with label XNA game. Show all posts

Tuesday, June 28, 2011

XNA Game Design: Abstract Sprite (part 2)

In the last part I said our constructor will look a little bit funky. Here it is:


public ASprite(string path, Vector2? loc = null)
{
    pos = loc ?? Vector2.Zero;
    image = content.Load<Texture2D>("Graphics/" + path);
}

If you're baffled by what is going on, I don't blame you. It certainly looks quite funky, so let me explain what we're trying to do.

When the user wants to create a new sprite, they must provide the path name to the Texture2D resource they want to use, and they may also provide the Vector2 position of their sprite. C# takes in optional arguments by specifying their default values in the parameters (for example, method(int x, int y = 5) requires an int for x, and an optional value for y that defaults to 5 when unspecified).

The question mark after the Vector2 specifies the value we're taking in is nullable. A nullable type is like it's original type, but that it's range of values can also acceptably include null. In our case, we're saying that if the developer does not specify the optional value for our vector2, it will default to null.

Inside the constructor, the first line uses a special operator (??) that sets pos equal to loc if it has a value (is not null), or otherwise sets it to the value of Vector2.Zero (0.0, 0.0). This ensures that if the developer specified a starting Vector2, it's position is set. Otherwise, if it's not set, or set to null, it defaults to the top-left corner (0.0, 0.0).

If you're paying attention, you might be thinking, "Why don't we just say (... Vector2 loc = Vector2.Zero) and avoid the nullable thing all together?"

The problem here is rather asinine; to specify optional parameters you need compile-time constants (nothing that requires extra computation to determine). This means you can't specify it as (... Vector2 loc = new Vector2(0.0, 0.0) ) either.


Wow. That's a lot of explanation for a simple constructor. Lets finish this bad boy up. All we need now is just a draw method. As it should, it will take our spriteBatch and use that to draw our sprites.


public void Draw(SpriteBatch sb)
{
sb.Draw(image, pos, src_rect, blend_color, angle, origin, scale, effect, z);
}

That's a lot of parameter's, innit?
Now, we just need to make an actual Sprite Class that we can make instances of. In a separate class, add the following code:
class Sprite : ASprite
{
    public Sprite(string path, Vector2? v = null) : base(path, v)
    {
    }       
    public new void Draw(SpriteBatch s_Batch)
    {
        base.Draw(s_Batch);
    }
}


The Sprite class now extends ASprite, and holds all it's properties and methods. These members can be overwritten with the new keyword (as we did with the Draw method) and the inherited members can be called with the base keyword.


To draw a new image, in the SCENE.Update(), just create a new instance of Sprite. initiate and tweak your Sprite instances as you see fit (Sprite.Rotation = 45). They can be easily rotated, flipped, and moved. There just simply needs to be a call somewhere in the SCENE.Draw() to actually draw the sprite.


Not shabby at all?


So why did we want to make an abstract class in the first place? After all, having only one class to shape after an abstract class is kinda pointless. Unless of course, we want to create more classes that would extend our abstract class. Such as a class to cut the image into pieces and draw only one piece at a time. Like, say for example, in a sprite sheet...


Slots animation by Mia. Unsure of how to link to her work, but this sprite sheet serves a good example of what we will be tackling.


Homework: Opacity in XNA images is calculated strangely. You would think it would be derived by the Alpha value used in the blending color, but it is in fact much different. Alpha values are imparted onto drawn graphics by multiplying a float value to the blending color, with 0.0f being full transparency, and 1.0f being fully opaque. So to draw an image at 50% opacity, you would blend with blend_color * 0.5f .


Create one extra public integer value for Sprite called Opacity. When the developer changes the value of opacity (from 0-255), change the draw method so the image blends with the set opacity.

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.