2. Game Object Class
We create a class named GameObject to be our base class for game assets, which includes the hero ship, the missiles, and the alien ships. The GameObject class handles the animation, Update, and Drawing for each object. We copied over the assets and sprite sheet logic from the SpriteAnimation sample project. Here is the GameObject constructor:
public GameObject(SpriteSheet loadedTexture, string spriteName, Rectangle screenRect)
{
SpriteAnimationSpriteSheet = loadedTexture;
SpriteCenter = new Vector2(
SpriteAnimationSpriteSheet.SourceRectangle(spriteName + 0).Width / 2,
SpriteAnimationSpriteSheet.SourceRectangle(spriteName + 0).Height / 2);
//Used to access sprite in SpriteSheet
//Assume starts at 0 so SpriteName+0 is first Sprite frame for animation
//NumberOfAnimationFrames is how many sprite frames that are available
SpriteName = spriteName;
_screenRect = screenRect;
//Default initialization
FrameTime = TimeSpan.FromMilliseconds(100d);
NumberOfAnimationFrames = 10;
Position = Vector2.Zero;
ElapsedFrameTime = TimeSpan.FromMilliseconds(0d);
Velocity = Vector2.Zero;
Rotation = 0f;
Alive = false;
}
The constructor for GameObject takes the following parameters:
Just as with the SpriteAnimation sample, all
of the individual object frames are combined into a single texture
shared by all of the objects, which is more efficient than loading
individual textures and switching textures when rendering. The loadedTexture parameter represents the single texture and is passed in to the constructor. The spriteName parameter is used by the animation code so that the correct object frames can be found in the loadedTexture. This code assumes a naming convention starting at spriteName+0 through spriteName+NumberOfAnimationFrames, which is hard-coded to 10 frames for all objects. The screenRect parameter is used to check when objects collide with screen bounds.
2.1. Properties
The GameObject class has quite a few public
properties declared that are used to animate the sprite and to hold
information on Sprite such as the center point, position, velocity and
rotation. Here are the declarations:
public SpriteSheet SpriteAnimationSpriteSheet { get; set; }
public string SpriteName { get; private set; }
public int NumberOfAnimationFrames { get; set; }
public TimeSpan FrameTime { get; set; }
public TimeSpan ElapsedFrameTime { get; set; }
public Vector2 SpriteCenter { get; set; }
public bool Alive { get; set; }
public Vector2 Position { get; set; }
public Vector2 Velocity { get; set; }
public float Rotation { get; set; }
There is an additional property related to collision detection that we will cover next.
2.2. Collision Detection
The BoundingRect property of type Rectangle is used to return the Rectangle
area that contains the sprite on screen. This property is used for
collision detection. If you have ever played a video game where the
objects seemed to touch but nothing happen, it is a result of imperfect
collision detection, as shown in Figure 4 where a regular shaped object, like a rectangle, is used to define the area of an irregularly shaped object.
Probably the best answer lies somewhere in-between
those extremes, such as using more than one bounding box or only
performing point-by-point comparison on sides that could collide, and so
on. For our purposes, we check for intersection using the BoundingRect property defined in GameObject:
public virtual Rectangle BoundingRect
{
get
{
return new Rectangle((int)Position.X, (int)Position.Y,
SpriteAnimationSpriteSheet.SourceRectangle(SpriteName + 0).Width,
SpriteAnimationSpriteSheet.SourceRectangle(SpriteName + 0).Height);
}
}
One trick to that you can use to adjust collision detection is to call the Inflate(horizontalAmount,verticalAmount) method to individually increase or decrease (with a negative value) the sides of the Rectangle to better match the object shape.
2.3. GameObject Methods
The rest of the GameObject class contain its
individual methods to reset its position if the object flies off the
screen, update the object state, and draw the object to screen. Methods
are marked with the virtual keyword so that they can be overridden in inherited classes as needed:
public virtual void ResetGameObject()
{
Position = Vector2.Zero;
Velocity = Vector2.Zero;
Alive = false;
}
public virtual void Update(GameTime GameTime)
{
if (Alive)
{
Position += Velocity;
//Check screen bounds
if ((Position.X < 0) ||
(Position.X > _screenRect.Width) ||
(Position.Y < 0) ||
(Position.Y > _screenRect.Height))
ResetGameObject();
//Update animation
UpdateAnimation(GameTime);
}
}
private void UpdateAnimation(GameTime gameTime)
{
ElapsedFrameTime += gameTime.ElapsedGameTime;
if (ElapsedFrameTime > FrameTime)
{
if (_spriteIndex < NumberOfAnimationFrames - 1)
_spriteIndex++;
else _spriteIndex = 0;
ElapsedFrameTime = TimeSpan.FromMilliseconds(0d);
}
}
public virtual void Draw(GameTime gameTime, SpriteBatch spriteBatch)
{
if (Alive)
{
spriteBatch.Draw(SpriteAnimationSpriteSheet.Texture, Position - SpriteCenter,
SpriteAnimationSpriteSheet.SourceRectangle(SpriteName + _spriteIndex.ToString()),
Color.White);
}
}
The ResetGameObject is called when an object is "destroyed" or flies off the screen. The method is pretty simple in just setting Position and Velocity to a zero vector and Alive to false.
The Update method checks to see if the object is Alive before updating Position by adding Velocity to it and then checking screen bounds. The Update method also calls the UpdateAnimation method, which leverages the code we developed in the SpriteAnimation sample. Finally, the Draw method simply applies the same logic we used in the SpriteAnimation sample to draw the correct frame to screen as part of the animation sequence.
Now that we have our base class out of the
way, we move on to cover the enemy alien class and then move on to the
user controlled hero ship and missile classes.