-
Notifications
You must be signed in to change notification settings - Fork 19
/
ExampleRenderer.cs
325 lines (278 loc) · 12.7 KB
/
ExampleRenderer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
namespace LDtk.Renderer;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using LDtk;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
/// <summary>
/// Renderer for the ldtkWorld, ldtkLevel, intgrids and entities.
/// This can all be done in your own class if you want to reimplement it and customize it differently
/// this one is mostly here to get you up and running quickly.
/// </summary>
public class ExampleRenderer : IDisposable
{
/// <summary> Gets or sets the spritebatch used for rendering with this Renderer. </summary>
public SpriteBatch SpriteBatch { get; set; }
/// <summary> Gets or sets the levels identifier to layers Dictionary. </summary>
Dictionary<string, RenderedLevel> PrerenderedLevels { get; } = [];
/// <summary> Gets or sets the levels identifier to layers Dictionary. </summary>
Dictionary<string, Texture2D> TilemapCache { get; } = [];
readonly Texture2D pixel;
readonly Texture2D error;
readonly GraphicsDevice graphicsDevice;
readonly ContentManager? content;
/// <summary> Initializes a new instance of the <see cref="ExampleRenderer"/> class. This is used to intizialize the renderer for use with direct file loading. </summary>
/// <param name="spriteBatch">Spritebatch</param>
public ExampleRenderer(SpriteBatch spriteBatch)
{
SpriteBatch = spriteBatch;
graphicsDevice = spriteBatch.GraphicsDevice;
if (pixel == null)
{
pixel = new Texture2D(graphicsDevice, 1, 1);
pixel.SetData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF });
}
if (error == null)
{
error = new Texture2D(graphicsDevice, 2, 2);
error.SetData(
new byte[]
{
0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF,
0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00,
}
);
}
}
/// <summary> Initializes a new instance of the <see cref="ExampleRenderer"/> class. This is used to intizialize the renderer for use with content Pipeline. </summary>
/// <param name="spriteBatch">SpriteBatch</param>
/// <param name="content">Optional ContentManager</param>
public ExampleRenderer(SpriteBatch spriteBatch, ContentManager content)
: this(spriteBatch)
{
this.content = content;
}
/// <summary> Prerender out the level to textures to optimize the rendering process. </summary>
/// <param name="level">The level to prerender.</param>
/// <returns>The prerendered level.</returns>
/// <exception cref="Exception">The level already has been prerendered.</exception>
public RenderedLevel PrerenderLevel(LDtkLevel level)
{
if (PrerenderedLevels.TryGetValue(level.Identifier, out RenderedLevel cachedLevel))
{
return cachedLevel;
}
RenderedLevel renderLevel = new();
SpriteBatch.Begin(samplerState: SamplerState.PointClamp);
{
renderLevel.Layers = RenderLayers(level);
}
SpriteBatch.End();
PrerenderedLevels.Add(level.Identifier, renderLevel);
graphicsDevice.SetRenderTarget(null);
return renderLevel;
}
Texture2D[] RenderLayers(LDtkLevel level)
{
List<Texture2D> layers = [];
if (level.BgRelPath != null)
{
layers.Add(RenderBackgroundToLayer(level));
}
if (level.LayerInstances == null)
{
if (level.ExternalRelPath != null)
{
throw new LDtkException("Level has not been loaded.");
}
else
{
throw new LDtkException("Level has no layers.");
}
}
// Render Tile, Auto and Int grid layers
for (int i = level.LayerInstances.Length - 1; i >= 0; i--)
{
LayerInstance layer = level.LayerInstances[i];
if (layer._TilesetRelPath == null)
{
continue;
}
if (layer._Type == LayerType.Entities)
{
continue;
}
Texture2D texture = GetTexture(level, layer._TilesetRelPath);
int width = layer._CWid * layer._GridSize;
int height = layer._CHei * layer._GridSize;
RenderTarget2D renderTarget = new(graphicsDevice, width, height, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents);
graphicsDevice.SetRenderTarget(renderTarget);
layers.Add(renderTarget);
switch (layer._Type)
{
case LayerType.Tiles:
foreach (TileInstance tile in layer.GridTiles.Where(_ => layer._TilesetDefUid.HasValue))
{
Vector2 position = new(tile.Px.X + layer._PxTotalOffsetX, tile.Px.Y + layer._PxTotalOffsetY);
Rectangle rect = new(tile.Src.X, tile.Src.Y, layer._GridSize, layer._GridSize);
SpriteEffects mirror = (SpriteEffects)tile.F;
SpriteBatch.Draw(texture, position, rect, new Color(1f, 1f, 1f, layer._Opacity), 0, Vector2.Zero, 1f, mirror, 0);
}
break;
case LayerType.AutoLayer:
case LayerType.IntGrid:
if (layer.AutoLayerTiles.Length > 0)
{
foreach (TileInstance tile in layer.AutoLayerTiles.Where(_ => layer._TilesetDefUid.HasValue))
{
Vector2 position = new(tile.Px.X + layer._PxTotalOffsetX, tile.Px.Y + layer._PxTotalOffsetY);
Rectangle rect = new(tile.Src.X, tile.Src.Y, layer._GridSize, layer._GridSize);
SpriteEffects mirror = (SpriteEffects)tile.F;
SpriteBatch.Draw(texture, position, rect, new Color(1f, 1f, 1f, layer._Opacity), 0, Vector2.Zero, 1f, mirror, 0);
}
}
break;
}
}
return layers.ToArray();
}
RenderTarget2D RenderBackgroundToLayer(LDtkLevel level)
{
Texture2D texture = GetTexture(level, level.BgRelPath);
RenderTarget2D layer = new(graphicsDevice, level.PxWid, level.PxHei, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents);
graphicsDevice.SetRenderTarget(layer);
{
LevelBackgroundPosition? bg = level._BgPos;
if (bg != null)
{
Vector2 pos = bg.TopLeftPx.ToVector2();
SpriteBatch.Draw(texture, pos, new Rectangle((int)bg.CropRect[0], (int)bg.CropRect[1], (int)bg.CropRect[2], (int)bg.CropRect[3]), Color.White, 0, Vector2.Zero, bg.Scale, SpriteEffects.None, 0);
}
}
graphicsDevice.SetRenderTarget(null);
return layer;
}
Texture2D GetTexture(LDtkLevel level, string? path)
{
if (path == null)
{
throw new LDtkException("Tileset path is null.");
}
if (TilemapCache.TryGetValue(path, out Texture2D? texture))
{
return texture;
}
Texture2D tilemap;
if (content == null)
{
string directory = Path.GetDirectoryName(level.WorldFilePath)!;
string assetName = Path.Join(directory, path);
tilemap = Texture2D.FromFile(graphicsDevice, assetName);
}
else
{
string file = Path.ChangeExtension(path, null);
string directory = Path.GetDirectoryName(level.WorldFilePath)!;
string assetName = Path.Join(directory, file);
tilemap = content.Load<Texture2D>(assetName);
}
TilemapCache.Add(path, tilemap);
return tilemap;
}
/// <summary> Render the prerendered level you created from PrerenderLevel(). </summary>
/// <param name="level">Level to prerender</param>
/// <exception cref="LDtkException"></exception>
public void RenderPrerenderedLevel(LDtkLevel level)
{
if (PrerenderedLevels.TryGetValue(level.Identifier, out RenderedLevel prerenderedLevel))
{
for (int i = 0; i < prerenderedLevel.Layers.Length; i++)
{
SpriteBatch.Draw(prerenderedLevel.Layers[i], level.Position.ToVector2(), Color.White);
}
}
else
{
throw new LDtkException($"No prerendered level with Identifier {level.Identifier} found.");
}
}
/// <summary> Render the level directly without prerendering the layers alot slower than prerendering. </summary>
/// <param name="level">Level to render</param>
public void RenderLevel(LDtkLevel level)
{
ArgumentNullException.ThrowIfNull(level);
Texture2D[] layers = RenderLayers(level);
for (int i = 0; i < layers.Length; i++)
{
SpriteBatch.Draw(layers[i], level.Position.ToVector2(), Color.White);
}
}
/// <summary> Render intgrids by displaying the intgrid as solidcolor squares. </summary>
/// <param name="intGrid">Render intgrid</param>
public void RenderIntGrid(LDtkIntGrid intGrid)
{
for (int x = 0; x < intGrid.GridSize.X; x++)
{
for (int y = 0; y < intGrid.GridSize.Y; y++)
{
int cellValue = intGrid.Values[(y * intGrid.GridSize.X) + x];
if (cellValue != 0)
{
// Color col = intGrid.GetColorFromValue(cellValue);
int spriteX = intGrid.WorldPosition.X + (x * intGrid.TileSize);
int spriteY = intGrid.WorldPosition.Y + (y * intGrid.TileSize);
SpriteBatch.Draw(pixel, new Vector2(spriteX, spriteY), null, Color.Pink /*col*/, 0, Vector2.Zero, new Vector2(intGrid.TileSize), SpriteEffects.None, 0);
}
}
}
}
/// <summary> Renders the entity with the tile it includes. </summary>
/// <param name="entity">The entity you want to render.</param>
/// <param name="texture">The spritesheet/texture for rendering the entity.</param>
public void RenderEntity<T>(T entity, Texture2D texture)
where T : ILDtkEntity
{
SpriteBatch.Draw(texture, entity.Position, entity.Tile, Color.White, 0, entity.Pivot * entity.Size, 1, SpriteEffects.None, 0);
}
/// <summary> Renders the entity with the tile it includes. </summary>
/// <param name="entity">The entity you want to render.</param>
/// <param name="texture">The spritesheet/texture for rendering the entity.</param>
/// <param name="flipDirection">The direction to flip the entity when rendering.</param>
public void RenderEntity<T>(T entity, Texture2D texture, SpriteEffects flipDirection)
where T : ILDtkEntity
{
SpriteBatch.Draw(texture, entity.Position, entity.Tile, Color.White, 0, entity.Pivot * entity.Size, 1, flipDirection, 0);
}
/// <summary> Renders the entity with the tile it includes. </summary>
/// <param name="entity">The entity you want to render.</param>
/// <param name="texture">The spritesheet/texture for rendering the entity.</param>
/// <param name="animationFrame">The current frame of animation. Is a very basic entity animation frames must be to the right of them and be the same size.</param>
public void RenderEntity<T>(T entity, Texture2D texture, int animationFrame)
where T : ILDtkEntity
{
Rectangle animatedTile = entity.Tile;
animatedTile.Offset(animatedTile.Width * animationFrame, 0);
SpriteBatch.Draw(texture, entity.Position, animatedTile, Color.White, 0, entity.Pivot * entity.Size, 1, SpriteEffects.None, 0);
}
/// <summary> Renders the entity with the tile it includes. </summary>
/// <param name="entity">The entity you want to render.</param>
/// <param name="texture">The spritesheet/texture for rendering the entity.</param>
/// <param name="flipDirection">The direction to flip the entity when rendering.</param>
/// <param name="animationFrame">The current frame of animation. Is a very basic entity animation frames must be to the right of them and be the same size.</param>
public void RenderEntity<T>(T entity, Texture2D texture, SpriteEffects flipDirection, int animationFrame)
where T : ILDtkEntity
{
Rectangle animatedTile = entity.Tile;
animatedTile.Offset(animatedTile.Width * animationFrame, 0);
SpriteBatch.Draw(texture, entity.Position, animatedTile, Color.White, 0, entity.Pivot * entity.Size, 1, flipDirection, 0);
}
/// <summary> Dispose Renderer </summary>
public void Dispose()
{
pixel.Dispose();
GC.SuppressFinalize(this);
}
}