{"id":36,"date":"2022-06-23T14:35:27","date_gmt":"2022-06-23T14:35:27","guid":{"rendered":"https:\/\/trew.tk\/blog\/?p=36"},"modified":"2023-12-05T09:34:46","modified_gmt":"2023-12-05T09:34:46","slug":"creating-a-world-generator","status":"publish","type":"post","link":"https:\/\/trewest.dev\/blog\/index.php\/2022\/06\/23\/creating-a-world-generator\/","title":{"rendered":"Creating A World Generator"},"content":{"rendered":"<div class=\"wp-post-series-box series-susminer wp-post-series-box--expandable\">\n\t\t\t<input id=\"collapsible-series-susminer69dec4d680185\" class=\"wp-post-series-box__toggle_checkbox\" type=\"checkbox\">\n\t\n\t<label\n\t\tclass=\"wp-post-series-box__label\"\n\t\t\t\t\tfor=\"collapsible-series-susminer69dec4d680185\"\n\t\t\ttabindex=\"0\"\n\t\t\t\t>\n\t\t<p class=\"wp-post-series-box__name wp-post-series-name\">\n\t\t\tThis is post 2 of 4 in the series <em>&ldquo;Susminer&rdquo;<\/em>\t\t<\/p>\n\t\t\t\t\t<div class=\"wp-post-series-box__description wp-post-series-description\">\n\t\t\t\t<p>A game that never was.  Posts related to Procedural Generation and coding.<\/p>\n\t\t\t<\/div>\n\t\t\t<\/label>\n\n\t\t\t<div class=\"wp-post-series-box__posts\">\n\t\t\t<ol>\n\t\t\t\t\t\t\t\t\t<li><a href=\"https:\/\/trewest.dev\/blog\/index.php\/2022\/06\/23\/procedural-generation-heatmaps\/\">Procedural Generation Heatmaps<\/a><\/li>\n\t\t\t\t\t\t\t\t\t<li><span class=\"wp-post-series-box__current\">Creating A World Generator<\/span><\/li>\n\t\t\t\t\t\t\t\t\t<li><a href=\"https:\/\/trewest.dev\/blog\/index.php\/2022\/06\/24\/graphical-programming-weirdness\/\">Graphical Programming Weirdness<\/a><\/li>\n\t\t\t\t\t\t\t\t\t<li><a href=\"https:\/\/trewest.dev\/blog\/index.php\/2023\/01\/12\/project-susminer\/\">Project: Susminer<\/a><\/li>\n\t\t\t\t\t\t\t<\/ol>\n\t\t<\/div>\n\t<\/div>\n\n<p>When developing Susminer, a small 2d building\/mining survival game, I needed to create a procedural world generator.  And so I jumped head first by making a tile system, and then a basic bit of code to generate a small world, which gave me something like this.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"341\" height=\"604\" src=\"https:\/\/trew.tk\/blog\/wp-content\/uploads\/2022\/06\/image-8.png\" alt=\"\" class=\"wp-image-38\" srcset=\"https:\/\/trewest.dev\/blog\/wp-content\/uploads\/2022\/06\/image-8.png 341w, https:\/\/trewest.dev\/blog\/wp-content\/uploads\/2022\/06\/image-8-169x300.png 169w\" sizes=\"auto, (max-width: 341px) 100vw, 341px\" \/><\/figure>\n\n\n\n<p>So, then with this code working a bit I decided to make it expand out a bit horizontally.  And then I also threw in some code to give it some extra variation on the bottom of the island.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"735\" height=\"597\" src=\"https:\/\/trew.tk\/blog\/wp-content\/uploads\/2022\/06\/image-9.png\" alt=\"\" class=\"wp-image-39\" srcset=\"https:\/\/trewest.dev\/blog\/wp-content\/uploads\/2022\/06\/image-9.png 735w, https:\/\/trewest.dev\/blog\/wp-content\/uploads\/2022\/06\/image-9-300x244.png 300w\" sizes=\"auto, (max-width: 735px) 100vw, 735px\" \/><\/figure>\n\n\n\n<p>Then, I made it start with nearly no height, and tried to make it generate in a near semi-circle design.  But it&#8217;s far from perfect.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"550\" height=\"408\" src=\"https:\/\/trew.tk\/blog\/wp-content\/uploads\/2022\/06\/image-10.png\" alt=\"\" class=\"wp-image-40\" srcset=\"https:\/\/trewest.dev\/blog\/wp-content\/uploads\/2022\/06\/image-10.png 550w, https:\/\/trewest.dev\/blog\/wp-content\/uploads\/2022\/06\/image-10-300x223.png 300w\" sizes=\"auto, (max-width: 550px) 100vw, 550px\" \/><\/figure>\n\n\n\n<p>With some extra work, I got some more variety and tuned it to something a bit better.  Procedural generation is really hard to get perfect, but &#8220;pretty good&#8221; is a fine metric for something like this.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"725\" height=\"529\" src=\"https:\/\/trew.tk\/blog\/wp-content\/uploads\/2022\/06\/image-11.png\" alt=\"\" class=\"wp-image-41\" srcset=\"https:\/\/trewest.dev\/blog\/wp-content\/uploads\/2022\/06\/image-11.png 725w, https:\/\/trewest.dev\/blog\/wp-content\/uploads\/2022\/06\/image-11-300x219.png 300w\" sizes=\"auto, (max-width: 725px) 100vw, 725px\" \/><\/figure>\n\n\n\n<p> <\/p>\n\n\n\n<p>Oh, and I also eventually added ore clusters which just replaces some blocks with an ore block.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"724\" height=\"589\" src=\"https:\/\/trew.tk\/blog\/wp-content\/uploads\/2022\/06\/image-12.png\" alt=\"\" class=\"wp-image-42\" srcset=\"https:\/\/trewest.dev\/blog\/wp-content\/uploads\/2022\/06\/image-12.png 724w, https:\/\/trewest.dev\/blog\/wp-content\/uploads\/2022\/06\/image-12-300x244.png 300w\" sizes=\"auto, (max-width: 724px) 100vw, 724px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"828\" height=\"445\" src=\"https:\/\/trew.tk\/blog\/wp-content\/uploads\/2022\/06\/image-13.png\" alt=\"\" class=\"wp-image-43\" srcset=\"https:\/\/trewest.dev\/blog\/wp-content\/uploads\/2022\/06\/image-13.png 828w, https:\/\/trewest.dev\/blog\/wp-content\/uploads\/2022\/06\/image-13-300x161.png 300w, https:\/\/trewest.dev\/blog\/wp-content\/uploads\/2022\/06\/image-13-768x413.png 768w\" sizes=\"auto, (max-width: 828px) 100vw, 828px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"438\" src=\"https:\/\/trew.tk\/blog\/wp-content\/uploads\/2022\/06\/image-14-1024x438.png\" alt=\"\" class=\"wp-image-44\" srcset=\"https:\/\/trewest.dev\/blog\/wp-content\/uploads\/2022\/06\/image-14-1024x438.png 1024w, https:\/\/trewest.dev\/blog\/wp-content\/uploads\/2022\/06\/image-14-300x128.png 300w, https:\/\/trewest.dev\/blog\/wp-content\/uploads\/2022\/06\/image-14-768x329.png 768w, https:\/\/trewest.dev\/blog\/wp-content\/uploads\/2022\/06\/image-14.png 1117w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Also, here&#8217;s the mess of the code I had for this at the end.  Don&#8217;t worry, it got cleaned up later.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    \/\/\/C#, as it is in this final image\nvoid GenerateMap() \n    {\n        Debug.Log(\"Generating Map\");\n        Vector2 center = new Vector2(0, 0);\n        BlockData grass = (BlockData) blocks&#91;1];\n        BlockData dirt = (BlockData)blocks&#91;0];\n        BlockData stone = (BlockData)blocks&#91;3];\n        BlockData ironOre = (BlockData)blocks&#91;4];\n\n        int maxWidth = 80;\n        int maxHeight = 40;\n\n        int minWidth = 40;\n        int minHeight = 20;\n\n        int heightDiff = maxHeight - minHeight;\n        int widthDiff = maxWidth - minWidth;\n\n        int height = ((int)(Random.value * heightDiff) + minHeight);\n        int width = ((int)(Random.value * widthDiff) + minWidth);\n\n        int coreWidth = (int) (width \/ 3);\n        int coreHeight = (int)(height);\n        int baseHeight = 2;\n\n        int prevHeightVar = (int) (Random.value * 4);\n        int curHeight = baseHeight;\n\n        int maxPosYVar = 5;\n        int maxNegYVar = 5;\n\n        int posYVar = 1;\n        int negYVar = 1;\n\n        Debug.Log($\"Height: {height}, Width: {width} \");\n        \n        for (int x = (width\/2) * -1; x &lt; width\/2; x++) \n        {\n            int calcX = x + width \/ 2;\n            int coreHeightDiff = coreHeight - curHeight;\n            int coreWidthDiff = coreWidth - calcX;\n            int coreWidthDiffLate = (coreWidth * 2);\n            float dist =0;\n            if (coreHeightDiff &gt; 0 &amp;&amp; coreWidthDiff &gt; 0)\n            {\n                dist = (coreHeightDiff \/ coreWidthDiff);\n                Debug.Log($\" coreWidthDiff: {coreWidthDiff} , coreHeightDiff: {coreHeightDiff} DIST: {dist}\");\n            }\n            else \n            {\n                dist = ((0 - curHeight) \/ (width - calcX) * 2);\n            }\n\n            \n            \n\n            if (coreWidthDiff == 0) \n            {\n                Debug.Log($\"Core Width Diff equals zero\");\n                coreWidthDiff = 1;\n            }\n\n            if (calcX &lt; coreWidth)\n            {\n                if (curHeight &lt; coreHeight)\n                {\n                    int temp = (int)(Random.value * (dist) );\n                    curHeight += temp;\n                    Debug.Log($\"CurHeight added to by {temp}\");\n                }\n                else \n                {\n                    curHeight -= (int)(Random.value * 3);\n                }\n            } else if (calcX &gt;= coreWidth &amp;&amp; calcX &lt; (coreWidth * 2))\n            {\n                curHeight = coreHeight;\n                Debug.Log(\"Is Centered\");\n            }\n            else \n            {\n                Debug.Log($\"CurHeight added to by {dist}\");\n                curHeight += (int)(Random.value * (dist));\n                \n            }\n\n\n            if (curHeight &lt;= 0) { curHeight = 1; }\n\n            if (posYVar &lt; maxPosYVar)\n            {\n                int coinflip = (int)(Random.value * 5);\n                if (coinflip &gt; 0 &amp;&amp; coinflip &lt;= 2 )\n                {\n                    posYVar += (int)(Random.value * 2);\n                }\n                else if (coinflip == 0)\n                {\n                    posYVar -= (int)(Random.value * 2);\n                }\n            }\n            else \n            {\n                posYVar -= (int)(Random.value * 2);\n            }\n\n            if (posYVar &lt; 0) { posYVar = 2; }\n\n            if (negYVar &lt; maxNegYVar)\n            {\n                int coinflip = (int)(Random.value * 2);\n                if (coinflip &gt; 0)\n                {\n                    negYVar += (int)(Random.value * 4);\n                }\n                else\n                {\n                    negYVar -= (int)(Random.value * 2);\n                }\n            }\n            else\n            {\n                negYVar -= (int)(Random.value * 4);\n            }\n\n            if (negYVar &lt;= 0) { negYVar = 1; }\n\n            int startHeight = posYVar;\n            int endHeight =  (curHeight + negYVar) * -1;\n            Debug.Log($\"StartHeight: {startHeight} End Height: {endHeight}\");\n            for (int y = startHeight; y &gt;  endHeight; y--) \n            {\n                if (y == startHeight)\n                {\n                    map.Add(new BlockInstance(grass,(short) x, (short)y));\n                }\n                else if (y &lt; startHeight &amp;&amp; y &gt;= startHeight - 2)\n                {\n                    map.Add(new BlockInstance(dirt, (short)x, (short)y));\n                }\n                else \n                {\n                    map.Add(new BlockInstance(stone, (short)x, (short)y));\n                }\n            }\n        }\n\n        \/\/Adding Ore Clusters\n        int clusterSizeMin = 4;\n        int clusterSizeMax = 16;\n\n        int maxClusters = 12;\n        int minClusters = 4;\n\n        int clusters = minClusters + ((int) (Random.value * (maxClusters - minClusters)));\n        Debug.Log($\"Clusters to be spawned: {clusters}\");\n\n        for (int i = 0; i &lt; clusters; i++) \n        {\n\n            int clusterSize = clusterSizeMin + ((int) (Random.value * (clusterSizeMax - clusterSizeMin)));\n            Vector2 &#91;] cPos = new Vector2 &#91;clusterSize];\n\n            int clusterX = 0;\n            int clusterY = 0;\n            cPos&#91;0] = new Vector2(0, 0);\n\n            for (int ii = 1; ii &lt; clusterSize; ii++) \n            {\n                int die = (int)(Random.value * 4);\n                if (die == 1) \/\/drawing a square for remainder on one\n                {\n                    int diff = clusterSize - i;\n                    int count = 0;\n                    if (diff % 2 == 0) \n                    {\n                        for (int x = 0; x &lt; diff \/ 2; x++) \n                        {\n                            for (int y = 0; y &lt; diff \/ 2; y++) \n                            {\n                                if (ii + count &gt;= clusterSize) { break; }\n                                cPos&#91;ii + count] = \n                                    new Vector2(cPos&#91;ii - 1].x + \n                                    x, cPos&#91;ii-1].y + y);\n                                count++;\n                            }\n                        }\n                        break;\n                    }\n                    else \n                    {\n                        for (int x = 0; x &lt;( diff \/ 2) -1; x++)\n                        {\n                            for (int y = 0; y &lt; diff \/ 2; y++)\n                            {\n                                if (ii + count &gt;= clusterSize) { break; }\n                                cPos&#91;ii + count] = new Vector2(cPos&#91;ii - 1].x + x, cPos&#91;ii - 1].y + y);\n                            }\n                        }\n                        break;\n                    }\n                }\n                else if (die ==2) \/\/ going down\n                {\n                    cPos&#91;ii] = new Vector2(cPos&#91;ii].x, cPos&#91;ii].y + 1);\n                }\n                else if (die == 3) \/\/ going down\n                {\n                    cPos&#91;ii] = new Vector2(cPos&#91;ii].x + 1, cPos&#91;ii].y);\n                }\n            }\n\n            BlockInstance start = (BlockInstance) map&#91; (int) (Random.value * map.Count) ];\n            while (start.blockData != stone) \n            {\n                start = (BlockInstance) map&#91;(int)(Random.value * map.Count)];\n            }\n\n            short startX = start.x;\n            short startY = start.y;\n\n            for (short s = 0; s &lt; clusterSize; s++) \n            {\n                BlockInstance block = findBlock(startX + (short) cPos&#91;s].x, startY + (short)cPos&#91;s].y);\n                block.blockData = ironOre;\n            \n            }\n        }\n    }<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>This is post 2 of 4 in the series &ldquo;Susminer&rdquo; A game that never was. Posts related to Procedural Generation and coding. Procedural Generation Heatmaps Creating A World Generator Graphical &#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,5,3],"tags":[6,7],"post_series":[56],"class_list":["post-36","post","type-post","status-publish","format-standard","hentry","category-coding","category-gamedev","category-susminer","tag-procedural-generation","tag-unity","post_series-susminer"],"_links":{"self":[{"href":"https:\/\/trewest.dev\/blog\/index.php\/wp-json\/wp\/v2\/posts\/36","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/trewest.dev\/blog\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/trewest.dev\/blog\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/trewest.dev\/blog\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/trewest.dev\/blog\/index.php\/wp-json\/wp\/v2\/comments?post=36"}],"version-history":[{"count":2,"href":"https:\/\/trewest.dev\/blog\/index.php\/wp-json\/wp\/v2\/posts\/36\/revisions"}],"predecessor-version":[{"id":291,"href":"https:\/\/trewest.dev\/blog\/index.php\/wp-json\/wp\/v2\/posts\/36\/revisions\/291"}],"wp:attachment":[{"href":"https:\/\/trewest.dev\/blog\/index.php\/wp-json\/wp\/v2\/media?parent=36"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/trewest.dev\/blog\/index.php\/wp-json\/wp\/v2\/categories?post=36"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/trewest.dev\/blog\/index.php\/wp-json\/wp\/v2\/tags?post=36"},{"taxonomy":"post_series","embeddable":true,"href":"https:\/\/trewest.dev\/blog\/index.php\/wp-json\/wp\/v2\/post_series?post=36"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}