Harvesting mushrooms

A variety of things this week, mostly related to content. I decided to get some big asset pack, with lots of mediocre assets and some good ones, so I'm very slowly combing through. This brought up one of the main ever-looking challenges for projects of such scale. Which is ... how to scale effectively :) First of all, a bit of math. I'm pulling some numbers out of my behind, but bear with me.

Given some rough calculations, I might need around 20,000 different sprites. For 32x32 sprites, where I have the regular sprite and a distance field representation, this means 5KB/sprite, for a total of 100MB, which is peanuts. Good!

I store sprites in texture arrays, and the fixed limit of each array is 2048 elements. For 20,000 sprites I need about 10 texture arrays, which is also peanuts. As long as the same shader using just a single texture array, instead of having to repeat for several ones, it's optimal.

In some areas in the code I've been using arrays with a number of elements fixed to the number of elements of some database type. For example an attribute array will always have a size of 6. That's nice and low. This could be used with bools, to make an attritube mask, to define for example what attributes does a skill get bonuses from. But another example is the item or active ability database type, which both can contain hundreds of elements later on, so in that case creating an array of 500 just to set 1 item is rather wasteful. So, care needs to be taken to not utilise these types inappropriately for data that are likely to scale up quite a bit in terms of numbers.

Another scaling issue which is more or less dealt with is the inevitable explosion of JSON data. At the moment, my JSON configuration data are about 220 files/4MB, and content will only be increasing at ... increasing rates. Adding an object, like a a table, does not just increase the sprite count, but adds several lines of json. For abilities, far more so. And because all the data is primarily json, I don't have facilities for variant specification, although it's something that I'm looking at. The problem of loading JSON data is already solved by having a binary serialization format alongside json, so that data can be loaded at game startup really really fast, without parsing a single line of json.

It's mushroom season

Alright the rest of the work was due to me choosing to fixate in a single contained and mostly ok-ish asset pack with mushrooms - 47 in total. Several bits of work had to be done.

  • Color palette: The color palette was not exactly quite compatible, so that needed fixing. I chose AAP64 and extended it with about 1000 more colors, to have good enough set of colors to map the originals to.
  • Re-palettising: I just go through all the pixels of the image and find the closest ones from the palette, in Lab space. This doesn't work too well with small palettes, that's why the above by-a-thousand expansion
  • Adding silhouette: I keep my objects with a silhouette of (38,38,38,255). These assets didn't have that silhouette, so I wrote a script to add it.

So, with the above, I get some more reasonable 32x32 mushroom sprites. Cue the question...

... Where on earth do we find those mushrooms??? ...

Houston, we've got a new problem task! Create 32x32 sprites that contain a multitude of mushrooms. How many? Let's say 1,2,3 or 4. So, this is the approach I took:

  • Downscale mushroom to 24x24 with Lanczos
  • Re-palettise with above approach
  • MANUALLY add some DIY ambient occlusion around the roots (hate doing things manually), as (0,0,0,64)
  • Crop sprite to its bounding box
  • Create 1-, 2-, 3- and 4-mushroom patches by pasting the cropped sprite to random positions. A few gotchas:
    • Pasting needs to happen from top to bottom, to simulate background/foreground
    • Points are chosen using Poisson disc sampling

That's it! Result? 47 single-mushroom sprites and 188 patch sprites. That 20,000 sprites figure won't be hard to reach like this.

Are we done? Nope. Now we need to put them in the game! How to do that? Here are the steps:

  • JSON: Add a new entry in the "standard features to spawn in a dungeon" JSON file. Let's call that a "plant patch". We specify position requirements (basically, anywhere on land), environment types (biome, a bit in cavern and settlements) etc. We also reference some "collection" preset...
  • JSON: Add a new entry in some "collection preset" database json file. Here "plant patch" maps to all 47 mushroom patches, equally likely.
  • JSON: Add mushroom patch objects, all 47 of them. They all specify some "harvestable" tag and specify all the patch variants.
  • JSON: Add single mushroom objects, all 47 of them.
  • JSON: Add "harvest" active ability, that is like "use level object" but might take a bit more time eventually. In code, based on the patch we can infer the single mushroom
  • C#: Some support code for some of the above, probably about 100 lines total

So, basically what happens is:

  • When spawning a patch based on the json rules, we randomly pick how dense the patch is: 1,2,3 or 4 elements max, with corresponding sprite.
  • In the context menu for object interactions, we now check if an object near/under us can be harvested
  • When harvesting, we infer the type of mushroom from the sprite (hacky!), create a number of them, transfer to player and destroy the patch
Killing villagers won't make you popular in the village

Teams and stealth

Some miscellaneous fixes on teams and stealth earlier in the week. When we kill a villager, the rest of the villagers should be angry with us.

That's all for now, have a nice weekend!