using System;
using System.Collections.Generic;
namespace Otter {
///
/// Grid Collider. Can be mainly used to create collision to correspond to a Tilemap.
///
public class GridCollider : Collider {
#region Private Fields
List collisions = new List();
#endregion
#region Public Properties
///
/// The width of the tiles.
///
public int TileWidth { get; private set; }
///
/// The height of the tiles.
///
public int TileHeight { get; private set; }
///
/// The total number of rows on the grid.
///
public int TileRows { get; private set; }
///
/// The total number of columns on the grid.
///
public int TileColumns { get; private set; }
///
/// The width of the grid. (TileColumns * TileWidth)
///
public override float Width {
get { return TileColumns * TileWidth; }
}
///
/// The height of the grid (TileRows * TileHeight)
///
public override float Height {
get { return TileRows * TileHeight; }
}
///
/// Convert an X position to a tile on the grid.
///
/// The position to convert.
/// The X position of the tile that was found at that position.
public int GridX(float x) {
return (int)(Util.SnapToGrid(x, TileWidth) / TileWidth);
}
///
/// Convert an Y position to a tile on the grid.
///
/// The position to convert.
/// The Y position of the tile that was found at that position.
public int GridY(float y) {
return (int)(Util.SnapToGrid(y, TileHeight) / TileHeight);
}
///
/// The area in tile size.
///
public int TileArea {
get { return TileRows * TileHeight; }
}
///
/// The area in pixels.
///
public int Area {
get { return (int)(Width * Height); }
}
#endregion
#region Constructors
///
/// Create a new GridCollider.
///
/// The width in pixels of the GridCollider.
/// The height in pixels of the GridCollider.
/// The width of each tile on the grid.
/// The heigth of each tile on the grid.
/// The tags to register for the collider.
public GridCollider(int width, int height, int tileWidth, int tileHeight, params int[] tags) {
if (width < 0) throw new ArgumentOutOfRangeException("Width must be greater than 0.");
if (height < 0) throw new ArgumentOutOfRangeException("Height must be greater than 0.");
TileColumns = (int)Util.Ceil((float)width / tileWidth);
TileRows = (int)Util.Ceil((float)height / tileHeight);
TileWidth = tileWidth;
TileHeight = tileHeight;
for (int i = 0; i < TileRows * TileColumns; i++) {
collisions.Add(false);
}
AddTag(tags);
}
public GridCollider(int width, int height, int tileWidth, int tileHeight, Enum tag, params Enum[] tags) : this(width, height, tileWidth, tileHeight) {
AddTag(tag);
AddTag(tags);
}
#endregion
#region Public Methods
///
/// Set the collision status of a tile on the GridCollider.
///
/// The X position of the tile.
/// The Y position of the tile.
/// True if collidable.
public void SetTile(int x, int y, bool collidable = true) {
if (x < 0 || y < 0) return;
if (x >= TileColumns || y >= TileRows) return;
collisions[Util.OneDee((int)TileColumns, (int)x, (int)y)] = collidable;
}
///
/// Set the collision status of a rectangle area on the GridCollider.
///
/// The X position of the top left corner of the rectangle.
/// The Y position of the top left corner of the rectangle.
/// The width in tiles of the rectangle.
/// The height in tiles of the rectangle.
/// True if collidable.
public void SetRect(int x, int y, int width, int height, bool collidable = true) {
for (int i = x; i < x + width; i++) {
for (int j = y; j < y + height; j++) {
SetTile(i, j, collidable);
}
}
}
///
/// Clears the entire grid.
///
/// Optionally set to true to clear grid with collidable cells.
public void Clear(bool collidable = false) {
for (var i = 0; i < collisions.Count; i++) {
collisions[i] = collidable;
}
}
///
/// Get the collision status of a tile on the GridCollider.
///
/// The X position of the tile.
/// The Y position of the tile.
/// True if the tile is collidable.
public bool GetTile(int x, int y) {
if (x < 0 || y < 0) return false;
if (x >= TileColumns || y >= TileRows) return false;
var index = Util.OneDee((int)TileColumns, (int)x, (int)y);
if (index >= collisions.Count) return false;
return collisions[index];
}
///
/// Get the collision status of a tile at a position in the Scene.
///
/// The X position in the Scene.
/// The Y position in the Scene.
/// True if the tile at that position is collidable.
public bool GetTileAtPosition(float x, float y) {
var ox = x;
var oy = y;
x -= Left;
y -= Top;
x = (float)Math.Floor(x / TileWidth);
y = (float)Math.Floor(y / TileHeight);
return GetTile((int)x, (int)y);
}
///
/// Check for a collidable tile in a rectangle on the GridCollider.
///
/// The X position of the top left corner of the rectangle.
/// The Y position of the top left corner of the rectangle.
/// The width in tiles of the rectangle.
/// The height in tiles of the rectangle.
/// True if any tile in the rectangle is collidable.
public bool GetRect(float x, float y, float x2, float y2, bool usingGrid = true) {
//adjust for grid position
x -= Left;
x2 -= Left;
y -= Top;
y2 -= Top;
if (!usingGrid) {
x = (int)(Util.SnapToGrid(x, TileWidth) / TileWidth);
y = (int)(Util.SnapToGrid(y, TileHeight) / TileHeight);
x2 = (int)(Util.SnapToGrid(x2, TileWidth) / TileWidth);
y2 = (int)(Util.SnapToGrid(y2, TileHeight) / TileHeight);
}
for (int i = (int)x; i <= x2; i++) {
for (int j = (int)y; j <= y2; j++) {
if (GetTile(i, j)) {
return true;
}
}
}
return false;
}
///
/// Convert a string into tiles on the GridCollider.
///
/// The source string data.
/// The character representing an empty space on the grid.
/// The character representing a collidable space on the grid.
public void LoadString(string source, char empty = '0', char filled = '1') {
int xx = 0, yy = 0;
for (int i = 0; i < source.Length; i++) {
if (source[i] != empty && source[i] != filled) continue;
if (xx == TileColumns) {
xx = 0;
yy++;
}
SetTile(xx, yy, source[i] == filled);
xx++;
}
}
///
/// Convert a CSV file into tiles on the GridCollider.
///
/// The source CSV data.
/// String representing an empty space on the grid.
/// String representing a collidable space on the grid.
/// The separator character for columns.
/// The separator character for rows.
public void LoadCSV(string source, string empty = "0", string filled = "1", char columnSep = ',', char rowSep = '\n') {
string[] row = source.Split(rowSep);
int rows = row.Length;
string[] col;
int cols;
int x;
int y;
for (y = 0; y < rows; y++) {
if (row[y] == "") {
continue;
}
col = row[y].Split(columnSep);
cols = col.Length;
for (x = 0; x < cols; x++) {
if (col[x].Equals("") || !col[x].Equals(filled)) {
continue;
}
SetTile(x, y, col[x].Equals(filled));
}
}
}
///
/// Load collision data from a Tilemap.
///
/// The source Tilemap.
/// The layer of tiles to use to mark collisions.
public void LoadTilemap(Tilemap source, int layerDepth) {
if (source.Width != Width) throw new ArgumentException("Tilemap size and tile size must match grid size and tile size.");
if (source.Height != Height) throw new ArgumentException("Tilemap size and tile size must match grid size and tile size.");
if (source.TileWidth != TileWidth) throw new ArgumentException("Tilemap size and tile size must match grid size and tile size.");
if (source.TileWidth != TileWidth) throw new ArgumentException("Tilemap size and tile size must match grid size and tile size.");
foreach(var t in source.GetTiles(layerDepth)) {
SetTile(t.X / TileWidth, t.Y / TileHeight, true);
}
}
///
/// Load collision data from a Tilemap.
///
/// The source Tilemap.
/// The layer of tiles to use to mark collisions.
public void LoadTilemap(Tilemap source, string layerName) {
LoadTilemap(source, source.LayerDepth(layerName));
}
///
/// Load collision data from a Tilemap.
///
/// The source Tilemap.
/// The layer of tiles to use to mark collisions.
public void LoadTilemap(Tilemap source, Enum layerName) {
LoadTilemap(source, source.LayerDepth(layerName));
}
///
/// Draw the collider for debug purposes.
///
public override void Render(Color color = null) {
base.Render(color);
if (color == null) color = Color.Red;
if (Entity == null) return;
float viewLeft = Game.Instance.Scene.CameraX - TileWidth;
float viewRight = Game.Instance.Scene.CameraX + Game.Instance.Scene.CameraWidth;
float viewTop = Game.Instance.Scene.CameraY - TileHeight;
float viewBottom = Game.Instance.Scene.CameraY + Game.Instance.Scene.CameraHeight;
for (int i = 0; i < TileColumns; i++) {
for (int j = 0; j < TileRows; j++) {
if (Left + i * TileWidth > viewLeft && Left + i * TileWidth < viewRight && Top + j * TileHeight > viewTop && Top + j * TileHeight < viewBottom )
{
if (GetTile(i, j))
{
Draw.Rectangle(Left + i * TileWidth + 1, Top + j * TileHeight + 1, TileWidth - 2, TileHeight - 2, Color.None, color, 1f);
}
}
}
}
}
#endregion
}
}