-
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.