Let’s create a hack’n’slash game to learn Unity and C#!
This article is also available on Medium.
Last year, I published a series of 52+ tutorials on how to make a RTS (real-time strategy) game in Unity/C#. That series of posts was sort of a “Unity Game Dev 101” course: the point was to introduce a wide variety of systems, discuss how they can be implemented in Unity and provide a detailed walkthrough of how to make your own basic RTS prototype. It was primarily aimed at Unity enthusiasts and beginners – the systems I presented were not necessarily prod-ready versions, the focus was rather on detailing the why and the basics than polishing everything.
I decided to continue this adventure with “year 2” of this “Unity Game Dev” course – this time, let’s explore the Hack’n’slash genre and discover more advanced Unity features, packages and tricks!
Of course, since I’m starting from scratch here, I might re-talk of systems I designed or hinted at in the RTS series… but all in all, I’ll make my best efforts to introduce new techniques and to show more robust workflows, and this series will be independent from the previous course. So don’t worry if you haven’t read the RTS tutorial! 😉
Also: it’s always important to have a set of references when you start a new creative project. Here, there are several video games that pop in my mind whenever I hear “hack’n’slash” and that will be my main references for this new series of tutorials:
- Diablo (Blizzard North, 1997): it is one of the original hack’n’slashes and is considered to be a revolution for RPGs, with the introduction of the “point-and-click” mechanic to make the game more intuitive and quick to play. It revolves around the idea of “crawling through dungeons to kill monsters and loot stuff” – this seems somewhat normal now but it was a real novelty at the time! And the game has even given birth to a category of games: the “Diablo-likes”!
- Path of Exile (Grinding Gear Games, 2013): this free-to-play RPG got noticed for its world design as well as its renewal of several Diablo-inspired mechanics.
- Hades (Supergiant Games, 2020): this much more recent video game is primarily a roguelite dungeon crawler – but it also shares a lot with the hack’n’slash genre, in particular for its combat system.
Note: there might be other references from time to time, but those three are the games that had me love this genre, thus I’ll often turn to them to prepare mechanics or discuss design choices.
Alright and with all that said – time to finally get to it 🙂
And to begin with: let’s start with an essential feature for any hack’n’slash: player movement… as well as a camera that keeps up with it! Here’s a little preview of what we’ll have by the end of this tutorial:
Moving our player
A quick overview of my inputs system
For now, I’ll work on a very simple input system where I use either the WASD or the arrow keys to move my player up, down, left and right. Here, I’m aiming for something more along the lines of Hades than Diablo: I’ll use the keyboard for the movement and the mouse for the attacks and actions.
This is because this keyboard-based inputs scheme is just a first version to start things off gently; in the end, I want to handle cross-platform inputs and to have a gamepad-compatible system – we will see in the next tutorial how to switch to Unity’s new input system to do all of this… but in the meantime, let’s focus on keyboards! 😉
How to implement player movement in Unity
Making our player move is of course essential for our hack’n’slash – in this type of game, everything is about your hero, and in particular the gameplay is entirely focused on your player avatar (its current stats, its position on the screen, the bunch of neat effects surrounding it…).
So, the hero has to be in the center of screen and control our current viewpoint on the scene. Ok — but how do you get this hero to run around, then?
In Unity, when you want to have some object move, you have three techniques you can use:
- modifying the Transform’s position directly: the idea is simply to access and set the
Vector3field on your object’s transform; although it is very easy and quick to do, this is not recommended because it completely bypasses Unity’s collisions and physics system and usually makes for a weird interaction with the rest of the world…
- using Rigidbody-based physics: to better integrate your object with your level, you can also move it by adding a Rigidbody component on it and then applying forces or setting a given velocity on this Rigidbody – this time, you use Unity’s physics system and you automatically get physically realistic movements with gravity, torque, friction, etc.
- using a Character Controller: however, sometimes, you’d rather have “cartoony” and less realistic controls (for example, remember the old Doom game that had this character that ran insanely fast and stopped instantly to turn sharply at the corner of a corridor?); in this case, to avoid physics simulation but still work well with the collisions system (although in a more permissive way), you can use a CharacterController component to move your object
For this hack’n’slash, I want my controls to be sharp and the hero to move quickly. Of course, I still want to have some collisions with the obstacles in the level. But I won’t be jumping around, so I don’t really need gravity simulation, and I also don’t want my character to slowly slow down when I release the key while its velocity is decreasing back to zero. All of this means I have to use the third technique and design my player controller with a CharacterController workflow.
Let’s see how to prepare our hero object with this in mind!
Setting up a test scene
Of course, for now, I don’t have a real 3D avatar for my hero, or any other assets to import – so let’s just prepare a sample scene with some primitives: the player will be represented by a red capsule, we’ll have a few gray walls and a white plane ground.
The walls and the ground all use BoxColliders to allow for collisions with the player.
To create the player, I started from a primitive capsule and then added my CharacterController. For now, I can keep the default values because it matches the base settings of the capsule – but of course we might need to adjust those in the future when we load up a 3D model!
Also, we can move our player object up so that it is not stuck in the ground like this (simply set its Y position to 1).
In the end, here is a screenshot of my sample scene:
Coding up our player controller
We are now ready to start writing some code and create our controller script! I’ll code this logic in a new C# file called
PlayerController.cs that I’ll store in a
Scripts/ folder inside my
Now, even if it is dead-simple at the moment, our hack’n’slash will gradually become more and more complex as we start to add more mechanics. When you can predict such complexity, it’s usually good practice to prepare for it and anticipate how crowded your
Scripts/ is going to be after a while.
For example, something nice is to separate your codebase in namespaces so that your code is split in small logical units. Namespaces also allow you to easily re-import and re-use snippets somewhere else in the codebase (and they are useful to better organise your compilation units, too, as explained here).
To create this namespace, let’s update Unity’s default C# script contents and “nest” your C# class inside the namespace “Player”:
You can even match this C# namespacing with an actual subfolder in your Unity project assets so that it’s more organised for you and your game dev teammates:
Because we know that this
PlayerController depends on a CharacterController, we should insure that any object that has this component on also has a CharacterController: this can be done easily by adding a
[RequireComponent] class attribute in our C# script:
This tells Unity that whenever you add the
PlayerController component on an object, if it hasn’t got a CharacterController yet, then one will automatically be added (else, nothing happens).
GetComponent() function is pretty slow – that’s why it’s always better to cache your references once at the beginning and then re-use a variable like
_controller, when possible!
So – our player movement will work with a few variables:
Vector3variable to store the current direction of the movement based on the current inputs – don’t forget that, for now, we’re using Unity’s old input system so we can use the pre-defined input “Horizontal” and “Vertical” axes to know if the player is currently pressing a WASD or arrow key (you can see them in the Edit > Project Settings > Input Manager panel):
_MOVE_SPEEDconstant float to define the movement speed of our hero: we will simply multiply our move direction by this value to get our real position delta
📌 Tip: remember that the naming of your C# variables and functions is an important part of keeping your project clean and clear 🙂
My convention here is going to be the following:
- variable names are (mostly) written in
camelCaseand function names in
- private and protected variables or functions are prefixed with an underscore
- constant or static readonly variables are written in
- (static variables that are not readonly will be in
- those rules can combine: for example, here,
_MOVE_SPEEDis a private const so it has the uppercasing and the prefix
- I’ll favour long explicit names over short fuzzy ones: I don’t mind typing a few extra characters if it makes the variable all the more understandable at first glance 😉
- I’ll explicitly set the
privateaccess modifier, even if it’s the default in C#
- I’ll make my variables private as much as possible to get better data encapsulation – we’ll use the
[SerializeField]attribute to show private variables in the Inspector if need be!
Anyway – I’ll define these variables as private, and I’ll put them in a region inside my C# class so that I can locate them simply:
Of course, you can play around with the speed until you find something that you like; for now, I’m sticking with a somewhat low value so that the player doesn’t move too fast in the videos 😉
Finally, let’s use all of these variables in our
Update() method. First, we’ll fill the
_move vector with the input axes, and then we’ll use this
_move on our
_controller to move forward at the given
Here, you’ll notice that I also use the Unity built-in
Time.deltaTime variable – this allows me to smooth out the values for the move computation and get a consistent result from one computer to another.
A little fix: setting the inputs “gravity”
If you try to run the game, you may notice that when you release a WASD or arrow key, the player doesn’t stop moving instantly. It continues to run for a little while after you’ve stopped pressing the input.
But wait – we said we didn’t have any velocity and friction with the CharacterController… so what’s going on?
Well the issue actually comes from our default input axes! If you check the settings of the “Horizontal” and “Vertical” axes in the project’s Input Manager, you’ll see that they have a “Gravity” parameter set to 3:
This parameter defines how fast the input goes back to zero after you’ve released the input. So let’s up this value a lot (to something like 1000, like in the other “instantaneous” inputs) – and now our player will stop properly as soon as we release the key(s)!
Trying it out!
Thanks to just those few lines and this little tweaks, we can now move our player object with the keyboard and we get a basic movement scheme… and we still have automatic collisions thanks to the CharacterController 🙂
Have the camera follow the hero
That’s great but, at the moment, we just have a camera pointed at the center point, and with the default angle Unity gives it when we first create a scene… this is not very readable and we’ll lose our player if we move it too far from the origin point!
We need to fix this – so let’s talk a bit about graphical projections, discuss what point of view our camera should use and how we can set all of this quickly thanks to a great Unity package called Cinemachine 🙂
Graphical projections and camera viewpoints
In most Diablo-like games, and hack’n’slash games in general, the camera looks down at the scene from above, with a slight angle, and uses an orthographic projection.
Basically, when people started to dive into 3D and introduced that into video games, game designers were forced to re-think the camera system because with this new dimension, the interaction to the medium was very different and you had to show a lot more to the player at once.
We as humans are naturally good at understanding depth and 3D environments (thanks to millennia of evolution). But with video games, like with movies, the issue is that you still end up showing up your image on a 2D screen – so how can give your players a comprehensive view of the virtual 3D world through a 2D render?
Don’t worry – we hopefully have devised lots of techniques to represent 3D shapes and spaces on a sheet of paper. Those are called “graphical projections” and they can be further grouped into various categories depending on how they approximate reality. Do you try and keep the proportions, or rather decompose the object as much as possible to clearly identify all of its parts? Do you use vanishing points? Do you use isometry?
All of these variations result in numerous graphical projection types:
There is no absolute “best” projection – it depends on the context and on what you want your players to experience. Perspective cameras and close-up shots are better for immersion and keeping the player hooked in; but orthographic views (and side-views, even, for 2D) are usually better to get a nice analytical representation of the scene – like what we want here.
In other words, the camera should convey info and atmosphere, and it should support your game’s world design and narrative. That’s a lot to ask, and, in truth, that’s why it’s near impossible to create a perfect camera in a video game that will work in every situation. But we can still rely on some proven techniques to create okay camera angles…
… and if you’re working with the Unity game engine, then you definitely need to hear about the Cinemachine package! 🙂
Using Cinemachine to configure a great camera quickly!
Roughly put, Cinemachine is an amazing package developed by the Unity team itself that makes it quick and easy to create cameras for your games or films; it abstracts lots of the complexity and gives you a lot of control but also a lot of tools to prepare various types of cameras in your scene: flying cameras, dolly cameras, translating cameras…
This is how it’s described in the Unity official docs:
[…] Cinemachine is a suite of tools for dynamic, smart, codeless cameras that let the best shots emerge based on scene composition and interaction, allowing you to tune, iterate, experiment and create camera behaviors in real-time.
It is so useful that lots of tutorials have actually pointed out it’s surprising that it is doesn’t come with Unity by default! But luckily, Cinemachine is fairly easy to install thanks to the package manager 🙂
To add this plugin to your project, just open the package manager window and look for “Cinemachine” in the Unity registry; then click “Install”:
Once it’s installed, you’ll have access to new objects in a brand new “Cinemachine” menu:
Cinemachine relies on the idea of creating a second “virtual” camera that controls the actual real Unity camera. The “Create Virtual Camera” option lets you create a basic virtual camera that you can then configure in various ways. The other options are pre-configured Cinemachine cameras.
For now, we can just use a very basic setup based on a simple Virtual Camera – so click the “Create Virtual Camera” to automatically get a new object in your scene, as well as a CinemachineBrain on your original main camera object:
Note that I’ve also taken this opportunity to turn my camera orthographic:
This CinemachineBrain links Unity’s camera to the Cinemachine virtual camera so that it can be controlled by the Cinemachine logic. Meaning that from that point on, the orthographic size of the camera and its transform will be directly computed and assigned by the Cinemachine logic.
And this logic is set up with just a couple of parameters – since we want to have a flying camera look down at the scene and follow the player, we can configure our virtual camera like this:
Here’s a quick summary of how this camera works.
First, we set the properties of the camera’s lens – in this case, we want to pick a larger orthographic size than the default value of 5.
Then, by using the “Follow” field, you can have your camera track a specific transform in the scene and translate parallel to it, as if it was on rails next to the target object. In our case, we designate the “Player” object as the target and we can use a simple Transposer to translate our camera continuously and keep the target in sight. We need to set the Follow Offset, too, so that the camera is a bit up and on the side. Also, because we want sharp movements and tracking, we’ll reset all the damping values to zero.
Finally, we just need to use the “Look At” field and a Hard Look At aim type to have the camera locked on our “Player” object.
And here we are! We now have a simple camera that tracks our player as it moves around 🙂
Today, we’ve kicked off this new series of tutorials by implementing a core feature for our hack’n’slash game: the ability to move the player, and also a camera to follow the avatar throughout the scene.
Next time, we will improve our movement system and see how to make it cross-platform using Unity’s new input system…