Raccoon Revenge
Snatcher the Raccoon Bandit wants to overthrow the human race! Well, overthrow all their trashcans at least.
The Game
Raccoon Revenge is a stealth-action collectathon about a michevious raccoon. The game is published on the App and Google Play store (there's currently an issue with the google play storefront) It was made during the 2020 Mass DiGI Summer Innovation Program.
The Team
On Raccoon Revenge there we're 6 total team memebers including myself. Focuses within the team include an artist, UI designer, two programmers, and a producer.
My Role
With a small, talented team I naturally found myself falling into an assortment of roles for the project. Some major one's include:
Designer
My main roles as a designer was in level design. I prototyped around 100 levels in the development cycle. I wrote our level design document and used results from our data platfrom to iterate and make informed level design decsions.
Programmer
I implmented systems such a level based randomly-generated powerup system and a 32x32 texture to playable level generator which was the foundation of a rapid level design system.
Movement was a big challenge in the project and we took many different approches to solve the problem. The first version had Snatcher follow to a point using a A* system. It worked as a movement system but playtesters wanted more visability of the screen. We ended up using a classic mobile joystick for ease of players use, although even then specfic refinements were put in place to make rounding corners and diagonal movement easier to manage.
I worked with the other programmers to maintain a profesional code base and project structure.
Associate Producer
As the associate producer I was in charge of the digital project and how the team worked with it. This meant I managed our digital kanban and oversaw our source control in Plastic.
Build Manager
A role I took twice a week on our designated build days. It was not the role I wanted going into the project, but I actually had a lot of fun writing the game's copy and build notes, although it was still painful when builds wouldn't work.
Those are the major areas I contributed to, however I wore many more (and drew a few) hats in this project because of the scope of the game and the small size of team. Some of those smaller roles include:
- Trailer Editor
- Playtest Leader
- Icon artist
- Credits Writer
Process
In total, the game's development took 4 months, from May to August 2020. As it was during 2020 the entire development was done remotely (I was personaly in a basement apartment in Brooklyn for that period of time)
Here are the major steps:
Conceptuilization
For our brainstorming we made heavy use of Miro. This was a complete lifesaver to organize our ideas while remotely working. Here's just a snippet of what the team's board looked like.
Production
Production lasted for 10 weeks once we settled on the idea of Snatcher the Raccoon bandit. We started with prototypes of the movement, attacking, trash collecting, and AI. After the basic systems were in place we shifted to maximizing polish of the games elements with art, sound, and UI. We also added new content in the form of hats and levels for the last two months of development.
Polish
Near the very end of the development period we pivoted heavily towards polish of all game elements. Along with this, to test how well our development processes worked on other teams we switched projects with other teams and spent a day developing content and polishing there games. This prepared us for when we would hand off the games to teams to continute work in the semester.
What I Learned
In short, a whole lot. If I listed out each lesson learned like my other project this page would take minutes to scroll down. To synthize the biggest lessons into the smallest form:
- Organization is work and is very important work
- Feedback is a potent and important force
- The stronger the pitch the stronger the game will be
- Appreciate scope and focus on making the best of what you can, not what you want
Code Blocks
Powerup System
This function establishes a powerup after collection. PowerUp is an abstract class that the powerups inherit from to run the specfic visual and gameplay effects.
/// <summary>
/// Begins powerup effects
/// </summary>
/// <param name="collector"></param>
protected virtual void PowerUpCollected(GameObject collector)
{
if (collector.CompareTag("Player") && (powerUpState == PowerState.Attracting))
{
// Sets state to collected
powerUpState = PowerState.Collected;
// Sets player reference
player = collector;
playerController = player.GetComponent<PlayerController>();
if (playerController.powerUpAmount <= 0)
{
// Set object onto player
playerController.collectedPowerup = this;
gameObject.transform.parent = collector.transform;
gameObject.transform.position = collector.transform.position;
// Message
if (LevelController.instance)
{
Debug.Log("Setting up big boom");
LevelController.instance.uiManager.
SetPowerUpButton(this, spriteRenderer.sprite);
LevelController.instance.meter.
ShowDisplayMessage("Got " + PowerUpName + "!");
}
// Turns off image
spriteRenderer.enabled = false;
// Hide particle system and disable collisions
//GetComponentInChildren<ParticleSystem>().Stop();
rbody.isKinematic = true;
coll.enabled = false;
transform.GetChild(2).gameObject.SetActive(false);
}
else
{
gameObject.SetActive(false);
}
playerController.powerUpAmount += 1;
LevelController.instance.uiManager.powerUpCount.text =
"Level " + playerController.powerUpAmount.ToString();
}
}
Knock Over Trashcan
This function runs when the Raccoon knocks over trashcan. This interacts with the powerup system by having specfic powerup rates for each level that this function usees to spawn trash and powerups.
public void KnockOverTrash(float dir, Collider2D collision = null)
{
if (!isKnockedOver)
{
isKnockedOver = true;
render.sprite = tippedSprites[spriteIndex];
render.color = Color.gray;
transform.localEulerAngles = Vector3.forward * dir * Mathf.Rad2Deg;
for (int i = 0; i < LevelController.instance.CurrentLevel.trashAmount; i++)
{
GameObject inst =
Instantiate(trashPrefab, transform.position,
Quaternion.identity, transform.parent);
SetTrashVelocity(inst, dir);
if (collision)
{
inst.GetComponent<Trash>().SetPlayer(collision.gameObject);
}
}
if (Random.value < LevelController.instance.GetTrashProb() && powerUps.Length > 0)
{
GameObject powerUpInst =
Instantiate(powerUps[Mathf.FloorToInt(Random.Range(0, powerUps.Length))],
transform.position, Quaternion.identity, transform.parent);
SetTrashVelocity(powerUpInst, dir);
LevelController.instance.HasSpawnedPowerup = true;
}
AudioManager.instance.PlayTrashSound();
LevelController.instance.TrashcanKnockedOver();
// Fade out as to not be as visually distracting
render.DOColor(Color.clear, 1).SetDelay(3);
}
}