jonmerrin

Ludum Dare 51

Justin Time: Man of the Hour!

The Nation of Procrasti and the State of Spacetime have long held onto an uneasy peace, but now it seems that Spacetime might once again be thrust into turmoil. Waves of Procrasti-Nation Clockwork Automata can be seen on the horizon. If left to their own devices, they'll bring Time itself to a halt! Only YOU can stop them, for you are...

Justin Time: Man of the Hour!

Justin Time Main Menu.png

You can't let those Clockwork Monsters get away with this on your watch! You may be surrounded and massively outnumbered, but thankfully these Procrasti-Inators put off their attacks to the last second. As long as you think on my feet, you should have enough Time to take them all down before they lay a finger on you.

Justin Time Donuts.png

Unfortunately, Time is not always on your side. You only have so much time to move before those Clockwork menaces get their act together and attack, so think fast!

Luckily their forces are staggered. Between waves, you can regroup and add new skills to your toolbelt. You might even be able to give yourself some extra Time to fight them with.

Justin Time Store.png

Can you push back the unyielding forces of Procrasti? Can you save all of Spacetime? Will you live up to the title of Man of the Hour? Play and find out!


Introducing Justin Time: Man of the Hour, a roguelike-deckbuilder where time is of the essence! You get 10 seconds to take your turn, but each of your cards costs time to play! Think on your feet if you want to have enough time left to play the cards you need.

Each turn the surrounding enemies will take a step closer to you. Some enemies must reach the center of the circle to attack, some may be able to attack from further away. Clear all the enemies in a round to get a reward to refine your deck and improve your abilities to help with the later, more difficult fights.

We're really proud of this one, so we'd really appreciate your feedback! Please come check it out!

Justin Time: Man of the Hour Postmortem

Justin Time Main Menu.png

Hey all,

Our team usually hops on after a jam to do a postmortem of how what worked and what didn't for us, but we usually don't share it. This game felt like it went particularly well for us though, and we've been seeing the other postmortems being posted, so we wanted to make a post about why it felt so good to make, and what we thought we could still improve on.

Some context:

About the game:

Our game, Justin Time: Man of the Hour is a rogue-like deck builder that uses a 10 second timer as your mana pool. Each of your cards costs time, so you need to think on your feet if you want to have enough time to play all the cards you want to in a turn. Once you run out of time, your turn ends and the enemies get a turn. On each enemy turn, waves of Clockwork Automata from the Nation of Procrasti (Get it? Procrasti-Nation? hehehe) will try to close in on you, and if they get close enough, they'll start to attack.

About us:

We're three developers, though we've sort of settled into roles of Producer/programmer, artist/programmer, and lead programmer. FplatfoThis is our 6th jam together as a team, and 4 of them have been platformers (and one didn't make it). If you want to see how we've been progressing as a team, feel free to check out our itch games :smile: It's been fun tracking our progress as we've improved.

Game Concept: Brainstorming and Selection

We always start our jams with a brainstorming session where we put a few minutes on the clock and we each start writing out word-clouds with whatever comes to mind when thinking about the theme. Sometimes they're game pitches, sometimes they're genres, sometimes they're mechanics, and they're just words or feelings we'd like to capture. Then we come back together, go around offering one or two at a time, then we try to draw out each idea to its logical conclusion, mixing and matching where we can.

I personally wanted to make a game based on the combat from Mega Man: Battle Network. I thought that 10 second rounds of real-time combat with a break to select new cards feels like great pacing and very on-theme.

Here's my brainstorming page for this project: Justin Time Brainstorm Yoni.jpg

As I mentioned, I'm a big Mega Man Battle Network fan...

What we wound up making was really designed out loud though, which brings me to the first big thing that went well:

We designed this game out loud. (Good!)

This wasn't anyone's idea, it was all of ours. In a way we lucked into it, but I think this is the first time we did a jam where I can't actually point to who came up with this game. Aspects of it, sure, but we all really own this idea. I don't think this game actually appears on any of our brainstorming sheets, but aspects of it appeared on all of ours.

We took our time (Good!)

Usually with 48-hour jams, we're itching to get started. This was our first 72-hour jam, and we took an extra couple of hours on design, cracking jokes, and ideating. Not only was it fun and less stressful, but a lot of that extra content really shaped the final outcome of our game.

We couldn't use the software we wanted to (Bad!)

I think we had card games on the mind already when we started brainstorming because we'd recently bought the Humble Software Bundle: Unity Tools, which included a license to use CCG Kit, an out-of-the-box card game asset pack. It became immediately clear once we started working on the game that CCGKit was too complex to learn as you go. It seems awesome and really extensible, but it's also really complicated and without easy tutorials. In hindsight, I'm glad that we didn't know that going in, because we probably wouldn't have tried such an ambitious game if we thought we'd have to do it all from scratch, but by the time we realized we couldn't use it we'd already spent a few hours designing this game. We like using jams to learn new skills and software, but now we know to look ahead into whether something can be picked up as you go or requires playing around with ahead of time.

Development and Distribution of Tasks

Separating out work (Good!)

I can't express enough how useful it was to have someone in the group step into the producer role. One of our team members managed our taskboard and distributed work whenever one of us was idling. There's always so much to do and keeping the big picture in mind is hard. Having someone who's keeping an eye on that, who can say what we're on track to do and what we're not, or just keep the project organized, is such a big help. That's not all our producer did, she also coded and worked on a lot of the infrastructure of the game, but the only reason that our development of this game flowed as smoothly as it did was because we had her to keep us on track. We also used Monday.com as our task tracker app for the first time, and it seemed to work better for us than other ones we've used before.

This also may seem silly and obvious to some, but it's the first time we've tried it: We each made our own playground scenes to mess around with. Up until now, we've all been working in one kitchen sink scene for work in progress, then building up a new scene that's the "release" version. That leads to conflicts, breaking each other's work, etc. Just having separate scenes and pulling aspects of each other's work as needed made early prototyping much faster.

Extensible systems (Good!)

Card generation

We invested early in making systems that would allow us to make new cards with little or no code. I'll have a more detailed post on how we did that a little later, but for now, here's a little glimpse into how it worked.

The card prefab itself didn't do anything. It was a collection of TextMeshPros and images, with a single script that would initialize the card, handle being dragged, and activate the card's affect when released (though what that affect would be was unspecified). Then, we made a ScriptableObject called a CardDescriptor, which would contain all of the information a card might need. For those of you who are unfamiliar with Unity's Scriptable Object system, I highly recommend checking it out. They're basically fancy data stores, and they're great for making many different kinds of the same thing (among a long list of other uses). We used them for individual cards, groups of cards (like a deck), groups of enemies, individual enemies, and our event system. Here's how it worked with the cards:

Each card would get a CardDescriptor ScriptableObject. You could make a new CardDescriptor through Unity's UI, then fill it in with all the information you needed through the inspector:

Justin Time CardCreate.png

Justin Time Card Inspector.png

The card effects would select some effect from a static enum, and a static function would take a value from that enum and call the function associated with that effect:

Justin Time Card Effect Enum.png

Once a new card was made, adding it to the game was just a matter of including it in the CardList (also a scriptable object) of cards included in the game. That would ensure that it can show up in the shop between rounds. If we wanted to add it to the starting deck, we could just add the card to the CardList for the starting deck:

Justin Time Card Effect Enum2.png

This made the design process of the game extremely simple. Once we had cards working, we just had to think of a handful of interactions that we enjoyed that we wanted to show off in the game.

Enemy Widgets

We knew we wanted some aspect of randomly generated enemies to fill out the roguelike genre, but we only had 3 enemies, and randomly selecting when to spawn them felt too stochastic and unconstrained. We wanted and randomly generated but well-designed experience; something that felt more intentional. We wound up doing a widget system, inspired by roguelike dungeoncrawlers that use pre-designed tiles that are shuffled randomly, so each tile is a crafted experience, and the game always feels well designed.

Our levels were built out of "widgets", where each widget described a set of enemies, where they spawn relative to each other, how many turns it takes before they spawn, and how many turns after this widget begins the game should wait before beginning to spawn the next widget.

Here's an example:

Justin Time Widgets.jpg.png

Here we have a widget that spawns 3 enemies, 1 ranged and 2 melee (Clockwork Mike was our name for the little round guy). The first two spawn immediately when the widget is activated, and the last melee guy waits one turn, then spawns. The "Wait Time" field at the bottom specifies how many turns the game should wait before activating another widget. This one isn't a very hard widget to beat, so it only waits 1 turn before beginning the next widget, but our widgets that use the Big Boy give you a few turns to deal with the new threat before sending in another wave of enemies. These widgets are categorized by difficulty 1-5, and at the start of each level, a few widgets are randomly chosen at various difficulties to make the experience we want for the player. For example, level 5 would have 5 level 1 widgets, 3 level 2 widgets, 2 level 3 widgets, 1 level 4 widget, and 1 level 5.

In the end, we did all of our level designing in about an hour. The last hour. But that's all we needed to throw together a really solid experience. Sadly, that does lead to our next thing-we-could-have-done-better.

Priorities

We didn't test it (Bad!)

We didn't have time to test the game. At the time, it was something we wanted to do once the game was ready, but we only submitted it right before the deadline. It seemed natural at the time, but I think that if we had prioritized getting testers (just friends in the neighborhood) to come play the game from the start, we would have made us factor that into our plans and we'd be better off for it. Most of the constructive feedback we got in our comments were simple to resolve, if only we had people tell us about them before release. One big thing that would've taken seconds to fix is that we were way too stingy with sending out Big Boys, when actually they would've kept the game feeling fresh for longer. Also getting outside opinions on UI and HUD would've saved our players some grief!

Cutting corners (Good!)

It sounds bad when I say it like that, but it's pretty accurate. In the past we've liked to have a settings menu that controls things like volume or sound effects or whatever is adjustable in this game. This time we just... didn't. Saved one of us a couple hours of work, including not needing to design the buttons or layout. We used a puppet-style (just transforming the limbs and body and recording it in the animator) animation instead of full sprite-based animation style. Saved us a ton of time on animations, and looks pretty good I think, especially because we really leaned into it. From now on we're going to pay more attention to what we can cut out to save time.

Reusing Code (Good!)

We've done a few jams now, and we've started to notice what infrastructural code we keep rewriting in mostly the same way. We've been trying to reuse more code from old jams that can be easily transferred (a lot of managers, mostly). I think this was the first time we saw real dividends from doing that. Saved us probably several hours of work, even though we've never done a card game before.

We didn't have a tutorial (Bad!)

Not having an interactive explanation for how the game worked really shot us in the foot. The game isn't actually that complicated, but it does throw a bunch of information at you. Most people who left reviews didn't have a problem, but we know the game experience would have vastly improved if we had a tutorial. It's something we kept saying we needed, but no one took it up until it was too late to make a good one. We could have prioritized better, but also we could've just used more time.

No ending (Bad!)

We didn't think we'd have time to fully craft a few strong levels and a satisfying ending, so we opted to go for an unending game. We also didn't have much enemy variation. I think that the game suffers a bit from being aimless at the end. There comes a point where you've mastered the game, but the game has stopped scaling with you, and it becomes a little monotonous. Aside from more enemy variation, I think we could have used more of the Big Boy, as we called him. He was tougher and more exciting, and I think we were too stingy with using him.

I think having an ending -- something to work towards -- would be a great motivator for people not only to continue playing our game, but also to come back to it if they were to lose.

Remembering that this is a jam (Good!)

We usually try to imagine a fully formed game and try to translate that directly into the game we're making. This time, we imagined a fully realized game, but during the actual design steps when we were deciding what cards to include in the game and what pacing to have for certain things, we tried to think of how to best convey our vision to the player, rather than trying to fully realize it all at once. We picked a starting deck full of interactions that we wanted to guarantee the player would experience (rather than balance it for a long game), and make sure that they get the full experience of playing the game even if they only play for 5-10 minutes. I think the result was that players could see the potential in the game, if it just got built out a little and had more content. Considering that this is a game jam and how players tend to interact with game jam games helped us keep perspective on what was important and craft an experience more geared to who our actual users were.


That about sums it up. Overall, I think we did really well with this one. We learned a lot of lessons for what works well for us, and a few about what doesn't and what we should try prioritizing in the future. If you haven't tried our game, please go check it out, we're really proud of how it turned out!

Building Extensible Systems with Unity's Scriptable Objects

Hey Jam Community,

I had a really great time this jam, both because I was really proud of the actual game we made, but also because I think this was some of the best engineering work my team has done in a jam before. In particular, I wanted to share the level design tools we made and how we used them. For more details on the general design of our game, please check out our postmortem, but this post will just be about how we made our cards, enemies, and levels.


Context

For context, we made a time-themed roguelike deckbuilder called Justin Time: Man of the Hour where your turns are 10 seconds long, and your cards cost time from your turn. Between turns, enemies either attack you if they're in range or approach you if they're not. Between rounds, you get to add one of three random cards to your deck and take a perk (like bonus health or time in future rounds). Aside from just the gameplay mechanics of making a real-time card game, we also needed to make a bunch of cards with different effects to fill out the game. We decided to invest early in a scriptable object-based system that would allow us to make cards quickly and entirely through the UI, with little-or-no code necessary.

Scriptable Objects

For those of you that aren't familiar or haven't experimented much with them before, Scriptable Objects are essentially a fancy data store. They're not MonoBehaviors, so they don't have any of the gameloop functions (like Start or Update), and they're not GameObjects so they're never instantiated. Instead, they're just objects that contain information and scripts. The really magical thing about them though is that you can create them in the Unity Editor outside of a scene. I'll give you an example:

Making Cards as Scriptable Objects

Here's our card prefab:

Justin Time Card Prefab.jpg.png

The prefab contains all the text and images needed for a generic card, but in terms of functionality, it only includes a collider and single script that can play the card (activating its ability, which is not yet specified) and initialize the card given a CardDescriptor scriptable object.

Let's take a look at that scriptable object:

Justin Time CardDescriptor SO.png

That's really it. The Card Descriptor holds all the relevant information that a card might need (Name, card description text, cost, whether it returns to your hand after playing it, the image on the card, a sound effect if the card has a sound effect, and a list of card effects that describe what the card will do when played). When we want to make a card in the game, we take the blank card prefab and call initialize with a CardDescriptor, and that fills in all of the relevant information for the card.

But this is just a generic CardDescriptor script, it doesn't actually detail any cards in particular. This is where some of the fun of Scriptable Objects comes in. The CreateAssetMenu line at the top adds our scriptable object the Unity's Create menu, so we can create a new "instance" of our CardDescriptor object through Unity's UI:

Justin Time CardCreate.png

Justin Time New Card.png

You can see that this creates a new asset in our assets folder that contains all the information laid out by our scriptable object which we can fill in the inspector. This is really powerful because it allows you to make several distinct objects that are basically just information containers, but they are distinct from each other while all following a model that's outlined by the ScriptableObject script. It's sort of like making a bunch of prefabs based on one prefab, but much cleaner and easier to reference in code.

Now let's take another look at that list of card effects. Each card effect is an instance of the following struct:

Justin Time Card Effect Struct.png

The System.Serializable line at the top of the struct is just there to tell Unity that this object can be edited in the inspector. You can see in the image above how this struct connects to fields you saw in the inspector. The relative position list specifies the area of effect of a card relative to where you placed it. An entry of (0,0) would mean that the space you placed the card on is affected, (0,1) means that the next space clockwise in the same ring as where you placed the card would be affected, and (1,0) would mean that the space behind where you placed the card in the same lane would be affected. From here, we can make a list of relative positions that make up the card's entire area of effect. For example, the list of relative positions for a card that hits an entire ring would be [(0,0),(0,1),(0,2),(0,3),(0,4),(0,5)]. These values are used both for the highlighting the grid before you play a card and applying the effect to the correct spaces after.

The effects list in CardEffects (aside from being poorly named), also shouldn't be a list. We found when using it that we never actually had more than one element in this list, so it should have just been a single element. If we ever decide to reuse this system in the future, we'll know to change that. This field specifies what the card actually does using this enum:

Justin Time Card Effect Enum code.png

From the card creator's perspective, making a card effect looks something like this:

Justin Time Card Effect Enum.png

Then the values list allows you to give the values that a particular effect might need. A damage dealing effect should know how much damage it's going to do. An effect that pushes enemies should know what direction to push them in and how far. If we were trying to make this more professional/a little safer, we'd include asserts in our code that forced you to input the correct number of values, but since this was for a jam, we felt ok leaving it a little messy.

When a card is played, we evaluate what the card should do with a switch case that funnels the values input to the correct function:

Justin Time Card Evaluator.png

And just like that, we have card effects! To add a new effect (that can't just reuse old code), all you have to do is write a function that performs that effect, add an entry to the enum for it, and add a case to the switch statement and then you can add that effect to any card through the UI.

The last note I want to make about this is how useful it was to be able to add multiple effects to one card. This added a lot of versatility to how we could design our cards by allowing us to compose multiple card effects. For example, take the card Hammer Time:

Justin Time HammerTime.png

Hammer Time does 5 damage to the enemy where you placed the card (in melee range) and 3 damage to everyone in a T shaped area around it. Here's how the card's CardDescriptor looks in the inspector:

Justin Time HammerTime Effect.png

We have one effect that does 5 damage only at the position (0, 0), then a second effect that only does 3 damage, but at positions [(0,1), (0, -1), (1,0), (2,0)]. When playing the card, you get the full T shape for targeting, but the different spaces have different effects. By being able to compose our cards from multiple base effects, the amount of complexity we could make out of simple functions shot way up. Here's what it looks like in action with Hammer Time:

Justin Time HammerTime Target.png

All together that made for an extremely easy and streamlined process for making new cards. All of that code was from a single file called CardDescriptorSO.cs, which comes out to about 150 lines of code including comments and all of the implementations of the card functions. No one thing in this code is particularly complex, but the amount of power it gave us to design the game the way we wanted to was enormous.


Card Lists

Now we have a great system for creating cards, but now we needed a place to put them. We could just have a list of CardDescriptors somewhere in the game that defined which cards are in the game, what's in the player's deck, etc., but we decided once again to use ScriptableObjects. You may have noticed that in the create menu, there was a generic card and a card list. This is a great example where we didn't actually do anything complex in the scriptable object itself, but it still wound up being a huge convenience for us. Here's our whole CardList ScriptableObject:

Justin Time Card List SO.png

That's the whole file (other than includes at the top). Here's what it looks like when making a new card list:

Justin Time Card Effect Enum2.png

Here we've made a list of all the cards we are including in the game, and if we want add another one, we just add it to this scriptable object, and anywhere that references that scriptable object will now know that the new card is a part of the game (and conveniently, since it's a list of card descriptors, you can use Unity's UI to find the card you're looking for rather than needing to drag and drop it, also pictured in this image).

We also made a CardList object for the player's starting deck. This was great not only for ease of editing, but also if we ever wanted to have other characters with different starting decks, we could just swap out the scriptable object representing the deck and everything else could be kept the same!

I love this example because it's really just a wrapper for a list, it doesn't do anything, and yet it still made our lives so much easier.


Enemies and Widgets

I go into more detail in the design side of this in our postmortem, but I wanted to go a little into the implementation of it here too. The basic idea is that we wanted to have randomized levels so every time you play the game, the levels feel different, but we didn't want to just randomly spawn enemies in. That felt too chaotic and hard to balance for. So we decided to do a sort of tiling system where we randomly apply premade sets of enemies which we called widgets. Each widget would specify information about the enemies, when they spawned, and how long to wait after applying the widget before moving on to the next one (harder widgets should give the player a few turns to deal with it before spawning new enemies -- unless the reason that they're hard is that they don't give the player time!). This allowed us to randomly generate our levels, but still have each set of enemies be carefully handcrafted.

And we did with ScriptableObjects! Woohoo! Here's our Widget scriptable object and an example in the inspector:

Justin Time Widget SO.png

Justin Time Widgets.jpg.png

Here we have a widget that spawns 3 enemies, 1 ranged and 2 melee (Clockwork Mike was our name for the little round guy). The first two spawn immediately when the widget is activated, and the last melee guy waits one turn, then spawns. The “Wait Time” field at the bottom specifies how many turns the game should wait before activating another widget. This one isn’t a very hard widget to beat, so it only waits 1 turn before beginning the next widget, but our widgets that use the Big Boy give you a few turns to deal with the new threat before sending in another wave of enemies.

Once again, we can create a new widget by going to Create->Level Design->New Widget from the create menu and fill in the information in our new asset. Then we categorized the widgets by difficulty, which allowed us to bring in a few widgets of varying difficulty to give levels a feeling of having difficulty spikes followed by slightly easier breaks and assemble a randomly generated level that we feel is the correct difficulty for the player at this time.

This turned out to be a really good system for quickly making level design bits that could easily slot into the game without needing to change a single line of code. Even now, we could create tons more widgets with brand new enemies that don't exist yet, and they would just work in game without needing to change a single line of code.

While I'm proud of the widget system and it worked really well for us, I think that there was room for improvement (and room for more scriptable objects!). We should have had our widget lists also be scriptable objects, for the same reasons that the card lists were so useful. We also would've liked for enemy creation to be more streamlined in the same way that cards were, but that required a bit more thought and it was a still jam after all.


More Scriptable Objects?! A Scriptable Event System

Ok, we used them for cards, we used them for enemies, we used them for level design. What else could ScriptableObjects possibly do in this project??

Well reader, we can use them for events! I won't go into too much detail about this, since I think it's a more common example online and there are much better explainers on how to do it. There was a talk at Unite Austin that goes REALLY deep on how this works that's really good, but it's a lot of information. It also makes a compelling case for replacing everything (even floats!) with scriptable objects, which I think is going too far, but gave me a lot of appreciation for what they could do.

Basically, we created a UnityEvent in a scriptable object and added functions to interact with it. Now whenever we wanted something to subscribe to the event, we could just drop that event into a public field in the inspector and we'd be guaranteed that any object referencing that event is talking about the same event. For this project, we had an OnTurnEnd event and an OnTurnStart event, but we often have many more. Some that we've used in the past are OnTakeDamage, OnSelect, and too many others to name. This makes syncronized reactions really nice too. For example, if you wanted the screen to shake, your healthbar to be updated, and an animation to trigger all when you take damage, you don't need to communicate between these separate GameObjects to make sure they all happen. Just make sure they all have the same ScriptableObject event, and it'll be taken care of for you. Here's the code we used for ours:

Justin Time Void Event SO.png

I called this one a void event because the event handler doesn't take in any arguments, but you can make others that do take arguments. I'll often have a Float Event, a GameObject Event, etc. (Note that the ScriptableObject scripts should be general, like "event that takes an int" and the instances should be specific, like "OnDamageTakenEvent")

Also note that there's no public variable on this event at all! Up until now the scriptable objects we've been looking at have been data stores where we can edit them in the inspector, but these actually don't let us put any information in at all! Their usefulness just comes from existing and being different from each other. Super cool!


That's all I've got for you. If you've got any questions, please don't hesitate to ask, and I hope some of you found this useful! ScriptableObjects are really cool, but if you're not seeking them out, I feel like there isn't much opportunity to learn how to use them.

If you haven't yet, please go check out our game: Justin Time: Man of the Hour and see some of these systems at work!