Hi, my name is Mark. For years I've wanted to make my very own
video games, using software like Unity. Unity is the powerful game engine behind
titles like Cuphead, Neon White, Tunic, Outer Wilds, Hearthstone, Firewatch,
and even the Pokemon Diamond remake. But I've always found that lengthy, multi-part,
meandering tutorials just send me to sleep. I can't learn by watching someone else - I have
to get hands-on and figure things out for myself. And so last year I developed a
solution that actually works. It's a three-step technique where you: one
just learn the absolute basics of Unity. Then, two, cement those
lessons with simple exercises. And then, three, figure out
the rest as you go along. And it totally worked! In
the space of about a year, I went from ripping off iPhone games to working
on my very own puzzle platformer about magnets. And I released an interactive video
essay that's had over 100,000 plays. But wait, I hear you say! How do you do
step one? How do you learn the basics, when the software is so complicated to figure out? Well for me it was about writing down
a list of things I would need to know, regardless of what game I was going to make. Things like how to make a character
appear and move them around the screen. How to make stuff spawn in and
then delete it again later. How to have collisions and game over
and animations and sound effects. Then I learned all that by hunting through
lengthy tutorials, reading the Unity docs, Googling esoteric words, and
doing a lot of trial and error. And so the whole point of this
video is to save you that hassle. This video is the tutorial I wish
I had when I was learning Unity. So in the next 40 minutes we're going
to use the engine to make Flappy Bird. Not because we want to make Flappy
Bird, but because in order to remake this addictive iPhone game, we'll need to
learn basically everything I just listed, from spawning objects to getting game overs. This tutorial will cover every step
of the way from downloading Unity, to understanding the UI, to writing your
very first line of programming code, to building a game that you
can share with your friends. And then, when the tutorial is
over, I'll share some concrete next steps that you can take in order to
continue learning the rest by yourself. Sound good? Then let's get started. Okay, let's start by getting
Unity from the website. Download and install the Unity Hub. And then you'll need to make a
free account to actually use it. Once that's done, you'll be asked to install the
Unity Editor - I'm using version 2021.3 for this tutorial, if you're watching a million years in
the future and wondering why things are different. Let's pretend I have fast internet - Neeooowwwwmm. We're not quite done yet. Under installs, hit the cog icon on
the Unity Editor and pick modules. You'll see that Microsoft Visual
Studio has been ticked - this is the software we'll use to write programming code. So hit continue. And install Visual Studio. On this screen, scroll down and
tick game development with Unity, and untick Unity Hub, because we already have it. Neeooowwwwmm. We don't need to make an account
to use Visual Studio, so skip that. And don't bother loading it, we'll open it later. Okay, that's all done now. So in Unity Hub, pick new project. Choose all templates. And use 2D, Core. This is an empty project, with a few
configurations to make it suitable for 2D games. Give your project a name, hit
create, and let's get game makin'. In step one, we're going to become familiar
with the default Unity user interface. And as we explore the different panels,
we'll make the bird appear on screen. Right. So this is the default screen layout for
Unity, and it's split into four panels. First of all, down here, is the Project panel. This will contain everything that
is in our game - like sprites, sound effects, scripts, tiles, fonts, and so on. Some of this stuff will be
made in Unity as we go along. But we can also just drag and drop
files from elsewhere on our computer. Like, I've made some sprites for the bird and the pipe in Photoshop and I'm going to
import them into my project like so. I'd recommend you make your own
- that's always more fun - but if you have zero artistic ability then
check the description for these assets. The next panel is the hierarchy. This contains all of the stuff
that's in the current scene - which, in most games, will be a level. We're going to start by making the bird,
so right click and choose Create Empty. This has made an empty GameObject... so what's that? Well, a GameObject is essentially
an invisible container. It has a position in space,
a rotation, and a scale. Then, you can fill that container with
components - to add extra features. For example, if we add a
Sprite Renderer component, we can slap the bird image onto the GameObject. Absolutely everything in our level will be
a GameObject with components - the bird, the pipes, even the user interface and the camera. All of this magic happens in the third panel, the
Inspector - which is for messing with GameObjects. So, once we've selected our new, empty GameObject we can put a name in
the top field - let's call it Bird. And we can see and change the GameObject's
position, rotation, and scale, under Transform. We can now press Add Component, pick
Rendering, and pick Sprite Renderer. To make this work, we need to fill in
the sprite field - so just drag the bird image from the project panel into
the field and viola, we have graphics! That will, of course, show up in the
fourth and final panel, the scene view. Here we can see what's in our current scene, and, if you want, you can use these tools to
move stuff around, scale it, and so on. This section has an extra tab for game view, which shows us what the game will look like
from the main camera when it's running. Also, from this dropdown, we can set
a resolution or aspect ratio to get a better idea of what it will look like when
played - so I'm going to choose 1920 by 1080. Oof, the bird takes up way too much space. We could scale it down, but let's
actually just zoom out the camera. Like I said before, the camera itself
is a GameObject in the hierarchy. And it has a camera component
with stats we can mess with. By changing the size, we can zoom out. I'm also going to change the background colour. Lovely. We can now press the play
button up here to start... the world's most boring game. Okay, let's make it a bit more exciting. A quick recap. Unity has four panels by default. Project holds all the stuff in our game. Hierarchy lists all of the
GameObjects in the current level. Inspector lets us see and
change those GameObjects. And we can see the level in the scene view. And a GameObject is an invisible container that we
can fill with components, like a sprite renderer. In step two we're going to use
more components to make the bird into a physics object that is affected by gravity. And then we're going to write some programming code to make the bird fly up
when we press the space bar. So let's add another component
to our bird: a Rigidbody 2D. This turns our bird into a
physics object, with gravity. So when we hit play, the bird
drops, and falls off the screen. Cool. We'll also want this bird to be able to interact
with other objects, so let's add a collider. A circle collider 2D. Back in scene view we can see
the collider as a green outline. It's a bit off-center for me, so
I'll use the offset to move it. And, a little game design trick - if we make
the collider a bit smaller than the image, it will let the player get through pipes
even if they juuust touched the edge. It gives the game a bit of leniency
and makes it feel more fair. The final thing to add right now: a script. This essentially lets us make our
own custom component - but we'll have to write it ourself using programming code. Choose New Script from the components list. And call it BirdScript. Once it's loaded, double click
the script field to open it up. This will open the file in Visual
Studio, which we installed earlier. So, welcome to programming!
It's not too scary, promise. We'll take it slow. We're writing in C sharp,
that's the programming language. And the only thing to worry about right now
is these two chunks here: start and update. Start is for any code that will run
as soon as this script is enabled. And it runs precisely once. Update runs constantly
while the script is enabled. And it will fire off every line
of code, every single frame. Over and over and over again. So the main thing we're going to be doing
with code right now is - well, if we go back to Unity - see these numbers and text fields in
the components? And how we can change them in the Unity editor? We're just going to write code
to change these stats while the game is running. Just as a dumb example, and
we'll delete this in a second. In start, we can type gameObject
- that refers to this bit up here. And then a dot. You'll see a list appear, and many of the
items refer to stuff back in the Inspector, like isStatic, tag, layer, and name. So let's pick name. Then write an equals sign. And in quotes, give our bird a name. Finally, we must always use a
semi-colon to mark the end of a command. And we must always save the script
before we go back to Unity. Now, when we run the game... the name of the GameObject has been changed. Nice. Okay, delete that code. That was just for sillies - but it shows
us how we can use code to talk to the game. We can write a command by choosing someone to
talk to - in this game, the GameObject - and then a topic of conversation - its name - and
then a command - change it to Bob Birdington. We'll be doing this a lot. So what we actually want to do is... in the Rigidbody 2D's component, under info,
we'll see a greyed-out field for velocity. And we want to write some code to add upward
velocity to the bird to make it fly into the air. The problem is... initially, a script can only talk to the
GameObject's top bit and the transform. Right now, this script is completely
unaware of the other components. So we need to sort that out first. We need to make a special slot on this script for a Rigidbody2D - so we can then
talk to it and send it commands. This is called a reference. We're going to create the reference up here,
between the class name and the start function. We're going to write public
Rigidbody2D myRigidbody. So we now have a slot to store a Rigidbody2D. And we have a name that we
can refer to - to make sure we're talking about this specific Rigidbody2D. And because we made it public, it means we
can access this slot from outside the script. So, if we save. And go back to Unity, we'll see that the script
component now has a field for a Rigidbody2D. We can drag the component
into that slot, and viola. We have established a line of communication
between the script and the Rigidbody. Okay, back in Visual Studio. In update, we can type myRigidbody. Then dot. And now look at all the things we can talk about. Angular drag, gravity scale, mass - these
are all properties on the component. The one we want is velocity. We want to set this to a new number, and so, just
like before with the name, we'll write an equals. Now what we're actually writing here
is a vector, which is two numbers, to represent a position in 2D space. And in this case, it's used to represent
a direction for the bird to travel. We want the bird to go straight up, so
zero, comma one would be a good one. I'm just going to use Vector2.up, which
a built-in shorthand for zero comma one. And to give it a bit more power, I'm
going to multiply that vector by a number. Say, 10, which should send
the bird flying up in the sky. Now, like I said before, any code in update
will run, over and over again, every frame. So if we save the script and hit play in Unity... off goes our bird. Bye!! That's not what we want. We want this to only happen when
the player hits the space bar. So it's time to use the most fundamental
bit of programming code: the if statement. An if statement is like a gate. You can surround some code with a fence, and
every frame that code will be completely ignored. Unless, the game meets some specific
conditions that are written on the gate - in which case the gate is open,
and the code is read and executed. So we want to say "if the player hits
the space bar, then add upward velocity". To do this... we can write if, and then in
brackets we can write the condition. This time we're not talking to a component, we're talking to Unity itself -
specifically its input system. So we'll write Input. Then we can pick GetKeyDown,
and in brackets, KeyCode.Space. This asks Unity if the space bar
has been pressed on this frame. And then we'll finish with equals, equals true. A quick note on equals signs -
we use one to make the thing on the left be the same as the thing on the right. And we use two if we're just checking if the thing
on the left is the same as the thing on the right. Cool? Anyway. So this code says... if the space bar has just been pressed, then... and then we'll use curly brackets - these are the fence in our little analogy -
and put the flap code in here. So, now in update - every frame the game
will go to the gate and be asked "hey, has the spacebar just been pressed?" If yes,
the code will fire and the bird will flap. If not, it will skip the code in the
curly brackets and try again next frame. So - save the script and go back to Unity. We can now hit play and tada: the
bird goes up when we press space. We have now created a character
and made it react to input. This is a video game. Hooray! However, it feels like trash. The flap isn't right, and it doesn't
feel like the original iPhone game. So we could change this number. Save. Open Unity. Run the game. Not quite right. Stop. Change the number. Save. But that's slow and dumb. Let's do something smarter. First, we're going to make a variable. Let's go back to the top of the script
and under our reference to the Rigidbody, let's make a public float called flapstrength. A float is a floating point number - basically
a number that can have a decimal place. And then back in our update code, we'll multiply
the vector2.up by flapstrength, instead of 10. Now, back in Unity, you'll see that the script
component has a new field: flapStrength. And we can change that whenever we
want to make the game feel different. We can even change it during the
game, but note that anything you change while the game is running
won't save when you press stop. This means you can play with
values to your heart's content without worrying about screwing up your game. So, if we mess with the flapStrength, and
also the gravity scale on the Rigidbody, we'll hopefully get to something that feels good. Ah, changing numbers back and
forth: honey, that's game design! Recap time. We can use code to change the properties
of a component, while the game is running. A script cannot talk to the other
components on the gameobject, by default. You have to make a line of communication by
storing a reference to that specific component. We create the reference in code, and then
fill it in Unity by dragging and dropping. Code in start runs once, when
the script comes into existence. Code in update runs
continuously, every single frame. But, we can use if statements to skip
some code, unless a condition is met. And we can use public variables to change certain values in Unity's inspector -
even while the game is running. Okay, so the secret to Flappy
Bird is that while it looks like a bird is flapping along through
a world of pipes - it's actually not. The bird stays completely still and
the pipes move across the screen. So in step three we're going to
make pipes spawn into the world, move across the screen,
and then delete themselves. We'll start by making the object we want to spawn. This will be two pipes which move across
the screen, from the left to right. Let's make another GameObject called pipe. Put it exactly on the bird for
now, to get the sizing right. And then we'll make another object
within this one, called top pipe. This is a child of the first GameObject's parent. This way we can nest multiple GameObjects, and move all of them at once
just by moving the parent. So let's repeat what we did for the bird. Add a sprite renderer for the pipe image. And add a collider - a Box Collider 2D, this time. We don't need a RigidBody because it's
not going to be affected by physics. We can then move it up above the bird
- but keep the X position as zero. Finally, we can duplicate
this whole top pipe object. Call it bottom pipe. And flip it upside down by
changing the Y scale to minus one. Then move it down below the bird. As you can see, if we mess with the pipe
parent GameObject, both pipes move, scale, and rotate along with it, with
the parent as the pivot point. So let's add a script to this parent's
object to make it move across the screen. We'll start by creating a variable for moveSpeed. If we give it a number here, it will
fill this as the default value in Unity. But we can always change it there, later. Then we'll write code to
move the object, in update. Now it would be lovely if we could
just type transform.position.x, and change this number directly - but, no, boo,
you have to change the entire Vector in one go. Oh, and this time we're gonna have to use Vector3, instead of Vector2, because the
transform has three numbers. Even though we're making our game in 2D,
Unity is still fundamentally a 3D engine and so it's keeping track of the
object's depth with the Z value. So, here's what we'll do. We'll take the current transform.position. And then equals. We want to add to its current position,
so write transform.position again. And then plus. And finally, in brackets, we'll
do Vector3.left * moveSpeed. Back in Unity, press play and vroooof. That's way too fast. Now, you might think that you could just change this moveSpeed variable down to
a really small number like 0.001. And that will work - but that's
not actually the problem here. You see, code in update just
runs as often as it can. In fact, if we check the stats in Game view, we'll see the game is running
at over 1,000 frames per second. Heh, sorry PlayStation 5. 120 fps? Pfft, that's got nothing on Flappy Bird. And the real problem is that the game may run
at different speeds on different computers, and we don't want the pipe to move
faster or slower depending on your rig. Real games have actually made this mistake -
in Dark Souls 2, weapon durability was once tied to frame rate, so your swords would break
twice as fast at 60 FPS, compared to 30 FPS. That was a whoopsie. Luckily, it's a pretty easy fix. We just multiply it by Time.deltaTime. This ensures the multiplication happens
the same, no matter the frame rate. We didn't need it for the velocity
code because physics runs on its own little clock, but otherwise we will need it. if you want to know more - about this, or anything
really, the Unity docs are a good place to check. You'll find info and sample code. Okay, now with that fix in place, our
pipe moves smoothly across the screen. Lovely. Next, we want to create a system that
will continually spawn new pipes. To start, take the parent GameObject from
the hierarchy and drag it into your project. This creates a prefabricated GameObject. Or prefab. This is like a blueprint for a
GameObject and we can create new versions of this entire GameObject- with all
its children, components, and properties. Oh, and before we move on, we can delete
the original in our hierarchy now. Bye bye. Let's make a new GameObject called Pipe Spawner. We'll put it just to the right of the camera. And we'll make a script for it. The purpose of this script is to spawn new
versions of the pipe prefab every few seconds. And because the pipe already
has code to move left, the pipe will automatically move across
the screen as soon as it spawns in. We're going to write some code to
spawn that prefab we just made. So we'll start by making
a reference to the prefab. Up here, we'll type Public GameObject pipe. Then in Unity, we'll use the same drag and
drop method to fill the slot, but this time, instead of a component, we'll drag
the prefab from the project panel. Now, Unity has a nice built-in method
for spawning new GameObjects. We'll type Instantiate,
and then open the brackets. In here, the command is
asking for some extra details. we can actually flip through these to find
different, I dunno, recipes? I guess? Number 4 looks good - it will create an object
at a specified position and rotation. So, for the GameObject, we can type pipe. For position we can just type transform.position to get the
position of the object holding this script. That will make it spawn on top of the spawner. And for rotation, let's just
use transform.rotation so, again, it's the same as the spawner. Let's run it and oh my god,
that's not what we want. Spawning works great, but they're
coming out every single frame - and we want them to come out on a
nice interval that we can control. So, back to Visual Studio. What we're going to do now is to
write some code to make a timer. this will count up for a
specified number of seconds, run some code, and then start the count again. To do this, we'll need to make a couple variables. A spawnRate is how many seconds
it should be between spawns. And then a timer is the number that counts up. We can make this one private as we won't be
changing it in the editor or anywhere else. In update, we'll do another if statement. This time, if the timer is
less than the spawnRate, then we want to make the timer count up by one. So we'll take the timer as it currently
is, and add Time.deltaTime to it. This creates a number that counts up every frame, and works the same no matter what
your computer's frame rate is. We can actually shorten this by changing it to +=,
but, don't feel like you need to make your code as short as humanly possible just to
avoid getting sniffy YouTube comments. If timer = timer + is easier to read
and grasp, then that's absolutely fine. You can always swap to the other version
in the future when you feel more confident. Now, before I said an if statement is like a gate. And we can add another gate
to the side of it, with else. This means, if the condition isn't met, then
skip the code - and do the code in else, instead. So we'll put the spawn code in here,
and also reset the timer to zero. So now, every frame, it asks if the
timer is less than the spawn rate. If it is, then count the timer up. If it's not - i.e. the timer has actually met or exceeded the spawn
rate, then spawn a pipe and start the timer again. Put this in Unity and - pretty good. I'm happy with that. The only problem is... we have to wait ages for the first pipe to spawn. It would be good if this
came out immediately, right? Now, we could copy and paste the spawn code
into start, so it happens once in start. And then happens over and over in update. But that's a bad idea. You should generally try to avoid having the
same, or even similar code in multiple places. What happens if we want to change how the spawn works? We'll have to find
and change it everywhere. No good. Instead, we can put the spawn code in a new
function, and then just run that function. So here, below update - but above
the final curly bracket - we'll make a function called void spawnPipe(). And then cut and paste the
Instantiate code into there. Now we can just write spawnPipe, with
empty brackets, in both update and start. This will run all the code in that
function when these lines are executed. And with that done, it will make
a pipe as soon as the game begins, and will make new pipes every
time the timer maxes out. Perfect. However - this is a pretty boring game, right?
The pipes always come out in the middle. we want them to come out at random heights. So, remember that when we
wrote the instantiate code, we had to pick a position for the object
to appear? We'll change that value. Right now the pipes always spawn on
the same position as the spawner. We want the X value to be the same... but for Y, we want to pick a random point
somewhere above or below the spawner. So let's create a public variable
for a heightOffset, maybe 10. And then we'll make a float called lowestPoint. Because we're making this variable inside the
function, rather than at the top of the script, it means it can only be used within the function. But, also, it means we can
set it by doing a calculation. so we'll do equals
transform.position.y - heightOffset. And then we'll make another one for highestPoint,
but this time it's plus heightOffset. That gets us these two numbers. Then we'll replace the transform.position
in our Instantiate code. We're gonna write new Vector3, we have to write that whenever we're
specifying our own numbers for a vector. and then in brackets we'll specify the X,
Y, and Z values as three different floats. For X, we want this to be the same as the
spawner, so we'll do transform.position.x. But for Y, we can do Random.Range. And in the brackets for that, we can supply
a minimum and maximum point to pick from. That's lowestPoint and highestPoint. Then a 0 for Z. And close the brackets. Back in Unity.... nice! The pipes will spawn
anywhere between these two numbers. Oh, one last thing. Every time these pipes spawn
they'll appear and move left.... forever. Which isn't great practice - they're
off screen and doing absolutely nothing, and yet they're still in memory
and running code every frame. And if too many spawn they'll
start to spill out the side of your monitor and make a right mess of your desk. So let's fix that. Now we could make a timer, and
delete the pipe after a few seconds. But instead, we'll check
the X position of the pipe, and delete it if it goes past a certain point. We'll borrow the bird to find out the
X coordinate of the left of the screen. Looks about minus 45. In the pipe move script, we'll
add a float for a deadzone. -45. And then a simple if statement - if
transform.position.x is less than deadZone, then destroy the GameObject
that holds this script. Run it in Unity and, bam, they're dead. Let's do one more thing,
just as a teachable moment. Just before the destroy line, let's write
Debug.Log, and in brackets, Pipe Deleted. Then, back in Unity, you'll see one
other panel I skipped during the UI demo - it's a tab next to project, called console. Then when we run the game... every time a pipe is deleted, our
message is sent to the console. This is a wonderfully useful
way to debug our code, because we can find out exactly what the code is up to. Recap time! GameObjects can be turned into prefabs, by dragging them from the hierarchy,
and dropping them into the project. You can then drag these into scenes - I use prefabs to create levels in
my puzzle game, for example. Or you can make a spawner to
instantiate new ones during the game. Timers are a great way to make
code happen on a certain interval, but always use Time.deltaTime to keep things
consistent across different computers. If statements can have an else gate, to
make code fire if the condition is not met. You can also have else if, to
make more complicated gates. And you should try to delete GameObjects if
they're no longer needed, to free up memory. Okay, our next step is to keep
track of the player's score, and show it to the player on the user interface. Then, we want the score to go up by one,
every time the bird goes through the pipes. So, remember that a GameObject doesn't
have to be a physical thing in your game world like a character or an enemy - it
can be a completely invisible manager that's just keeping track of critical
data like health, or time, or score. And then, we can make that information
visible to the player, using a user interface. So let's start by making the UI. Like everything else, it's a
GameObject in the hierarchy. This time go down to UI and pick
text - which may be under legacy. We'll need to zoom really far out on
the scene view to actually see the UI. To make sure the UI looks the same on every
device, we'll pick this new canvas GameObject and set the canvas scaler component's
UI scale to scale with screen size, and choose a sensible reference
resolution - I'm gonna use 1080p again. We can then move our text around. You'll notice that UI has a rect
transform, rather than a normal transform. The most important thing to
note is that you don't really want to mess with scale of elements -
instead, change the width and height. I'll then increase the font size
and set the default text to 0. And then check it all looks nice on the game view. Okay, now we want to make a script
that will store the player's score, and change the number on the UI to that score. We'll make a GameObject called Logic Manager. And we'll give it a script. This script is going to keep track of high
level information like the player's score. And it will have various meta-level
functions that we can run. So we'll delete start and update,
we don't need them in this script. We can always add them back
later if we change our mind. We want to store a number for the player's score. This time, we don't want a float
because we only ever want round numbers. So let's do an int, instead. That's an integer. No decimal places. And because we want to update the UI text we just
made we will, as always, have to make a reference. Except... text doesn't seem to be a thing? Ah, well. By default, a script only loads
in the stuff you need for basic Unity functionality - but if we
go up to the top and type using UnityEngine.UI;, we can now access more
functionality - in this case, UI stuff. Now we can make a reference to text. We'll need to drag the text component
into this field back in Unity. Because we're referencing a component
on another GameObject - the text on the UI - the best way to do this is to just
drag the whole GameObject into our slot. This will automatically find
the text component for us. Handy. So now we want to make a function. And we'll call it addScore. And because we're going to run this function
from other scripts, we'll set it to public void. This function needs to do two things. Add one to the player's score. Easy enough, we know how to do that now. And change the text on the UI to be this number. Oh, the text box is looking
for a string - a sequences of characters - and our score is an integer. They look identical to us
humans, but robots are fussy. Easily fixed, mind you, by adding
.toString() to the game score. To make sure this works, let's give ourselves
the power to run this function from Unity itself. All we need to do is write ContextMenu,
and a name, above the function. Now, in Unity, while the game is running, hit the
little dots on this script and pick the function. Nice! This sort of thing comes
in real handy for testing. Okay, so now that we know the function runs, we specifically want to run it when
the bird goes between the pipes. And the way to do this is collisions. Now if two objects have colliders, they will
bash into each other - in fact, in our game, the bird will already crash into the pipes
because we've added colliders to both. However - you can also have
invisible colliders, called triggers. They don't create an actual collision, but they do let you know that two objects have
touched - and you can run code at that moment. So we're going to put a trigger
in between the pipes, so we know that the bird has passed through them.
And then at that moment, we'll run addScore. Let's open up the prefab for the pipes. We'll make another GameObject called
middle - and it needs a box collider. Let's make it this sort of shape. And this time we'll tick the box isTrigger. Finally, let's add a script
to this new middle GameObject. Beneath Update, type ontrig, and the autocorrect
will help us type out OnTriggerEnter2D. Just press tab to autofill. Anything in this function will run
whenever an object first hits the trigger. There's also OnTriggerExit and
OnTriggerStay, for future reference. And its in here, that we want to run the
addscore function we wrote earlier... except. ah. once again, this script doesn't know
about any other scripts in the game, until we make a reference to it. So we can write public LogicScript logic. But back in Unity, you'll quickly realise
that you can't drag the script into this slot. You can't drag it from the project panel - we can only talk to an instance of a
script that lives on a GameObject. But we also can't drag from
the scene into the prefab. That's because the pipe doesn't
exist in the scene yet, it will only exist when the game is running,
and the spawner starts making pipes. So, instead, we'll need to
fill this reference using code. and this needs to happen
when the pipe first spawns. To do this, we'll need to help
the code find the logic script. To do this, take the Game Logic object, and look
at the top of the inspector: you'll see tags. From the drop down, choose add tag. Make a new tag called, say, Logic. And make sure you go back to the
GameObject and actually set this new tag. You will forget to do this approximately eight thousand times in your Unity
career, so look forward to that. Now, back in the PipeMiddleScript, under start we can write logic =
GameObject.FindGameObjectWithTag("Logic"). this will look for the first GameObject
in the hierarchy with the tag, Logic. In our case, there will only
ever be one in the scene, so we know it will always find the
right one - but do be mindful of that. And then we can add .GetComponent<LogicScript>(); So, as soon as a new pipe spawns, it will look through the hierarchy to
find a GameObject with the tag Logic. Then, it will look through
that object's components to find a script of the class LogicScript. And if it finds one, it will
put that in our reference slot. It has done the exact same thing as
dragging and dropping the component in the Unity editor - except it has
done it instantly, during run time. Excellent. So now, the pipe's middle script can
find and talk to the logic script. And if we write logic.addScore,
this will run that code. Back in Unity, hit play and
if we did everything right, the score will go up by one
when we pass between the pipes. Oh, and just for future proofing and whatnot, let's make sure that it was
actually the bird that went through. We'll do this by putting the bird on a layer, and checking if the colliding
object was on that layer. Go to the bird's GameObject and this time,
instead of the tag, we'll change the bird's layer. Make a new one, remember to actually
assign it, and make a note of the number. Now, on the pipe's middle script,
we can add an if statement around addScore, and check if the collision that just happened
was with a GameObject on the bird's layer. One more bit of future proofing,
while we're on the subject. Go back to the Logic Script. And, let's take the AddScore function, and in
these empty brackets we'll write int scoreToAdd. And then instead of adding
one, we'll add scoreToAdd. Then in the pipe middle script, we can
write a 1 in the brackets after addScore. Right now this does exactly the
same thing as we had before. But, as you can surely guess, you could later add some other goal in
the game that adds, say, 5 to your score. This allows us to make a function more versatile, as it can be used in different
ways, from different places. Part of being a good programmer, I think, is making stuff less rigid, and
keeping it open for future ideas. This makes it easier and faster
to iterate on your designs. Right! Recap! UI is just another GameObject,
but if we want to reference any of these components we'll need to add using
UnityEngine.UI to the top of the script. GameObjects can be completely invisible things, merely there to keep track of
rules, logic, score, and so on. If we want to a reference a component when
one of the GameObjects is not in the scene, we'll need to find that component during run time. One way to do this is to use tags,
findGameObject, and GetComponent. A public function can be run from another script,
as long as you have a reference to that script. And we can even pass in variables
when that function runs. And Collisions and triggers can be used to
make stuff happen when two objects touch. Speaking of collisions, let's
move on to the next step... The final step is to add a fail state. When the bird hits the pipes, the game is over. We'll do this by making a game over screen, and
have it appear when the bird crashes into a pipe. The game over screen will have a button,
which we can use to reset the game. First, let's make that game over screen. On the canvas GameObject, add a new
empty one called game over screen. Then, in that parent, add a text for game over. And also a a button - that's also under legacy. Resize it. And change the text on the button - the text
can be found as a child on the button itself. So back on the button GameObject, on the button
component, you'll see this bit that says On Click. This is an event, and it allows us to
call a public function on a GameObject. So let's make a function for restarting the level. We can put this code in the logic
script, underneath our addScore function. You could make a seperate script if
you want, but I think this is fine. Let's make another public
function called restartGame, and in here we'll write code to restart the scene. Just like before with the UI,
if we're managing scenes then we'll need to add a line the top - this
time, using UnityEngine.SceneManagment. Now in our function, we'll call up the
SceneManager and then, dot, LoadScene. This is looking for the name of a scene. Literally the filename. But because we want the current scene we can simply type SceneManager dot
GetActiveScene, brackets, dot name. Close off all the brackets. Now back in Unity, add an event to this button. Then drag in the logic GameObject. and find the restartGame function. Give it a test and... nice. Every time we press the
button, the game begins anew. Now obviously we don't want this to be on
the screen all the time - just when we fail. So, we can just take the whole game over screen
GameObject and disable it with this checkmark. Then we'll make it show up when
the bird hits into the pipes. Let's write the function first. Again in the logic script, let's
make a public function for gameOver. We'll need to make a reference to
the game over screen GameObject. And fill it in Unity. And then we can simply type
gameoverscreen.SetActive true in this function. So we want this function to trigger
when the bird crashes into a pipe. Back on the bird script, let's
reuse that code from before to access the logic script from the bird script. Yes, we could drag and drop the reference in
Unity, but hey, we've written this code now. And then we're going to do a similar thing
to the trigger code, but this time we'll use OnCollisionEnter2D, because the pipes are
solid objects, and not set to be triggers. And when that collision occurs, trigger
the game over script with logic.gameOver. Back in Unity... it kind of works, but we can still
play in the game over screen. Not ideal. So, I've talked about a few
key variable types, already. Floats and ints are numbers. And string is usually for text. The other important one is
a bool, short for boolean. This is a really simple type
that is either true, or false. On, or off. Yes, or no. It's a great way to simply check
or change something's state. So let's have a bool called birdisalive,
and make sure it starts as true. Then when the collision happens,
we'll set birdisalive to false. And finally, we'll add an extra
condition to our very first if statement. We're going to say if the space
bar has just been pressed and... written with two ampersands... and birdisalive is equal to true. Actually, we don't need to add
this equals equals true thing. It does the exact same thing without it. But, again, it's up to you - maybe it's easier
to read this with the full code written out. Anyway, now, the bird won't flap if it's
dead, which seems quite logical to me. The final thing to do is to build the game. Which is really easy.
Pick file, build settings, and build. Pick a folder on your hard drive. And let Unity do its work. Then you can open this file
to play your game! Amazing. In a very short period of time, we
have made a pretty functional game. And what’s more, we’ve learned loads
of fundamental lessons about Unity. We have made a character that
moves in response to our input. We have spawned in new objects on a timer. We have created a UI that shows a score, and
made that score tick up when conditions are met. And we've got the ability to get
a game over, and start again. Now, I should note that there are different - and perhaps better ways to do pretty
much everything in this tutorial. For example - I used Unity's
old way of checking for inputs, and the company has since developed
a much, much better Input System. But it's a lot more complicated to use -
so this simple method is great for now, and you can look into the new input system later
down the line, when you feel more confident. That's how it went for me. There's also TextMeshPro, which
has replaced the old legacy UI system - so you'll want to graduate
to that, at some point, as well. Anyway, these are lessons that will be useful,
for making all sorts of games. But... the game isn't quite finished yet. There's still a few more things to figure out. Though, I don't want to tell
you how to do everything. So i'm gonna give you some suggestions
for how to finish up the game, but I want you to try and
figure it out for yourself. So first of all, we need to have a game
over if the bird goes off the screen. That shouldn't be too hard. There's also a bug where the score
can go up, even after a game over. Try to solve that one too. We also want sound effects. I want you to add an Audio Source
component to the logic manager. fill it with a sound effect file. Reference it on the script. And have it play when the score goes up. Then, i want you to play around with the particle
system to make clouds appear in the game. Next, open the animation window, and
add some flapping wings to the bird. Then i want you to add another
scene to make a title screen, so the game doesn't immediately
launch into the action. Here's a clue: you'll need to add this
new scene to the build settings window. And finally, if you want a real
challenge - use PlayerPrefs to save the player's high score to the hard
drive, and draw that on the UI as well. For each one of these, you will probably want to
Google the relevant terms, read the Unity docs, watch some quick tutorial videos, or
ask for help in the comments down below. Next, you could expand on Flappy Bird. Get creative and add in ideas or designs that
weren't there in the original iPhone game. For example, with a little messing
around I gave the bird the ability to shoot out a missile, and then
I added targets to the pipes. You've now got to hit the target with a
missile to open a gap you can flap through. It's pretty cool, and adds a lot
more depth to the simple game. In fact, I'd love to see how you
might expand on the original game. If you make something interesting, record
a bit of footage, pop it on YouTube, and drop a link in the comments. I might feature some of them in the future. And then, finally, I'd recommend that
you take another simple game and try to remake it in Unity, like we just did right now. This is a great technique because you
don't have to worry about art or design... just code. And the problem-solving puzzles you'll face are
a perfect example of what real game development will be like. Good candidates for this include Pong, Space
Invaders, Breakout, Pop the Lock, Angry Birds, various WarioWare mini games, and that dinosaur game that plays
in Chrome if your internet's broken. So, in this video I wanted to teach you the fundamental concepts behind
Unity - but, the rest is up to you. Luckily, I reckon this sort
of hands-on, self-directed, learn from your mistakes style of learning is the
most fun and effective way to make stuff stick. But we’ll see! Let me know how you got on
in the comments down below. And if you want to watch my
game development story - which is still ongoing, promise - then click
here, for episode one of Developing. Thanks very much to my Patrons
- they're the reason you don't get mid-roll ads in a looong video like this one. You can help support GMTK at Patreon.com.