For my Living Dex Project, I need Pokémon sprites for my huge list of caught Pokémon in Making a Living Dex: Appendix A - The Whole Living Dex Roster. The spites come from Pokesprite, which I think is what everyone who needs Pokémon sprites uses.
I was originally using the spritesheet from PokdexTracker which was generated by their own spritesheet generator.
However I didn't like the direction PokedexTracker went with Pokemon Legends Arceus where they used the more rounded and 3D look that the game did for sprite portraits. And with that, this project was created so I could easily generate my own spritesheets exactly to my needs.
Pixel-based | 3D-based |
---|---|
This work is be heavily influenced by the work from PokdexTracker.
- Get latest sprites and associated metadata
- Scale the Pokemon sprite if needed
- Trim whitespace such that there is zero whitespace padding around the sprite
- Generate single spritesheet
- Generate
.css
files - Copy to output location
Decided to force in a few newer features to learn too.
This project is also an experiment to learn about the newish System.Threading.Channels
and how to create parallel and linked up pipelines using it. Originally I had a place in another repo of mine for learning these: System.Threading.Channels Learnings.
Channels are used here to process each Pokemon through the pipeline asynchronously.
Having a few RaspberryPi projects using .NET, I became excited to see the feature request to add System.Formats.Tar
. The Pokesprite NPM package that gets downloaded in a .tar
format and needs to be extracted. Feel free to check out more information in my post How to Natively Read .tgz Files With the New C# TarReader Class.
In .NET it often feels like IDisposable
types are heavily paired with using
statements. Then as of C# 8 (.NET Core 3.x+) we got a new feature: using
declarations. With these you don't explicitly set the scope and these IDisposable
objects are disposed when the outer scope is completed.
For example:
// using statements
using (var gzip = new GZipStream(tgzStream, CompressionMode.Decompress))
{
using (var unzippedStream = new MemoryStream())
{
await gzip.CopyToAsync(unzippedStream);
unzippedStream.Seek(0, SeekOrigin.Begin);
using (var reader = new TarReader(unzippedStream))
{
}
}
}
// using declarations
using var gzip = new GZipStream(tgzStream, CompressionMode.Decompress);
using var unzippedStream = new MemoryStream();
await gzip.CopyToAsync(unzippedStream);
unzippedStream.Seek(0, SeekOrigin.Begin);
using var reader = new TarReader(unzippedStream);
However, this pattern can't be used everywhere. For instance when using the Graphics
object to manipulate a Bitmap
object, the Graphics
needs to be disposed before the changes occur in the Bitmap
(I think..). Meaning there is a piece of code here that uses both styles of using
:
using var imageStream = new MemoryStream(item.Image);
using var pokemonImage = new Bitmap(imageStream);
using (var graphics = Graphics.FromImage(spritesheet))
{
graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
graphics.DrawImage(pokemonImage, column * maxWidth, row * maxHeight);
}
Introduced in C# 7 (Framework and Core 2.x+) were local functions. I don't think the project has a good use case for it, but I wedged it into Npm.cs
to get what the most recent version of the NPM package is.
These have since been removed and replaced with normal classes.
I was originally using record struct
types to represent data. But when I moved to also bringing in balls, I wanted them to be treated the same in the pipeline, meaning I could then inherit from a common object. Only record class
types can have inheritance.
At the time of writing this, Raw String Literals is a C# 11 preview feature. They're strings that let you put all sorts of characters in it like quotes or backslashes without escaping them - because you cannot escape anything inside a raw string literal.
The syntax using triple double quotes on each side of the string: """..."""
. For this project it gets used as part of the css generation just to make the presentation nicer for the base css class:
private string RootClass = """
.pkicon {
@include crisp-rendering();
display: inline-block;
background-image: url("pokesprite.png");
background-repeat: no-repeat;
}
""";
Then used the interpolated version of raw string literals to create each Pokemon (and form) class. Again, I didn't technically need to use them, but it made it much easier to deal with curly brackets required for css:
var cssClass = $$""".pkicon.pkicon-{{item.Number}}{{FormData(item.Form)}} { width: {{item.TrimmedWidth}}px; height: {{item.TrimmedHeight}}px; background-position: -{{column * maxWidth}}px -{{row * maxHeight}}px }""";
In order to use the raw string literals, I had to edit my project file to use <LangVersion>preview</LangVersion>
.