Thursday, June 24, 2021

Painting Massive Darkness: Warrior Priests vs. the Spearmaiden Cyclops

It was a rough academic year. Even knowing that material goods cannot bring true happiness, I found myself excited to see that some Massive Darkness expansions showed up at Miniature Market. One that I picked up was Warrior Priests vs. the Spearmaiden Cyclops, and that is the set I just finished painting. I was in one of my periodic painting droughts, and this was a fun set for getting back into the swing of things.

Interested readers are welcome to check out my other Massive Darkness painting posts:

This set, then, contains the first Monster that I have painted: the Spearmaiden Cyclops. All those unpainted monsters in the Lightbringer box are going to be quite jealous!

First, the three heroes.



The first one I painted was this sorceress. It is the one I was most excited to paint due to the evocative character art. If I recall correctly, her artwork was used not only for the original Massive Darkness, but also in some of the promotional material or updates for Massive Darkness 2. I was disappointed to unpack the figure and see that she has a really strange pose. Rather than standing powerfully with her flaming staff, she is sort of leaning forward from the hips. It does not look heroic or menacing, but like she's getting ready for yoga. The face was also pretty badly cast. Despite my initial disappointment, I had fun painting her, and I like the warm purples, reds, and magentas. I opted against doing OSL with the flame.



Here's Malleus. He was a pretty straightforward paint job, and I used the opportunity to tinker with some of the inks-and-metallics techniques that Dr. Faust recently discussed. None of the techniques in his video were new to me, but sometimes it's good to be reminded of fundamentals. One significant difference I found, though, is that where he says he is just thinning the inks with water, I had a hard time getting a useful consistency with just tap water. I doped mine with matte medium, and that made it behave better on the brush and the miniature.

There are two different tones used on Malleus, following the subtle distinction in the card art. You can see it most prominently in his left leg, where the top of the knee is the toned down, greyer color and the rest is a more silver, more metallic. It's easier to see the difference in person since the light reflects differently off of the more matte parts vs. specular metallics. It adds a nice contrast.

Also, he was a reminder that all the Massive Darkness gang is addicted to fanny pack chains. 



I puzzled over Ostara for a bit. The cart art makes the armor look more yellow than gold, and so that's what I decided to do. I remember at the time being very happy with my mix of paints and mediums that I used to get this color... I wish I had written down what it was. I'll give you my best guess later on. The result, though, is nice: a bright yellow armor in contrast with vibrant purple cloth. I'm glad I spent some time putting a little extra dark lining around the armor panels, because that really makes it pop whereas originally it was too starkly yellow.

And here's the Monster, which is really monstrous.

Spearmaiden Cyclops

Spearmaiden Cyclops

Spearmaiden Cyclops

This is a big, heavy miniature.

I started with the flesh tone, but my first approach was too Caucasian compared to her card art, which has a similar purple tone to Moira. I spent a lot of time on the skin since, clearly, she is mostly skin. My first reaction was excitement, and when I looked at it the next day, I worried that I had taken the contrast too far. I suspected that this fear would diminish as I painted the rest of the figure, but it actually got worse. Looking at it now, I'm happy with it as an exercise in contrast. I don't think I took it too far, but it definitely has a kind of cartooniness. Incidentally, if I recall correctly, the base flesh tone is a mix of Vallejo's Flat Flesh, Buff, Magenta, and Neutral Grey, which I am jotting down in case I need to reference it should I get to painting the Stonebreaker Cyclops monster. The hair is Sorastro's famous blend of Vallejo Dark Sea Blue and Black. I was worried it might be too blue, but with the purplish skin tone, I think it's a good fit.

When I got to the yellow features on her pauldron and shield, I figured I could approach it as I did Ostara. Unfortunately, by this point, I had forgotten what I had done. I mixed up some test batches of metallics and inks, and the one that looked the best was a mix of Vallejo Lemon Yellow, White, and Vallejo Metal Medium. There's a very good chance that this is what I used on Ostara as well. Once again, I used inks to add some depth to the metallic parts.

Here's the whole crew:

I had a lot of fun painting this, although now I have to figure out where to store one painted Massive Darkness monster. As I mentioned above, the unpainted ones are happily jostling around in a cardboard box, but I don't think I can do that with the Spearmaiden Cyclops. That's a problem for another day.

Thanks for reading, and happy painting!

Wednesday, June 23, 2021

Summer Course Revisions 2021: CS315 Game Programming

The first of my summer course revisions is CS315: Game Programming. The online course plan can be found in the usual place. This was a fairly easy revision since I have recently revised a manuscript dedicated to the course. The paper has been accepted for publication in the Journal of Computing Sciences in Colleges, and I will be presenting it at the annual meeting of the Midwest chapter. Writing and revising that paper required me to carefully read through notes about the planning and execution of the course as well as my reflections, which are publicly available in a blog post. The paper includes some thoughts about what might be done in the future, so planning the course became a matter of following my own advice.

I decided to keep Godot Engine for Fall even though we will be back in the lab. I discussed this choice with my Spring game production studio course, which included students who had taken CS315 in Fall 2019 using UE4 and in Fall 2020 using Godot Engine. They unanimously agreed that using Godot Engine was a better choice and that focusing on 2D games in particular helped control project scope. 

I kept the overall course structure from Fall 2021, including the scaffolding of projects. The instructions are all based on Godot 3.x, and if 4.0 is released before the semester starts, I will have to think about which version to use in class. I removed the one-week 3D project from the course plan. Of all the work from Fall 2020, this was the least interesting. It will be interesting to see if students really care about this or not. It's not clear to me if 3D has the mystique to them that it once did for undergraduates. It's so common now that perhaps they will understand that it's fundamentally just like 2D, but with more asset work required and therefore slower cycle times.

Because we will be meeting in person, I have removed the requirement for web builds. I am a little sad to see this go because it's an interesting process, but it is no longer required when we can instead go back to showcase-style presentations. I can always add continuous integration as an option for the final project, which will be the first place where public dissemination becomes desirable.

I plan to have a one-week midsemester jam, potentially to be called the "Midsemester (Ex|J)am." This was a fun break for the students last year, to have a week to just make something that shows off specific technical skills. It should provide a useful transition to the final project. The descriptions of both of these are commented out of the course site right now since they will require a bit more revision. It does not seem worth that revision until I have a chance to get to know this crop of students, and Projects 0–2 should nicely fill up the first six weeks of the semester.

The schedule for the weeks will have students presenting their work on Tuesdays, and so in my plans, I have sketched in a few short tutorials for Thursdays. Some of the things I had in ad hoc videos last Fall I can do in class meetings, with the benefit of live Q&A. Without quizzes or attendance, there's a chance that students may decide to skip these meetings and just work by themselves. It certainly makes more sense for me to reuse the lengthy project descriptions I wrote for my asynchronous online semester rather than to write up shorter ones that are supplemented with ephemeral live demos. I intend to be open with the students here, to get a sense of what excites them and trying to use our time together most fruitfully, whether that means tutorials, Q&A, or philosophical discussions about the role of higher education in 21st century America.

One major addition I intend to make for the final project is the inclusion of development logs. I used design logs in Game Design in Fall 2020, where students had to log their efforts to earn a grade each week. Here, I would like to do something similar to make clear a distinction that some students seem to miss: the difference between "something is due in two weeks" vs "you are expected to make continuous progress on this over the next two weeks." It was the post-Thanksgiving, final iteration of the Fall 2020 game programming projects where I really became aware of this confusion. I hope that using development logs will help me get out in front of the problem. If nothing else, it will prompt me to have a discussion about this with the students, and perhaps I can even learn more about their experience through that.

Tuesday, June 22, 2021

Summer Flutter Development: Internationalizing the Thunderstone Quest Randomizer

Last week, I started laying down some of my Summer projects and began putting together course plans for Fall. I expected that, on Monday, I would jump in with both feet and get those new course sites up and ready. However, on Sunday, an unexpected thing happened on the GitHub page for my Thunderstone Quest Randomizer: a stranger posted a feature request to localize the app to French. 

I first wrote about this app about a year ago, when I described the reason for implementing a new randomizer in Flutter. Since then, I've released the odd improvement, including a significant internal improvement at the beginning of summer to allow the app to be built using null safety. That effort reignited my interest in the game, and so we've been playing a lot of it since then.

Sunday's request for localization support intrigued me for a few reasons. First, it showed that someone besides me actually looked at the open source code and cared that it was hosted up on GitHub. Second, the person who made the request is involved in proof-reading the actual French translation of the game, and it's neat to have "insiders" taking a look at a hobby project. Third, I talk about internationalization a bit in my classes, but I have never actually tried to internationalize a nontrivial application before. A quick look at Flutter's internationalization documentation intrigued me enough to make me want to get my hands dirty.

There were two major problems to solve regarding internationalizing the app: the app interface and the card database. It was pretty clear from early in my analysis that I could not easily use the same approach for both. I will address each in turn.

Internationalizing the app interface was mostly a matter of following the Flutter documentation. I brought in the Intl package and did the requisite configuration. I extracted all the user-facing strings into an application resource bundle file, which is a format I had not seen before. Authoring this JSON file involved a lot of tedious typing, and though I was on the verge of looking for tool support, I ended up just powering through it. I wrote descriptions for each string key, and I documented the parameters for each template string (which they call "placeholder resources"). Something that was not obvious to me from reading the documentation was that, while the intl tool converts regular string resources into dart literals, it converts placeholder resources into functions. This is quite convenient but it was not what I expected.

I used Visual Studio Code for all of my development, and it's worth noting that there's a slight hiccup in the toolchain when building internationalized applications this way. The arb file is transformed into dart code during the build process, but that build process is not triggered by saving the arb file. Writing UI code that uses the generated dart files then will give false-negative compiler errors until the whole application is reloaded. Fortunately, this can be done with the hot-reload tool. It's a workflow I got used to, but it seems like it would be convenient to hook the arb file's saving into a regeneration of the dart code.

Adding localization support to the card database was a more interesting technical challenge. The cards are all specified in a large YAML file that is loaded during application initialization. My original plan was to load only the localized names into memory, which would work fine if one only ever wanted the cards in their platform's default locale. However, when I got into it, I realized that I needed an easier way to change languages for testing. I had assumed there would be a toggle in Chrome that would simply change locale, but all the documentation I found said that it requires restarting the browser as well. That's too fiddly. I also ran into a synchonrization problem: the rest of the application assumed that the YAML would be parsed only once, but if changing the language required re-parsing, then the app architecture needed adjustment to account for the time this takes. 

After having written an almost complete solution using this approach, I abandoned it in favor of a  simpler one. Now, the YAML file is parsed only once, but all the localized strings are stored in memory in simple string maps. This certainly uses more memory than the alternative approach, but it is much easier to swap languages: using Flutter's simple state management tools of Providers and Consumers, the UI can watch for the language key to change and then alter which localized text is shown on the screen. I have not done any profiling to see what the actual memory costs are, and so I'm just banking on contemporary environments having memory to spare.

As of this writing, the app does not have French language support exposed because I only have the two resources localized that the original GitHub feature request gave as examples. However, it is set up so that uncommenting a single line in the settings view will expose language selection options. When someone wishes to localize the data into any language, then, it should be an easy matter to incorporate it into the app.

It was fun to spend a few days learning about Flutter's internationalization support and adding that to my app, even if right now, there's no difference to the user. I learned a few new tricks, and importantly, I gained some experience that I will be able to share with my students in the future. No more hand-waving about internationalization: I can show them an example of exactly how I've done it.