Left to Die was a platformer inspired by traditional Mario and by some of its quirky spin-offs, namely Syobon Action (AKA Cat Mario), I Wanna Be the Guy, and VVVVVV. It was made as part of VGDC UCI's 2015 winter quarter game jam. The theme of the jam was 'Choose a Side', and we decided to put our own spin on the platformer genre, where facing left inverses all the blocks the character interacts with.
Left to Die was one of the first jams where I took a lot more of a leadership and mentorship role over a game jam project. I was kind of put into the position just based on how groups were picked by the VGDC officers. Since I had been a pretty active part of the club and was experienced as an artist, I was weighed pretty heavily when deciding groups.
I'm not 100% sure how they decide groups now, but we used to use a random generator that Masana, a programming officer, wrote for us. The generator chose groups based on expertise, e.g. programming, art; and years of experience. The officers then do a double check over to reassign members to make balanced groups. Masana's generator saved us a lot of time, so I imagine it's still being used/maintained to this day.
On Left to Die I did work in almost all parts of the game, doing art, programming, then sound, and back to programming again. It kind of had to be done because we had so few people on the team. Out of the half a dozen or so that we were assigned, only 4 of us showed up. But we made the best out of our situation.
Andrew was a first year who wanted to jump into a game jam for the first time. I thought he would probably be our weakest link for this project due to his lack of experience, but he pulled himself together really quickly, picked up knowledge on the fly, and really contributed a lot of work for us, to my surprise. He mainly worked on animations, background parallaxing, and game logic.
Kinsey was also pretty shy about her experience, but she too also picked things up quickly and did her assigned parts. It was kind of funny because she seemed the most hateful of Pygame coming into the jam, but I think she learned to eventually tolerate it over the weekend. She mainly worked on the level editor and making all the levels for our game.
Hector to my bigger surprise, didn't quite live up to my expectations. By year count, he should've been the most experienced and knowledgeable out of us, but he struggled quite a bit during this jam. He was mainly tasked with game logic and game structure, setting both up properly, but I eventually had to go in and rework a lot of it to my personal liking. I guess we can chock some of it up to Hector's lack of Python experience.
Funny story though, somehow Hector was using TortoiseSVN successfully to push his code even though we were using a Git repo. How that was even possible, I don't know, but we didn't find this out until the end of the jam. That probably explained why things were being constantly overwritten for no apparent reason.
The start of the weekend was somewhat rough. I think we were a little dismayed that we didn't have a full team and that a good chunk of us were quite inexperienced in jams. We also couldn't think of a great idea for the theme. But I think as we kept on discussing ideas, we gained back morale as got to know each other better. Kinsey and Andrew were definitely the talkative bunch and constantly gave suggestions/feedback.
We started off thinking about genres. Eventually we landed on puzzle games and specifically platformer puzzle games. These are the kind of games went around a map and then activated objects affect the game. I believe we were discussing some kind of platformer where you had to choose either the left or the right side of a map to navigate and activate things. And once you've beat both sides, you then beat the level.
That got us thinking about maybe some kind of mirrored level that was similar on the left and right sides but one side would be a lot more difficult to complete. Perhaps activating one side's end goal would make the return path a lot harder. We started thinking of very simple activation mechanisms, and we eventually connected this back to the theme itself, where moving left or right actively changes the landscape.
And that's where Left to Die was inspired from. You played as a character that had to get to the end of each level. On the level were two kinds of tiles: normal tiles that you could bump into and spike tiles that killed you. You had to carefully maneuver around the map to reach the end. The trick of the game came when you switched directions. This would cause all normal tiles to switch to spike tiles and vice versa.
This was really an awesome idea in my opinion. Not only did it follow the theme really well, but it seemed fun and, most importantly, seemed very doable. There weren't a lot of assets that I imagined we needed for this, which meant less work for both me as the artist and the rest of the team. We didn't need to move the camera or do anything too special, and I think things could be pieced together pretty easily.
We then decided what language to use. Python/Pygame seemed kind of a natural choice to me since I had some experience in it and for the most part all of the members should have used Python at least a little bit in the past. UCI's introductory courses were now taught in Python. Most teams as usual though, use Unity because of its flexibility and speed, but I think as a learning experience, our team definitely got a lot out of this project.
I did some quick brainstorming and wanted to task out individual parts of the game to everyone. For myself, I would be working on the art first. I wanted someone to make a very simple animation class because I know we'd need that for at least the character. This I gave to Andrew as a way to get used to Pygame's GUI and drawing. For now, all he would have to do is write code to be able to display an animation running on his local screen.
This seemed like a good separated task for him. I explained the concept of sprite sheets to him, and he seemed to understand it pretty well. Best case scenario, his animation class works great and we plop it into the game without a problem. Worst comes to worst we just abandon animations altogether and just have still frames, not a huge loss for this kind of game.
Kinsey I gave the task of making a level editor since I think she said she was interested in design. Apparently she had some experience with Pygame already, though a pretty dreadful experience at that, but nevertheless at least she knew some the basics. I explained to her how a simple level editor could work. Basically she'd be making an interactive GUI that lets you edit tiles and eventually save your changes as a text file.
The text file matched the changes you've made, so if you made an entire stage full of spike blocks, the entire text file could be filled with, for example, 'S' characters, for spike. The same logic applied to normal blocks, perhaps an 'N'; or blank/no tiles, 'B'. Then on the game side, you read in these character text files and recreate the level onscreen.
This was essentially just a step further than editing text files directly. I thought this would be another good section for a member to specifically focus on and could be easily integrated into the game. This would fit Kinsey the best since she was planning on making levels anyways.
For Hector, since he had the most experience out of the programmers, I wanted him to write the general structure for the game. I imagined it would be somewhat similar to the Capstone project we were working on, Block Buddies. I'm not exactly sure what he wrote, but I did have to go in and touch a lot of it later, with the help of Andrew.
I'm not sure of how to call the design pattern that I modeled off of, but it was inspired from the good parts of what I worked with from Tatami Galaxies and Tetris Buddies. Basically, parts of the game were broken up into separate classes/entities, which had update() and draw() functions. The former function was used for calculating changes per frame while the latter was used to draw the image. Entities also had constructors to set up anything they needed.
These would all be controlled by a general game manager. When initialized, the game manager constructed all the entities, e.g. player, blocks; and other systems it needed, e.g. Pygame, screen, input, sound. The manager had a state variable which controlled what got updated/displayed.
And that was mostly what I envisioned, but with Hector's code, basically everything got squashed down into one gigantic manager class. It was definitely a challenge to work with. Merging was always a problem since we had one bloated file, especially with Hector somehow pushing with TortoiseSVN of all things.
For me, art was not very intensive seeing how there were so few assets we needed. The most difficult task I had was really just deciding the aesthetics of the game. In homage to Mario, and mostly Cat Mario (Syobon Action), I worked in a pixel-art style and themed our character off of the main cat guy of Cat Mario. Animation was thankfully very simple. All I did was basically shuffle his body a little and move his eyebrows.
Tiles were easy since they were just simple blocks, and we only needed two of them. The end goal, a flag, was the only other animation I needed to draw. I then made some background art like clouds and bushes. And to hone in more on the inverse aspect, I made a copy of all the assets I made and inversed their colors..
When facing right, you would have a normal colored, jolly old landscape, but when you reversed, all the colors would flip, the screen would start shaking, and all the background bushes and clouds would travel backwards. I implemented the screen shake, lifted off of Winston's lifted off code from Tetris Buddies. Andrew worked on the moving background, which took a really long time for him, but he eventually figured it out after maybe or maybe not some hacks.
Eventually I also worked on sounds for the game. Before this project I never contributed music or sounds for a project, so this was an interesting experience. It's not like I did anything too creative though. For music, I lifted off a pretty iconic theme from Cat Mario. The song's a bit annoying and repetitive but just what fitted the kind of game we're making. In addition, I put in a repeating sound track of static that would play when the player turned left and went into the inverse world.
The sound effects were made using the website Bfxr. It's a very convenient tool that lets you randomize a bunch of sounds and tweak each one to your liking with a bunch of different knobs and bars. It has a very 8-bit sounding tone though, so it's not something that would probably work for all games.
The rest of my time was spent on doing programming. Since I made the sounds, I naturally also worked on the sound manager, a large chunk of which came from Tetris Buddies. Screen shake wasn't too bad to write since I had reference code. I fixed some game logic problems with Andrew, and Andrew was the one who polished the remaining logic issues.
For almost the last entire day I worked on trying to make collisions work. In my head, I had everything perfectly planned out as I'd like. Pygame already had collision detection for overlapping rectangles inside its Rect class. This was convenient because I could make a slightly smaller bounding rect for the player, to trim off white space. I then compared this player collision rect to the blocks and tested if a collision occurred.
The next part was resetting the player. If the player hit a normal block, then the player needed to be pushed out a certain distance based on which direction he came in from. To determine this, I kept track of a previous position value. The game saved this previous position, calculated the new predicted position, and then threw both of these values into the checkCollision function. For example, if a player collided on the left side of a normal block, he would need to be reset along the left side.
For some reason though, this didn't work. I tried for hours to figure out the issue, but by the end of the jam, I wasn't able to find a solution. This error probably costed us placing higher in the game jam rankings. We placed 3rd overall, which I'm not too sad about, but I felt like we definitely could have done better. To me, this was a really amazing and polished game that fit the theme really well. In my heart we were first.
As for why the collision bug occurred, it turned out I just didn't know Python or scripting languages that well. I assumed that my code behaved more similarly to C++, where if you set a variable to another, you get a copy of the variable's value, not a direct reference. This was not true in Python, however.
What was happening was when I did previousPosition = playerPosition, then I updated playerPosition, previousPosition would be updated as well because the two variables pointed to the same thing. In order to bypass this I needed to make a special copy the position value. It's a pretty dumb error, but at the very least it's fixed now and collision looks fairly clean.
Also what might have cost some points with the judges were errors running the executable. Apparently on some computers, the build couldn't open or crashed upon opening. At the time, none of us knew what was wrong, but now almost a year later, we think we've found a solution.
Basically it boiled down to Pygame having problems and cx_Freeze, what we used to build an executable from our Python files, having problems as well. I'll leave the details in Going Home, the Pygame project in which we found the solution. Props to Andrew for figuring out the issues.
And that about wraps it up. This was a super cool project that I had a lot fun on and learned a lot from. I've been giving Hector some flak, but everyone was pleasant and cordial to work with regardless of experience. For some reason this project would lead me into basically doing Pygame projects for the next year or so in VGDC game jams. I don't know how I feel about this, but it's all in good fun.
Oh yeah, one more thing. The 2 in the introductory level was put there because we couldn't fit 'to'. I guess if we maybe made the 'to' letters smaller and spread them diagonally we might have been able to fit them in, but the 2's fine and more readable I suppose.