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

Saturday, June 25, 2011

XNA Game Design: Abstract Sprite (part 1)


The problem with displaying a texture2D image is that there are a number of variables associated with how it should be displayed (Vector2 position, Vector2 scale, Color blending color, float rotation... you get the idea). In many cases we wouldn't want to deal with most of variables, but so many are tied together, it actually makes sense to create a object that holds all this information for us. That is why we will begin creating an abstract ASprite class.

Alongside that, it will also allow us to create methods that can be called from SCENE.Update() that would allow us to do more advanced things with our images, such as make them flash a certain color with a certain intensity for X frames (a bonus project I'll put up later. Too tough for a homework assignment -_-). Being able to call Player.Sprite.Flash(...) is very gratifying, and easy on later development.

Our abstract Sprite class will hold nine variables. These are;
  • the Texture2D image
  • a Rectangle source rectangle (for if we only want to display part of an image)
  • a Vector2 position
  • a Vector2 scale
  • a Vector2 origin
  • a SpriteEffects effect (for indicating whether to flip horizontally or vertically)
  • a private Float angle with a public Int Rotation (you'll see)
  • a Float z scale (for determining the draw order of images and how they "stack")
  • a Color blend color

These are a lot of variables that do a bunch of different things, and if you have any questions about them I'll answer them in the comments. We will also need a static ContentManager to load our texture2D, a simple constructor, and a few extra methods. The only necessary value we will require is our Texture2D image, and we retrieve this by asking for a string.

(Note: depending on your game, you might not want to be holding all of these variables. A NES clone would not use a blending color or a rotation, and if you're crafty you might not even want a source rectangle or scale value. Again, all of this depends on the game YOU are making.)

As established, we will begin by making an abstract class to hold all of these variables.

abstract class ASprite
{
    public Texture2D image;
    public Rectangle src_rect;
    public Vector2 pos = Vector2.Zero;
    public Vector2 origin = Vector2.Zero;
    public Vector2 scale = Vector2.One;
    public float z = 0.5f; //default z value
    private float angle = 0.0f; //rotation in radians
    public Color blend_color = Color.White;
    public SpriteEffects effect = SpriteEffects.None;
    private static ContentManager content = 
    new ContentManager(Game1.services, "Content/Graphics");

Now there's one more variable we need: the int rotation. The reason we need another value to represent our angle is because the draw want's the angle of rotation as a float value in radians. If you're so comfortable with radians that you feel you can specify your rotations with exact values, I commend you. But for us humans, we will instead use rotation to specify our angle of rotation in degrees, and use a set accesor to change the angle value into radians. 

The get and set accesors allow the developer to specify extra operations whenever properties are changed. They are very useful for situations like these.

public int Rotation
{
    set
    {
        angle = (MathHelper.Pi * value / 180)
        % (MathHelper.TwoPi);
    }
    get
    {
        return (int)(angle * (180 / mathhelper.Pi);
    }
}

Comic by Bill Amend. It's not that radians are difficult to think in terms of. It's that it's easier to say rotation = 45 as opposed to angle = 0.785398 (pi / 4 as a float).


We will want the constructor to look a little funky, which is why this is being split into two parts. We're almost finished though! No homework because I couldn't think of any just yet -_-  

Thursday, June 23, 2011

XNA Game Design: Thinking Abstractly

The second part of this guide will focus on making two abstract classes to better handle certain aspects of our game. The first will involve creating a class that will better handle image drawing and holding related bitmap data (such as position, rotation, reflection, etc.). The second will use our improved sprites to create a standardized game window that will contain elements such as text, sprites, and selectable choices.

The reason this section is called thinking abstractly is because it will focus intently on elements of polymorphism and inheritance through the use of abstract classes.

To refresh your memory, an abstract class uses the abstract keyword and cannot have instances made of it. Simply put, it acts as a parent class perfect to hold the messier, often untouched variables used by it's child classes.


The Grandpa isn't dead, he's just abstract.
When thinking back to our biggest unsolved problem, we still have the spriteBatch lingering around our SCENE.Draw() method. At the moment, all image drawing must be called from the spritebatch.Draw() method. This makes for horrible looking code because all the methods are directed from the spriteBatch.

  • spriteBatch.Draw(Player.Texture2D, Player.Vector2, Player.Color...)
  • spriteBatch.Draw(Enemy.Texture2D, Enemy.Vector2, Enemy.Color...)
  • spriteBatch.Draw(PowerUp.Texture2D, PowerUp.Vector2, PowerUp.Color...)
  • //This repetitive code is boring!

Every line would be that repetitive. The less code we have to write, the better. So the first part of Abstraction will focus on creating a set of classes to let us instead say;
  • Player.Sprite.Draw(spriteBatch);
  • Enemy.Sprite.Draw(spriteBatch);
  • PowerUp.Sprite.Draw(spriteBatch);
  • //Easy like strawberry daiquiris in Tahiti!

The second part will focus on making a standardized game window, which is just a background with borders and organized elements. While every game does this visual aspect differently, It's inclusion is videogames is so ubiquitous it honestly deserves it's own focus. If you don't believe me, just see for your self. Menus, text boxes and selectable choices are so common in video games, and they're all composed of related objects. As such, it makes perfect sense to tackle them early on. By the end of this chapter, you should have the perfect framework to make and menu system or options screen your game would need (boring, yeah, but it'll make those parts of the game seem like a piece of cake).

Monday, June 13, 2011

XNA Game Design: Part 1 Conclusion

Alright, I'll be taking a two week break before starting part 2, so for now here are all our files and work we created in the first part of our guide.

Game1.cs

Input.cs
Cursor.cs
Audio.cs
IScene.cs
SCENE_Main.cs


Hooray! We finished part 1!

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.