The first week of the Ludum Dare rating and judging season has passed! Time certainly flies when you’re busy playing and rating different entries, preparing material to be posted, and recovering sleep due to the unescapable claws of self-inflicted game jam crunch. Sounds like our usual way of doing things here at Whales And Games indeed!
We were expecting to post a blog post going in-depth about how the characters for our latest entry, Super Sellout (which has just smashed our last entry’s record!) were created in terms of art, but unfortunately, our artist has gone internet-less for the weekend and just recently recovered it, meaning that probably for the first time ever, our first in-depth blogpost will be about the programming instead!
So let’s dive right in! This post goes into the technical deeps on how the different Sponsors in Super Sellout were made, how the scoring system of the game works, and the overall benefits of Scriptable Objects and why we keep using Unity! Hope you’re ready, because it’s a long one!
We’re still in ? with Unity
In case you haven’t seen our latest in-depth programming post from Jazzy Beats, our team at Whales And Gamesmostly develops projects through Unity. At least for me personally, it has been my engine of choice for five years and, as expected, I’ve grown comfortable with it in terms of workflow. Our other programmer, Kroltan, has also got some experience in Unity but also has his own hand-full of qualms with it. Our IDE of choice is Visual Studio, and Kroltan utilizes ReSharper on top of it. Visual Studio is pretty much the standard IDE for everything related to programming, being utilized together with other game engines beyond Unity (such as the Unreal Engine) as well.
For this edition of the jam in specific, we decided to risk it and utilize the beta of Unity 2018.3, hopefully to get accustomed to utilizing the new nested prefab workflows that are being introduced in this version (at long last!!) for development. While we did catch a few crashes here and there, and a lot more of them than usual, if we hand’t decided to stick with this version, we probably wouldn’t have been able to properly complete the game during the allocated jam time.
While our post on Jazzy Beats was mostly about Inheritance and how different behaviours were inherited by different characters to perform different actions, with Kroltan‘s inclusion in the Whales And Games team we’ve been trying to do things a lot more through composition. This means, breaking much more behaviours into Components and more specifically, some very underrated data containers in Unity called Scriptable Objects, which allow for fast data management and insertion without necessarily affecting the rest of the game’s structure.
Scriptable What? ?
If you’ve been following Unity in the past years, then certainly you must have already seen them trying to promote the concept of Scriptable Objects through articles, tutorials and talks especially during the last year. While they certainly have been around for a while, there’s still a lot of potential case-scenarios where people could be using them but end up utilizing the standard tools instead. We were guilty of this too, and only recently with us rewriting one of our past Ludum Dare projects for a future update we’re planning did we realize just how handful they can be.
In a short explanation (you can read everything about them in the documentation), Scriptable Objects are essentially instances of classes that can be stored and re-used as if they were assets. If you’re already accustomed with C# and using it in Unity, you’re probably already accustomed to the idea that you can have classes dedicated to holding data without them necessarily having to inherit from
Scriptable Objects take this concept further by allowing you to create these data classes as if they were project assets, rather than having them be created by either code, or exposed to an inspector through
Monobehaviour. These data assets can then be added to other behaviours, read from the project, etc.
Our Sponsors in Super Sellout utilize Scriptable Objects. Each different sponsor is a different asset, composed of their name, icon, explanation, how many rounds they take to unlock the sponsor, etc. While you can add logic to Scriptable Objects, we strive to keep all of the logic that is not relevant to the concept of the object itself (such as creating objects on the scene, storing runtime values associated with it) on the Scriptable Object class itself. This allows these objects to be independent on their own, serving only as data references, meaning that all of the state related to them (such as if a Sponsor is equipped or not) is stored elsewhere, and not keep with the Scriptable Object when it is serialized and deserialized.
(It should be noted that Scriptable Objects show the Unity standard asset icon by default, whereas we display icons thanks to the Asset Icons extension available on the Asset Store.)
As such, what we do is add all of these sponsors to our
GameManager and then utilize that list through the game. The sponsors screen is the best example of this. All of the buttons for the different sponsors are created at runtime, and the icon and name of the sponsor are filled-in at the start. This would allow us to scale this screen and system infinitely if we wanted. We’d have to make graphical changes to accommodate, but we wouldn’t have to directly change anything in the logic itself if we were to add more sponsors.
Each sponsor is delegated to these buttons by creating their
EventTrigger when the buttons are instantiated, keeping all the logic in a single place. Sponsors that are picked by the player are then added to another list that keeps track of the sponsors that the player has equipped, and removed accordingly (such as if the player changes their mind on the sponsor equip screen). This list is frequently checked, and helps establish the magic of the following section.
Sponsor Behaviours ?
With the sponsor system in place, and with the UI and
GameManager wired in, the next part is to wire the different sponsors to actually affect the different facets of the game related to them.
If you have played Super Sellout by now you will have understood the whole gimmick of the game is that you sacrifice movement and your comfort while playing (which we so called sacrificing your integrity) by enabling different sponsors that cause a variety of different effects and obstacles to react to you in exchange of getting an higher score when you rescue people throughout the game (and depending on some sponsors, even based on time you have left).
It’s also worth noting that most Sponsors also have different cosmetics that are overlaid on top of the character. These are stored in arrays together with the rest of the Scriptable Object, and are checked every frame to check if the cosmetic frame matches the one of the current player’s frame.
There are a few gimmicks that are standard and re-used through the game due to the limited time-frame that we had to put the whole game together. For each of these sponsors, their logic is kept in different places, most notably being the ones that the sponsor affects. Sponsors that affect a player’s abilities or controls are kept together with the player, while sponsors that affect objects are kept with those objects.
One of the most standard gimmicks, for example, is the speed reduction gimmick with different sticker sponsors you can add to you which reduce your character speed (while keeping the world speed increasing as always) in exchange of score over time. Being a sponsor that affects the player, the logic for the sticker sponsors are kept together with the rest of the player’s scripts under a
When the game begins, the scripts checks through each of the sponsors to check if they’re activated, and applies the correspondent speed reduction, reducing the player’s speed on the spot. The same is done for other sponsors that also affect the player, such as the Dog Sitters sponsor which applies random forces on the player’s velocity at regular intervals.
As for other behaviours that are affected by sponsors, such as metal objects following you, the check is done on each individual behaviour instead. One such example is the
MoveToPlayerBehaviour which causes both metal objects and the dogs to follow the player if their sponsors are active. These perform a similar check to the previous speed-affecting behaviours, checking if the sponsors that are needed to run that behaviour are equipped by the player, before actually executing it’s logic in
Finally, there are the sponsors that make certain objects appear on the stage if they are enabled, such as the billboards and the different puddles. Just as you might expect given the way the logic of the previous objects is checked, these are actually placed on the different rooftop segments ahead of time (or are spawned in at randomly on places dedicated to random obstacles). Once the game starts, they make the same check on
ConditionalObjectSpawn likewise to the following behaviour, and depending on the result, choose to disable the object (if the sponsor is not equipped) or leave as it is (if the sponsor is equipped).
If we were to look back and think of a few ways we could have improved the way sponsors are managed and utilized throughout behaviours, we could have potentially done a single
SponsorBehaviour class (for objects other than the player) to hold the checks if a sponsor is active or not in a single-place, and then inherit that class to add each specific behaviour.
Inheritance isn’t inherently (heh) bad, however it can very quickly lead to a complicated upkeep of classes and deeper-than-they-need-to-be inheritance levels on top of the classic diamond problem. However, depending on very specific situations, they can potentially reduce project complexity as well, making it a double-edged sword. However, that’s beyond the scope of this post, and instead we will be talking about dough, moolah, simoleans, money, because at the end of the day, that’s the scoring of our game.
Money Dough’ and Scoring ?
Similar to the Sponsors, Score Types are also a Scriptable Object in our project. This allowed us to create different score types and rates that are affected by the different behaviours and actions without having to individually declare each of them by code and creating adequate functions. Instead, having them as Scriptable Objects allow us to re-use the different score types, and even derive score types from each other.
We wanted to have a score breakdown screen, and for that the game needed to track where each amount of score came from. We started by making some Scriptable Objects that had some basic information about each score source, where, for example, one “Grandma Saved” would be worth $10. That worked perfectly for simple proportions, but for anything more complicated it was a bit limited.
When sponsors started being implemented, we carefully employed inheritance and created a new kind of
Derived Score Type, which in addition to the base multiplication-based monetary value (where each score-type adds a certain amount of score each time) can query the value of other score types to calculate its final value. This was used in some sponsors, which awarded money proportionally to a specific type of heroic act or time.
This approach allowed quick balancing by non-programmers, as well as a clean implementation for the graphical interface, which didn’t need to differentiate between derived and pure score types. It also allowed us to quickly connect any given Sponsor or character in need of rescue to a score-type, and implement them on the scoreboard without any new overhead. Of course, even with such systems in place, there was still a lot of content ideas that had to be cut.
The Cutting Room Floor ✂
Even with all these systems in place, there were still a lot of ideas for different sponsors and modifiers that didn’t make the final cut into the game due to it’s scope. Here are some highlights of these ideas in order to not extend this post by much longer but that we still wanted to share for pure curiosity:
- Sponsors that would invert or change your controls to random keys.
- Sponsors that would make you be followed by bees or ghosts, stunning the player when they caught up with them.
- Sponsors that would play startling sounds every now and then to keep the player on their toes.
- Sponsors that would cause characters to randomly interrupt the game and talk over it, allowing as little visibility as possible.
- Sponsors that would add way more visual modifiers to the game, such as making the whole game grayscale, make it look retro, etc.
- Sponsors that would hid how much money you have achieved or how much time you have left.
With the current system in place, we could theoretically add these new effects and scenarios without much challenge other than creating their respective Scriptable Objects and adding-in their respective behaviours where it’d make the most sense to keep their functionality.
At the moment we have no plans to do an this nor update the game with new sponsors and content. However, if that was to ever become the case, the introduction of several of these ideas would definitely be one of the plans for an expansion!
Phewsh, and that ends that! I hope for those that are into programming this post is at least an interesting read or that it sheds some light in the way that we have decided to handle things with creating our sponsors in our entry, Super Sellout. Likewise to our previous jams, we still have our usual post-mortem on the way, not to mentioned the aforementioned art-cantered post.
In behalf of our team, thank you once again for all the support you have given us so far in terms of ratings and coverage. Ludum Dare is a very important and heartful event for us, and it’s specially thanks to it that we are able to grow as a team and spread our word around. If you haven’t had the chance to play Super Sellout, we invite you to do so, and make sure to send us your game too!
Finally, we also invite you to join us at our team’s Discord server! We have a whole room dedicated to #gamedev and for talking about this type of technical jargon, and we’d love for you to share your own entries with us there!
If you’ve managed to reach this far, cheers, and thank you for reading! ?