Sherlock Jack Devlog
Several weeks ago, some friends of mine were playing a tabletop mystery game where we were investigators, helping Sherlock Holmes chase after Jack the Ripper.
One of my friends made a reference to "Sherlock/Jack", and the seeds were planted.
I'm going to try to dump as much "interesting" tech knowledge here, talking about what I was trying to do, what I think were interesting bits, what didn't work as well as I wanted.
Procedural Cities
I was planning to have huge procedural cities, built off-line, and, through generated code, baked into the game binary. I didn't get around to doing this, though I had some fun writing a Python script that generated some highway maps.
I knew it'd take a while to get that working, so I instead made a quick and dirty function that generated small cities at random, and it was good enough, so I kept it.
The approach I used is inspired by the discussion here: https://gamedev.stackexchange.com/questions/122015/how-to-generate-a-city-street... I build the street network out, one "Street Segment" at a time, making sure that the local constraints are satisfied. I start with a single intersection (2d point), then find a point at a random distance and random heading. If it's close to an existing intersection, I select that intersection, rather than my random point. I ensure that the origin point and the destination point aren't already joined, I make sure that the distance is neither too far nor too close, and I make sure that the new street segment isn't too angularly close to existing street segments.
Over time, this joins up a lot of street loops, but also gives a satisfying number of dead ends, which felt acceptable for my game.
64px Display in Unity
There are, I'm sure, dozens of approaches to this. I've used one a few times (for other LowRezJams), and it works acceptably for what I want to do.
I first start by making a 2d Unity project, and create a Quad that is scaled to fill the window. I make sure the quad is using an "unlit" shader, turning off every fancy bit of everything I can so that I'm getting the simplest display possible. I create a script on the quad - this time I called it PixelRenderer, but really, the entire game is behind that one script, so a better name might be SherlockJackComponent.
In SherlockJackComponent, I grab the game object (the quad), find the material, and the primary texture, and proceed to write Color data out to the texture, which gives me the effect of having a 64x64 pixel screen display.
I, occasionally, have to pull in some texture assets in, which I do by making public Texture2d properties of my PixelRenderer component. I then have to remember to go through the asset settings to make them read/write, make sure there's no compression, and make sure that the filtering is turned off.
Street Rendering
At one point, a friend commented that the game felt like he was running through blood vessels inside a body, which isn't exactly what I had in mind. Still, it's not a bad comparison.
I started making a loose quadtree for my street information, and ended up not getting very far on implementing that before I got antsy, bored, and wanted to try something else. So, I put lists of street segment ids into a dictionary, keyed by integer tile coordinates. That was pretty simple, and got fast retrieval of a small amount of street segments.
So, I retrieve the segments near the player, and then I run through each pixel, convert it to world coordinates, and see if that world position is within the segment's "thickness". This makes each street segment render as a "capsule", or if you prefer, as a "sausage".
Early versions of this just rendered the streets as a solid gray color against a red background. This led to a weird optical illusion where, if you were running slightly off the direction of the street, it looked like you were slowly moving toward the side of the road, for lack of any visual reference points. So, instead of a solid gray street, I called into Unity's noise function for the world position, and got a lumpy gray texture, which I want to believe works for cobblestones, but I know it doesn't. It does give your eyes something to look at, though.
Beetle AI
It doesn't entirely work, but the basic idea is a "LOD AI" approach - simple AI when the units are far away, more fancy stuff when the AI unit is closer. When far away, I calculate an A* path to the player, and set the AI on that path. It will continue to follow this path until it gets to the next intersection, when it recalculates the path.
When the unit is "close" to the player character, it tries to go straight toward the player character if it can.
Once the player character is injured, the AI unit will back off, to give the player a chance to recover.
Beetle Rendering
I got pretty good at using Quaternions to rotate Vector2s, which feels like overkill, but better than having to write "x * sin a + y * cos a" and getting the signs right, bleh. The big gotcha, and it got me several times, was that Unity's Quaternion.AngleAxis(angle, vector) takes an angle in degrees.
In the next programming language I make, angle variables will have internal tags indicating if you're working in radians or degrees, and the trig functions will handle them correctly without having to convert stuff all over the game code.
For most of development, I rendered the beetles as red dots. The last night of the jam, I replaced the red dot with a sprite rendering function. The dot renderer just checked to see if a world position was within the dot's radius of the dot's center, and if so, colored the pixel red.
The sprite renderer still figures out dot radius, but uses that to scale the position and project it into spritespace. If the sprite coordinates are outside the sprite boundaries, draw nothing. If the Color32 (RGBA) value's alpha is less than 128, draw nothing. Otherwise, copy the sprite's color to the screen at that x,y.
Clue Coin Rendering
Really, this whole game is a shaggy dog story, giving my Sherlock Jack character a chance to say "A CLUE". It's funny to me, even after all this time. For most of development, clues were blue dots. (Which leads to a "Blue's Clues" reference that I didn't intend, but if that's your cup of nostalgia, feel free to drink.) I added in a "spin" angle and rotational speed for the coins. I compute the sine of that angle (or, heck, maybe cosine). If that value is negative, I select the second texture to draw. When I project the screen coords into sprite coords, I divide by this sine (or, as I say, cosine) to get the sprite texture coordinates. When I was determining which sprite to draw, I already determined if the coin is fully edge on, so I won't be dividing by zero.
It amuses me that the beetle sprite renderer is rotating around the z axis, and the coin sprite renderer is rotating around the y axis, and there's basically no shared code between the two.
Sherlock Jack Rendering
You might have a guess how I was drawing Jack for most of development - as a dot. This wasn't quite good enough for me; I wanted a "particle system" for Jack. By which, I mean 4 dots. One for each shoulder, one to approximate the torso, and a final gray dot that is his stylish deerhunter cap.
Really, the dot drawing code was pretty simple, convert the screen coordinates to world coordinates, check point in disk. Done. The only fancy bit is to have the particles' coordinate frame rotated based on Jack's facing.
Sword movement
I've been on a little bit of a kick for cheap keyframed animation for the past month or so, so that's how I approached Jack's sword animation. I made a list of sword animations, each of which was a list of keyframes. Each keyframe had a position for the hilt of the sword, and a position for the tip of the sword, as well as a timecode. A list of 2 of keyframes was sufficient to get started, and in the end, I had 5 keyframes for each of 3 swing animations (swing left, swing right, big swing). When the player hits the button, a random animation is selected, and the sword interpolates its way along those list of keyframes.
Sword Rendering
It's just a line, that's like the first thing we learned in my computer graphics class. Well, first of all, I keep a list of recorded sword positions so that I can draw a history of them, with alpha, so that I get a fancy sword blur effect.
Ok, so still, then it's just drawing a white line, right?
Yeah, and I wrote some code to do a simple Bresenham-like line draw, but I think I got a little too fancy, and some sign errors crept in somewhere, leading to stuff near the end of the swing looking weird.
So, I simplified, and instead of the efficient bresenham code, I instead just figured out a step size, marched along the line segment, and rounded to integer pixel coordinates. Computers are fast, and the lines are only a few pixels long.
Game Modes
This is a technique I've been using for a while - I'll create sometimes a stack, or in some way, have an object that represents the "Mode" that the game is in. In this game, the title screen is a mode. Each cinematic where characters talk to each other is a mode. The main game, as it happens, is not a mode. That way, when the cinematic is done, it sets the current mode to NULL, and the main gameplay happens.
Modes have Update and Draw methods. Inside Update, time and input-based actions happen. Draw does the drawing for that mode. It's pretty simple. The cinematic dialogues were created by just having a list of lines (along with an indication of which sprite to display), which I passed in to the cinematic mode. It knew how to walk through the lines, and mostly, when a cinematic was done, it dropped into normal gameplay.
Padlock Radar
The little icons on the edge of a pilot's Heads Up Display (HUD), indicating where a target outside their Field of View (FOV), those are called padlocks. I want to watch Top Gun again.
For each thing that the player might be interested in, I find the vector to that thing, see if the screen length of that vector is 32 or greater in x or y, and if so, clip it to be exactly 32, and draw a pixel at that spot on the edge of the screen. If it's less than 32 for both, then you don't need a padlock, you've got e.g. a beetle breathing down your neck.
Things I Wish I Had Done
This was a reasonably long game jam, and I still slacked off and didn't get everything I wanted in. Some stuff that would have been nice:
Sounds - at the very least, footsteps, sword swing sounds, sword connect sounds.
Redo the portal - it didn't look how I intended it to. It's swirly and weird, but not the way I wanted.
Sherlock Jack Blinking - this is an outright bug; I intended to have an injured Jack pulse red and black, and when I was using the simpler dot renderer, this was working. Maybe I'll submit a bugfix for this one.
Seeded Procedural Cities - I could have used the names of neighborhoods in London to seed the RNG, giving me neighborhoods that players could learn and exploit. I never figured out how to get that into the dialog. "NOW TRY TO FIND THE WIMBLEDON TIME PORTAL, JACK". So, I left it as completely random each time. Enjoy your endless gameplay.
Better Collision Response - right now, if you try to move along a wall, you stick like velcro. Sliding along walls isn't hard to do.
Debugged (ha!) the Long Range AI - it's funny, because they're the BEETLE gang. Bugs. Something was wrong, and the beetles often pathfind the wrong way, taking them away from Jack. This can be excused as giving the player an opportunity to play a stealth game, but it's not even reliable enough for that. Mostly, the game works, though, because Jack needs to kill a certain number of beetles to collect the clues.
Files
Get Sherlock Jack
Sherlock Jack
The Samurai by Gaslamp
Status | Released |
Author | BigDiceDave |
Genre | Fighting |
Tags | clue, london, Mystery, Pixel Art, Procedural Generation, Swords |
Leave a comment
Log in with itch.io to leave a comment.