//-----------------------------------------------------------------------------
// TileGrid.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Media;
namespace TiledSprites
{
///
/// EDUCATIONAL: Class used to align tiles to a regular grid.
/// This represents a tiling "layer" in this sample
///
public class TileGrid
{
private int[][] grid;
private GraphicsDeviceManager graphics;
private SpriteSheet sheet;
private int width;
private int height;
private int cellWidth;
private int cellHeight;
private Vector2 worldOffset;
private bool visibilityChanged;
private Rectangle visibleTiles;
//drawing parameters
private Vector2 cameraPostionValue;
private float zoomValue;
private Vector2 scaleValue;
private float rotationValue;
private Matrix rotationMatrix;
private Vector2 displaySize;
private Color layerColor = Color.White;
public TileGrid(int tileWidth, int tileHeight, int numXTiles, int numYTiles,
Vector2 offset, SpriteSheet tileSheet,
GraphicsDeviceManager graphicsComponent)
{
if (graphicsComponent == null)
{
throw new ArgumentNullException("graphicsComponent");
}
graphics = graphicsComponent;
sheet = tileSheet;
width = numXTiles;
height = numYTiles;
cellWidth = tileWidth;
cellHeight = tileHeight;
worldOffset = offset;
visibleTiles = new Rectangle(0, 0, width, height);
grid = new int[width][];
for (int i = 0; i < width; i++)
{
grid[i] = new int[height];
for (int j = 0; j < height; j++)
{
grid[i][j] = 0;
}
}
scaleValue = Vector2.One;
zoomValue = 1.0f;
CameraPosition = Vector2.Zero;
displaySize.X = graphics.GraphicsDevice.PresentationParameters.BackBufferWidth;
displaySize.Y = graphics.GraphicsDevice.PresentationParameters.BackBufferHeight;
graphicsComponent.DeviceReset += delegate (object sender, EventArgs e)
{
displaySize.X = graphics.GraphicsDevice.PresentationParameters.BackBufferWidth;
displaySize.Y = graphics.GraphicsDevice.PresentationParameters.BackBufferHeight;
visibilityChanged = true;
};
}
public Vector2 CameraPosition
{
set
{
cameraPostionValue = value;
visibilityChanged = true;
}
get
{
return cameraPostionValue;
}
}
public float CameraRotation
{
set
{
rotationValue = value;
rotationMatrix = Matrix.CreateRotationZ(rotationValue);
visibilityChanged = true;
}
get
{
return rotationValue;
}
}
public float CameraZoom
{
set
{
zoomValue = value;
visibilityChanged = true;
}
get
{
return zoomValue;
}
}
public Color Color
{
set
{
layerColor = value;
}
get
{
return layerColor;
}
}
public Vector2 TileScale
{
set
{
scaleValue = value;
visibilityChanged = true;
}
get
{
return scaleValue;
}
}
public Vector2 Position
{
set
{
worldOffset = value;
visibilityChanged = true;
}
get
{
return worldOffset;
}
}
public void SetTile(int xIndex, int yIndex, int tile)
{
grid[xIndex][yIndex] = tile;
}
///
/// This function determines which tiles are visible on the screen,
/// given the current camera position, rotation, zoom, and tile scale
///
private void DetermineVisibility()
{
//create the view rectangle
Vector2 upperLeft = Vector2.Zero;
Vector2 upperRight = Vector2.Zero;
Vector2 lowerLeft = Vector2.Zero;
Vector2 lowerRight = Vector2.Zero;
lowerRight.X = ((displaySize.X / 2) / zoomValue);
lowerRight.Y = ((displaySize.Y / 2) / zoomValue);
upperRight.X = lowerRight.X;
upperRight.Y = -lowerRight.Y;
lowerLeft.X = -lowerRight.X;
lowerLeft.Y = lowerRight.Y;
upperLeft.X = -lowerRight.X;
upperLeft.Y = -lowerRight.Y;
//rotate the view rectangle appropriately
Vector2.Transform(ref upperLeft, ref rotationMatrix, out upperLeft);
Vector2.Transform(ref lowerRight, ref rotationMatrix, out lowerRight);
Vector2.Transform(ref upperRight, ref rotationMatrix, out upperRight);
Vector2.Transform(ref lowerLeft, ref rotationMatrix, out lowerLeft);
lowerLeft += (cameraPostionValue);
lowerRight += (cameraPostionValue);
upperRight += (cameraPostionValue);
upperLeft += (cameraPostionValue);
//the idea here is to figure out the smallest square
//(in tile space) that contains tiles
//the offset is calculated before scaling
float top = MathHelper.Min(
MathHelper.Min(upperLeft.Y, lowerRight.Y),
MathHelper.Min(upperRight.Y, lowerLeft.Y)) -
worldOffset.Y;
float bottom = MathHelper.Max(
MathHelper.Max(upperLeft.Y, lowerRight.Y),
MathHelper.Max(upperRight.Y, lowerLeft.Y)) -
worldOffset.Y;
float right = MathHelper.Max(
MathHelper.Max(upperLeft.X, lowerRight.X),
MathHelper.Max(upperRight.X, lowerLeft.X)) -
worldOffset.X;
float left = MathHelper.Min(
MathHelper.Min(upperLeft.X, lowerRight.X),
MathHelper.Min(upperRight.X, lowerLeft.X)) -
worldOffset.X;
//now figure out where we are in the tile sheet
float scaledTileWidth = (float)cellWidth * scaleValue.X;
float scaledTileHeight = (float)cellHeight * scaleValue.Y;
//get the visible tiles
visibleTiles.X = (int)(left / (scaledTileWidth));
visibleTiles.Y = (int)(top / (scaledTileWidth));
//get the number of visible tiles
visibleTiles.Height =
(int)((bottom) / (scaledTileHeight)) - visibleTiles.Y + 1;
visibleTiles.Width =
(int)((right) / (scaledTileWidth)) - visibleTiles.X + 1;
//clamp the "upper left" values to 0
if (visibleTiles.X < 0)
visibleTiles.X = 0;
if (visibleTiles.X > (width - 1))
visibleTiles.X = width;
if (visibleTiles.Y < 0)
visibleTiles.Y = 0;
if (visibleTiles.Y > (height - 1))
visibleTiles.Y = height;
//clamp the "lower right" values to the gameboard size
if (visibleTiles.Right > (width - 1))
visibleTiles.Width = (width - visibleTiles.X);
if (visibleTiles.Right < 0)
visibleTiles.Width = 0;
if (visibleTiles.Bottom > (height - 1))
visibleTiles.Height = (height - visibleTiles.Y);
if (visibleTiles.Bottom < 0)
visibleTiles.Height = 0;
visibilityChanged = false;
}
public void Draw(SpriteBatch batch)
{
if (visibilityChanged)
DetermineVisibility();
float scaledTileWidth = (float)cellWidth * scaleValue.X;
float scaledTileHeight = (float)cellHeight * scaleValue.Y;
Vector2 screenCenter = new Vector2((displaySize.X / 2), (displaySize.Y / 2));
//begin a batch of sprites to be drawn all at once
batch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
Rectangle sourceRect = new Rectangle();
Vector2 scale = Vector2.One;
for (int x = visibleTiles.Left; x < visibleTiles.Right; x++)
{
for (int y = visibleTiles.Top; y < visibleTiles.Bottom; y++)
{
if (grid[x][y] != 0)
{
//Get the tile's position from the grid
//in this section we're using reference methods
//for the high frequency math functions
Vector2 position = Vector2.Zero;
position.X = (float)x * scaledTileWidth;
position.Y = (float)y * scaledTileHeight;
//offset the positions by the word position of the tile grid
//this is the actual position of the tile in world coordinates
Vector2.Add(ref position, ref worldOffset, out position);
//Now, we get the camera position relative to the tile's position
Vector2.Subtract(ref cameraPostionValue, ref position,
out position);
//get the tile's final size (note that scaling is done after
//determining the position)
Vector2.Multiply(ref scaleValue, zoomValue, out scale);
//get the source rectnagle that defines the tile
sheet.GetRectangle(ref grid[x][y], out sourceRect);
//Draw the tile. Notice that position is used as the offset and
//the screen center is used as a position. This is required to
//enable scaling and rotation about the center of the screen by
//drawing tiles as an offset from the center coordinate
batch.Draw(sheet.Texture, screenCenter, sourceRect, layerColor,
rotationValue, position, scale, SpriteEffects.None, 0.0f);
}
}
}
batch.End();
}
}
}