2019-09-18 14:09:15 +02:00
using System ;
2019-12-07 02:24:02 +01:00
using System.Collections.Generic ;
2019-09-18 14:09:15 +02:00
using Microsoft.Xna.Framework ;
using Microsoft.Xna.Framework.Graphics ;
2020-05-22 20:32:38 +02:00
using MLEM.Cameras ;
2019-09-18 14:09:15 +02:00
using MLEM.Extensions ;
2020-03-21 00:49:43 +01:00
using MLEM.Misc ;
2019-09-18 14:09:15 +02:00
using MonoGame.Extended.Tiled ;
2020-03-21 00:49:43 +01:00
using RectangleF = MonoGame . Extended . RectangleF ;
2019-09-18 14:09:15 +02:00
namespace MLEM.Extended.Tiled {
2020-05-22 20:32:38 +02:00
/// <summary>
/// A tiled map renderer that renders each tile individually, while optionally supplying a depth for each tile.
/// Rendering in this manner allows for entities to be behind or in front of buildings based on their y height.
/// </summary>
2019-09-18 14:09:15 +02:00
public class IndividualTiledMapRenderer {
private TiledMap map ;
private TileDrawInfo [ , , ] drawInfos ;
2019-09-19 11:11:47 +02:00
private GetDepth depthFunction ;
2019-12-07 02:24:02 +01:00
private List < TiledMapTilesetAnimatedTile > animatedTiles ;
2019-09-18 14:09:15 +02:00
2020-05-22 20:32:38 +02:00
/// <summary>
/// Creates a new individual tiled map renderer using the given map and depth function
/// </summary>
/// <param name="map">The map to use</param>
/// <param name="depthFunction">The depth function to use</param>
2019-09-18 18:47:10 +02:00
public IndividualTiledMapRenderer ( TiledMap map = null , GetDepth depthFunction = null ) {
2019-09-18 14:09:15 +02:00
if ( map ! = null )
2019-09-18 18:47:10 +02:00
this . SetMap ( map , depthFunction ) ;
2019-09-18 14:09:15 +02:00
}
2020-05-22 20:32:38 +02:00
/// <summary>
/// Sets this individual tiled map renderer's map and depth function
/// </summary>
/// <param name="map">The map to use</param>
/// <param name="depthFunction">The depth function to use</param>
2019-09-18 18:47:10 +02:00
public void SetMap ( TiledMap map , GetDepth depthFunction = null ) {
2019-09-18 14:09:15 +02:00
this . map = map ;
2019-09-19 11:11:47 +02:00
this . depthFunction = depthFunction ? ? ( ( tile , layer , layerIndex , position ) = > 0 ) ;
2019-09-18 14:09:15 +02:00
this . drawInfos = new TileDrawInfo [ map . TileLayers . Count , map . Width , map . Height ] ;
for ( var i = 0 ; i < map . TileLayers . Count ; i + + ) {
for ( var x = 0 ; x < map . Width ; x + + ) {
for ( var y = 0 ; y < map . Height ; y + + ) {
2019-09-19 11:11:47 +02:00
this . UpdateDrawInfo ( i , x , y ) ;
2019-09-18 14:09:15 +02:00
}
}
}
2019-12-07 02:24:02 +01:00
this . animatedTiles = new List < TiledMapTilesetAnimatedTile > ( ) ;
foreach ( var tileset in map . Tilesets ) {
foreach ( var tile in tileset . Tiles ) {
if ( tile is TiledMapTilesetAnimatedTile animated ) {
this . animatedTiles . Add ( animated ) ;
}
}
}
2019-09-18 14:09:15 +02:00
}
2020-05-22 20:32:38 +02:00
/// <summary>
/// Updates the rendering information for the tile in the given layer, x and y.
/// </summary>
/// <param name="layerIndex">The index of the layer in <see cref="TiledMap.TileLayers"/></param>
/// <param name="x">The x coordinate of the tile</param>
/// <param name="y">The y coordinate of the tile</param>
2019-09-19 11:11:47 +02:00
public void UpdateDrawInfo ( int layerIndex , int x , int y ) {
var layer = this . map . TileLayers [ layerIndex ] ;
2019-09-24 12:26:38 +02:00
var tile = layer . GetTile ( x , y ) ;
2019-12-01 16:57:34 +01:00
if ( tile . IsBlank ) {
this . drawInfos [ layerIndex , x , y ] = null ;
2019-09-19 11:11:47 +02:00
return ;
2019-12-01 16:57:34 +01:00
}
2019-09-19 11:11:47 +02:00
var tileset = tile . GetTileset ( this . map ) ;
2019-12-07 02:24:02 +01:00
var tilesetTile = tileset . GetTilesetTile ( tile , this . map ) ;
2019-09-19 11:11:47 +02:00
var pos = new Point ( x , y ) ;
var depth = this . depthFunction ( tile , layer , layerIndex , pos ) ;
2020-01-01 12:18:35 +01:00
this . drawInfos [ layerIndex , x , y ] = new TileDrawInfo ( this , tile , tileset , tilesetTile , pos , depth ) ;
2019-09-19 11:11:47 +02:00
}
2020-05-22 20:32:38 +02:00
/// <summary>
/// Draws this individual tiled map renderer.
/// Optionally, a frustum can be supplied that determines which positions, in pixel space, are visible at this time. <see cref="Camera"/> provides <see cref="Camera.GetVisibleRectangle"/> for this purpose.
/// </summary>
/// <param name="batch">The sprite batch to use</param>
/// <param name="frustum">The area that is visible, in pixel space.</param>
/// <param name="drawFunction">The draw function to use, or null for default</param>
2020-04-29 22:14:33 +02:00
public void Draw ( SpriteBatch batch , RectangleF ? frustum = null , DrawDelegate drawFunction = null ) {
2020-04-30 15:57:20 +02:00
for ( var i = 0 ; i < this . map . TileLayers . Count ; i + + ) {
if ( this . map . TileLayers [ i ] . IsVisible )
this . DrawLayer ( batch , i , frustum , drawFunction ) ;
}
2019-09-18 14:09:15 +02:00
}
2020-05-22 20:32:38 +02:00
/// <summary>
/// Draws the given layer of this individual tiled map renderer.
/// Optionally, a frustum can be supplied that determines which positions, in pixel space, are visible at this time. <see cref="Camera"/> provides <see cref="Camera.GetVisibleRectangle"/> for this purpose.
/// </summary>
/// <param name="batch">The sprite batch to use</param>
/// <param name="layerIndex">The index of the layer in <see cref="TiledMap.TileLayers"/></param>
/// <param name="frustum">The area that is visible, in pixel space.</param>
/// <param name="drawFunction">The draw function to use, or null for default</param>
2020-04-29 22:14:33 +02:00
public void DrawLayer ( SpriteBatch batch , int layerIndex , RectangleF ? frustum = null , DrawDelegate drawFunction = null ) {
2019-09-18 14:09:15 +02:00
var frust = frustum ? ? new RectangleF ( 0 , 0 , float . MaxValue , float . MaxValue ) ;
var minX = Math . Max ( 0 , frust . Left / this . map . TileWidth ) . Floor ( ) ;
var minY = Math . Max ( 0 , frust . Top / this . map . TileHeight ) . Floor ( ) ;
var maxX = Math . Min ( this . map . Width , frust . Right / this . map . TileWidth ) . Ceil ( ) ;
var maxY = Math . Min ( this . map . Height , frust . Bottom / this . map . TileHeight ) . Ceil ( ) ;
for ( var x = minX ; x < maxX ; x + + ) {
for ( var y = minY ; y < maxY ; y + + ) {
var info = this . drawInfos [ layerIndex , x , y ] ;
if ( info ! = null )
2020-04-29 22:14:33 +02:00
info . Draw ( batch , drawFunction ) ;
2019-09-18 14:09:15 +02:00
}
}
}
2020-05-22 20:32:38 +02:00
/// <summary>
/// Update all of the animated tiles in this individual tiled map renderer
/// </summary>
/// <param name="time">The game's time</param>
2019-12-07 02:24:02 +01:00
public void UpdateAnimations ( GameTime time ) {
foreach ( var animation in this . animatedTiles )
animation . Update ( time ) ;
}
2020-05-22 20:32:38 +02:00
/// <summary>
/// A delegate method used for <see cref="IndividualTiledMapRenderer.depthFunction"/>.
/// The idea is to return a depth (between 0 and 1) for the given tile that determines where in the sprite batch it should be rendererd.
/// Note that, for this depth function to take effect, the sprite batch needs to begin with <see cref="SpriteSortMode.FrontToBack"/> or <see cref="SpriteSortMode.BackToFront"/>.
/// </summary>
/// <param name="tile">The tile whose depth to get</param>
/// <param name="layer">The layer the tile is on</param>
/// <param name="layerIndex">The index of the layer in <see cref="TiledMap.TileLayers"/></param>
/// <param name="position">The tile position of this tile</param>
2019-09-18 14:09:15 +02:00
public delegate float GetDepth ( TiledMapTile tile , TiledMapTileLayer layer , int layerIndex , Point position ) ;
2020-05-22 20:32:38 +02:00
/// <summary>
/// A delegate method used for drawing an <see cref="IndividualTiledMapRenderer"/>.
/// </summary>
/// <param name="batch">The sprite batch to use for drawing</param>
/// <param name="info">The <see cref="TileDrawInfo"/> to draw</param>
2020-04-29 22:14:33 +02:00
public delegate void DrawDelegate ( SpriteBatch batch , TileDrawInfo info ) ;
2020-05-22 20:32:38 +02:00
/// <summary>
/// A tile draw info contains information about a tile at a given map location.
/// It caches a lot of data that is required for drawing a tile efficiently.
/// </summary>
2020-04-29 22:14:33 +02:00
public class TileDrawInfo : GenericDataHolder {
2019-09-18 14:09:15 +02:00
2020-05-22 20:32:38 +02:00
/// <summary>
/// The renderer used by this info
/// </summary>
2020-04-29 22:14:33 +02:00
public readonly IndividualTiledMapRenderer Renderer ;
2020-05-22 20:32:38 +02:00
/// <summary>
/// The tiled map tile to draw
/// </summary>
2020-04-29 22:14:33 +02:00
public readonly TiledMapTile Tile ;
2020-05-22 20:32:38 +02:00
/// <summary>
/// The tileset that <see cref="Tile"/> is on
/// </summary>
2020-04-29 22:14:33 +02:00
public readonly TiledMapTileset Tileset ;
2020-05-22 20:32:38 +02:00
/// <summary>
/// The tileset tile that corresponds to <see cref="Tile"/>
/// </summary>
2020-04-29 22:14:33 +02:00
public readonly TiledMapTilesetTile TilesetTile ;
2020-05-22 20:32:38 +02:00
/// <summary>
/// The position, in tile space, of <see cref="Tile"/>
/// </summary>
2020-04-29 22:14:33 +02:00
public readonly Point Position ;
2020-05-22 20:32:38 +02:00
/// <summary>
/// The depth calculated by the depth function
/// </summary>
2020-04-29 22:14:33 +02:00
public readonly float Depth ;
2019-09-18 14:09:15 +02:00
2020-05-22 20:32:38 +02:00
internal TileDrawInfo ( IndividualTiledMapRenderer renderer , TiledMapTile tile , TiledMapTileset tileset , TiledMapTilesetTile tilesetTile , Point position , float depth ) {
2020-04-29 22:14:33 +02:00
this . Renderer = renderer ;
this . Tile = tile ;
this . Tileset = tileset ;
this . TilesetTile = tilesetTile ;
this . Position = position ;
this . Depth = depth ;
2019-09-18 14:09:15 +02:00
}
2020-05-22 20:32:38 +02:00
/// <summary>
/// Draws this tile draw info with the default settings.
/// </summary>
/// <param name="batch">The sprite batch to use for drawing</param>
/// <param name="drawFunction">The draw function used to draw, or null if there is no override</param>
2020-04-29 22:14:33 +02:00
public void Draw ( SpriteBatch batch , DrawDelegate drawFunction ) {
if ( drawFunction = = null ) {
var region = this . Tileset . GetTextureRegion ( this . TilesetTile ) ;
var effects = this . Tile . GetSpriteEffects ( ) ;
var drawPos = new Vector2 ( this . Position . X * this . Renderer . map . TileWidth , this . Position . Y * this . Renderer . map . TileHeight ) ;
batch . Draw ( this . Tileset . Texture , drawPos , region , Color . White , 0 , Vector2 . Zero , 1 , effects , this . Depth ) ;
} else {
drawFunction ( batch , this ) ;
}
2019-09-18 14:09:15 +02:00
}
}
}
}