Saturday, March 13, 2021

2021 7DRL: Finished

 Here is the 7DRL submission on Itch.io. Here is the latest version on Github.io.

Long ago I spent a miserable year in graduate school. The research assistant job I had before I dropped out was in the cancer wing of the university's hospital. The radiation treatment facilities were adjacent and I'd gotten a tour of them when I started my job. It was a row of rooms with twisty entrance passages to break the lines of fire.

My parents came to visit me shortly after I had dropped out. I took them to see where I had been working. It was a Sunday and the radiation wing was largely deserted. Eventually, though, we were confronted by a woman who said "I don't think you should be in here!" and threatened to summon security. I hustled on out of there, chagrined. It really drove home that I no longer belonged at the school.

This feeling of being accosted where you don't belong was what I had in mind for this year's 7DRL challenge. I played the HITMAN reboot games recently and enjoyed the disguise mechanics. My primary goals for this year were:

  1. Produce a web-playable game
  2. Experiment with disguises in the framework of my existing thief roguelike game

Getting a web-playable game took about half the time. I was using languages, platforms, etc. that I haven't really worked with before. Working on the disguise mechanics took the other half. I don't know that I've succeeded in making a really fun disguise system yet, but there is a fair amount that went into it.

All the new tech:

Rust is not bad. I'm not sure yet if it's where I want to write all my code going forward, but it isn't bad. The compiler gives good error messages, and the game seemed to perform adequately. I didn't do any profiling. I did run into one crazy problem with the compiler; it doesn't properly handle excluding library features on a per-platform basis. I asked on a forum and the nightly build has a fix for it. In the end I just targeted WebAssembly alone; I haven't got a native target. My framework is simple enough that I expect I could write a Win32 version of it fairly easily in Rust.

WebAssembly still feels half-baked. I had trouble with wasm-bindgen versioning, so I ended up just wiring up the imports and exports myself. I stuck to simple integral data types in the function parameters and didn't try to do any memory sharing.

Javascript is also not bad. It always seems to work pretty much the way I expect it to. I only wrote around 450 lines of Javascript for this project.

WebGL is very similar to OpenGL. It works okay. I'm not a huge fan of the OpenGL way of doing things, but I am familiar with it so that helped.

Github.io was new to me. It turns out to be a really great way to set up a static website. Just point a Git repo at it and away you go. I'm told you can set up Github actions to automatically build the site when a repo over on Github changes. I'll probably try to set that up; I ended up manually doing that step and it was getting annoying.

The new game design elements:

Since the player's thief can look like the enemies I added a little overhead caret to mark him. I added bump-to-interact functionality (for changing clothes) which wasn't in the previous game. (It was in the first ThiefRL so I referred back to that.)

I created two different colors of guards, in order to have two different disguises (plus your default outfit). I set it up so one class of people is not allowed in the private rooms of the house; if the private-wing denizens see that outfit they'll attack. The public area is patrolled by both the public and the private guards. I'm not thrilled with how this turned out; bifurcating these already-small levels results in more un-patrolled space, since guards try not to go into a room where they will have to go back out via the same door. I have thought about changing the patrolling behavior to allow for going into dead ends; maybe they would stop and stand for a bit. Haven't gotten to that yet though.

Guard awareness got some adjustments. Since you are in disguise they can't just attack you, which means you can be doing things in their view like picking up loot or climbing on tables and that looks weird. When the player does anything "suspicious" it sets a flag for the next guard update. If they see the player, and the player is being "suspicious" then they escalate. In the old thief game if you got spotted you could duck into a bush or under a table in full view of the guard and hide successfully. This is no longer possible. I felt like I needed to nerf hiding somewhat to make the disguises serve more of a purpose.

Once I hit on the idea that disguises would effectively cut guards' maximum distance they can see you at down to one or two squares it was pretty fun to try to maneuver around them in the rooms to avoid being too close. Right at the end I realized that the inner-room disguise was strictly better than the outer-room disguise, which was not very fun. I ended up making it so that people who match the disguise are not as suspicious as people who don't. It doesn't make a lot of sense logically, but it means you want to be wearing the outfit that matches the area you're in (and the people who are in it) which gives a reason to switch back and forth. It's still not much of a reason.

I experimented with having special "boss" guards who could see through disguises from a distance. This was an idea borrowed from Hitman. It complicated things too much, I felt, so I removed it.

For a while I disallowed hiding in bushes or under tables (or underwater) while in disguise. This is potentially interesting; it gives much more of a mode shift between running around in the thief outfit and wearing a disguise. It means the thief outfit has its own perks and isn't strictly inferior to the disguises. I took it out because I felt like it was complicating the rules. It also made for some really difficult maneuvering sometimes in the current levels. It's worth further consideration though.

I made the one-way windows so the player can see through them both ways. I'd tried this out on the original thief game and rejected it because I didn't want players to feel like they had to run around the house to peek in all the windows and uncover as much of the map as possible before going in. However I think this is a really positive change. One of my big design tenets is that you should have areas you can see but not get to easily and this serves that goal handily. Often you'll go past a window and see a disguise in the room on the other side and it gives you a goal to work toward.

Placing the disguises so it will be satisfying to find them is something I haven't really fully worked out. Right now they're placed toward the back of their respective zones, so you will have to move through at least some of the zone without the benefit of its disguise. I really would like there to be more of a sense of accomplishing something when you get to a disguise, though.

Overall it was not my strongest game but it should hopefully be at least somewhat fun. Disguises make things easier, and they give you more detailed interactions with guards. Both of these present challenges to keeping the game fun. I think there are lots more things that could be done with disguises though. A couple ideas I wanted to try:

  • Identify a particular guard who is a secret ally. To do this you need to see the faces of guards from close range; they'll get marked off as they're identified as either your ally or not.
  • Confront a particular person when none of the other guards are nearby.
  • Disguises that provide environmental protection (from cold, or dust storms, say)
  • Ways to influence the patrolling behaviors of guards. Could be by activating "activity stations" that will occupy a guard for a time (cleaning up a mess or something). Or it could be by opening and closing doors, and guards would assume that closed rooms (that aren't on the main path) don't have to be checked but open rooms should be checked.
  • Add stationary guards; "bouncers" to make it much harder to get into parts of a level without the right disguise.

Thursday, March 11, 2021

2021 7DRL: Continued

The work-in-progress is playable here. It is basically the thief game with a disguise added. (The port of the old game, for comparison, is playable from here.)

If you find the outfit (generally in a dead-end room in the tiled part of the house) you can swap clothes. Guards don't see through the disguise unless you are very close. If they are suspicious you can move away and keep things from escalating.

I tried having one guard who was specially marked and could see through the disguise but that was not adding much. I'm trying making it so when wearing the disguise you can't hide under tables or in bushes. That seems promising. I'll probably also make it so you can't steal things while seen without raising suspicion.

I've also managed to fix a few bugs along the way. The most long-standing one, dating back to the original game, was that guards who couldn't move would spin in place. This was just due to how it updated their facing when the movement vector was zero.

I'm currently working on having three different sets of people, with distinct disguises that work for each. It will be interesting to see if this adds. The people will occupy different (possibly overlapping) parts of the house and will only see through the disguise that matches their own outfit. This might be too easy; I don't know.

Tuesday, March 9, 2021

2021 7DRL: Begin

I'm participating in this year's Seven-day Roguelike Challenge after a couple of years away. It always seems like a bad time in terms of whatever is going on in my life, but participating in previous years has been really rewarding for me personally.

This year I am starting from my 2016 7DRL entry, ThiefRL2. It was a simple game of stealth. My main goals are to make something playable on the web, and to try to implement a disguise mechanic along the lines of the Hitman games.

For starters I have ported ThiefRL2 to Rust+Javascript, using WebAssembly. The port is mostly complete and playable here. I'll probably be rearranging that website as I figure out what I'm doing. The source code is on Github.

The main gameplay feature that isn't yet implemented in this port is that the guards don't hear each other shouting. Other features that aren't yet implemented are viewport resizing and scaling.

Otherwise it seems to work pretty well! I had originally done the port to Rust using a library called Quicksilver, but performance was not good and the library's creator ended up dropping it. I asked around on the roguelikedev reddit and somebody mentioned Dose Response, a Roguelike that Tomas Sedovic had created and then ported to Webassembly. The blog post linked above was really helpful for me. He started from an ultra-simple example by Richard Anaya. I started from the same point and was able to bootstrap my way up to a working framework. Fortunately my needs were really simple.

I ended up using WebGL for the rendering, which I'd never used before. WebGL hews very closely to OpenGL, for better and worse; in my case since I was already familiar with OpenGL it made things pretty easy to put together. Dose Response was using a memory buffer exposed from the WebAssembly module for collecting the tiles to be rendered. That is probably a faster method than what I'm doing, which is to collect the geometry on the Javascript side. As a result there's one call from the WebAssembly to Javascript per rendered quad. In practice that seems to work fine.

The current interface between the Javascript and the Rust is very narrow. Javascript calls a function to start the game, passing in a 64-bit random seed. It then calls a function each time a key is pressed, and another function when it wants the canvas drawn. Rust, in turn, calls a function for rendering an untextured quad; another function for rendering a textured quad; and a function to request that the canvas be redrawn. That's it!

The Javascript loads up the textures ahead of launching the webassembly; the Rust code just refers to them by index. The WebAssembly interface only handles basic integral and floating-point types; I had some issues trying to get the fancier wasm-bindgen stuff to work so I skipped that.