I've already shared the image above on Mastodon and Twitter, but I think it shows well what I've been working on and should provide some context on why I use the tools I do.
The environment tiles were done in Photoshop. I'm using Aseprite a lot more for pixel art these days, but I started these tiles before I got it. The tiles are all sorted into a lot of layers and lack animation, so Photoshop's layers palette has been more convenient to use than Aseprite's in this particular case.
I'm not using the faded tiles just yet (and they haven't been updated in a while to reflect the changes to the main tree tiles, oops), but they're just recolours of the main terrain and tree tiles for use in the far background. In theory I can do this recolouring in-engine and save that texture space for something else, but this gives me more control. Perhaps eventually the background trees will have unique tiles!
The character sprites were done in... everything. I made the initial idle pose in Photoshop, then broke it up into segments, and took those into Spriter Pro, which I'd gotten in a Humble Bundle ages prior and forgotten about. Spriter is a bone-based animation program, and it allowed me to iterate on the animation without redrawing any frames. I'm bad at animation, and I'd never even tried animating a quadruped before, so this was very helpful.
Automatic rotation and pixel art make for an awful combination, but the output from Spriter served as a good base. Photoshop's animation tools aren't great, so in order to continue working, I got Aseprite. Or rather, it was a more specific reason: Spriter Pro is not meant for such small art, and didn't export the spritesheets correctly, the frames overlapped by a pixel here and there. Exporting as an animated gif was the only way to get correct output, but Photoshop couldn't import those. Aseprite imports gifs without any problems, and I'd been hoping to try it anyway, so that was that.
I used Aseprite to clean up the distortions. I also made many changes to the tail, since the animation from Spriter Pro used just two segments for the tail and thus looked rather stiff. Here's an example of a frame from output and a cleaned-up version:
After this was done, I added a few more animations in Aseprite without going through Spriter, mostly easier stuff that didn't require much experimentation with timing, such as the braking animation that's played as you decelerate from a run. When I need to make new complex animations, I'll probably be using Spriter to plan them out again.
Aseprite can export spritesheets as sprite atlases, this proved very useful. Unfortunately, Aseprite has no way to set an anchor/pivot point on the sprites, which I need to position the sprites correctly relative to the entity's hitbox. The vyt sprite is asymmetrical, so this is an important piece of information. To remedy this, I wrote a little tool that adds pivot information to each frame based on some user input and on the frame's cropping information. Since I was writing a tool anyway, I took that opportunity to also restructure the code into something easier for my engine to import. Tools like this might be boring, but they're very useful.
They are also a great opportunity to flex your UI design muscles ;D
I happily push individual pixels around when making sprites and tiles, but when it came time to make some simple levels to test the artwork, I quickly and firmly decided, "NOPE, I am not placing all these tiles by hand, not even once."
Many people associate procedural generation with rogue-likes and other games that generate things in-engine, but procedural generation is very useful "offline" too, for creating static game content. That's exactly the sort of procedural generation I'm using for this game.
Although I am using Tiled Editor to create the maps and it has a lot of tools for automating tile placement, its tools seem better-suited to top-down maps than sidescrollers, with poor handling of empty tiles and no easy way to deal with slopes. Or maybe I just don't get its tools ¯\_(ツ)_/¯ Whatever the case, I opted to use Tiled the least amount possible, and wrote a few tools to do the tedious work for me.
My dinky level generator takes a tileset, a set of labels describing the shapes of those tiles, and a sketch that describes the shape of the level, and builds a map using the tiles:
This makes it easy to work with slopes since all I have to do is draw them with the line tool in Aseprite or Photoshop. It handles using variant tiles at random and fills in little details like the tops of flat terrain tiles and little corners on the platforms. Tiled can do the latter, but I found it easier to code that stuff into this tool than use Tiled's tools. It generates collision maps too.
The Download button saves the results as a JSON file that Tiled can understand, so I can easily import this stuff into it. Then, I can use Tiled to make some changes like make the dark terrain more organic and save the map as a TMX file than my engine can understand.
This is what the labels look like when displayed on top of the tiles:
The left/right sides describe the shape of each tile. For the main terrain tiles, this is generally the slope of the tiles. When the tool scans the sketch, it only looks at the sides of the sketch-tiles and seeks to find tiles that match, it completely ignores the middles in the sketch. The detail tiles (like tile-tops and corners) are various special cases that never occur in sketches. The colour determines the general type of tile (in this case, green for grass and moss, red for leaves and some special cases, blue for caves). Tiles that have the same shape and colour are functionally equivalent, they're alternates of one another. The middles describe the shapes of the tiles that go below (bottom of the middle section) and above (top of the middle section) this tile, the tool uses this information to fill out the cosmetic details.
If you think you could get some use out of this tool, tell me and I'll see about writing some documentation on how to make the labels and sketches so that I can share it.
The terrain would've been tedious to do by hand, but that's still a pretty small number of tiles to work with. Where automation was really crucial was building trees, which use over 200 tiles. My tree tiles are intended to be versatile enough to create a variety of trees, but it's a lot of work to arrange those tiles into a bunch of trees by hand, so I set out to write a tool to make it easy.
As you might guess by the fact that trees get their own section, it was not easy to make creating trees easy.
At first, I tried doing it with rough sketches, with the tool finding the best visual match for each segment of the sketch from among the tiles. I adapted this approach into the terrain generator I showed above, but for trees, it was never quite satisfactory. I wrote about working on it on Mastodon.
The sketch-based tool did a particularly poor job with my spruce tiles, since those have rather rough shapes. So, just for spruces, I wrote a tool that generates them based on a few parameters of the overall tree shape. The spruce tileset has a very simple structure, which made it perfect for this kind of automation: on each side of the tree in each row of tiles, there can be one branch, and branches never cross into other rows. All I had to do was define which tiles connect to which other tiles, and pick randomly from those for each branch.
This tool lets me generate random spruce trees with one click. So much less work than doing it by hand or sketching the trees! So easy, in fact, it spoiled me hardcore. I had to extend this to building the other kinds of trees.
Pines were tougher because their branches can break out of the row in which they start. To deal with this, I defined the leafiness of each tile, somewhat similar to the labels for the terrain generator, except in code rather than with an image, and filled in empty tiles based on the leafiness surrounding those tiles. So, if an empty tile is left of a tile that requires leaves to the left, and above a tile that requires leaves above, then it'll be filled with a tile that has leaves arranged in an L shape.
It took me a while to think of this method, but once I got it, the results were pretty good!
That left me with birches being the only tree type that still required sketches to generate. The birch tileset is structured differently from the spruces and pines, the birches are covered with leaves and branches are visible only in glimpses and are diagonal, so the one-branch-per-row approach didn't work for them.
Instead, I decided to make the foliage the important thing. For birches, I first generate a "plan". In the middle is a column of trunk tiles, and sticking out of the trunk are leafy masses, generated similarly to the branches of the other trees. If I shift this "plan" over by half a tile in each direction, each tile ends up with a rough description of the sort of tile that goes in there, so all I have to do then is fill those tiles. The tedious part here was describing all the tile shapes for the code, since many tiles can fill multiple roles.
Here's what a plan might look like once shifted over, with the main tile grid for reference:
And with the tiles filled in according to the shapes:
And of course, the end result without the plan and grid:
These tree-generating tools are a lot less general than the level generating-tool and so I doubt I'll be sharing them. Still, I hope they've given you some ideas on how you can automate this sort of stuff.
I love parallax effects. Tiled Editor has no support for them (yet?), but as you can see in the gif at the very top, I didn't let that stop me. My solution was to pretend Tiled supports them.
For finite, non-looping maps, I prefer to think of parallax layers as having different sizes, and with the edges all lining up when the camera is close to them. This means that larger layers scroll faster compared to smaller layers as the camera moves. In this example map, the main layers are 55x36, the background trees are 52x34, and the two foreground layers are 58x38 and 61x40.
In Tiled, the map and therefore all the layers are as large as the biggest (=most foreground) layer. I give each layer "width" and "height" properties that define its actual size, and tiles beyond these dimensions aren't read in by the engine. That giant dark block towards the right is on a background layer, and it never shows up in-game because that data isn't read in, and that layer never scrolls that far right anyway. This approach does waste some space by having parts that aren't seen in-game, but it's not a lot since the maps aren't gigantic, and the largest layer isn't that much larger than the smallest.
And lastly, I want to mention the software I've been using to record the gameplay gifs I've been sharing. I use two different programs, depending on what I want to do with the gif.
For sharing gifs, I like GifCam. It's lightweight and has a very simple interface. Pick a framerate, record, stop, save.
When debugging split-second rendering bugs, GifCam's 33fps limit means that it often misses the problem frames, making it difficult to record the bug for closer inspection. For these situations, I used ScreenToGif for its ability to record up to 60fps. Although it does a great job at lower framerates too, I don't like using it because it opens up a fancy editor after you're done recording and adds more steps for saving the file. For quick recordings, GifCam is still my favourite.
Whew, this was sure a lot of text. Congrats on getting through it all, if you're reading this!