Sunday, December 18, 2022

December focus: Flora System

During the month of December, I am focusing on the flora system.  To give you an idea of just how complex I want the flora system to be, consider that ASTS will have no less than twenty different species of trees, six bushes including 5 that produce berries, along with mushrooms and other fungi.  Each tree will have four growth stages: sapling, young, mature, and old.  Players will be able to plant saplings that will grow through these stages.  Each tree will also have seasonal sprites.  Keeping with the survival theme, many items will be harvested from the flora.  That is my focus this month, to code the harvesting of the various materials a player will be able to obtain from plants and to add more and more species.

Depending upon the species, trees will provide sticks (long and short), branches (deciduous) and boughs (coniferous), roots (for cordage, medicine), nuts, fruit, fibers (for starting fires), bark (for cordage and food), resin (for medicine, glue), sap (for food, medicine), mushrooms (both poisonous and edible), cones (for fuel).  Every individual tree will have its own random set of resources based on species, age, and health.

It is important to me that every aspect of the game is fun and rewarding.  To that end, even gathering materials from trees presents different levels of time, energy, and reward.  Since this is a 2D pixel game, there is a heavy reliance on sounds which draw upon the imagination. Let's take harvesting a root as an example.  Only certain trees allow the chance for obtaining roots.  Even if the tree has several root opportunities in its definition, the player may discover that the root he finds in a given search is simply too large to harvest.  But once he does find a good root, he will need to listen as his player scratches the ground, clears away dirt and then pulls up sections of the root, repeating this dig and pull cycle a few times before cutting the root free from the ground.  The sounds are all there and really make the whole experience open up in the mind, even if the character isn't animating every action.

I create almost all the graphics in my game.  Some are completely hand drawn.  Others are inspired from obtaining free and/or paid images of real-world items which are then morphed into beautiful pixel-things. Almost all the trees in the game came from images of real trees that were isolated, clean-up, touched up and pixelated into their final form.  I know this isn't the professional way to create 2D pixel art.  I don't have a set palette of colors I always use.  But then, nature is not limited to 32 colors, so why should I be? I do implement a few common effects to tie all the art together.  I will have to wait and see if my unorthodox methods come together or not.  However, when it comes to animating, I am admittedly intimidated.  Therefore, I am hiring pixel artists to do this work for me.  Let me just say, it is NOT cheap.  And since I am not made of money, I will introduce characters and NPCs over time as I can afford to hire out.

I truly believe it is important to be as detailed and intricate as I can be if I am to make a survival game worth talking about.  This is why I spend a long time researching game content and using real-world data.  Consider the trees.  I researched each tree and categorized and sized it on a scale of 0 to 10.  Here is a sample of the tree data which helps me produce the images as well as define the resources.

species (size)      young w/h    mature w/h    old w/h    health        hard/soft    zone    growth    max age

apple (0)            64/64              96/96             128/128    10/15/20    hard              7          fast          45

birch (3)            96/96              144/144         192/192    40/60/80     hard              3         medium   120

maple (6)          96/128            144/192         192/256    70/105/140  hard              7          slow        125

olive (0)            64/64              96/96             128/128    10/15/20     hard             10        medium   500

spruce (2)         64/96              96/144           128/192     30/45/60     soft              1          medium   200

 

The procedural generated maps will have growth zones where trees will be found.  This means an olive tree (southern) will never spawn in the same area of the map as a birch tree (northern).  This also means a player may need to travel north to find spruce roots if that is the kind of cordage he wants to make.  Or, he may need to venture south for olives to make oil.  But that is what it is all about, exploring and surviving!

Until next time...  

Sunday, November 27, 2022

A Seamless and Wrapping Tilemap Without Chunking

Back when I was working on this game in UNITY, I had developed a chunk engine for my tilemap data, and I was quite happy with it.  I generated map chunks that were stored in files and loaded them into memory as the player moved around the world - the typical map chunk approach.  It had its good points and not-so-good ones.  The game world could be massive because it only loaded chunks of map data.  But, the world did have edges and if the player reached the edge of the world, he would need to alter direction.  Also, there was always a noticeable lag spike when the engine decided it was time to load and unload chunk data.  Then too, there were so many tiles in memory at any given moment.  The chunks were 25x25 if I remember correctly, and there were 9 chunks possibly in memory.  That amounts to upwards of 5625 tiles.

When I switched to Godot, I wanted to do something different.  I didn't want the player to ever see the edge of the tilemap world.  Also, I didn't want to deal with chunk loading and lag spikes.  Finally, I wanted the map to be seamless so the tiles would blend at the edges.

A Wrapping Technique

In order to visualize tilemap wrapping, I did what software engineers do.  I threw together a program to simulate my goal.  I opened up Visual Studio and created a Winforms application.  I generated a grid of random numbers to represent tiles.  I then wrote an algorithm to determine which numbers needed to be populated based on the center target number, the player.  While the code turned out to be fairly straightforward, taking this approach allowed me to visually verify my code was working as intended.

The above example seems easy enough to figure out.  But the code needed to figure out which tiles would surround the player no matter where the player was on the map.  Consider the following example.

Since the player is on the east edge of the map, the western edge needed to be included in the surrounding tiles of the player.  Let's get a little more complicated and put the player on two edges of the map.

The above example needs to pull from all four corners of the map because the player "4" is against both the east and south edges.  The code in the WinForms app successfully pulled together all the numbers to display the correct area around the player and assured me that it worked.  Here is my Winforms C# code for populating the numbers on the right side of the screenshots, which represents the view of the map in the game viewport.

private void btnLocate_Click(object sender, EventArgs e)
        {
            tbView.Clear();
            var playerX = numX.Value;
            var playerY = numY.Value;            
            int start_X = decimal.ToInt32(playerX - (decimal)Math.Floor(viewSize * .5));
            int start_Y = decimal.ToInt32(playerY - (decimal)Math.Floor(viewSize * .5));
            for (int y = 0; y < viewSize; y++)
            {
                for (int x = 0; x < viewSize; x++)
                {
                    int pos_x = start_X + x;
                    int pos_y = start_Y + y;
                    int key_x;
                    if (pos_x >= mapSize)
                        key_x = pos_x - mapSize;                    
                    else if (pos_x < 0)
                        key_x = mapSize - Math.Abs(pos_x);
                    else key_x = pos_x;

                    int key_y;
                    if (pos_y >= mapSize)
                        key_y = pos_y - mapSize;
                    else if (pos_y < 0)
                        key_y = mapSize - Math.Abs(pos_y);
                    else key_y = pos_y;

                    tbView.AppendText(" " + mapData[key_y, key_x].ToString() + " ");
                    if (x == viewSize - 1)
                        tbView.AppendText("\r\n");
                }
            }
        }

The "key" is the key!  If position steps outside of the map size, the key compensates by pulling tile data from the opposite side of the map.  How did I implement this?  Well, first I need to explain my concept of a world without chunks.  When I generate my perlin noise tilemap, I store the tilemap data in a dictionary where the key to the dictionary is a Vector2 map position and the value is a tilemap tile type.  The dictionary is held in a singleton and gets saved and loaded along with all the rest of the game's save data.  I also store the current tile position of the player in this singleton.  The tilemap never gets loaded with the entire dictionary of tile data.  It only gets loaded with those tiles around the player's map position, the viewport plus a few tiles' worth of border.  Every time the player moves, I update the player position and then I clear the tilemap and re-populate the tilemap with the new tile content around the player.  This means every move of the player will clear the tilemap, load it with the new tiles, and render them to the viewport.  The code above handles the wrapping of the map so that I know which tiles to read from the dictionary.  The effect is that the player can move around the game world, no matter how large the dictionary world is, without chunking and without lag.  I have an established view size of 49x33 (which is very close to 16:9 ratio).  This configuration loads 1617 tiles and renders them in the tilemap at the end of every player move.  Does it studder?  Not even remotely!  There is zero lag, zero flicker, and the player never sees the edge of the tilemap.  Not only that, but my tilemap tile types are all AUTO_TILE types.  It is simply beautiful.  The map data for the entire map is in memory, not as tile nodes, but simply as dictionary entries of a tile type indexed by a Vector2, which is quite small in comparison.  This makes the tilemaps only ever 49x33 in size even though the dictionary may hold a million tile entries or more!

Seamless Perlin Noise

The wrapping tilemap would be of little benefit unless I was able to create seamless Perlin noise.  It took me quite a while of digging and searching to figure out how to do this using Godot's get_seamless_image() feature.  It is not well documented.  Fortunately, I found an implementation of it and it works well.  This is the start of my generate_world_data function().  The following code is GDScript:

    randomize()   
    var noise = OpenSimplexNoise.new()
    noise.seed = randi()
    noise.octaves = 2
    noise.period = 192
    noise.persistence = .75
    noise.lacunarity = 4
    var noise_image = noise.get_seamless_image(auto_global.map_size.x)   
    noise_image.lock()
    for x in auto_global.map_size.x:
        for y in auto_global.map_size.y:                       
            var pixel_map = noise_image.get_pixel(x,y)
            var noise_value = pixel_map[0]    #0 to 1           
            var map_tile_cord = Vector2(x,y)

Auto_global is a singleton where I store the size of the game world and the tilemap dictionary data.  The noise_value uses the first value from the pixel data.  This value ranges from 0 to 1, unlike noise.get_noise_2d(x, y) which contains values ranging from -1 to 1.  Taking this along with the logic from the code above, it is very easy to take a flat 2d tilemap world and turn it into a circular ball of sorts.  The result is a seamless, wrapping tilemap with no chunking required.

Feel free to leave comments or ask questions.  Thanks for visiting and reading along on my game-dev journey.

Until next time...

Saturday, November 19, 2022

The Inventory System

My Godot achievements are growing, thanks to just how easy it is to do stuff in the Godot game engine.  In just a month's time, I have been able to create a robust inventory system for the game.

Eyesgood's Inventory System

The inventory system is a resource-based system that manages containers and items. It was very important to me that my inventory system could separate mutable properties from immutable ones.  What does that mean?  It simply means the definition resource classes hold all the properties that do not change for the containers and items.  For example, name, description, texture (display image), slot size, max stack size, number of small, medium, and large slots (for containers), weight, length, width, height are all properties that do not change.  They are immutable.  That means I don't have to save these properties in the save game file.  They are static and can reside in a resource file.  However, when I create instances of containers and items, they have properties that do change.  For example, id, parent id, parent slot id, stack amount, UI position (where a container is when locked on the screen), etc.  By taking this approach, the save game file only contains the mutable (changeable) data for each instance of each container and item in the game.  This will reduce loading time, which is something I need when I contemplate a game world of millions of tiles - yes millions!

The UI consists of a single dynamic inventory window that is constructed based on the container's definition.  In the above screenshot, all three windows are the same scene, only they are constructed at runtime from the container definition, which states how many small, medium, and large slots are defined as well as how many columns should be drawn for each slot size.  Since everything is data-driven, I can create an inventory container for every container in the game in any configuration I can imagine.

The Player Inventory window is special in that it has no parent.  But it is a container and therefore has a container definition of two large slots, four medium slots, and twelve small slots.  The backpack fits into a large slot in the player inventory.  Opening it up, it is also a container and is defined containing zero large slots, one medium slot, and six small slots.  Finally, the leather pouch fits into a small slot. But opening it up reveals four small slots.

I wanted to make the inventory system robust but also logical.  A Struggle to Survive is all about working with what you have - which is not very much.  It isn't an MMO where you can hold 50k items in your inventory and run around at full speed.  That's not what I am trying to design.  I want pseudo-realistic design that really forces the player to make decisions just like in a real-life survival scenario. Take the small pouch as an example.  It takes up one small slot in the player's inventory.  Opening it up reveals that it has four small slots for storing small-slot items.  But looking at the player inventory window, we see that a canteen is also a small-slot item.  So is a bear trap.  Does it make sense to put four canteens or four bear traps inside a small leather pouch?  Certainly not!  Therefore, I had to come up with a way to create limitations to the containers that make sense from a pseudo-real-world perspective.  Furthermore, I also wanted to limit just how much a player can carry around.  The solution to these two problems was solved with the concepts of Volume and Weight.

Taking the simple formula of volume = length x width x height, I assigned a volume value to each container.  I also assigned a volume for each item.  The leather pouch has a volume of fifty (5 inches x 2 inches x 5 inches).  When an item is dropped, the volume of the item is compared to the remaining volume of the container.  If there is room, the item will be placed in the container.  If there is not enough room, the item will be rejected and the player will be told the item will not fit inside the container.  Volume allows me to prevent a canteen from being placed inside a pouch.  But it will fit nicely inside a backpack (18 inches x 6 inches x 18 inches).  Since the player inventory is itself a container in this system, I simply set the player inventory to 100 inches x 100 inches x 100 inches, which provides a volume of 1 million - more than enough to handle the slots and volumes of items placed inside.

Weight is currently just used to make sure the player can actually carry what is in the inventory.  The player's strength coupled with a few other attributes will determine how much the player can carry before being hindered from running or even walking.

I am extremely excited to have been able to complete an inventory system for my game.  This is something I struggled with when I was coding the game in UNITY.  But with Godot it was a breeze!  And, having so much community exchanges of ideas and the documentation was extremely helpful.  Permit me to share with you some of the features of this inventory system.

  • Resource-based with game saves only storing mutable data
  • Three slot sizes: small, medium, and large
  • A dictionary-based list of containers and items
  • A single scene for the inventory window, created at runtime based on the container definition
  • Definable slot configurations with column settings
  • Drag-and-drop based on slot size recognition
  • Transfer of items across open containers
  • Item stacking with defined maximum stack limits per item
  • Container volume calculations with checks for remaining volume based on existing container items
  • Recursive volume and weight calculators to get total weight and volume regardless of how many containers are stacked inside other containers
  • Container windows spawn with a tween grow from nothing to max size when opened and reversed when closed, providing a very smooth animation
  • Containers can be positioned anywhere on the screen and locked to prevent accidental movement
  • Container windows can be stacked and will jump to front when clicked
  • Weight can be shown in metric or standard designations to two decimal places
  • Container total weight is shown on each container and includes child container content weights
  • Tool-tips provide instant visibility of container and item name, weight, and volume stats

I haven't decided yet whether I will attempt to create a series of videos showing how to create this system.  It isn't that I want to keep its design to myself.  I am happy to share it.  It is more that creating videos often takes as much or even more time to do than coding the feature the video is about.  We shall see.  In the mean time, thanks for reading along.

Until next time...

Wednesday, September 28, 2022

Back to the Bytes - With a NEW Game Engine!

It is September, 2022, and I am happy to report that I have picked this project back up.  To offer an explanation for a years' absence, and so I do not forget myself why I stepped away, I want to memorialize the reasons here.

Back in July of last year, I had managed to fix the issues I had with chunk edge lines.  Once I completed that, my intention was to keep working on the map and also start coding an inventory system.  That was simply not to be.  I was getting updates to various third-party packs from the Unity store, when I started experiencing breaking changes.  Those are typically rare and I knew that.  However, they required some real under-the-hood digging to fix and I was frustrated about the need to.  Around the same time, I started picking up chatter on Twitter and other places about this open-source game engine called Godot (pronounced "guh-dough", not "go-dot").  Anyway, I started reading up on the engine, not with serious intentions, but just casually to see what all the hype was about.

Along about the same time, yet another distraction had me.  I was heads-down into Valheim, a Unity-based sandbox that needs no real introduction.  That game really pulled me from my game development time.  I loved it.  I hated it.  I loved it again.  It was something that made me step back and re-evaluate the game I was making and the reasons for making it.  

Let me interject a thought right here for anyone reading who aspires to be a game developer.  I don't have any published games under my belt, so take my advice in that context.  But what I do have is 18 years of real-world software engineering experience developing world-class business software.  I am a software engineer and I plan to retire doing what I do.  Having said that, let me also say that game development is NOT easy.  In some respects it is much harder than what I do every day.  But what I wanted to say about being a game developer is just this.  Don't set your sights on money.  Let your day job take care of that for you.  Don't make a game with the thought that making money is the goal.  If you are one of those (like me) that salivate at the thought of making the, "game I always wanted to make," do not let money be the motivation. Forget about hoping your game will be the next Valheim, or the next Minecraft, or the next anything.  Maybe it will.  But likely it won't be.  Instead of that as your motivation, make your game for the love of gaming and the joy of creating something special.  Make it because YOU love it.  My wife tells me all the time that she has absolutely zero expectation that my game will be anything but a hobby I enjoy working on.  If it makes me happy and helps me unwind, she is happy for that.  You know what, there is a ton of wisdom in that.  Let me tell you, not only does that help me not to feel like a failure when I need to step away, it also takes a load off my mind trying to justify the time-sink that is game development.  But it also helped me to understand what my priorities ought to be.  I don't need the game to take care of my family.  I don't need the money it may or may not generate.  I don't need this game to succeed.  It is a hobby, something I enjoy thinking about, writing about, contriving about, and working on.  I just wanted to share that.  Back to my excuses...  

Problems with Unity coupled with this buzz about some game engine that people can't figure out how to pronounce, and a sweet success called Valheim literally caused me to push away from this project.  And so, dormant it sat on my development drive waiting for my ultimate decision going forward.

That decision came to fruition over a month ago as I started thinking about all the pixel art I wanted to do for the game.  That is always my fallback and I typically regain interest as I create art and document game mechanics. I have literally 20 years of documentation on a game that doesn't exist. It's true.  I went back to the art first, adding new images for new things and growing my library of pixels under the single theme of survival.  Doing artwork is also extremely relaxing to me.  That led to me thinking about the broader themes and setting for my game.  I needed to settle on something.  I have really wanted to zero-in on a theme or historical period so I could have a more focused picture of the end-goal for my survival game.  After some thought, I believe I have found the setting and time period that holds my interest for this game.  More on that later.

I knew I needed to confront my Unity project issues.  So, I opened my work in Unity and downloaded the latest updates.  It was all the typical housekeeping.  But once again, I was met with more broken code and  less desire to fix it.  Third-party assets are great and all that, but ultimately, you are stuck with them.  Meanwhile, I had been hearing that Godot 4.0 was just around the corner.  I knew if I switched engines, it would mostly erase over a year of work.  I knew I would need to learn yet another development environment and yet another language.  But then I thought it might just be the reset I need to get me focused again.  I made up my mind I would wait until Godot 4.0 came out and then I would experiment with the engine and see how it felt.  That was about 5 weeks ago.  

Somehow, I talked myself into downloading Godot 3.x so I could learn GD Script, I think was my reason.  I installed it and began to poke around.  This Node tree thing... It was ridiculously simple, yet wholly profound.  Of course, my attention was immediately focused on the Tilemap node.  It took me every bit of 5 minutes to have a scene with my tile art in it.  My interest grew daily. DAILY!  I couldn't stop thinking about the possibilities of having an open-source game engine that actually made sense to me. I read that c# support was available, but I determined not to use C# (my daily staple) and instead focused on GD Script because I knew integration with the game API would mean I could pick up the nuances of the engine faster.  The speed at which the language was entering my mind and exiting my fingertips made me giddy.  Seriously, Godot has impressed me to no end, and I only just started working with it in the last few weeks.

My mind was made up rather quickly and solidly too.  Unity, for all its popularity and power, would not be the engine I would use to develop A Struggle to Survive.  Godot is my pick.  I don't even care that I have to start over.  I know I will catch up to where I was in much less time than it took me with Unity.  All the best Unity, but I bid thee adeau!  Welcome, Godot, to A Struggle to Survive!

Title Screen Concept - Godot Engine
Thanks for reading.  Until next time....