Happy just-after-Thanksgiving! I’ve been adding to my game’s graphics and putting in some useful touches to release the first playable prototype soon. In particular, I’m going to talk in this post about camera shake, and then tips for improved “randomness”.
But first, here’s a quick peek at some additional enemy sprites:
(monsters by Paxton Paddington)
This was just a mockup of combat in the first-person dungeons. In particular, I did that mockup to test camera shake when enemies attack you:
On the one hand, this effect doesn’t look any different from any other approach to camera shake out there. However, I was inspired to code a little script component of my own after seeing this rather fruitful discussion with some interesting ideas for how to implement it.
In particular, the suggestion to use Perlin noise was a really cool trick; now the shake looks correct even when played in slow-mo! That brings up some interesting ideas for status effects (eg. an enemy that casts a slow-mo field on you)…
I also did some utility work regarding randomness. As explained here, “true” randomness may feel lousy sometimes. While that works fine for certain uses in games, sometimes it feels bad when there’s (for example) a streak of lower numbers. A different approach to generating random numbers that often gives a better feel is a technique referred to as shuffle bag.
That approach is basically programmatically creating something like the bag of tiles in Scrabble. Instead of calling a random number generator for every number, you fill up a “bag” with every value that can appear, in the proportions that they appear, and then pull out the values in random order. As for the implementation specifics, the code inside the black box doesn’t really matter so long as the public parts of it operate the way described.
Here’s the script I wrote. The “bag” is simply an array, while the NextValue getter returns the value at the current index as well as incrementing the index. I used the Knuth shuffle algorithm to randomize the order of elements in the array. NextValue shuffles the array again and returns to index 0 when it reaches the end of the list. As a final detail, I made sure the first number after reset is different from the last number before reset.
Oh and I wrote a couple different constructors: one to initialize the bag with an explicit list of numbers, but there’s also a convenience wrapper for the most common use case: a bag with every number in some range.
As a comparison, here are the encounters after 100 steps using RNG vs shuffle bag. Every move in the dungeon has a 1 in 5 chance of an encounter; the plus signs are when enemies show up.
RNG: -++---+--------+----+-++---+--------+-----+---------------+---+--+----------------+-+-++------+----- Bag: ----+----+-+------+----+-----+--+----+--+------+---+------+-----+-+---+-------+-----+--+------+---+-
You can see that the first string of encounters is a lot more clumpy. Even though there are about the same number of encounters both times, the RNG resulted in long streaks of no encounter with clumps of several encounters in a row. The shuffle bag on the other hand evened out the results; while it was still random when exactly encounters occurred, there was a more regular cadence to them. This gives the game a better feel.
The last thing I want to discuss are woes I had with Git LFS. Overall that’s a good solution to your repository inflating when there are binary files in it, so I implemented that for all the graphics. However I didn’t realize BitBucket restricts you to only 1GB of LFS storage for free (in fairness, GitHub doesn’t even allow free private repositories) so I had to do a bunch of cleanup to prevent filling up my quota.
I would strongly recommend planning ahead for this if you’re going to use LFS. I moved all my work-in-progress art over to Dropbox, and I’ll save LFS space by only copying finished assets over to the git repository.