{"id":2426,"date":"2022-08-04T17:28:04","date_gmt":"2022-08-04T22:28:04","guid":{"rendered":"https:\/\/badecho.com\/?p=2426"},"modified":"2023-02-20T20:41:08","modified_gmt":"2023-02-21T01:41:08","slug":"drawing-tiles","status":"publish","type":"post","link":"https:\/\/badecho.com\/index.php\/2022\/08\/04\/drawing-tiles\/","title":{"rendered":"Drawing Tiles With MonoGame"},"content":{"rendered":"\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2022\/08\/DrawingTilesMonoGame.png\" alt=\"Drawing Tiles With MonoGame\" class=\"wp-image-2427\" width=\"856\" height=\"456\" srcset=\"https:\/\/badecho.com\/wp-content\/uploads\/2022\/08\/DrawingTilesMonoGame.png 856w, https:\/\/badecho.com\/wp-content\/uploads\/2022\/08\/DrawingTilesMonoGame-300x160.png 300w, https:\/\/badecho.com\/wp-content\/uploads\/2022\/08\/DrawingTilesMonoGame-768x409.png 768w, https:\/\/badecho.com\/wp-content\/uploads\/2022\/08\/DrawingTilesMonoGame-480x256.png 480w\" sizes=\"(max-width: 856px) 100vw, 856px\" \/><\/figure><\/div>\n\n\n\n<p>Recently, I embarked on a new venture in life doing something I never thought I&#8217;d ever want to do: developing my (well, my and my wife&#8217;s) own computer game.<\/p>\n\n\n\n<p>I decided early on that my weapon of choice for learning about and doing the game development would be MonoGame.<\/p>\n\n\n\n<p>One of the first things I wanted to figure out was how to deal with the design and rendering of <em>tile maps<\/em>. After much learning, I recently added rudimentary support for the <a href=\"https:\/\/doc.mapeditor.org\/en\/stable\/reference\/tmx-map-format\/\" target=\"_blank\" rel=\"noreferrer noopener\">TMX map format<\/a> to the <a href=\"https:\/\/github.com\/BadEcho\/core\/tree\/master\/src\/Game\" target=\"_blank\" rel=\"noreferrer noopener\">Bad Echo Game Framework<\/a>. Hooray!<\/p>\n\n\n\n<p>While a lot could be written about how the tile maps work at large, I wanted to focus this article on one small part of the process: the rendering of the individual rectangular tiles.<\/p>\n\n\n\n<h2>How to Approach Rendering Tile Data?<\/h2>\n\n\n\n<p>One of the first rendering mechanisms you&#8217;ll come across when learning to draw 2D graphics with MonoGame is the ubiquitous <code>SpriteBatch<\/code> class. If we&#8217;re interested in rendering any sort of 2D sprite, actor, particle effect, etc., we&#8217;re more than likely going to be doing it using a <code>SpriteBatch<\/code>.<\/p>\n\n\n\n<p>There&#8217;s a reason for this: the <code>SpriteBatch<\/code> class is <em>pretty awesome<\/em> at what it does. It does a great job at rendering most things that you throw at it, batching them in a very efficient manner, and drawing them all to the screen with the result being a highly performant experience.<\/p>\n\n\n\n<h6>Typical SpriteBatch Usage<\/h6>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n_spriteBatch.Begin();\n\n_spriteBatch.Draw(someTexture, new Vector2(200, 200), Color.White);\n_spriteBatch.Draw(someOtherTexture, new Vector2(400, 400), Color.White);\n\/\/ Insert more and more things for the SpriteBatch to draw...\n\n_spriteBatch.End();\n<\/pre>\n\n\n<p>And this will render all of the many objects, actors, particles, or whatever else you feel like drawing just fine.<\/p>\n\n\n\n<p>But what about the sometimes thousands upon thousands of tiles that need to be drawn when rendering a tile map? Does the typical way to render 2D graphics still cut it?<\/p>\n\n\n\n<h3>Screen-wide Tile Maps Require a More Specialized Solution<\/h3>\n\n\n\n<p>For most 2D games, it&#8217;s fairly safe to say that a sizeable majority of a screen&#8217;s real estate is going to be dedicated to the rendering of the active tile map. Tile maps are essentially the all-encompassing <em>backgrounds of the game<\/em>!<\/p>\n\n\n\n<p>Although we might be able to squeak by just using a <code>SpriteBatch<\/code>, there&#8217;s also a very good chance we&#8217;ll start to run into its limitations. Although I&#8217;m still very much learning and cannot call myself an expert yet, I&#8217;ve read numerous times of performance problems with <code>SpriteBatch<\/code> when rendering large areas filled with many static textures (which, aside from animated tiles, basically describes a tile map).<\/p>\n\n\n\n<p>To most efficiently render the large number of tiles found in a typical tile map, we&#8217;ll need to skip past the <code>SpriteBatch<\/code> middle man and communicate more directly with the GPU using <em>vertex <\/em>and <em>index buffers<\/em>, populated with 3D primitive data describing each tile.<\/p>\n\n\n\n<h3>3D Rendering for a 2D Tile Map?<\/h3>\n\n\n\n<p>Well, sure! <\/p>\n\n\n\n<p>Don&#8217;t be fooled, there is no such thing as graphics that are purely 2D with today&#8217;s GPUs. All graphics, in the end, are 3D. It&#8217;s just that, in the case of tiles and other 2D graphics, we&#8217;re working with z-coordinate values that are always zeroed out.<\/p>\n\n\n\n<p>Using vertex and index buffers, we can render thousands of 3D primitives with a single draw call, instead of needing to hit up <code>SpriteBatch.Draw<\/code> for every single tile.<\/p>\n\n\n\n<p>I won&#8217;t be going too much into detail as to how vertex and index buffers work, but there are <a href=\"http:\/\/rbwhitaker.wikidot.com\/index-and-vertex-buffers\" target=\"_blank\" rel=\"noreferrer noopener\">plenty of resources out there <\/a>that&#8217;ll do the job just fine instead.<\/p>\n\n\n\n<h2>How to Render Tiles Using 3D Primitives<\/h2>\n\n\n\n<p>Primitives are the building blocks for 3D graphics, with each primitive usually being a simple triangle. In the end, all those fancy graphics you might see on your screen are probably a really big bunch of triangles smacked together.<\/p>\n\n\n\n<p>So, to render a rectangular tile using 3D primitives, we&#8217;ll need to do so using triangles!<\/p>\n\n\n\n<h3>Getting the Shape Right<\/h3>\n\n\n\n<p>A triangle is not a rectangle; however, if we stick two triangles next to each other: a rectangle is exactly what we get! All we need to do, then, is define vertex data for two triangles that are adjacent to each other, and feed that data to the GPU.<\/p>\n\n\n\n<p>Special consideration must be given, however, to the order in which the vertices for our triangles are indexed.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2022\/08\/TileTriangles.png\" alt=\"Shows how a tile square can be drawn with two triangle primitives.\" class=\"wp-image-2428\" width=\"400\" height=\"400\" srcset=\"https:\/\/badecho.com\/wp-content\/uploads\/2022\/08\/TileTriangles.png 400w, https:\/\/badecho.com\/wp-content\/uploads\/2022\/08\/TileTriangles-300x300.png 300w, https:\/\/badecho.com\/wp-content\/uploads\/2022\/08\/TileTriangles-150x150.png 150w\" sizes=\"(max-width: 400px) 100vw, 400px\" \/><figcaption>A rectangular tile can be formed with two triangle primitives, as long as the vertices for both triangles are defined in a clockwise winding order so culling is avoided.<\/figcaption><\/figure><\/div>\n\n\n\n<p>Both of the triangles must use a clockwise <em>winding order <\/em>for their vertices, as illustrated above. A triangle has a clockwise winding if its three vertices, in order, rotate clockwise around the triangle&#8217;s center.<\/p>\n\n\n\n<p>Using the above two triangles as an example, we&#8217;d want to index the first triangle&#8217;s vertices in the order of <code>(a, b, c)<\/code>, and the second triangle&#8217;s vertices in the order of <code>(b, d, c)<\/code>.<\/p>\n\n\n\n<p>If we define the second triangle&#8217;s vertices using a more natural order of <code>(b, c, d)<\/code>, then that triangle will have a counter-clockwise winding. This is problematic, as polygons with counter-clockwise windings are treated as back-facing, and back-facing polygons are automatically culled (by default) by MonoGame.<\/p>\n\n\n\n<h3>Getting the Texture Right<\/h3>\n\n\n\n<p>If we follow the above steps, we&#8217;ll be able to easily render thousands of rectangular meshes: one for every tile in our maps. That would be a rather pointless endeavor, however, if we didn&#8217;t also map the texture for our tiles onto said meshes.<\/p>\n\n\n\n<p>Tile maps use any number of <em>tile sets<\/em> as sources for the actual tile imaging data meant to appear on the map. Instead of loading a texture for each tile, we simply load a single texture for each tile set. <\/p>\n\n\n\n<p>Using the tile set&#8217;s texture, we can render an individual tile by specifying a bounding rectangle of the region found in said texture where the tile resides. At least, this is how we do it when working with something like a <code>SpriteBatch<\/code>.<\/p>\n\n\n\n<p>When dealing with 3D primitives and vertex buffers, we&#8217;ll instead need to make use of <code>VertexPositionTexture<\/code> values, which allow us to define texture-mapped vertex data. Unfortunately, if we want to use this approach, we need to define the source region of the texture using <em>texels<\/em> as opposed to normal screen coordinate pixels.<\/p>\n\n\n\n<h3>Mapping With Texels<\/h3>\n\n\n\n<p>Texels are essentially pixels normalized to the texture itself, describing a particular position within a texture, as opposed to a position within a greater plane. It is a <a href=\"https:\/\/en.wikipedia.org\/wiki\/UV_mapping\" target=\"_blank\" rel=\"noreferrer noopener\">two-dimensional <code>(u, v)<\/code> vector<\/a> with values ranging from 0 to 1. <\/p>\n\n\n\n<p>Let&#8217;s use the very crappy tile set I made for testing\/development purposes as an example. The tile set is composed of four 16 x 16 pixel tiles, giving it a total size of 64 x 16 pixels.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" src=\"https:\/\/badecho.com\/wp-content\/uploads\/2022\/08\/TileTexels.png\" alt=\"Shows texel coordinates for a 64 x 16 pixel tile set.\" class=\"wp-image-2429\" width=\"300\" height=\"200\"\/><figcaption>A 64 x 16 pixel tile set (lines added to help visualize the four separate tiles) with texel coordinates displayed.<\/figcaption><\/figure><\/div>\n\n\n\n<p>To further elaborate: instead of using <code>(0, 16)<\/code> to point to the bottom left corner of the texture, we&#8217;d use a <code>(0, 1)<\/code> texel.<\/p>\n\n\n\n<p>Once we&#8217;ve figured out how to describe the texture regions being sourced using texel coordinates, we&#8217;ve got everything we need to render our tiles.<\/p>\n\n\n\n<h2>Tile Rendering Code<\/h2>\n\n\n\n<p>The <a href=\"https:\/\/github.com\/BadEcho\/core\/tree\/master\/src\/Game\" target=\"_blank\" rel=\"noreferrer noopener\">Bad Echo Game Framework<\/a> makes available a few types that help organize the generation and rendering of 3D models at runtime. Vertex data is added to and managed by a particular <code>ModelData&lt;TVertex&gt;<\/code> derived class, which is then fed to an <code>IPrimitiveModel<\/code> implementation that renders said data.<\/p>\n\n\n\n<p>For tiles, I make use of the <code>QuadModelData<\/code> type (the term <em>quad<\/em> is a typical term for a four-sided polygon), which generates vertex data exactly in the manner just described in the previous section. <\/p>\n\n\n\n<p>The star of the show is the <code>AddTexture<\/code> method, which takes several specifications about the texture region to map from, and then generates the appropriate vertex data.<\/p>\n\n\n\n<h6>Relevant <a href=\"https:\/\/github.com\/BadEcho\/core\/blob\/master\/src\/Game\/QuadModelData.cs\" target=\"_blank\" rel=\"noreferrer noopener\">QuadModelData<\/a> Code<\/h6>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\npublic void AddTexture(Rectangle textureBounds, Rectangle sourceArea, Vector2 position)\n{\n    float texelLeft = (float) sourceArea.X \/ textureBounds.Width;\n    float texelRight = (float) (sourceArea.X + sourceArea.Width) \/ textureBounds.Width;\n    float texelTop = (float) sourceArea.Y \/ textureBounds.Height;\n    float texelBottom = (float) (sourceArea.Y + sourceArea.Height) \/ textureBounds.Height;\n\n    var vertexTopLeft\n        = new VertexPositionTexture(new Vector3(position, 0),\n                                    new Vector2(texelLeft, texelTop));\n    var vertexTopRight\n        = new VertexPositionTexture(new Vector3(position + new Vector2(sourceArea.Width, 0), 0),\n                                    new Vector2(texelRight, texelTop));\n    var vertexBottomLeft\n        = new VertexPositionTexture(new Vector3(position + new Vector2(0, sourceArea.Height), 0),\n                                    new Vector2(texelLeft, texelBottom));\n    var vertexBottomRight\n        = new VertexPositionTexture(new Vector3(position + new Vector2(sourceArea.Width, \n                                                                       sourceArea.Height), 0),\n                                    new Vector2(texelRight, texelBottom));\n\n    AddIndices(VertexCount);\n\n    Vertices.Add(vertexTopLeft);\n    Vertices.Add(vertexTopRight);\n    Vertices.Add(vertexBottomLeft);\n    Vertices.Add(vertexBottomRight);\n}\n\nprivate void AddIndices(int offset)\n{   \n    Indices.Add((ushort) (0 + offset));\n    Indices.Add((ushort) (1 + offset));\n    Indices.Add((ushort) (2 + offset));\n    Indices.Add((ushort) (1 + offset));\n    Indices.Add((ushort) (3 + offset));\n    Indices.Add((ushort) (2 + offset));\n}\n<\/pre>\n\n\n<p>The output here is fed into an <code>IPrimitiveModel<\/code> implementation, which will load the data into the appropriate types of vertex and index buffers. <code>IPrimitiveModel.Draw<\/code> then takes the loaded buffers and makes the magic happens with a single call to <code>DrawPrimitives<\/code> \/ <code>DrawIndexedPrimitives<\/code>.<\/p>\n\n\n\n<h6>Relevant <a href=\"https:\/\/github.com\/BadEcho\/core\/blob\/master\/src\/Game\/PrimitiveModel.cs\" target=\"_blank\" rel=\"noreferrer noopener\">PrimitiveModel<\/a> Code<\/h6>\n\n\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\npublic void Draw(BasicEffect effect)\n{\n    Require.NotNull(effect, nameof(effect));\n\n    if (_texture != null)\n    {\n        effect.TextureEnabled = true;\n        effect.Texture = _texture;\n    }\n    \n    Device.SetVertexBuffer(VertexBuffer);\n\n    if (IndexBuffer != null)\n        Device.Indices = IndexBuffer;\n\n    foreach (var pass in effect.CurrentTechnique.Passes)\n    {\n        pass.Apply();\n\n        if (IndexBuffer == null)\n            Device.DrawPrimitives(PrimitiveType.TriangleList, 0, _primitiveCount);\n        else\n            Device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, _primitiveCount);\n    }\n}\n<\/pre>\n\n\n<p>And that&#8217;s how 3D rendering techniques end up getting used to render giant blobs of 2D rectangular tile data. Works like a charm!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Recently, I embarked on a new venture in life doing something I never thought I&#8217;d ever want to do: developing [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[10],"tags":[41,42,74,75],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v14.9 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\r\n<title>Drawing Tiles With MonoGame - omni&#039;s hackpad<\/title>\r\n<meta name=\"description\" content=\"An overview of how to implement the efficient rendering of the often copious amounts of tiles found in tile maps for a 2D game using MonoGame.\" \/>\r\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\r\n<link rel=\"canonical\" href=\"https:\/\/badecho.com\/index.php\/2022\/08\/04\/drawing-tiles\/\" \/>\r\n<meta property=\"og:locale\" content=\"en_US\" \/>\r\n<meta property=\"og:type\" content=\"article\" \/>\r\n<meta property=\"og:title\" content=\"Drawing Tiles With MonoGame - omni&#039;s hackpad\" \/>\r\n<meta property=\"og:description\" content=\"An overview of how to implement the efficient rendering of the often copious amounts of tiles found in tile maps for a 2D game using MonoGame.\" \/>\r\n<meta property=\"og:url\" content=\"https:\/\/badecho.com\/index.php\/2022\/08\/04\/drawing-tiles\/\" \/>\r\n<meta property=\"og:site_name\" content=\"omni&#039;s hackpad\" \/>\r\n<meta property=\"article:published_time\" content=\"2022-08-04T22:28:04+00:00\" \/>\r\n<meta property=\"article:modified_time\" content=\"2023-02-21T01:41:08+00:00\" \/>\r\n<meta property=\"og:image\" content=\"https:\/\/badecho.com\/wp-content\/uploads\/2022\/08\/DrawingTilesMonoGame.png\" \/>\r\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\r\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebSite\",\"@id\":\"https:\/\/badecho.com\/#website\",\"url\":\"https:\/\/badecho.com\/\",\"name\":\"omni&#039;s hackpad\",\"description\":\"Game Code Disassembly. Omnified Modification. Madness.\",\"publisher\":{\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":\"https:\/\/badecho.com\/?s={search_term_string}\",\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/badecho.com\/index.php\/2022\/08\/04\/drawing-tiles\/#primaryimage\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/badecho.com\/wp-content\/uploads\/2022\/08\/DrawingTilesMonoGame.png\",\"width\":856,\"height\":456,\"caption\":\"Drawing Tiles With MonoGame\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/badecho.com\/index.php\/2022\/08\/04\/drawing-tiles\/#webpage\",\"url\":\"https:\/\/badecho.com\/index.php\/2022\/08\/04\/drawing-tiles\/\",\"name\":\"Drawing Tiles With MonoGame - omni&#039;s hackpad\",\"isPartOf\":{\"@id\":\"https:\/\/badecho.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2022\/08\/04\/drawing-tiles\/#primaryimage\"},\"datePublished\":\"2022-08-04T22:28:04+00:00\",\"dateModified\":\"2023-02-21T01:41:08+00:00\",\"description\":\"An overview of how to implement the efficient rendering of the often copious amounts of tiles found in tile maps for a 2D game using MonoGame.\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/badecho.com\/index.php\/2022\/08\/04\/drawing-tiles\/\"]}]},{\"@type\":\"Article\",\"@id\":\"https:\/\/badecho.com\/index.php\/2022\/08\/04\/drawing-tiles\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2022\/08\/04\/drawing-tiles\/#webpage\"},\"author\":{\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\"},\"headline\":\"Drawing Tiles With MonoGame\",\"datePublished\":\"2022-08-04T22:28:04+00:00\",\"dateModified\":\"2023-02-21T01:41:08+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2022\/08\/04\/drawing-tiles\/#webpage\"},\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\"},\"image\":{\"@id\":\"https:\/\/badecho.com\/index.php\/2022\/08\/04\/drawing-tiles\/#primaryimage\"},\"keywords\":\".NET,C#,MonoGame,XNA\",\"articleSection\":\"General Dev\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/badecho.com\/index.php\/2022\/08\/04\/drawing-tiles\/#respond\"]}]},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\/\/badecho.com\/#\/schema\/person\/3de67496328be7ae6e1f52faf582e9d2\",\"name\":\"Matt Weber\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/badecho.com\/#personlogo\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/7e345ac2708b3a41c7bd70a4a0440d41?s=96&d=mm&r=g\",\"caption\":\"Matt Weber\"},\"logo\":{\"@id\":\"https:\/\/badecho.com\/#personlogo\"}}]}<\/script>\r\n<!-- \/ Yoast SEO plugin. -->","_links":{"self":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts\/2426"}],"collection":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/comments?post=2426"}],"version-history":[{"count":13,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts\/2426\/revisions"}],"predecessor-version":[{"id":2443,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/posts\/2426\/revisions\/2443"}],"wp:attachment":[{"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/media?parent=2426"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/categories?post=2426"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/badecho.com\/index.php\/wp-json\/wp\/v2\/tags?post=2426"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}