JOSHUA CHRISTIANSEN

CARDSLINGER

An Action Roleplaying Game scripted in Unity C#


 

The Pitch 

Cardslinger is an Action-RPG in which you defeat enemies and foes by using ability cards, which are drawn from an ever-growing deck of cards. Throughout your journey you will discover and pick new cards, that will be added to your deck.

 

Buckle up and Fasten your seatbelts

The overaching meta goal with this project as a whole, was and is as accurately as possible to convey my skills and capabilities. And i decided that there were no better way to do so, than to make one singular project that would show it all. Whether it be Level- and Game-design, Scripting and Technical know-how or an iterative work process. I wanted this to be the game that proved it. 

Because of this sentimentality, the documentation blog for Cardslinger is rather exstensive, and perhaps a bit out of the ordinary for a Student-Portfolio project. The project has evolved quite a bit over the last five months, and i would love it if you would follow me through this journey back in time. So without further ado, grab a cup of coffee and get comfy, because this is a long ride!

 

 

 

Mission Statement

I wanted to create an Action-RPG in the Veins of Diablo and Path Of Exile, that blends in card-game mechanics in order to shift the focus from long-term progression to moment-to-moment gameplay, while still retaining the genre defining character and ability customization. 

 

How the card system works

So i came up with the idea of mixing the Action RPG genre with a Card Game. All the abilities would be cards, and the player would only be able to play the abilities on their hand. The customization would come in form of the player slowly finding new abilities to add to their deck. To combat repetitive rotations, the player would only have a certain amount of cards on hand at any given moment, which meant they had to think on the fly and get the most out of what they had access to.

The player would have a hand of three ability cards, whenever an ability where used that card would be thrown in a discard-pile. When the player had used two cards they would discard the remaining card of their hand and draw a new hand of three cards. The reason for this last rule was to force the player to always make a choice as to which cards to choose, If the player could simply use all the cards in their hand the element of choice would be diminished.

To discourage player needlessly spamming abilities, the deck only features a certain amount of cards, so a careless player could very well end up without anymore cards, and thus abilities to cast.

Whenever the player kills an enemy they are rewarded with a small amount of Energy, and when their energy bar fills up, the players ability to quickly reload his deck is enabled. Reloading the deck returns all cards from the discard pile into back into the deck.

By tieing energy regeneration to enemy kills the player is encouraged to try and get the most out of each card, which drastically alters the way the players engage with the game. It also allowed for some interesting macro decisions, since reloading always returns all cards, the player might choose to hold off reloading to gain more value out of their reload, or conversely a attentive player might choose to reload early if the know a lot of there more valuable cards are in the discard pile.

To ensure that the player doesn't end up without any cards and energy, the player is given an emergency option. By choosing to channel the self-drain ability, which slowly fills the players energy with the unfortunate expenditure of health. This either results in the player gaining the last remaining amount of energy to reload their deck, or a swifter more merciful demise.

 


Level Design

PRE ALPHA

Emulating exsiting conventions

My first pre-alpha blockout level, took direct inspiration from Diablo. Mirroring Diablo’s procedural nature, featuring multiple rooms and corridors that all intermingled and connected into each other. Enemy placements consisted mostly of various blobs haphazardly scattered around the different rooms.

This iteration showed multiple issues. The levels lacked direction, and since there was no loot from the enemies, getting to a dead end didn’t feel good. Most encounters also felt too similiar, which again went against the core goal of creating more interesting moment to moment gameplay.


ALPHA

 

A handcrafted approach
 

For the second pre-alpha iteration i went with a fully linear approach. I wanted to even out the difficulty curve and make each encounter more interesting and distinct. It also helps that way less of the content was skippable, which is definitely a plus given the project's scope.

The liniearity made it easier to pace out the gameplay, as a result the level felt alot better. Hoewever the flow of the level itself was somewhat unintersting, with the player basically just moving in a straight line through most of the time. So i knew for the next iteration i would need to feel less linear.

However i did discover that having single enemies scattered around the levels felt very bad, as it didn’t really give the player any opportunity to be efficient with their cards. And wasting one to two aoe ability cards on a single enemy just felt inefficient and unfun. It became clear that enemies should always come in packs, and that i would more enemy and encounter variations to keep the game interesting

 
A Top-down of the Alpha Level

A Top-down of the Alpha Level


BETA - GOLD

Determing the pace

The first Beta Iteration, and thereby the first full build of the game, had levels that followed the aforementioned linear structure. However by encourporating more twists and turns into the layout, the level felt less liniear. I also found that the levels where more visually interesting as not only did the environment vary, but so did the directional perspective of the level.

As the final level where begining to form, it became increasingly important to pace out the different mechanics throughouth the game, so that each level could have a sense of purpose.

 

The diagram shows when each encounter mechanic and player card set was introduced. It was very important that each level had something new to bring to the table.


LEVEL 1: THe prison

PrisonBeuaty_OnTable.JPG

Teaching the game in Five minutes

Naturally due to the games novel design there were quite alot to teach the player. Due to the small scope of the game, i knew that i needed to introduce and teach the player the core conceit and gameplay within the first few minutes. 

I obted to create a very handholdy turtorial, that walked the player step by step through the card interactions and asking them to perform discussed interactions to progress.


LEVEL 2: THE RUINS

Getting the player into the flow

With the player understanding the basics, the game can finally begin proper. This level is purposefully designed to not be very difficult. The main goal here was for the player to get into the main rythm of the card system. Hopefully be the time the next set of cards where introduced, the player would have a better sense of how they want to play. 


LEVEL 3: THE SEWERS

Upping the challenge

The sewer is setup to contain the first significant challenges in the game. Mechanics introduced in this level where more complex in nature. 

The first half of the level is spent introducing the patrolling spirits, preparing the player for the dificulty spike. After which the Fire Skeleton is introduced, as inherintly more demanding enemy than most. The last half of the level is spent combining the two in various difficult encounters.

 

LEVEL 4: THE PALACE

Ending on a high

I wanted to make sure that the last level felt memorable, so that the player would leave with a good impression.

The first half of the level featured a gambit, wherin the player would mow through a large amount of enemies down a narrow hallway to defeat the dark crystal which kept summoning new minions. This was meant to be a high power moment that also introduced the crystals which would be relevant doing the boss fight.

The last half of the level featured a three phased boss fight, which the player was greeted to in a small cinematic  cutscene This was designed as the final challenge to test the player skills. It was also setup to showcase the combat system in the best light, while hopefully showcasing some of the natural intriciaties and consequences the card system had to offer. More information on the boss fight can be found below.


GAME DESIGN

ABILITY DESIGN

Understanding the goals

When it came to designing the abilities it was clear that each of them needed to have distinct strengths and weakness, this was needed in order to achieve our goal of encouraging interesting moment to moment decision making. For instance, if all abilities had the same outcome and potential in any given situation, there would be no choice at all.

On the flipside the differences couldn’t be too strong, if the game had enemies that were immune to fire damage, any fire ability in the players hand would be a dead card. Which in turn reduces the complexity of the card choice.

In found that all well designed cards could roughly be split into two inherent categories; Finishers and Enablers.


 

Finishers

Finishers are cards that does not really change or alter the effectiveness of other cards after their use. Instead they derive their core value from conditions purely found in the environment. A straight forward example would be an card the creates a Explosive Nova around the player, damaging all enemies close to the player. An ability like this has a clear use case, but it does not really affect the value proposition of any other card in the players hand, outside of maybe eliminating some targets for the other cards to kill.

Another example would be the boomerang, that flies straight out and back, which is more useful in scenarios where enemies are lining up, than when they are surrounding the player.


Enablers

Enablers are cards, whose core value are determined by which other cards the player has access to. An example would be the “Blink Dagger” an projectile which teleports the player behind the first enemy hit. Such ability is a lot more useful when the player has an Nova on hand, as opposed to a boomerang. This dynamic is almost universally true regardless the environment surrounding the player.


The benefit of categorization

By categorizing all abilities like this, it became a lot of easier to design and balance the various cards. It also helped me better contextualize their use cases, which is helpful when trying to determined which niches each ability represents and fulfills.

In hindsight though i wish i had looked out more out for this balance, as currently the game features way more finishers than enablers, which definitely skews the balance.


ENEMY DESIGN

Setting up the tool box

In order to fully fulfill our design goal of having abilities that excel in different type of scenarios, there need to be different scenarios in the first place. A lot of this is derived from the level-design, which dictates the flow of encounters through the map layout and enemy spawns.In this context It is useful to think of the different enemy variants as tools in the level designers arsenal, each one giving the designer more knobs and levers to tune the experience. Given the limited nature of this project, it was important to me, that each enemy variant felt different and solved different issues, as i couldn’t afford to spend time developing multiple solutions to the same problems, when others were still unanswered.

 

 

Designing with purpose

When it came to the individual design of each enemy, i found that the most important question ask myself when designing each one was, “What will this Enemy Asks the player to do?”. Each enemy behaviour will naturally, encourage o push the player towards certain responses,  and by designing each enemy around which response a player ideally should have to them, helped me outline a smaller subset of enemies, that each felt more distinct from each other.


The Basic Melee Enemy

The Melee Enemy can be seens as the default and simple enemy type. It dosn’t ask the player to do alot, all the player has to do is keep distance to the enemy and kite it around.


The Slow Strong Melee

This enemy is a slower and harder hitting variant of the basic melee enemy. It’s main function is to disrupt the pace at which the enemies die. Without it all the melee enemy groups basically feel the same, regardless of size, as they all die at the same time due to the AOE abilities of the player. By having a higher health enemy, the player is encouraged to focus the bigger one, while trying to make sure the attacks also hits the smaller enemies.


Lightning Skeleton

The Lightning Skeleton is the first ranged enemy introduced in the game. It’s main goal is to prevent ranged and kite centric playstyles from being to dormant. It Creates and Aoe around the players current position that strikes after a short delay. Asking the player to keep moving regularily. This is naturally a bigger hindrance for ranged playstyles, as their abilities tend to have longer cast times, which leaves them stationary for longer. Melee playstyles would most often be equipped with faster abilities and more rapid movement options. The lightning Strikes also hurt other enemies, which again is a boon to a playstyle that stays close to other enemies.

 

Fire Skeleton

The Fire Skeleton is the projectile based enemy. It fires fiery projectiles in a straight line towards the player.  It encourages the player to move somewhat perpendicular to the Fire Skeletons position. Unlike the Lightning Skeleton, it asks the player, not only to keep moving, but to think about where to move. It is roughly equally difficult for both, ranged and melee centric playstyles. As an melee player naturally moves around enemies while fighting, but ranged players have a larger breathing room before the projectiles hits.


Patrolling Sphere

The patrolling sphere is more of an enviromental hazard than an enemy. As it cannot be killed. Instead it is just a patrolling entity, that moves from point to point damaging the Player should they come close. Its patroll path is denoted by a redline of dust on the ground, making it clear to the player when and where to expect them. They mostly serve as a diverse tool for the level-designer to create more encounter specifik patterns for the player to deal with.


ABILITY SETS

Enabling Player Achetypes

As per my reference games, i wanted to let the player choose between different gameplay archetypes.  Since the game didn’t have any class systems, it was important that the card selection aspect could cover those. So a player seeking a melee centric or crowd control heavy playstyle could successfully achieve their fantasy.

Therefor each set of cards that the player could choose between each fit within a unified archetype. For instance the melee centric abilities where put in the same set, while the summoner centric spirits where in their own aswell.

This meant that they shouldn’t have to select at card that didn’t fit the fantasy they were pursuing, just to get the card that did.

 

 

Setting up synergy

Outside of aligning each card set with a gameplay archetype, i also wanted the two cards in the set to interact synergistically.

Because all cards where categoriesed in Enablers  and Finishers it made sense to have most sets be a combination of each. By combining say the aoe root and the aoe damaging gas cloud in the same set, i also ensured that there at least one interesting combo that could come up doing the players game, regardless of their choice.


Using player healing to empover archetype fantasy

I wanted the first card set to meaningfully shape the characters playstyle, and have a lasting effect throughout the entire game. So i decided to couple the players only health regenerating ability to their first card choice. This drastically shaped the gameplay of each set and pushed each one down an archetype immediately.

The ticking aoe cloud healed for each enemy killed within it, encouraged a aoe and cc heavy playstyle. By rewarding the player for grouping up enemies and holding them within one space.

The Close range aoe shout healed for each enemy hit closely around the player. Incentivising a melee play style by rewarding the player for surrounding themselves by enemies.

The Leeching curse that slowly drained life from a single enemy, encouraged a more kite centric playstyle, by encouraging the player to keep a single enemy alive for as long as possible whilst life is slowly drained from it, the player naturally stays out of harm's way by moving a lot.

An Extra benefit of this approach was that it gave me some balancing levers between different playstyles. Since the game didn’t have any passive damage mitigation systems like armor, a melee centric playstyle would naturally feel weaker as it would find itself in danger more often. I was able to counter this by giving them the most potent and most immediate heal in the game.


Scripting - C#

Card System - Implementation

 

A flow chart that details how all the different core Card and Player related scripts intereract

 

 

Player Controller

Everything starts at the player. I needed a player that could cast and use different abilities based on which cards where available, I needed a system that could keep track of each ability both on hand, deck and discard pile. I needed each ability to be Callable through the same commands, and i needed to support multiple instances of the same ability at the same time. While this system wouldn't be very demanding performance whise, it required pretty tight logic and a lot of systems needed to cooperate together.


Ability Data in scriptable objects

While early iterations had all Card instances be literal spawned gameobjects, which would move around the world physically between the The Player, The Deck and the Pool. The final iterations used Unity Scriptable Objects as data containers. Each Ability SO contained all relevant information. Multiple card instances would simply be multiple references to the same SO. The SO would contain all relevant variables, such Name, Description Icon, PrefabBehaviour, Damage, Castime etc. When an ability was casted the relevant SO would simply be promted to spawn the relevant ability behaviour and pass on the relevant variables to it. This meant that all instances of player Fireball always shared the same variables, but could still be used for other ocations like enemy projectiles and such. It also meant that new abilities could be made incredibly fast, just create a new SO, put in the relevant variables, correct behaviours and boom, an ability is ready to be referenced and used.

 

"The gif showcases how AbilityData Scriptable Objects are made, and which informations they store"


Ability Flowchart.png

Ability Behaviour Inheritance Tree

All Ability SO's where part of the same inheritance tree, all ultimately inheriting from the same base SO. This was a boon as it meant that by tweaking the base SO, i could ensure that the update would be reflected in all abilities in the game.

The same was true for most ability behaviours, all projectiles for instance inherited their behaviour from the same projectileBehaviour. As i felt it was important that all abilities in any given category behaved uniformely on everything that wasn't their core designed difference. (IE: Rooting or Dotting)


The flaws and limits

Storing all the universally shared variables in a Scriptable Objects was great, Like Names and Descriptions. But having SO's try to account for the non universally shared variables, proved to be more of a hazzle than a boon. Either it would bloat the SO Inspector with a bunch of irrelevant variables like Root time for Fireball or travel speed for Totems, or a large amount of custom variables had to be stored in the spawned prefabs and scripts anyway, effectively decentralising where variables and tweaks happend. 
This made the system un-intuitive and would have been a huge problem if i where to share the system with anybody else, as you had to have made all the behaviours to know where you could tweak them


The Modular Event System

 

Overview

The Modular Event System is a series of scripts i have created, that allow a designer to quickly chain together multiple different behaviours to create a significant amount of unique custom encounters, without needing to add any more code. 

For instance you could quickly setup up a scenario that alerts all enemies nearby once the player steps on a pressureplate. Or an encounter that spawns a continous barrage of enemies until the specified "Leader" unit is killed, which would stop more from spawning and open a nearby door.

 


The Need for scripted events

As the level design shifted towards a more liniear and handcrafted approach, i realized that i would need scripted encounter and events throughouth the levels to really make the world feel more alive.

To not overburden myself, and to ensure that i didn't have to hardcode and make custom scripts for each and every of these encounters, i chose to create a dynamic set of scripts which would allow me to design my combat encounters modularily. 

 

The C# Code used in the EnemySpawner. Which is one of the scripts that fit within the Modular Event System.


The trigger box will call the "Activate()" function on the enemyspawner upon player collision. After a 10 second initial delay, the spawner will then spawn three skeletons distirbuted over two spawnpoints with a 0.5 second interval between each set. These Skeletons while be alerted to the players position and chase them.

The trigger box will call the "Activate()" function on the enemyspawner upon player collision. After a 10 second initial delay, the spawner will then spawn three skeletons distirbuted over two spawnpoints with a 0.5 second interval between each set. These Skeletons while be alerted to the players position and chase them.

 

Object to Object messaging

My approach was quite straightforward. I split up each desired event into small all-purpose behaviours. Each behaviour would send messages to other connected behaviousr based on it's own internal conditions. Similiar to how scripting is interfaced in the "SourceEngine Hammer Editor".

Each behaviour has an Input and/or an Output behaviour. The Input outlines a condition the object needs before it will activate its output behaviour.  

If and output call is send to another object, that object will recieve an "Activate()" call. By chaining together multiple different nodes a lot of quite complex encounter behaviours can be achieved. 

 


Keeping it designer friendly

I wanted to make it visually clear, how every behaviour was connected to each other when developing more complex encounters.

So i made each behaviour draw lines to all it's Output behaviours. Which meant that all behaviours in any given encounter could be followed by following the lines around. 

I altso made all behaviours have a visual representation in the editor, by drawing boxes for the colliders and adding labels to spawn points an the-like. 

 

List of Modular Behaviours:

  • Activate Timer (Delay Node)
  • Enemy Counter (Activates after X enemy dies)
  • Enemy Alerter (Calls behaviour in nearby enemies)
  • Enemy Spawner (Spawn X enemies among Y points)
  • Hint Trigger (Trigger X Tooltip in Upper Left corner)
  • Object Mover (Moves X from A to B over Y time)
  • Kill Trigger (Kills all connected enemies on Call)
  • Sound Player (Plays X sound or song)
  • Sound Stopper ( Stops X sound or song)

A good amount of extensibility

By activating everything through a generic Activate() call, it became incredibly easy to create more behaviours and attach them to the system.

Which was great whenever an encounter required behaviours that weren't already included, they could be added and made in mere minutes.

It also meant that the new behaviour could be used in tandem with all previous and future behaviours.


Flaws and weakneses

The modular event system definitely had it's problems. The generic "Activate()" call, meant that the current implementation can't have behaviours send variables and references between each other. Nor can a behaviour check whether their Activate call was succesfull or recieve respond functions. This was largely due to using "aTarget.Sendmessage()" as my carrier. 

If i instead had used a base class with all core functions within, and had the different behaviours inherent from that class, i could have supported far more complex interactions between the different scripts. It would have had the added benefit of enabling me to update the core struckture once in the core class and have all other behaviours automatically update, instead of updating each and everyone manually.


From idea to action

The Story

In The Turtorial:

"When the player kills the enemy in the room, a hint is displayed, and after a short delay, a door opens and two skeletons pour out and run after the player."

The Implementation

A graph showing how each behaviour prefab connects

The Result