20th August 2018
Ludum Dare 42 took place just over a week ago. I’ve been on quite a roll regarding game development over the past few months, having already participated in two game jams and created a total of three games this year, so I was determined to once again get involved. The rules were a little different this time around, with the jam beginning – and ending – three hours earlier than usual, which effectively gave me three hours less time in which to work (due to the fact that I typically work right up until the cut-off of 2AM on the Monday morning, whereas on this occasion I only had up to 11PM on the Sunday evening.) Furthermore, I had other commitments for the best part of Sunday, meaning that, as is typically the case, I was somewhat restricted regarding the length of time I actually had to work on my game. Nonetheless, I threw myself into the jam.
The theme on this occasion was ‘Running out of space‘, and I quickly decided on a Tetris-esque game, as I’m generally a fan of arcade games, and was interested to see how easily I could implement Tetris functionality. However, I was acutely aware that a straight Tetris clone was neither innovative nor humourous (two of the voting categories for Ludum Dare). Thus, Petris was born: Tetris, but pet-themed.
The core functionality features that I needed to implement were:
- Pieces (petriminos)1 should ‘fall’ down the well at a set rate;
- Petriminos should be able to rotate 90 degrees clockwise at the touch of a button;
- Petriminos should be able to be moved left and right within the bounds of the well;
- Petriminos should stop falling when they collide with already-settled petriminos;
- Once a row is complete, it should be cleared, and all petriminos above it should fall down to fill the gap;
- Once a petrimino has settled, a new petrimino should spawn at the top of the well; and
- Once a settled petrimino sits above the top of the well, the player should lose.
As it turned out, these features were relatively easy to implement: the key was to build easy individual petrimino out of the individual squares that constitute it, all childed to a blank parent object. Thus, this is how I was able to create the core functionality of my game:
- Making the parent object move down one game unit every elapsed unit of time;
- Not allowing the parent object to rotate, nor the individual child squares – rather, making the individual squares change their local position within the parent object by recognising the connection between the coordinates of a particular square after each rotation2;
- Allowing the parent object move left/right only so far that the x-coordinates of the child squares remain within the bounds of the well;
- Checking the world coordinates that the individual child squares would occupy after each ‘drop’, and making the petrimino stop if any of the coordinates immediately below it are already occupied, upon which the parent object is destroyed;
- Iterating through the squares left behind after their parent objects have been destroyed and forming an array of their world coordinates, then checking how many squares have y-coordinates associated with each row – if a row has ten squares associated with it, it must be complete, so those squares should be destroyed, and all squares with y-coordinates greater than the row number should move down one (or more, depending on the number of rows completed);
- Ensuring all petriminos were created as prefabs, and randomly choosing the next petrimino to be instantiated by generating a random number (the petrimino index);
- Checking after each petrimino settles where any individual squares have a y-coordinate that it outside of the bounds of the well.
Now, I’m not saying that this was the most efficient way of doing things (I really haven’t thought into it that much!), but it did the job. My other task for day 1 (Saturday) was to create some of the artwork, to help the game better reflect my vision and not just look like a somewhat plain Tetris clone. I had a lot of fun producing the pixel art of the seven animals, and below is a screenshot of the state of my game as I ended day 1.
A problem I have with 48-hour game jams is that I always find day 1 – coming up with the idea and producing the main game functionality, creating something that sort-of works out of nothing – to be the most enjoyable part, and then day 2 – making the game look nice, sound nice, and work properly, with a good UI and menus and settings and a title screen – to be a bit of a slog. On this occasion it was a half-slog, as I only had a few hours in which to actually work (probably around 5-6 in total) on the Sunday. Indeed, my other commitments gave me time to think carefully about matters such as the art direction and sound effects, and actually come to some reasonable decisions that I may not have reached if I had been involved in the game for the entire day. Thus, most of my available time was spent creating, editing, and implementing the sound effects, creating some art assets to make the main play area and background look nice, and creating the game menus, such as a ‘controls’ menu, a general ‘pause’ menu, and a title screen. It was time well-spent, I feel, as the game ended up looking like this, which, compared to the above screenshot, I consider to be a marked improvement:
Finally, there was my least favourite part of any game jam: the rush to submit. Typically, I’ll have the game working smoothly, spend ages building it to WebGL, upload it, give it a quick play-through, then notice a bug/aesthetic problem (on this occasion, text that has fitted nicely during my play testing in Unity suddenly didn’t fit within it’s bounds and was thus cut off!), so I’ll have to go back and fix this, rebuild it, reupload it, give it a quick play-through, then notice another problem! Fix. Build. Upload. Test. BUG! Repeat.
On this particular occasion, problems with the Ludum Dare site meant that they extended their ‘submission hour’ to and entire day, dubbed ‘submission day’; however, I didn’t actually find out about this until I’d already finally submitted, having ironed out all of the problems (having rebuild the game a good five or more times in total!). Sleep.
I must say, I think that this is one of my favourite game jam entries. It’s not overly original, and I didn’t even get to implement everything that I wanted to, but I actually find it to be really fun! I’ve never been a massive fan of Tetris (it’s okay, but nothing special in my opinion), but the bright and colourful art style, the quirky pet artwork, and the amusing sound effects make it a lot mroe enjoyable to play.
I hope to come back to the game at some point in the future (after the Ludum Dare voting has ended) in order to create a ‘deluxe’ edition, including extra features. I originally intended to create two main play modes: endless, which would essentially be regular, play until you lose, Tetris; and challenge, which would have a number of levels, each with a particular goal to win, e.g., reach a particular score, complete a particular number of lines, or survive for a particular length of time. However, due to time constraints I was only able to implement the regular mode. Looknig forward, I’m not sure whether to add the challenge mode, achievements that can be unlocked from the endless mode for completing certain challenges, or indeed a combination of the two. Either way, I certainly hope to expand this game and make it something truly special.
1 The pieces in Tetris are examples of tetrominos, geometric shapes composed of four squares, connected orthogonally. The tetrominos that appear in Tetris are officially known as tetriminos, so for my game what could I really call them other than petriminos!?
2 For example, let’s take the L-block petrimino. Taking it’s point of rotation as the coordinates (0,0), the starting local coordinates of the individual squares that comprise this petrimino are (-1,0), (0,0), (1,0), and (1,1). After a single 90 degree rotation, these coordinates become (0,1), (0,0), (0,-1), and (1,-1), respectively. You should be able to see some relation between these two sets of coordinates. Essentially, the x- and y-coordinates swap, and the old x-coordinate (new y-coordinate) is negated. This can be seen from a further 90 degree rotation, which yields the coordinates (1,0), (0,0), (-1, 0), and (-1,-1), respectively. This is all due to the plane of reflection associated with each rotation.