Learn Game Coding - Step by Step - with C# + Unity
The following is a complete course, structured as step-by-step lessons, that teaches you everything you need to know as a beginner about programming and game development using C#, Unity and Visual Studio. No prior experience is required and, best of all, the entire video course is available right here for FREE!
If you want to really fast-track your learning - and make the experience more interactive with Q&A, quizzes and more - please consider becoming a member!
Section 1 - Setup & Crash Course
This video sets out the course outline, the topics covered within its different sections, as well as installation of the Unity and Visual Studio tools we'll be using throughout the course.
This and the next set of lessons is just a quick crash course introduction that helps set the table for future lessons. At the end of the previous introduction video we installed the tools we needed and launched our very first 2D game project. This lesson continues from there, where we look at creating basic GameObjects and setting their position in the game world - or "scene" - by accessing their Transform Components. We also establish the initial camera and screen resolution settings as well.
This lesson gives a sense of the typical workflow for prototyping a game in Unity. This includes creating GameObjects in the inspector and then attaching script Components to drive their behaviour through the Start() and Update() method definitions. We then create the foundation for what will be the "Prototype" game project that we'll build throughout this course. We'll code up the following: a basic controller script that moves a "Player1" GameObject with user input, an "enemy chaser," a movement constraint to the confines of an "arena," and destroying the Player1 GameObject if it collides with the enemy.
Reflecting on what we did in the previous lesson, this lesson serves as a code review and basic primer on how the code we wrote works. This is a mostly non-technical explanation (for now) in order to set the foundation for better understanding how the Update() method drives change through a "game loop" which we look into deeper in the following lesson.
Understanding the how the game loop operates is crucial to writing effective code in Unity. Here we explore several visualizations of the game loop, as well as a slowed-down dissection of how code elements in the Update() method produce the appearance of motion/change frame-by-frame.
Continuing our prototype game project, we get right back to coding by spawning (or "instantiating") enemies at a random location over time, as well as adding a special teleport ability for the Player1 GameObject, along with a "cooldown" counter. We also look at attaching SpriteRenderer Components to each GameObject so that we can add sprite images as visual representations of those GameObjects in the Scene.
Section 2 - C# Language Fundamentals
Moving into the next stage of learning, we now focus our attention on understanding C# language fundamentals. Learning these basics will be instrumental towards gaining a deeper understanding of Unity, and developing the skills necessary to create your own game mechanics. This lesson starts the process by creating a "Hello World" output through the Unity Debug Log. Topics covered included class containment, as well as method definitions and calls.
This lesson looks at basic C# data types, declaring variables, and performing operations on them. We combine values and operations together to form statements in code, similar to how nouns and verbs are combined to make statements in a natural language.
Continuing with our look at variables and operations in C#, we focus on basic mathematical operations, operation precedence, and the difference between how the equals sign is used in math and in code.
Having explored strings and ints in previous lessons, we round out our understanding of the most common C# data types we'll be using in Unity by covering floats and bools. We also look at some simple data conversion, including explicitly converting from one type to another using casting.
A major part of game design is handling branching possibilities from one moment to the next. This is most often handled in code with a variety of conditional statements that evaluate whether or not the statement it evaluates returns "true." Here we look at the simplest type of conditional statement called if(), as well as the closely related else() and else if() clauses. We also look at equivalence evaluations, as well as the common mathematical evaluations involving greater-than/less-than checks.
We continue exploring if() statements by looking at the "not" (!) operator, which lets us invert evaluations, operators, and operands in order to check for false values. We also look at adding complexity within conditional evaluations by using the binary "and" (&&) and "or" (||) operators. And, finally, we see how the "not" operator can be used as a unary operator on bools themselves to flip their values.
This lesson combines everything we learned about conditional evaluations and uses it towards creating a typical game-like "state handling" conditional with nested if() statements. In this example, a bool is used to store an "invincibility" state depending on whether or not a "power-up" was used.
It's important to distinguish between arbitrary code style and strictly enforced code syntax. This lesson covers those basics, as well as how to spot code errors in Visual Studio.
Possibly the most crucial coding concept to understand is that of variable scope, which determines the "visibility" of a variable depending on where it is originally declared. This lesson takes a look at that topic and how variables that are local to methods, or nested structures within methods, have a temporary life-span compared to those declared at the class-level. We see how these class-level variables (technically called "fields") have a profound impact on game elements in Unity.
C# is a strictly enforced Object Oriented Programming (OOP) language. Understanding its OOP principles is important towards writing effective code as well as grasping the Unity API. Speaking loosely, this lesson covers how classes act as "blueprints" or "object definitions" that broadly describe how an actual object built from it looks and behaves. We start by creating a test class from which we later produce object instances based on that blueprint within another class.
This lesson looks at adding access modifiers, such as "public" and "private," to fields and methods within a class. We do this to determine whether other classes can "see" these class members, and be able to access them, when we create actual object instances.
When we create an object instance of a class as a field within a different class, it creates a structure called "object containment" or "object composition." This is one of the ways in which we can make classes modular, allowing their object instances to import code for use within other classes. This brings with it benefits of abstraction, code reuse, and code separation of the individual purpose for each class.
The object composition structure covered in the previous lesson is sometimes referred to as a "Has-A" relationship. For example, you can have two separate classes: one for "Wizard" and another for "Weapon." You can then have an instance of the Weapon class within the Wizard class and say that the Wizard class "has a" Weapon object. This lesson covers that topic and in addition will briefly look at method returns.
When the "Has-A" containment structure doesn't suit the purpose you are looking for, you can instead turn to class inheritance in order to import code from outside classes. This structure is often referred to as an "Is-A" class relationship, meaning that the inheriting class is an extended version of the class it's inheriting from. For example, you can have two classes - Wizard and Enemy - and relate them in a way where you can say a Wizard "Is A" type of Enemy. This has important implications for how we look at objects in later lessons.
By creating instances of objects from a class blueprint, the fields and methods defined in the blueprint are considered as belonging to those instances. Simply put, each instance has its own field/method that has to be accessed through the instance it belongs to. However, sometimes it makes sense for a field or method to belong to the class itself and considered as shared across all instances. We do this by marking the field or method as "static" and then accessing it through the class name rather than any particular instance.
This lesson rounds out our understanding of methods by looking closely at adding input parameters and returns to method definitions. We also look at why these features might be useful depending on the circumstance.
Every type declared in C# is either a value type or a reference type. The distinction between the two lies in how each type is handled in memory. This has a variety of implications in how we go about writing and understanding the code that we write. This lesson aims to distill this concept by exploring simple examples of where the distinction often shows up.
Polymorphism is an OOP principle that adds flexibility to your code by treating objects as base types that they inherit from. It's an important concept to understand when creating games in general, and accessing the Unity codebase in particular.This lesson covers a game-like example of Polymorphism where "Knight," "Wizard," and "Elf" classes inherit from a base class "Combatant." We then explore some of the benefits gained form treating these inheriting classes as the base class.
Section 3 - Creating a Non-Physics Action Game
The Unity API is essentially a collection of classes that have been setup for you ahead of time. This lesson looks at the various ways you can come to understand the Unity API in order to tap into it to create any game behavior you want.
Having learned some C# fundamentals, we turn our attention back to writing code for our prototype game project that we originally started. We start by applying what we've learned and refactoring some of our previous code into distinct methods. We also take a look at naming conventions for local variables and fields, the "const" keyword and how to set a reference to an outside GameObject, in code, through the inspector.
Constructors are special methods that run at instantiation in order to set an object into a valid initial state. This lesson covers how to create Constructors in classes or structs, and highlights their differences. We also take a look at special considerations when using local variables in the Update() method rather than class-level fields.
Just about every game has some kind of item that you can collect by moving your avatar over it. Here we implement a simple PowerUp script that randomly instantiates PowerUp items, and then destroys them if an amount of time has passed, or if the item gets collected.
This lesson looks at how to use the Unity convention of Prefabs to instantiate GameObjects. Prefabs lets you bundle up the entire GameObject along with its attached components and scripts, and use that as your blueprint from which new instances can be created.
Now that we have a functioning PowerUp in our game we look towards implementing a meter that increases as PowerUps are collected, and drains when we enter a PowerUp state. We also determine the PowerUp state itself as having the effect of slowing time.
With the PowerUp meter logic in place all we have to do now is display it through a text overlay. For this we turn to the Unity OnGUI() method for displaying Text GUI Labels. Along the way we also look at the C# concept of overloaded methods.
In this lesson we look at the handy Unity feature of GameObject parenting, which can be done either directly in the editor or in code. When you parent GameObjects, the child object inherits the transform property of the parent. You can also use this feature to group GameObjects that logically belong together. This lesson also looks at how to reference GameObjects that were instantiated in code.
This lesson will introduce you to Lerp() methods found in the Unity Engine. The term "Lerp" stands for "Linear Interpolation," which refers to the process of transitioning from a given point-A to a given point-B, as a percentage between those two points. Becoming fluent with "Lerping" is an extremely useful skill that you can use to handle a wide variety of changes that have to occur over successive frames.
Now that we understand how to "Lerp," we put it to good use by creating pseudo-animations directly in code. This technique allows us to change the appearance of a GameObject over time without requiring spritesheets, drawing skills, or anything more complex than simply manipulating values that create a visible change.
Here we take a look at defining Generic classes and methods. Generics are yet another way for us to create classes and methods with added flexibility. We're mainly interested in learning Generics so that we can better understand how they work in the Unity Engine, such as with the commonly used GetComponent() method.
SpriteSheets are the most common way of creating animation in 2D game design. This technique takes a collection of pre-drawn animation frames and incorporates them all in a single image. Unity has extensive support for SpriteSheets that allows you to easily slice them up and add them to an Animation Clip. We then take those Animation Clips and set the conditions for one clip transitioning to another through the Animator, which can be controlled in code.
This lesson looks at arrays and loops in C#. An array stores a collection of variables - of the same type - within a single variable while a looping statement can be used to iterate through that collection and perform operations on each item or return each item's value. The most versatile looping statement in C# is the for() loop, which comes with a built-in counter and conditional evaluation. We also look at a simplified looping statement that works particularly well with arrays called foreach() and use it to solve a graphical issue in our "teleport" animation.
There will inevitably come a time when you will need to debug your code. Debugging is used to not only hunt down bugs in your code, but also to better understand its execution flow and inspect the values that variables hold as the script executes. Here we look at the variety of debugging tools available to you when working with Visual Studio and Unity.
In this lesson we add a visual enhancement, panning and zooming the camera when PowerUp is engaged. We also look at using the LateUpdate() method to handle camera tracking of GameObjects, which resolves certain script execution order issues that could arise if LateUpdate() isn't used.
This lesson covers adding audio clips to your project via the AudioSource Component, as well as how to play them through the inspector or through code. In specific, we add the following: a looping crowd noise sample, an airhorn signal, an intro voiceover, random fx signaling whenever a PowerUp is collected, and we also implement functionality that builds a chorus of cheers with every enemy we come close to.
We expand on working with audio in Unity by routing AudioSources through mixers. Mixers are commonly used to group AudioSources, apply effects and control audio parameters via code. In this lesson we incorporate a LowPass filter to create a suctioning "bullet time" effect when PowerUp mode is activated.
In this lesson we add a variety of enhancements to our game, including a scoring mechanic based on how close we get to the the enemy, an airhorn to warn us that the next enemy wave is coming, a PowerUp bonus after a certain score total is reached, and also a method that modifies in-game music. We also take a look at using generic Lists to store local reference to a collection of outside GameObjects.
In this lesson we delve into how to load one scene from another by creating a start screen. We will also re-use this code for quickly restarting the game upon "Game Over." Along the way, we experience an issue with how scripts are instantiated that determines whether or not default values should be set at instantiation or in the Start() method.
A Property is similar to a Field but with added functionality that executes whenever it's being referenced. This has a variety of uses, which includes "gate-keeping" against undesireable values, localizing code that is only relevant to the property itself, as well as further limiting its accessibility from outside classes. Whether you're accessing pre-existing Properties in the Unity API or creating your own, it's important to understand how they work and why you would want to incorporate them into your own projects.
Unity's built in Input Manager allows you to set up any kind of controller mapping, which you can then access in your scripts via the Input class. In this lesson we look at setting up a wireless XBOX360 controller as an input device to control the game. However, you can use just about any standard game controller recognized by your computer.
Enums are another fundamental C# concept that we have yet to look at, even though we've been using enums since the beginning of this series whenever we make reference to the Input class. For example: Input.GetKeyDown(KeyCode.Return) takes in an enum called "KeyCode" to handle keyboard inputs. Here we look at the common uses, and benefits, of using enums in your project.
A "Null Reference" happens whenever there is a reference to an object in code that doesn't exist. This can happen if the object is not initialized with the "new" keyword, or if the object is destroyed in memory. The latter case is the problem we're facing whenever the Cube object gets "eaten" and we destroy it with GameObject.Destroy(CubeReference). In this lesson we look at how to fix null references in general, and in this particular case, by creating a more interesting "Game Over" scenario.
We've been developing our game with the assumption that it will always run at an optimal, and constant, framerate. However, it's possible that the framerate will vary depending on the capabilities of the platform it's running on. If the game were to run at a rate lower than 60FPS it would make the game appear to slow down due to the Update() method running less frequently. Unity has a helpful property called Time.deltaTime to combat this problem and make the game appear to run at a fixed rate of change, or what's called "framerate independent."
In this lesson we put some finishing touches on the game in preparation for the final build. Most notably, we add pause functionality with Time.timeScale, clean up the font for the HUD using GUIStyles and make the font scale relative to the size of the display.
Before building your project to a standalone file you will need to consider a variety of project settings. Many of these settings depend on the capabilities of the device you are building to, such as the allowable resolutions, aspect ratios, graphic scaling, quality settings, and whether or not the viewport is windowed or fullscreen. These considerations also vary depending on the type of game being produced. In this lesson we look towards building our project to a PC standalone .exe file with some of these considerations in mind.
Section 4 - Creating a Physics Based Platformer Game
Having learned most of the important Unity basics, we now turn our attention towards creating a final project: a classic style 2D side-scrolling platformer. Here you will leverage everything you've come to learn - as well as some important new concepts - in order to build a more complex world filled with collisions, gravity, forces, parallax backgrounds, polymorphic enemies, and even some "procedural" design elements. The bulk of these features will center around understanding, and utilizing, the underlying physics engine available through Unity. These physics emulations available to you ultimately simplify a lot of otherwise complex game development mechanics, but it's important to understand the basics of how the engine works before getting started. The first two lessons will serve as a primer to these fundamentals, whereas the following lessons expand on the concepts while rapidly building the project.
In this lesson we explore the differences between the Update() and FixedUpdate() methods - which can both contribute to the main "Game Loop" - as well as why you would choose one over the other. Although this is a somewhat technical topic, it is important to understand how to use FixedUpdate() before delving fully into the the physics engine. Despite this, the concepts are easy to understand, and the lesson provides simple visuals for the most difficult part: understanding the call intervals between FixedUpdate() and Update(), particularly as the framerate changes. Towards the end of the lesson, we will also cover a few "gotcha" scenarios of getting both methods to work well together, particularly when handling user-inputs.
We begin developing our sidescrolling platformer by creating a script that moves the main character using physics. We then manage these different states that the character can be in - moving horizontally, jumping, and so on - by using enums. In order to view the enum states in the inspector we also look at how to create a "Singleton" object in code, which is a very common and easy to understand design pattern with a variety of uses in Unity.
In this lesson we'll create an attack script, starting with a punching attack, as well as determining what happens when the punch collides with the Enemy. For this we'll use a built-in method/event called OnCollisionEnter2D() and touch upon how events differ from the rest of the code we have been writing thus far.
Continuing on with the attack script we created in the previous lesson, in this lesson we add projectile and stomping attacks as well as some audio clips for these actions.
This lesson will show you how to create a parallax scrolling effect where background elements appear to move at a slower rate relative to foreground elements. This effect is common to all platformers and produces a result that mimics three-dimensional depth.
Now that we have a scrolling background, we can duplicate (or "tile") it to fill-in as we progress in either direction. We will also want to destroy these background GameObjects when they fall outside of the camera scope in order to keep things neat and tidy and free memory resources. This lesson will cover how to do all of that and, for added flare, we will also emulate an "overcast" cycle for the background elements.
This lesson will show you how to create and instantiate GameObjects - along with relevant Components attached - all within code. We will be doing this by creating OOP Enemy classes all in C#. This will utilize many of the important OOP concepts you've learned up to this point, and introduce some new ones, such as marking class members as "virtual", "abstract" and "override." We will combine these OOP principles to achieve maximum flexibility when creating GameObject variations - in movement and appearance, for example - while keeping the classes lean and reusable. We will also look at another Unity method called Awake().
Continuing on from the previous lesson, here we'll flesh-out the remainder of our Enemy classes. This includes calling a public Initialize() method as a "pseudo-constructor" at instantiation, as well as setting each enemy's unique movement patterns.
In this lesson, we'll set up a Trigger Collider that instantiates a random set of Enemies once it's been activated. We'll use a simple switch() statement - which is similar to an if() statement - to handle the conditional set of instantiation options in code. We will also create a DoDamage() method that we can access polymorphically whenever the player's attack collides with an Enemy.
In this lesson, we finally get to creating the actual platforms for our sidescrolling platformer. We'll have different attributes for each platform by utilizing simple Unity Materials and Effectors to get varied behavior, such as, 1-way collision (jump up through a platform and land on it when falling down onto it), bounciness and rigidity. On the coding side of things, we will be constructing the platforms procedurally - which is basically just controlled randomness - according to a set of possible positions that we establish using multi-dimensional arrays.
In this series-ending lesson we'll apply some finishing touches to the physics-based, sidescrolling platformer we've been working on. Afterwards, you can build the project by referring back to "Lesson 052 - Final Build and Project Settings."