Commit 091b53a2 authored by Nathan Bean's avatar Nathan Bean
Browse files

Added first half of sprite chapter

parent 5af13daf
......@@ -6,7 +6,7 @@ chapter = true
pre = "2. "
+++
### Chapter 1
### Chapter 2
# Player Input
......
---
title: "Introduction"
pre: "1. "
weight: 10
date: 2018-08-24T10:53:26-05:00
---
The term "sprite" refers to a graphical element within a two-dimensional game that moves around the screen - often representing a character, powerup, or other actor. The term likely was coined in relation to its older definition - a small fairy creature.
Traditionally, sprites are a part of two-dimensional games, and are a _raster_ graphic (one composed of a regular grid of pixels, aka a bitmap). As the sprites are simply an array of bits representing pixels, and the scene being presented on screen is _also_ just an array of bits representing pixels, we can place a sprite on-screen by simply copying its bits into the right location.
## Hardware Sprites
The earliest implementations of sprites did this by substituting the sprite bits for background image bits _as the bits were streamed to the screen_ as part of an analog frame signal. This was done by specialized hardware that supported a limited number of sprites (hence the name _hardware sprites_).
## Bit Blitting
Later games used _bit blitting_ (an abbreviation for bit-boundary block transfer), a technique for copying a smaller bit array into a larger one. Early graphics hardware implemented bit blitting as a hardware instruction, meaning it could be performed very fast, provided the sprite was drawn to scale.
## Textured Quads
Modern games (and MonoGame) often use the 3D hardware to render sprites, which means they are represented as _textured quads_. A textured quad is essentially a rectangle composed of two triangles that always faces the screen.
While it is more complex than traditional sprites, there are several benefits to this approach:
1. It is far easier to scale sprites composed as textured quads than bit-blitted sprites (and scaling is impossible with most hardware sprites)
2. Textured Quad sprites can be rotated to an arbitrary angle using the graphics hardware. Bit-blitted sprites could only be flipped (mirrored) in the X or Y direction, true rotations required additional sprite images drawn from the desired angle
3. Textured Quad sprites can take advantage of the Z-buffer to do depth sorting. Traditional bit-blitted sprites had to be drawn using the _painters algorithm_ or similar techniques to ensure proper layering.
4. Textured sprites are rendered using _shader_ programs on the graphics card, so many unique effects can be applied to them.
In this chapter, we'll examine how the MonoGame implementation of textured quads works.
\ No newline at end of file
---
title: "Drawing Sprites"
pre: "2. "
weight: 20
date: 2018-08-24T10:53:26-05:00
---
MonoGame provides the [`SpriteBatch`](https://docs.monogame.net/api/Microsoft.Xna.Framework.Graphics.SpriteBatch.html) class to help mitigate the complexity of implementing textured quad sprites. It provides an abstraction around the rendering process that lets us render sprites with a minimum of fuss, with as much control as we might need.
As the name suggests, the `SpriteBatch` _batches_ sprite draw requests so that they can be drawn in an optimized way. We'll explore the different modes we can put the `SpriteBatch` in soon. But for now, this explains why every batch begins with a call to `SpriteBatch.Begin()`, then an arbitrary number of `SpriteBatch.Draw()` calls, followed by a `SpriteBatch.End()` call.
We've already used this pattern in our Hello Game example from chapter 1:
```csharp
_spriteBatch.Begin();
_spriteBatch.Draw(_ballTexture, _ballPosition, Color.White);
_spriteBatch.End();
```
In this example, we draw a single sprite, using the `_ballTexture`, and drawing the graphic it represents with the upper-right corner at `_ballPosition`, and blend white (`Color.White`) with the sprite texture's own colors.
The `SpriteBatch.Draw()` method actually has a seven available overrides for your use:
* `public void Draw(Texture2D texture, Rectangle destinationRectangle, Color color)`
* `public void Draw(Texture2D texture, Rectangle destinationRectangle, Rectangle? sourceRectangle, Color color)`
* `public void Draw(Texture2D texture, Rectangle destinationRectangle, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, SpriteEffects effects, float layerDepth)`
* `Draw(Texture2D texture, Vector2 position, Color color)`
* `Draw(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color)`
* `Draw(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth)`
* `Draw(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, float scale, SpriteEffects effects, float layerDepth)`
Rather than explain each one individually, we'll explore what the various parameters are used for, and you can select the one that matches your needs.
#### Texture2D texture
The `texture` parameter is a `Texture2D` containing the sprite you want to draw. Every override includes this parameter. If the texture has not been loaded (is null), then invoking `Draw()` will throw an `ArgumentNullException`.
#### Rectangle destinationRectangle
The `destinationRectangle` is a rectangle whose coordinates are where the sprite should be drawn, in screen coordinates. If the rectangle's dimensions are not the same as those of the source image (or the sub-image specified by `sourceRectangle`), it will be scaled to match. If the aspect ratios are different, this will result in a stretched or squished sprite. Note that the `Rect` uses integers for coordinates, so calculated floats used to place the sprite will potentially be truncated.
#### Color color
The `color` parameter is a `Color` that will be blended with the colors in the texture to determine the final color of the sprite. Using `Color.White` effectively keeps the texture color the same, while using `Color.Red` will make the sprite's pixels all redder, `Color.Yellow` will make them more yellow, etc. This parameter can be utilized to make the sprite flash different colors for damage, invulnerability, etc.
#### Vector2 position
As an alternative to the `destinationRectangle`, a sprite's position on-screen can be specified with `position`, which is a `Vector2`. This position specifies the upper-left hand corner of where the sprite will be drawn on-screen (unless the `origin` parameter is set). Note that when we use the `position` parameter, the width and height matches that of the texture (or sub-image specified by the `sourceRectangle`), unless a `scale` is also provided.
#### Rectangle? sourceRectangle
The `sourceRectangle` is a rectangle that defines a _subarea_ of the source texture (`texture`) to use as the sprite. This is useful for _texture atlases_, where more than one sprite appear in the same texture, and also for _sprite animation_ where multiple frames of animation appear in the same texture. We'll discuss both of these approaches soon.
Note that the question mark in `Rectangle?` indicates it is a _nullable_ type (i.e. it can be null as well as the `Rectangle` struct). When it is `null`, the entire texture is used as the `sourceRectangle`.
#### float rotation
The `rotation` is a rotation value measured in radians that should be applied to the sprite. This is one of the big benefits of textured quad sprites, as the graphics hardware makes rotations a very efficient operation (without this hardware, it becomes a much more difficult and computationally expensive operation). This rotation is about the `origin` of the sprite, which is why all the overrides that specify the `rotation` also specify the `origin`.
#### Vector2 origin
The `origin` is the spot within the sprite where rotations and scaling are centered. This _also_ affects sprite placement - the `position` vector indicates where the `origin` of the sprite will fall on-screen. It is a vector measured relative to the upper-left-hand-corner of the sprite, in _texture_ coordinates (i.e. pixels of the source texture).
Thus, for our 64x64 pixel ball texture, if we wanted the origin to be at the center, we would specify a value of `new Vector2(32,32)`. This would also mean that when our ball was at position $(0,0)$, it would be centered on the origin and 3/4 of the ball would be off-screen.
#### float scale
The `scale` is a scalar value to scale the sprite by. For example, a value of $2.0f$ will make the sprite twice as big, while $0.5f$ would make it half as big. This scaling is in relation to the `origin`, so if the origin is at the center of the sprite grows in all directions equally. If instead it is at $(0,0)$, the sprite will grow to the right and down only.
#### SpriteEffects effects
The `effects` parameter is one of the `SpriteEffects` enum's values. THese are:
* `SpriteEffects.None` - the sprite is drawn normally
* `SpriteEffects.FlipHorizontally` - the sprite is drawn with the texture flipped in the horizontal direction
* `SpriteEffects.FlipVertically` - the sprite is drawn with the texture flipped in the vertical direction
Note you can specify both horizontal and vertical flipping with a bitwise or: `SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically`
#### single layerDepth
The `layerDepth` is an integer that indicates which sprites should be drawn "above" or "below" others (i.e. which ones should obscure the others). Think of it as assembling a collage. Sprites with a higher `layerDepth` value are closer to the top, and if they share screen space with sprites with a `lowerDepth`, those sprites are obscured.
\ No newline at end of file
---
title: "Texture Atlases"
pre: "3. "
weight: 30
date: 2018-08-24T10:53:26-05:00
---
A _texture atlas_ is a texture that is used to represent _multiple_ sprites. For example, this texture from Kinney's 1Bit Pack [available on OpenGameArt](https://opengameart.org/content/1-bit-pack) contains all the sprites to create a roguelike in a single texture:
![Kinney's 1Bit Pack Texture Atlas]({{<static "images/1bitpack/colored_packed.png">}})
In this case, each sprite is 15x15 pixels, with a 1 pixel outline. So to draw the cactus in the second row and sixth column of sprites, we would use a source rectangle:
```csharp
var sourceRect = new Rectangle(16, 96, 16, 16);
```
Thus, to draw the sprite on-screen at position $(50,50)$ we could use:
```csharp
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// TODO: Add your drawing code here
spriteBatch.Begin();
spriteBatch.Draw(atlas, new Vector2(50, 50), new Rectangle(96, 16, 15, 15), Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
```
And we'd see:
![The rendered sprite from the sprite atlas]({{<static "images/3.3.1.png">}})
This texture atlas is laid out in 16x16 tiles, which makes calculating the `X` and `Y` of our source rectangle straightforward:
```csharp
var x = xIndex * 16;
var y = yIndex * 16;
```
The formula involved for a particular texture atlas will depend on the size and spacing between sprites. Also, some texture atlases are not evenly spaced. In those cases, it may be useful to define a `Rectangle` constant for each one, i.e.
```csharp
const Rectangle helicopterSource = new Rectangle(15, 100, 200, 80);
const Rectangle missileSource = new Rectangle(30, 210, 10, 3);
```
{{% notice info %}}
The texture used in the above example has a brown background. If you would like to replace this with transparent black, you can set a _color key color_ in the mgcb content editor. Any pixel this color in the source image will be turned into transparent black when the content is compiled. In this case, our color's RGB values are (71, 45, 60):
![Setting the Color Key Color]({{<static "images/3.3.2.png">}})
The result is that sprites rendered from the texture now have a transparent background:
![The rendered sprite with a transparent background]({{<static "images/3.3.3.png">}})
{{% /notice %}}
\ No newline at end of file
---
title: "Animated Sprites"
pre: "4. "
weight: 40
date: 2018-08-24T10:53:26-05:00
---
To animate a sprite, we simply swap the image it is using. Animated sprites typically lay out all their frames in a single texture, just like a texture atlas. Consider this [animated bat sprite by bagzie](https://opengameart.org/content/bat-sprite) from OpenGameArt:
![Animated bat spritesheet]({{<static "images/32x32-bat-sprite.png">}})
The images of the bat are laid out into a 4x4 grid of 32x32 pixel tiles. We can create the illusion of motion by swapping which of these images we display. However, we don't want to swap it every frame - doing so will be too quick for the viewer to follow, and destroy the illusion. So we also need a timer and an idea of the direction the sprite is facing.
To represent the direction, we might define an enum. And since the enum can represent a numerical value, let's assign the corresponding row index to each direction:
```csharp
/// <summary>
/// The directions a sprite can face
/// </summary>
public enum Direction {
Down = 0,
Right = 1,
Up = 2,
Left = 3
}
```
With this extra state to track, it makes sense to create a class to represent our sprite:
```csharp
/// <summary>
/// A class representing a bat
// </summary>
public class BatSprite
{
// The animated bat texture
private Texture2D texture;
// A timer variable for sprite animation
private double directionTimer;
// A timer variable for sprite animation
private double animationTimer;
// The current animation frame
private short animationFrame;
///<summary>
/// The bat's position in the world
///</summary>
public Vector2 Position { get; set; }
///<summary>
/// The bat's direction
/// </summary>
public Direction Direction { get; set; }
}
```
We'll need a `LoadContent()` method to load our texture:
```csharp
/// <summary>
/// Loads the bat sprite texture
/// </summary>
/// <param name="content">The ContentManager</param>
public void LoadContent(ContentManager content)
{
texture = content.Load<Texture2D>("32x32-bat-sprite.png");
}
```
Let's make our bat fly in a regular pattern, switching directions every two seconds. To do this, we would want to give our bat an `Update()` method that updates a timer to determine when it is time to switch:
```csharp
public void Update(GameTime gameTime)
{
// advance the direction timer
directionTimer += gameTime.ElapsedGameTime.TotalSeconds;
// every two seconds, change direction
if(directionTimer > 2.0)
{
switch(Direction)
{
case Direction.Up:
Direction = Direction.Down;
break;
case Direction.Down:
Direction = Direction.Left;
break;
case Direction.Left:
Direction = Direction.Right;
break;
case Direction.Right:
Direction = Direction.Up;
break;
}
// roll back timer
directionTimer -= 2.0;
}
// move bat in desired direction
switch (Direction)
{
case Direction.Up:
Position += new Vector2(0, -1) * 100 * (float)gameTime.ElapsedGameTime.TotalSeconds;
break;
case Direction.Down:
Position += new Vector2(0, 1) * 100 * (float)gameTime.ElapsedGameTime.TotalSeconds;
break;
case Direction.Left:
Position += new Vector2(-1, 0) * 100 * (float)gameTime.ElapsedGameTime.TotalSeconds;
break;
case Direction.Right:
Position += new Vector2(1, 0) * 100 * (float)gameTime.ElapsedGameTime.TotalSeconds;
break;
}
}
```
We'll use a similar technique to advance the animation frame - once every 16th of a second, in the `Draw()` method:
```csharp
public void Draw(GameTime gameTime, SpriteBatch spriteBatch) {
// advance the animation timer
animationTimer += gameTime.ElapsedGameTime.TotalSeconds;
// Every 1/16th of a second, advance the animation frame
if(animationTimer > 1/16)
{
animationFrame++;
if(animationFrame > 3) animationFrame = 0;
animationTimer -= 1/16;
}
// Determine the source rectangle
var sourceRect = new Rectangle(animationFrame * 32, (int)Direction * 32, 32, 32);
// Draw the bat using the current animation frame
spriteBatch.Draw(texture, Position, sourceRect, Color.White);
}
```
Notice because our `Direction` enum uses integer values, we can cast it to be an `int` and use it to calculate the `sourceRect`'s x-coordinate.
We can then construct a bat (or multiple bats) in our game, and invoke their `LoadContent()`, `Update()`, and `Draw()` methods in the appropriate places.
{{% notice info %}}
You may have noticed that our `BatSprite` can be thought of as a _state machine_, or the [state pattern](https://gameprogrammingpatterns.com/state.html). You could argue that we have four possible states for each of the directions that bat is flying. Moreover, you could argue that each of those states has four sub-states for the animation frame that is being displayed. These are both accurate observations - the state pattern is an _extremely useful_ one in game design.
{{% /notice %}}
\ No newline at end of file
---
title: "Sprite Text"
pre: "4. "
weight: 40
date: 2018-08-24T10:53:26-05:00
draft: true
---
\ No newline at end of file
+++
title = "Sprites"
date = 2018-08-24T10:53:05-05:00
weight = 3
chapter = true
pre = "3. "
+++
### Chapter 3
# Sprites
Who are you calling a fairy?
<img src="https://media.giphy.com/media/6AreExUaRw1qw/giphy.gif">
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment