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.