Thursday, July 30, 2020

The Survive and Thrive Tile Engine

A Tiled World

Creating a tile engine is fun development work.  To start, I wrote down a list of core features I wanted my tile engine to support.  These included:
  • Many different tile types including multiple types of grasses, liquids, soils, and rocks
  • Digging up a tile to remove grass and expose the soil beneath
  • Mining down to an underworld with various ores, minerals, and gems to discover and mine
  • The ability to change tiles from one type to another
  • Random tile quality values
  • Planting and harvesting flora and crops including cultivation to improve harvest quality
In order to implement these features, I decided each tile would consist of top and core sections.  The top of the tile would be able to be planted with various grasses and crops.  Or, it could be packed down for paving and building purposes.  The core of the tile would contain different core types including rare resources.  The soil would be able to be cultivated to produce fertilized soil which would be required for some crops.  It could also be dug out to create a hole in the ground for accessing the underworld.

Since the game is going to be 2-D and top-down, the cores would be mostly hidden from view.  The player would need to dig up the grass to find out what is beneath.  Not all would be soil.  There could be random resources and dangers such as clay, tar, and quicksand.  I felt like having these two tile sections would provide diversity and make digging and exploration fun.

Wild Grass

I decided to use a 9-tile tileset like the one shown above.  With this tileset I can choose the correct tile to display based on comparing adjacent tile types in the North, South, East and West directions.  Diagonals are not considered at this time.  In this example, the edges that do not touch another wild grass tile have transparent Alpha channel edges allowing some of the Core of the tile to be exposed.  This is not quite as evident with the grasses because I adjust their pixel-per-unit size to make them overlap each other like real grass would when it grows.  Whereas, water, sand, rock, lava, moss, lichen, and others do not overlap their tile borders.  As of this writing there are 19 top tilesets and 27 core tilesets.  Here are a few examples of core tilesets.

Rock Core

Quartz Core

Notice the cores have pure black edges.  Whereas above ground tiles are seen, underground tiles are hidden until they are revealed by mining.  Every tile mined out reveals the North, South, East, and West tiles around it.

Unity and the Tilemap System

The Unity Tilemap system is composed of three main components:  the Grid, the Tilemap, and the Tile.  The Grid is the parent component that provides alignment and world-to-map and map-to-world coordinate translation.  The Tilemap is a single plane or layer of tiles positioned via a Z-layer with the Grid.  When I first started using the Tilemap, I envisioned two Tilemaps per game level.  There are three game levels as of this writing, so that would be a total of six Tilemaps.  But when I put my dev cap on, I realized I only really needed two, one for the top tiles and one for the core tiles since only one level would be visible at a time.  This is all having to do with what the user will see.  Behind the scenes, I have an array established for each Tilemap to store the tile object data that is used to determine which tiles to render and where.

A Random World Builder

Many games are designed from the standpoint of making scenes with pre-designed maps.  To that end, Unity has provided the Tile Palette feature that allows designers to paint a Tilemap with tiles using brushes and rule tiles that auto-select the correct tile based on what is being painted.  But my intention from the start has been to create a totally random-generated world and provide the player with options to manipulate the settings of the world generator.  This is an early screenshot of that effort where you can see an input for a seed, the world size, and the various tile types available to generate.

Early Concept For The World Builder

The sliders range from 0 (ignore) to 25 and allow the player to designate the relative presence each type will have when the map is generated.  Tiles with greater numbers take up more overall space on the map.  In order for this to be a useful tool, the player needed to see what the settings would produce.  Therefore, I added a preview feature that would generate the map.

Early 650 x 650 World Map

Clicking on the Generate World button allowed the player to see the game world and tweak the settings until the combination produced a world map worthy of playing in.  The settings above produced the following map.

The Preview

I guess an explanation is in order because 650 x 650 is not 1,690,00 tiles!  My numbers were base on multiplying the Tilemap size by 4 (in this instance) to give a total number of tiles that would exist with all Tilemap layers combined.  Thus, 650 x 650 = 422500 x 4 = 1,690,000 tiles.

The Perlin Noise Algorithm

So how did I turn those sliders into that map?  The real workhorse of the transformation was Perlin noise!  You can read about it here.  It is not difficult to find code snippets for a Perlin noise generator.  In fact, I believe I found perhaps the best C# implementations of it from a very talented developer on YouTube.  Here is the original code and a very well-deserved shout-out to Sebastian Lague and his video series on this subject.  The specific video where this snippet is discussed can be found here.

public static float[,] OriginalGeneratePerlinNoiseMap(int seed, int mapWidth, int mapHeight, int octaves, float scale, float persistence, float lacunarity)
{
    float[,] noiseMap = new float[mapWidth, mapHeight];
    System.Random prng = new System.Random(seed);
    Vector2[] octaveOffsets = new Vector2[octaves];
    for (int i = 0; i < octaves; i++)
    {
     float offsetX = prng.Next(-100000, 100000);
     float offsetY = prng.Next(-100000, 100000);
     octaveOffsets[i] = new Vector2(offsetX, offsetY);
    }
    if (scale <= 0)
     scale = 0.0001f;

    float maxNoiseHeight = float.MinValue;
    float minNoiseHeight = float.MaxValue;
    float halfWidth = mapWidth / 2f;
    float halfHeight = mapHeight / 2f;
    
    for (int y = 0; y < mapHeight; y++)
    {
     for (int x = 0; x < mapWidth; x++)
     {
     float amplitude = 1;
     float frequency = 1;
     float noiseHeight = 0f;
     for (int i = 0; i < octaves; i++)
     {
         float sampleX = (x - halfWidth) / scale * frequency + octaveOffsets[i].x;
    float sampleY = (y - halfHeight) / scale * frequency + octaveOffsets[i].y;
    float perlinValue = Mathf.PerlinNoise(sampleX, sampleY) * 2 - 1;
    noiseHeight += perlinValue * amplitude;
    amplitude *= persistence;
    frequency *= lacunarity;
                }
if (noiseHeight > maxNoiseHeight)
    maxNoiseHeight = noiseHeight;
else if (noiseHeight < minNoiseHeight)
    minNoiseHeight = noiseHeight;
noiseMap[x, y] = noiseHeight;
}
    }
    //normalize map back down to values between 0 and 1
    for (int y = 0; y < mapHeight; y++)
    {
     for (int x = 0; x < mapWidth; x++)
     {
         noiseMap[x, y] = Mathf.InverseLerp(minNoiseHeight, maxNoiseHeight, noiseMap[x, y]);
}
    }
    return noiseMap;
}

I did make some tweaks to Sebastian's code, but not much.  The resulting noise map array provided  values for each tile in a range from 0.0 to 1.0.  I then gathered the slider values for each terrain type and gave them a weight value based on their slider value.  Here is a snippet from my code that does this.

public static void DistributeTileTopAssetsByWeight(List<STTileAsset> tileAssetList)
{                            
float value = 0.0f;
int frequencyTotal = tileAssetList.Where(x => x.AssetType == STTileAssetType.Top && x.Scale != 0 && x.DistributionMethod == STTileAssetDistributionType.Perlin).Sum(x => x.Scale);
foreach (STTileAsset ta in tileAssetList.OrderBy(o => o.Sort).Where(x => x.AssetType == STTileAssetType.Top && x.Scale != 0 && x.DistributionMethod == STTileAssetDistributionType.Perlin))
{
float weight = (float)ta.Scale / (float)frequencyTotal;
value += weight;                
ta.Weight = value; 
if (ta.Weight > .9999)
ta.Weight = 1.0f;
}
}

The STTileAsset class stores information about each tile type.  The scale value is the slider value.  By summing up the scale values and dividing each one by the total I could establish a weight value for each tile type. Once weight was known, all I had to do was work through my map array and pick the first STTileAsset where the weight was >= the Perlin array value and that would be the tile that belonged in that position of the Tilemap.

Well, I guess that does it for this post.  In my next post I will delve deeper into some of the other features of the map engine and why I added a chunk engine to manage the maps.


Sunday, July 12, 2020

The Quest For A Real Game Engine

In my previous post, I showed some screenshots of my first real attempt at making a game.  I used my business development skills to put a face on some of my game ideas.  After months of work I felt like I had hit a wall that forced me to abandon that project.  I felt it necessary to seriously consider the tools and technologies I would need to bring the whole of my game into reality, not just a few systems.  All of that work on the web game took place back in 2017.

I went through an exhaustive and somewhat frustrating process of researching and reviewing as many of the popular game engines as I could find.  During this time I was careful to consider the following:
  • What kind of presence or reputation did the game engine have?
  • Was there an active, mature community of developers supporting it?
  • What platforms would the game engine support out-of-the-box?
  • What kind of language support did it have?
  • What was the learning curve like?
  • Did the engine provide solid online documentation?
  • How much would it cost to get started?
  • Once the game was published, were there royalty fees?
I will admit I looked at every game engine through the lens of my own existing skill-set.  After all, I did not want to have to learn everything from scratch.  Since I am a developer, I wanted an engine I could write code in.  I wasn't interested in a visual-only platform.  A few game engines supported lower level languages like C++.  But most of them used their own high-level scripting languages.  Ideally, I wanted one that supported the language I live in at work, which is C#.  It was really that one point that narrowed my search to a single choice.  You probably guessed it already.  It is Unity

I had looked at Unity before, many years ago, when it was not nearly as popular as it is today.  Back then, I was very frustrated with the lack of support for 2D development.  Oh, they claimed to be a 2D engine, but in truth their 2D tools were very limited and quite buggy.  That was then.  Today, while still being primarily a 3D game engine, the Unity 2D tooling has been given a respectable amount of attention.  It has finally matured to the point of being a viable 2D development platform and I am really enjoying it.  Now, do not get me wrong, there is no quick and easy path to learning Unity.  It is not that Unity lacks sufficient documentation, tutorials, or training materials.  It is the sheer volume of that content that makes Unity so daunting.  Every new piece of Unity I need to learn takes me into hours of videos.  But it is turning out to be totally worth the effort.

November, 2019 marked the start of my current game project using Unity as my development platform.  I had not totally settled on Unity.  In fact, I knew very little about it.  To get a feel for it, I decided to give myself one real goal.  I wanted to re-create the tile engine from my first game using Unity.  I figured by the time I had done that, I would know if Unity was the right choice for me.  

I started with Unity's tilemap system.  They have added some very nice tools, such as the Tile Palette, which can help you "paint" a map very quickly, including rule tile tools that select the appropriate tile from a tile set as you paint.  That is all well and good, but I haven't really been interested in the eye candy.  I wanted to procedurally generate a world that could encompass a million tiles if I wanted it to.  For that, I would need to store my map data in files and create a chunk engine to manage the tiles around the immediate area of the player.  Remember my underworld in the web game?  I wanted that too.

An Early Tile Engine Test

To my wonderful surprise, I was able to write a brand new chunking tile engine in only a few short months.  By the end of January, 2020 I had the chunk engine optimizations mostly completed.  By then I realized I was outpacing my first project.  It goes to show how using the proper tools makes all the difference in the world.

Another Early Engine Test

So, here I am eight months in and I feel really happy with my progress.  So much so that I am finally opening this up to others via this blog.  Thank you for reading and I hope you follow along with me as I blog about this experience and share my progress and discoveries with you.

Survive and Thrive




Saturday, July 11, 2020

The Beginnings Of This Journey...

In 1996, a game came out for the PC which established a hobby that has remained with me to this day.  Meridian59 was the game and it is considered by many to be the start of the modern graphical MMO genre.  You can read about the history of MMOs here.  Suffice to say, this game was amazing to me.  I played it for a long time as the industry grew to many dozens of MMORPGs.  I eventually moved on to other games, enjoying some of the best online experiences available.  Notable and monumental to me were Star Wars Galaxies (no longer available except from a few emulators like this), Ultima Online, EverQuest 2, LOTRO, Eve Online and RuneScape, all of which still have a place on my desktop.  The top spot since about 2009 has been Wurm Online.  Fortunately, my wonderful wife has patiently allowed me to enjoy this hobby throughout our 33 years of marriage.  Thank you, Precious!

Being a software developer by trade, I suppose it is only natural, if not inevitable that I would end up at this place in my life.  To relax, I have always jumped into a good crafting game to make and sell things, which is why I think Wurm Online has held me for so long.  But that is another article for another time.  In recent years, I have found  myself  playing games with an eye not as a gamer but as a developer.  It is a strange transformation.  I went from wanting to learn how a game was played to thinking how I would do something differently than the developers.  This progressed into habits of jotting down ideas, designs, features, and systems that only existed in my mind.  I kept asking myself, if I created a system for this or that, what would it look like and how would it work?  This documentation has accumulated into an embarrassing amount of amorphous content.  To be honest, more than a few times this thought process has completely ruined my gaming enjoyment.  I have to constantly remind myself to be careful not to let it happen.

One day I came to the realization that I wanted to make my own game.  "After all, I am a developer," I told myself.  Since I had no game development experience, I spent a ridiculous amount of time researching platforms and game engines, most of which had steep learning curves and used technologies I was not familiar with.  However, I did have development experience and a skill-set.  I thought how I might take those skills and translate them into a development platform for a game.  For example, taking one of my early concepts, I poked around Google until I was able to find a library for gauges... Yes gauges.  I jumped into WinForms and took off with simulating a crafting system idea I had.  Unfortunately, the following image is all that remains of that effort.

An Early Crafting Concept
This concept involved mixing ingredients to ultimately affect the properties of a component that would be attached to a game item -- think socketed weapons.  There was much intangible satisfaction for me during that time of creativity.  It was pure joy adding random reagents affected by so-called inhibitors and promotors to keep the volatility of my catalyst in the green, while attempting to step through the grades without damaging the catalyst too much.  Each step advanced one of the six properties listed to the right of the catalyst.  Naturally, WinForms was not a game engine and would provide no practical platform.  But I do not think that was my goal back then.  My goal was to test myself!  I wanted to see if my ideas were actually enjoyable.  After all, hobbies are meant to be enjoyable and if they are not, what is the point?  Therefore, games ought to be enjoyable, I thought.

While WinForms was not even a consideration in my mind, there was another set of skills under my hat that I could tap into.  Web browsers have been a very successful platform for gaming, and I had some web development skills.  I decided to dive into making a website that would be a platform for hosting an online game.  This was my first real attempt at bringing my ideas to fruition.  I worked hard for many months on my game concept.  It would be a tile-based 2D game world with some RPG elements, but mainly a sandbox crafting game.  I setup my solution like I would in any real-world application.  I created my data engine, object factory, logic engine, and services console (for real-time updates) as well as my web server to serve up the game.  I also added a WinForms application called the Data Manager to ease management of my game content stored in the database.

Creating the map system was pure joy!  Everything was data-driven.  The player would make a move in a direction which would then summon the updated set of tiles and refresh the page.  I was actually quite surprised how responsive and performant JavaScript coupled with SignalR could be.  But then, it was only running on my local machine.  What kind of lag would it have if I served it up from the web?  I never found out.  The following is the first screenshot I ever took of the "game."

First Screenshot of the Web Game
This was meant to represent a castle with a moat.  The yellow square was my "character".  Each tile had 4 sides which could display the walls in a top-down fashion.  This small feat really encouraged me.  I went back and re-worked the tile images until it looked like this.

The Castle
It was during this time that I learned how to work with vector graphics using Inkscape.  I wanted to use vector graphics because they can be scaled with no loss of quality.  However, due to the limitations with vector web libraries, I ended up having to convert them to raster (pixel) graphics.  This took me back to my old staple image editor Paint.NET.  Work on this game continued for several months.  I borrowed some public domain game assets and expanded into different tilesets.  I added trees and a growth engine.  I also mocked up a game screen with various windows, including a map window, chat, inventory, etc.  By this time I was focused on making my game similar to a MUD, only with a more visual aspect to it.

A MUDDY Look
I wanted the world to have more depth than a flat 2D map.  This led me to modify the game engine to provide a Z-level for an underworld.  The player could dig a hole to get access to a single underground level.  The result looked like this.

The Underworld
I eventually determined that I would pursue less of a MUD style and more of a graphic-centered game.  This meant moving the map from the far left to the center of the screen with everything else around it.  I was also heavily involved in trying to solidify my UI design tools.  Much was going on as my mind went in all kinds of directions.  I added the code to hide the outside world when a player entered a building.  I added the appropriate walking sound effects for indoors and out.  In the next screenshot, the player stands outside the door of a 6-tile house (top down).  The hole leading to the underworld is just northeast with a convenient ladder leading down.

A Roof and a Ladder
I was also focusing on what I believed would be a necessary immersive component.  I wrote a weather engine that would run via the console app on the server.  It would simulate seasons, cloud cover, wind strength and direction, temperature and humidity, all based on the time of year.  Of course, this meant providing all of the necessary sounds and visuals for the weather system as well.  More graphics, and a "real" player were added to the beautiful clouds that would roll by above when the weather engine was running.  As you can see, the UI was in constant transformation as I experimented with different technologies such as floating and collapsing div panels.

A Weather Engine
 I was able to add a mini map to the game as well as convenient buttons around the map.  It is amazing how often a game is transformed during development.  Things get worked and re-worked over and over.  It is for this reason that many developers rely on an 80/20 rule.  Get the feature 80 percent complete and then move on to another feature or else you will never be able to move on.  There is a lot of truth in that, and I admit it is a weakness of mine not to follow it sometimes.

The Mini Map
Another side project was adding the ability for the Data Manager, that WinForms application, to feed a map directly into the database records.  Up to this point, I was writing SQL scripts to add map tiles to the game.  This was very tedious as I had no way to visualize what it would look like.  The process to feed a map into the database involved identifying a standard set of tile colors to use to represent the various tile types and then reading the bitmap image file into a stream, parsing the colors of each pixel, and creating the tile records based off the color.  The wide screenshot below shows a test map image on the right with the game rendering the player standing at the northern mouth of the river.

Using Images to Create Maps
One of the last screenshots I took of this early game attempt shows yet more UI changes.  At this point, I was beginning to think about inventory code.

Inventory
Facing the UI hurdles around inventory control led me to set this project aside even though I had something I was working on and I was making progress with it.  But enthusiasm gave way to reality.  I began to see the deeper I went in my development of this web server, the more I had to fight with the technologies and tools I was using to make it.  I was sure that the road ahead was a dead end.  Setting aside the project was not easy.  I kept thinking about it and wanting to pick it back up, but I forced myself not to.  I had to step back and re-think my technology stack.  If I wanted to make a game, I needed to choose a game engine.  I needed proper tools.  I went back to my game engine research.

Next time, the quest for a real game engine.