COLTON OGDEN: Hello, world.
This is CS50 Twitch.
My name is Colton Ogden.
And happy 2019.
Happy new year to everybody.
Whether you're new or a
regular from last year,
welcome to our Twitch channel where
we do sort of live coding, demos,
tutorials, explanations, and all--
excuse me-- all kinds of stuff.
We might even get into
some hardware-related stuff
if some of the staff is
privy to that sort of thing.
We've got a bunch of people in the chat.
I'm going to say hello as usual to
everybody who's here with us already.
And let me know if I sound OK.
We just completely
re-setup our stream setup.
We took all the stuff that
we had setup in this room,
on our streaming room last year in
December, we took it all, basically,
and used it for the fair, brought
it back, and then reset a lot of it
up today.
So hopefully everything looks and sounds
like it did last year if not better.
But definitely let me know if not.
I'm going to go through the chat
here really fast, just to see
who joined us here to begin with.
Looks like MKloppenburg,
a regular from last year
when I was doing a little test message.
This test received.
[? BavikKnight, ?] another regular.
Hey, everyone.
Hi.
How is everybody?
Happy 2019 to everyone.
Happy 2019 to you as
well. [? SewerTon, ?]
who just followed, actually.
Thank you very much for the follow.
Hello, CS50 family
with the popcorn emoji.
They say-- Buddy2610, sup?
Nice.
Oh, I can't read that name at all.
The Gangus-- oh, that's
the GangusGoat19.
Also thank you for the follow.
I see GangusGoat19 just follow as well.
Thanks to everybody who
followed over the break.
It looks like there are about 200
people that followed while we were not
streaming over the winter break.
So I was in California.
I was far away from Boston over the
winter for the last three or four
weeks or so visiting family.
Back in Boston, it's very cold today.
It was fairly nice temperature
for the last week or so,
and now it's, like, 20 degrees today.
It's freezing.
[? KiraU, ?] with the two
clown faces, thank you.
[INAUDIBLE] smiley faces.
TuxMan29 [? int ?] new year, last year.
Return last year plus equals 1.
That's TuxMan29, a little
bit-- a little code snippet
for everybody to increment the year.
And then [? SuraTon ?] is another thing.
It's basically saying, if last
year is null, age is 1, right?
Because if we weren't any year-- if
we weren't any age last year, null
is not a valid a valid age, so
we need to be at least 1, right?
[? BellaCures, ?] happy 2019.
[? ForSunlight, ?] hello.
Colton, the regulars, and
all, thank you very much.
Let me see, make sure
I'm not missing anybody.
Didn't do the assignments
yet, but watched most of it
and worked with [? Django. ?]
Oh, that's for the--
talking about Brian's
web dev class, yes.
Brian actually may be in the chat today.
He and I chatted before
the stream started.
[? SerotonOFX. ?] Hi, Johnny Bravo.
[? OFX is ?] surely a
reference to my hair.
It is a little Johnny Bravo looking.
I need to get a haircut,
like, super badly.
I haven't gotten it
cut for, like, a month.
It's pretty insane.
Man, some of these
names are hard to read
because Twitch puts them in this
really obnoxious blue font on black.
M [INAUDIBLE], hi, Colton.
I hope I'm pronouncing that right.
I'm probably not pronouncing that right.
Sounds good.
Hello, regulars.
Hello, everybody.
OK, awesome.
So I think I got everybody
that was in the chat.
Thank you all so much for tuning in.
Looks like my head's getting cut off.
I think the camera might be a
little too zoomed in, but that's OK.
Today's stream is on Space Invaders.
Let me go ahead and switch my
screen to the full view here.
And this is sort of where
we left off last year.
And I noticed that the GitHub
repo was actually from November.
So if you're in--
if you want to basically get the code
base that we work with last November,
I'll write the URL here in the chat.
Actually, if I do that it's going
to screw up the chat overlay.
Basically, it's Coltonoscopy--
GitHub.com/coltonoscopy,
that's my GitHub handle, slash
space dash invaders dash stream.
And then that will
give you the code base
that we worked with
last year in November
where we basically had
the following working.
Let me go ahead and run this.
So it's not really
super feature rich yet.
We do have a ship that can move around.
And if you press spacebar,
you shoot lasers.
You shoot projectiles.
But it doesn't do anything beyond that.
The projectiles don't collide with
the ships, and the ships are static.
They're basically just sprites
that are being rendered.
And that's pretty much--
that's pretty much it.
That's pretty much all that's going on.
One second.
But yeah, not a whole lot
really going on there.
But today's goals are-- we
have a few goals, actually.
First goal is actually get
the ships to do something,
to move around left and right.
The next goal will be
once they're moving
to actually have the projectiles
collide with the ships
in order to trigger
a death, essentially,
and to give us some sort of score.
And then another thing
that we want to do
is the game is currently in just a
single state where we sort of start
the game up, the ship's there, all the
enemy ships are there, and we do stuff.
But there's not really a title screen.
There's not, like, a game over
screen, anything like that.
So I would say that our goals
today are sort of triple faceted.
We want to get the ships to move,
get the ships to sort of come down
towards the player, move left
and right, which is how it
worked in the original Space Invaders.
Then we want to have the projectiles
that we shoot from the player
not only dispose of
themselves after they've
gone past the edge of the screen--
for performance reasons if we just
let an unlimited number of projectiles
exist when we shoot, eventually,
we'll theoretically run out of memory.
Although they would be very difficult
to do that because the projectiles
currently are so small.
I'm not sure exactly how many
bytes they take up offhand.
But relatively few per projectile.
But it could eventually end
up slowing down the game.
And they'll exist and they'll
update every single frame.
And really, the updating
of the projectiles
is probably going to be more of a
performance issue than the Memory Cost
because in our code, and I have to
sort of remember how this all works.
But we do have--
make sure it's an update.
In our update function online 51 to 53,
and I'll answer some of the questions
in the chat in just a second.
But on lines 51 to 53, we
have this sort of for loop
that says for every projectile
in a table of projectiles
that we have created in advance,
an array of projectiles,
you can think of it as, we want
to call this update functions.
Update function essentially just
moves a projectile up or down
depending on its orientation,
whether it's going up,
or it's going down, whether
it's going towards the enemies,
whether it's going towards the player.
And so for every projectile we
add to this array of projectiles,
it's going to add more work
that gets done in this loop.
And eventually, this
loop could get cumbersome
and bring us down from 60 frames
to maybe 58 frames, 40 frames.
It would take a lot of
projectiles to do that.
I'm sure it would take
thousands of projectiles,
but it's something that
feasibly could be done.
So essentially, what we want
to do is when a projectile goes
beyond the edge of the screen
if it doesn't hit an alien,
we want to get rid of it.
We want to just essentially set it
to nil on the table or repurpose it.
You can use what's
called an object pool.
We're just going to delete it,
essentially, and then just create
new ones as we press spacebar.
And so we'll only ever have
maybe 5 to 10 projectiles
max on the screen at one time.
But that's something that you
want to take into consideration.
If you're spawning objects,
you want to de-spawn them,
sort of like freeing
memory in C. Whenever you
alloc memory, malloc
memory, you have to free it
in order to prevent memory leaks.
It's kind of the same
idea, although a little bit
more complicated in this
sense, because you're actually
dealing with things that are taking up
CPU cycles that are moving and doing
stuff every frame.
But that's the projectile side of it.
The ship side of it we just need to
have a loop where the ships kind of move
left and right and
come down when they've
moved all the way to
the edge of the screen
until they reach the
player, essentially.
And then the last part we'll be using
what's called the state machine, which
is a very important concept in game
programming which allows us to divide
up our game into multiple
different states that
function sort of as modules.
For example, a title menu state,
a game state, a game over state.
Rather than put all this logic in our
main.lua and bloat it unnecessarily,
we can divide it up using a state
machine class which we'll implement
from scratch here a little bit.
And this is a very effective
technique for modularizing your code.
And you can use it to not only
create states for the game itself,
but for individual
entities within the game.
For example, an entity might be in
an attack state, or an idle state,
or a moving state, anything
that you can think of.
If you can create a state
for it and piece it out,
it makes your life a lot
easier when you're dealing
with the actual editing of the code.
And you can let other people, therefore,
edit different states independently.
[? BevIgnite is ?] saying,
yeah, he's in the CS50-W course,
the web course right now, on hold,
concentrating on algorithms course
of Princeton, taking on a massive load.
KirbytheDonkey says, what
language are you using?
This is in Lua.
So Lua is a scripting language.
We are using Lua and
also LOVE 2D, which is
a framework and sort of an engine
for developing 2D games using Lua.
Lua is the language, LOVE 2D is the
actual framework, the functions,
and the runtime that allows us to
do all of the things that we can,
all the graphics, and sound,
and input, and all that stuff.
If you haven't downloaded it already,
go ahead and go to love2D.org
and grab the version most appropriate
for your operating system.
And also, watch the-- if
you haven't seen this,
for the first part of
the stream, you might
want to go back and look at part one
because this is a continuation of that.
And we're using that code
and sort of building on it
and adding the new features.
But that's on our Twitch
channel and our YouTube.
You can look up CS50 on
Twitch Space Invaders.
You can go on our Twitch
[? VODs ?] and pull it up there.
But we have all of the stuff that we did
in November of last year of the Space
Invaders is part one on GitHub,
the Space Invaders stream,
so you can clone that code and
also add the stuff that we're going
to be talking about today to that.
And TuxMan29 also said the same thing.
Andre says, Unity's animator controller
is essentially a state machine,
although it does allow blending
animation so not strictly
a discrete state machine.
Yes.
And Andre [INAUDIBLE] actually
made a clone of a game
that I created for our games course
using those animator controllers.
If I'm not mistaken, you used those
to sort of replicate that in Unity 2D,
which was a really cool project.
I saw it on Facebook and I
believe I commented on it.
I'm not 100%.
I believe I commented on it,
definitely hearted it, or liked it,
one of the two.
But yeah.
So thanks everybody for joining.
If you have any questions,
definitely toss them in the chat.
Watch the prior stream if you're
not familiar with this stuff,
maybe before digging into this one.
On Thursday, we'll have--
sorry, on Friday, we'll have a
completely different kind of stream
with David.
David will be joining
us to talk about Docker.
So that'll be very interesting.
And then we'll get into
our routine again where
we have these weekly going forward,
although the schedule itself
is kind of in flux at the moment.
So the three goals, again,
to recap, get the ships
to move, to get the projectiles
to collide, and then
get the game to exist in multiple
different states, not just
a regular game state.
So the first thing that I probably
want to do is work on the aliens,
get the aliens to actually
move left and right.
And right now, if I
just rerun the game--
and again, for those unfamiliar with
this, I'm using VS code as my editor.
You can use whatever editor you want.
It has built in macro for running
LOVE 2D from the text editor itself.
You can hit command L or alt L on a
Windows machine in order to do it.
And I think on a Linux machine you
can possibly do something similar.
So I don't have to click and
drag my main.lua or my folder
onto a shortcut on my desktop,
or whatnot, or on my dock.
I can use a command L, run
the game, and it's super fast.
So I highly recommend VS code for
that reason for LOVE 2D development.
So currently, these are the ships.
These are the aliens in question.
And I want them to sort of
move from top to bottom--
top to bottom, left to right,
sort of back and forth,
not like a scan line,
strictly speaking, but kind
of like a bi-directional scan line
in that it will kind of go like this,
almost like a snake style
movement pattern, which
is how the function in the original
Space Invaders, if I'm not mistaken,
which is a project that we created
for the games course last spring,
2018 spring.
So essentially, what I want to do
is I just want these on a tick,
on sort of a frame, a time
interval, to move maybe
50 milliseconds, 100 milliseconds.
That might be too fast.
Maybe 300 or 400 milliseconds.
I want the ships to
move in little chunks.
And then once the far right alien has
touched the right side of the screen,
they should all come down and then
start going the other direction,
then head down and go the other
direction, and so on and so forth.
So I kind of want to,
in that case, check
to see-- we have kind
of a for loop to check
to see if the direction the
aliens are moving is right,
check the far right alien.
If the far right alien is
at the edge of the screen,
move everyone down, set
the direction to left,
and then do the reverse
logic when the aliens
reach the left side of the screen.
Check to see if the far
left alien is at pixel zero.
If it is, bump them all down and
then set the direction back to right.
Andre says, yep, exactly.
We used your course as
assets for [? breakout ?]
and use the animator controller to
actually control the game's states.
Yeah, I thought that was a
super cool re-implementation
of that project from the game's course.
So thanks for doing that.
If you have a link to it or to
the medium article you guys wrote
about it, definitely
tossed that into the chat
so everyone else can take a look at it.
But yeah, so let's go ahead--
I haven't done too much
review of the code base
since we last looked at everything.
So let's just go ahead and take a look
at what we have going for us here.
We have the ship class.
So the ship class was what we use
to actually control the player,
and it just moves left and right.
So that's why we have an if,
else/if for moving left and right.
If love.keyboard is down
left, then say your Rself.x
to be no farther left than
pixel zero using math.max.
If love.keyboard is down right,
do the opposite with math.min,
making sure it doesn't go past the right
edge of the screen minus the ship's
width.
Alien size is the constant
that we use for that.
And then if we press the
space key on our keyboard,
notice that here we're
doing table.insert.
Remember, in Lua, table.insert is how
we add something to an array or a table.
They're all tables, but we can sort of
conceptually think of them as arrays
or hash maps depending on
the particular use case.
In this case, table.insert is
going to insert into something
that we've called projectiles which
we pass into ship update up here.
Projectiles is being used as an
array, I guess more like a python list
than an array, like a
c-array, technically speaking.
Because when used like this, when using
table.insert, it will dynamically grow,
but arrays don't dynamically grow,
at least in C, most static languages.
So in this case, we're
using it as a list
and we're just creating a new projectile
using this constructor here, projectile
taking in our self.x and
self.y minus projectile length
so that it spawns at the top of
the ship, at the ship's position.
And then notice that we're
passing in a string up, which
is just how we decide we want
to tell this constructor which
direction that projectile should go.
Should it go up?
Should it go down?
You can use strings.
You can use integers.
You can use constants that you have
defined to be strings or integers,
however you want.
I like the readability of strings.
And it's actually fairly--
what's the word I'm looking for?
There's a word I'm looking for to
describe whether something is often
adopted in a particular
ecosystem or community.
Conventional.
That's what I'm looking for.
I can't think of words too well.
It's conventional in
the LOVE 2D scene to use
strings that tell the
function or the constructor
how something should operate.
For example, love.graphics.rectangle
is a function that will create--
will draw a rectangle on the screen.
And the first argument of
love.graphics.rectangle, like so,
is a string that can
be either fill or line.
And so that's an example of using
a string in the LOVE 2D ecosystem.
And that's why I tend to like to
do that sort of thing as well.
Oh, yeah.
Andre's saying [INAUDIBLE] using
the animator controller state
machine on medium and
posted the link in the chat
there as well as they GitHub link
for the project, so check that out.
If you're interested in Unity
and interested in state machines
and breakout, which is a game that
we implemented in the games course.
Definitely check that out.
It was super cool when I saw--
Unity is a really awesome really
awesome editor, really awesome tool.
So this is the ship class.
So this is where we create our--
and really, our ship class is
almost completely done at this point.
We will need to check to see
if a bullet collides with us.
But we can do that in our main.Lua.
And if it does collide with us, then
we want to essentially lose a life
and go to the--
we can make a simple version
and go to straight to a game
over if we get hit one time,
but in actual Space Invaders,
you have multiple lives.
And so what we want to do in
a fully fleshed out version
of this would be to go to a
separate screen that just says,
you have two lives left, or three
lives left, or whatever, current score,
and then after maybe
three seconds, go back
to the game, which is kind
of how the actual game works.
The projectile class is here.
The projectile class is
pretty straightforward.
It takes in a xy and a direction, which
we just looked at from the ship class.
So self.x is x, self.y is y.
Pretty standard setting member
variables of this class.
If you're unfamiliar with
object-oriented programming,
you can think of a class as a blueprint
for how an object should behave,
what parameters it should
take, the functions that
define how it should operate.
It's fairly common in
games to have classes
that all have an update and a render
function so that in your main.Lua,
or wherever your main
game loop is, you can just
iterate over every object in your game
and call update in the update loop,
and call render in the draw loop.
And if we go to
main.Lua, for example, we
can see we do have a
update function here.
Functionlove.update takes
a delta time parameter, DT.
This is provided by LOVE 2D.
And in this, we can see
that we do call ship
update, which is from our ship class.
We call a for loop on all our
projectiles, and on each projectile
we call update.
And then we do something here where we
reset a global table for keyboard input
so that we can test for discrete
keyboard input outside of main.Lua.
And if you're unfamiliar with
all of this stuff, a lot of it
is explained in much more detail
in the first part of the stream.
This is not an advanced stream, I would
say, but a more intermediate level.
Certainly if you're
going into this blind,
I would check out the first
stream a little bit more.
It'll go on YouTube if you
want to watch the first stream
and come back to it later.
So definitely feel free to do that.
But paired with that update
function, very importantly,
is our draw function, love.draw.
And these are functions that LOVE
expects to exist in your main.Lua file.
And note that we are indeed
looking at our main.Lua file.
This is the main.Lua
file, hence the name,
that LOVE 2D looks for
when it runs your game.
So this is sort of the starting
point, just like in a C program
where you're looking for the
main function or a Java program,
where looking for the main
function in a class, or even Python
if you're running a module and
you have if name is equal main.
The same idea.
Main.Lua is that starting point and
it expects a certain set of functions
to exist.
And if you don't define them,
your game will not run them,
and therefore, it will not
function like you want to function.
Love.draw is a function
that just is supposed
to take care of all the
drawing stuff to the screen.
So everything updates first and then
everything draws to the screen, right?
So you move your ships,
you move your projectiles,
you figure out if things have
collided with each other,
and then you draw them to the screen,
or you don't draw them if they've been,
for example, removed from the scene
because maybe your ship got blown up,
or an alien got blown up, or
a projectile is off screen.
You don't draw anymore, right?
So here we are calling push start and
push finish at the start and end, which
just gives us a virtual resolution.
Again, we cover that in the
first part of the stream.
We clear the screen.
We do a loop over all the projectiles
just like we did in update,
but in this case we're
calling render on all of them.
And render's job on each
class that we define
it is just to draw whatever
class it belongs to,
draw that information onto the screen.
In this case, we're
setting our color to red
or using a rectangle, again,
love.graphics.rectangle,
which we just looked at.
Takes in a fill string, x and y.
This one is a--
I'm trying to remember.
That's the width in pixels, so we want
a one pixel wide rectangle and then
a projectile length y length,
height, of our rectangle.
[INAUDIBLE] width and height.
And I guess one should probably be
projectile width as well, a constant,
but since it's just one, I guess I
thought one would be good enough.
Nuwanda3333, that's [? Aslee. ?]
Says, hi Colton and everyone.
Thanks for joining us,
[? Aslee. ?] Good to you again.
Happy new year.
And here, we see that we're
setting our color to 1111
after we finish drawing the rectangle,
and that's to revert our color--
our sort of global state for LOVE 2D.
It always has one color to
draw stuff with at a time.
We set it to red here.
We draw the rectangle.
And then, on the line
after the rectangle,
we set it all ones, which is all white.
So the newer LOVE 2D versions, LOVE 11
and onwards, they use from zero to one
to create colors.
You might be familiar with
RGBA, which takes in--
well, RGBA is what we're using, but you
might be familiar with 256 valued RGBA.
So start at zero, end at 255.
That's how LOVE 2D used to do colors.
So in the old version, you
would say 255, 00, 255.
And that would be the same thing
as the current version of 1001.
But in version 11, they changed it all
from zero to one floating point values.
So I could say, 0.1, I could say 0.15.
It's more efficient.
I have to dig I'd have to
look back at the reason
as to why exactly they had to do it.
From what I remember reading
about, it's a performance reason,
and it also helps with shaders.
And underneath the hood,
it's just more performance.
But offhand, I don't
have the exact reason.
People are joking in the
chat about bringing me
pizza from an old sort of stream
we had, where [? Aslee ?] kindly
offered to get pizza for
everybody, which I'm not
sure of that offer still stands.
Hopefully it does.
Also, I think we were
looking at projectile.
Earlier, before we
went back to main.Lua,
the projectile update function
is just an if-else statement
that basically says, if
our direction is up, then
every time we update we want
to essentially decrement our y
position, which is what we do here.
So if that y equals up to y minus
projectile speed times delta time--
again, anytime you need to
update something in a game,
or at least in LOVE 2D--
typically, in most games--
in order for it to run at
a consistent frame rate,
every time you move something or
do something that can be variable,
you want to multiply it
by some constant value--
some value that's dependent on your
frame rate-- in this case, delta time.
Delta time will be different
depending on how many frames
have elapsed since the last
frame, whether it's usually one.
Sometimes it can be two or three.
It's some floating point value
that you can scale movement
by so that two computers at
different speeds will run your game,
and things will move
at the same speed, even
if one is more jittery than
the other one, essentially.
And that's why we are multiplying
projectile speed times delta time.
So that's the projectile
class, the ship class.
The alien class is empty.
The entity class is fairly empty.
This is the base class
from which we inherit--
the base class which we
superclass the alien class from.
So we notice in this particular
module, alien gets class.
This is the library we're using to use
object-oriented programming in Lua.
It's not something that
you get by default.
Again, I apologize if all this
is a little too much review.
I just want to make sure
we're all on the same footing,
and this also kind of helps me remember
the code base a little bit more.
But alien is a new class, and it
includes everything that's an entity.
And this is part of the class
library that we used last stream.
So includes entity,
and this is the class
from which we want to take all the
stuff that exists in that class
and create a new class from it.
So entity is this class.
So aliens all have this
constructor, xy sprite.
And they also have this render function,
which draws from our global textures
variable in our dependencies.
This is dependencies.Lua and
it has all of our libraries
here, using require statements.
It also has a global
textures table here,
so we can store all of our
textures-- all of our 2D images.
And then, we can store our frames, so
all of our actual sub-images on those
textures.
And we talked about this
last stream, as well.
In this case, we're just using
an image in our graphics folder.
[? SewerTon ?] says, I've
just downloaded the 2D engine.
What was the name of the
editor you were using?
I know you mentioned it, but
sorry, I forgot the name.
No worries, [? SewerTon. ?]
It's VS Code--
so Visual Studio Code.
It's a very popular Microsoft editor.
You can get it by going
to Code.VisualStudio.com.
Super awesome text editor.
You can also use Atom.
You can also use Sublime Text.
They all sort of have the same feature
set, but different plugins, often.
I haven't used Atom in a little while.
I imagine they'd probably have
something similar to the plugin
that I'm using for Atom.
If you want that plugin,
it's the pixel byte plugin.
So if you go if you're in Visual Studio
Code and you go to the extensions area,
get the LOVE 2D support
plugin by Pixel Byte Studios.
Awesome plugin, super recommended if
you're doing any LOVE 2D development.
Let's go back to here.
Click there.
So this is our dependencies file--
stores all your dependencies, all
your libraries, all your textures,
all your sounds, all your frames--
everything that you want--
all your assets and libraries.
I like to put them in
a dependencies file,
but you could also just put this all in
your main.Lua and it would be totally
fine--
not what I would recommend, because
then it gets a little bit bloated.
I try to keep my main.Lua fairly clean.
So notice it only has, at the
top, require source dependencies,
which basically imports all that stuff
that we defined in dependencies.Lua.
And then, we just have
all the core functions
that we need for our game
engine, all the core loops,
and then we defer all
the actual logic as much
as we can to the classes themselves--
the projectile class, the ship
class, the alien class,
entity class-- all that stuff.
And our main.Lua file ends up being
currently less than 100 lines of code.
It will probably be a little bit more
than that eventually-- hopefully not
too much.
In our main.Lua, the actual
aliens themselves get
generated in our LOVE.load function.
So LOVE.load is a function
that exists at the beginning--
it doesn't have to exist in the
beginning of the file, per se,
but it triggers at the beginning of
your LOVE 2D application running.
So before you start updating, before you
start drawing and checking for input,
you'll call LOVE.load.
LOVE 2D will call it for you.
You have to define it.
But this is all the stuff that you
want to happen before you update,
before you start drawing all that stuff.
In this case, we do a
bunch of initialization.
We set our filter to
nearest, nearest, which
allows everything to be very pixelated.
And actually this, I realized,
needs to go in dependencies.Lua
before we actually import any graphics.
So what I did was, I
took that LOVE.graphics--
that's that default filter
function-- and I put it
before we load any textures, because
on certain computers, what this will do
is, even though you call a
default filter nearest, nearest,
we were doing it technically
after the texture was loaded.
And as a result, certain computers
will show the aliens as being blurry.
So put this before we actually load
the textures to prevent that issue.
And I took it right out of
my LOVE.load function, here.
There will be no change on my
computer, because for some reason
it works fine on my computer.
The nearest thing
doesn't cause any issues.
It might be a result of the
push library overriding that.
But on certain computers, if you're
using a newer version of the push
library, or your graphics card is
funky, it will look kind of blurry.
So if that happens,
just do what I just did.
OK.
The moment you toss
pineapple on a pizza,
it becomes a sandwich and not
a pizza, says MKloppenburg.
We've got a war on pizza
in the Twitch chat.
Yeah, a long pineapple pizza
conversation going on-- which is good
stuff.
DanCoffeeCCD is Dan Coffee
who did a stream last year
on Draw 50, which is an awesome tool
which we actually used, I think,
last Space Invaders stream.
Is a huge fan of pepperoni pineapple,
so if Dan's looking in the chat,
shout-outs to Dan and his
pepperoni pineapple pizza.
I'm not sure if that's been mentioned--
if pepperoni pineapple specifically
was mentioned.
I think just pineapple and
pizza generally were mentioned.
So anyways, back to LOVE.load, which was
where we initialized all of our aliens.
We also initialized our ship.
And actually, shout-out [? Aslee ?] and
Bhavik, who are in the chat right now.
We, last stream, ended
up having a mini war
on which ship we wanted to
use at the start of the game--
the player ship.
So I think if I run this over and
over again, we see right here,
this little ship here--
this little red ship
is going to potentially change to a
another ship if I keep running it.
It's random.
Yeah, so right here.
So I don't remember offhand
which one [? Aslee ?] chose
and which one Bhavik chose, but
they both had a different ship
that they wanted to use, and we
ended up using a little math.random
two, which just gives you
value between one and two.
And basically, used a ternary--
Lua's ternary statement,
which is an and-or--
well, a conditional and
then an and and an or.
And then, what we ended
up doing was, the game
will randomly choose between
[? Aslee's ?] ship and Bhavik's ship.
And their ships are just frame indices.
So if we look at our file here--
let me go into Space
Invaders, graphics, aliens.
I think was aliens six--
no, it was aliens 12 I believe.
I'm not 100%.
I have to re-look and see.
Was it 12?
It might have been.
Oh yeah, it is 12, because
I recognize the ship here.
Essentially, all of these individual
ships on this one texture--
this one 2D image--
you can split this image
up into rectangles,
and it's called using a sprite sheet.
In LOVE 2D, they're called quads.
They're rectangles that define
a sub-image on a 2D image.
And what we can do is, we can assign
each of these an index between one
and however many ships there are,
counting top to bottom, left to right.
And by drawing our 2D image in LOVE
2D, and then referencing a quad,
we can draw that sub-image.
And all the quads are-- we
split it up by 12 by 12 pixels
and then put them into a table,
so a one-dimensional table.
And so therefore, it's from one to--
I think this is 1, 2, 3, 4, 5, 6, 7, 8.
It's 16 by 16, 256 ships long.
So between one and 256.
By sending a particular index and
then drawing LOVE.graphics.draw,
we can draw a particular sub-image.
And so that's what we're doing here.
We're getting an index.
So we calculated the number.
We figured out what row it was on--
16 times three, and then the
sixth one over is 16 times 12.
And then, the ninth one over.
So third row and then the 12th row.
And that's effectively how we did that.
That's what that bit
of code is there fore.
We instantiate a ship.
We put it in the middle
of the screen, which
is what this logic here does--
virtual width divided by two,
minus alien size divided by two.
Alien size is, I believe, 12 pixels.
All of the capitalized
things are in constants.Lua.
Excuse me.
Constants.Lua is a good
place to store constants.
Constants are just variables
that should not change.
Lua does not enforce using constants,
so you have to kind of use programmer,
I guess, heuristics in order
to use constants appropriately.
In this case, we do
that by capitalizing--
as is conventional amongst
many programming languages--
the variable name, and
using underscore notation.
So anything that we see that
follows this variable naming
convention we can assume is a constant,
and should not be assigned a value,
only used read-only in our code.
In this case, alien size is a constant.
It is 12 pixels.
Virtual width and virtual height are the
dimensions of our virtual resolutions--
so how we make our screen look as
if it's retro, even though it's not.
And there are some other ones
here-- projectile length.
We don't have a projectile
height, but just for good measure,
projectile height is one.
And then, we can use that in the
future when we reference that.
Yeah, Colton being very
diplomatic about the ships.
It's very important that we
embrace everybody's opinions.
Well, he multiplied it in a second.
Someone brushed on math
during the holidays.
Oh yeah, because last year I
was not able to, on the fly,
figure out what 16 times 16 was.
And I was made fun of for it.
It's 256-- something that most
programmers should probably know,
and I was not included amongst that
category of programmers, unfortunately.
OK, so I think we've done a rough recap.
The last thing, I think,
that we need to talk about
is the actual loop here
that initializes the aliens.
In this case, alien is just an
inherited version of entities.
So it's not really that
different from an entity.
It's actually pretty empty class.
And what we're doing here
is, we're instantiating it
at an xy with this four loop--
aliens tall, aliens wide.
That's how many aliens
fit on the screen.
Sorry, that's not how many
aliens fit on the screen.
That's how many aliens we
want in our set of aliens.
We're doing a nested loop, just
kind of like how we do Mario, for y
is one to aliens tall,
which aliens tall is--
we decided will be five.
So we want five by 11 aliens.
We do that in an yx loop, and then we
insert an alien into our aliens table.
Aliens just another table,
just like projectiles, sort
of stored the same way or
functioning the same way
and that it's one-dimensional
and that we just iterate over it.
Each alien maintains a reference
to its own xy position.
And alien is a class, which
takes in an xy and a frame.
So frame is the number between 1 and
256 that maps to our 2D texture--
all of our quads, right?
And our quads, again, are this G frames
table on line 22 in dependencies.Lua.
On line 23, we can see
we're assigning aliens--
the index string aliens to
the result of a generate quads
function, which we wrote last week--
not last week.
It feels like last week--
which we wrote last November.
If we go to util.Lua.
That's where our generate
quads function is.
It just splits up a
texture into quads based
on how wide and tall each sprite is.
In this case, the sprites
are 12 pixels by 12 pixels.
[MUSIC PLAYING]
So that's how we split it up.
Curehavenomana, thank you
very much for following.
Hope I pronounced that right.
I imagine I did pronounce that right.
And [? 08Rock, ?] if I
missed you, I apologize.
Thank you very much
for following, as well.
All right, so I think
that's a fairly solid recap.
I apologize if that was a little
bit cumbersome for everybody
if you're already familiar.
If you're brand new to the
stream or brand new to this game,
then that might be of some help.
Certainly, I need it getting
back into the code base.
But I think we have
enough information now
to where we can decide how we want to
go about moving our sprites, right?
So the first thing that we
can do is, in our main.Lua,
we do have access to the--
we have access to all the aliens, right?
So what I can do is-- and we probably
want this to be in our update function.
So the first thing that we can probably
do-- well, first, first thing that we
should do is, we're going
to need a variable that
keeps track of which direction
the aliens are moving in, right?
Because they can move to the
right, they can move to the left--
your right, my left.
To the right to the left.
So what we can do is, I can
just call another variable.
And ideally, this will all be sort
of encapsulated within a game state.
And we'll talk about game
states a little bit later.
And maybe even in future streams, we'll
implement things from a more state
machine-focused approach
from the beginning,
as opposed to this sort of ad
hoc everything in main approach.
But we kind of have to work our
way up there, I feel like it,
as it might be a little bit too much
to jump into that right off the bat.
But what I want to do is, I
want to create a variable called
alien direction.
And I'm just going to assign
it a string called right.
And again, using strings as the
way of classifying our variables,
because strings kind of make sense.
Oh, they're talking about the sound.
MKloppenburg, and Nwanda, and
Andrej says, the same song
from Khan Academy for following?
Yeah, it's-- I had thought that we had
used that follow sound for a long time.
It may be the case that
you all didn't hear it
prior to this stream
when we reconfigured
our audio setup a little bit.
But yeah, that's just the
follow sound that OBS,
or I guess Twitch lets
you have in your channel
every time someone follows or
subscribers, all that sort of thing.
So it's a nice little sound effect.
I like it.
When I hear it, I have
monitors back here,
so I know that somebody has followed.
So I can look up at the
screen and see and thank them.
So that's pretty great.
So we have a variable
called alien direction
that we've declared in main.Lua,
which is going to be the direction
that the aliens move each frame until
they reach the end of the screen.
And then, it should get
set to the other direction.
When it gets set to the direction,
they should move to the other side,
check to see if they've reached
that end, and so on and so forth.
And then, when the do reach either
end, they should all move down.
They should get closer to the player.
That's how the game works-- the
actual Space Invaders game works.
So I can say if alien
direction is equal to right--
again, just comparing it
directly to the string right,
because that's going to
equal either left or right.
Excuse me.
And I'm going to preemptively
say an else here.
It can only be right
or left, so we don't
need any we don't need
another conditional statement.
We can just say else.
What I want to do is, I want to
set every single alien's position
to the right just a little bit.
Well, I should say, I don't
want this to happen every frame.
I want to actually be on a timer.
So what I should do is--
and do I want this to be
using a timer library?
Probably not.
Probably not yet.
That might be a little bit too much.
So I'm going to say, alien
movement timer is zero.
And then, I'm going to
say, in our constants--
I don't have constants open.
Constants.Lua, I'm going to
set a alien movement interval.
[MUSIC PLAYING]
That startled me a little bit.
Djoker07, thank you for following.
That name looks familiar.
I thought that Djoker was already
falling, but if not, I apologize.
Alien movement interval, I'm
going to set that to be-- this
is in milliseconds.
Is it in milliseconds?
No, I think LOVE 2D measures
DT in fractional seconds.
So let's say I want the aliens
to move every 0.4 seconds.
And again, this is a constant.
This isn't going to change.
I could make this a
variable that does change
if I wanted the aliens to
move faster the closer they
get to the player, which is kind
of, I think, how it works in--
no, that's not how it
works in Centipede.
I don't think so.
I think it just looks like
they're moving faster.
I don't remember offhand.
But basically, it's going to
be a constant in this case.
And the timer itself
is going to keep track
of how much time has passed each frame.
And we're going to add to it.
And once alien movement timer is greater
than alien movement interval, which
means 0.4 seconds have elapsed, then I
should move all the aliens to the right
or to the left, do the condition
to check and see if they've reached
the edge of the screen.
And then, I'm going to reset
the timer back to zero,
or at least back to timer minus
alien movement interval, in case
they went over 0.4 just
a little bit, right?
And shout out to David in the chat.
He says, thanks for tuning
in today, everybody.
Thanks so much for tuning in, as well.
Djoker07 says, happy new year, Colton.
Thanks, Djoker, very much appreciate it.
Everybody's talking about
their amount of RAM--
16 gigs.
If it's in idle, it won't take
any RAM, says OneDayMyLove.
I might be lost in the
conversation there.
Oh, two instances of unity, open,
plus one instance of blender.
And everybody's saying hello to David.
Awesome.
Cool, cool.
So we have a timer now.
We can keep track of how much time
has elapsed since the last frame.
We can cumulatively add this until
we've gone past that amount of time.
So what I'm going to do is,
before I actually-- well,
I'm going to keep the
if statement there,
but it's actually going to be
nested within my other if statement.
I can say, alien movement timer gets
a movement timer, plus DT, right?
Remember, DT is a variable that we get
every frame passed into LOVE.update
by the LOVE 2D framework.
And this is going to be
measured in fractional seconds.
So it's going to be 0.013, 0.0013.
I forget offhand.
I think it's 0.013.
And if alien movement timer is
greater than or equal to the constant
that we've defined, which is
alien movement interval, then--
and this is where we nest that bit.
So I say, if alien direction is equal to
right-- so they're going to the right.
What we need to do is, we need
to increase every single alien's
x position, right?
So that means we need a loop.
So I can say, four_alien in pairs,
aliens do alien.x is equal to alien.x
plus--
and then we have to figure out how
much we want to move the alien by.
So I guess we can say, alien
size, alien step length.
We'll call it that, alien step length.
And we'll say that's
going to be five pixels.
I'm not entirely sure.
A lot of this you're going
to have to kind of do by
feel, too, especially
if you're doing things
that are based on time when you're
moving stuff, getting speeds right.
It's a little tricky to kind of guess
everything correctly right off the bat
unless you know your specs in advance.
I don't know the specs advance,
so I'm just going to take a guess.
And then, we can fine tune
it as we need to go along.
And this has a role to
play in game balancing,
because this movement of the alien x--
if it's too small, the
game will be too easy,
because the aliens will take a long
time to get to the bottom of the screen.
If it's too big, the aliens will get to
the bottom of the screen really fast,
and as a result, it'll be harder for the
player to defeat all of them in time.
So we need to sort of keep this in
mind as we're balancing the game
out-- tweak it, play test
it, and get a sense of it.
This is part of the QA
testing side of making a game.
So we're going to say,
alien step length.
So we have alien.x is equal to
alien.x plus alien step length.
In this pairs function, again, is
how you iterate over tables in Lua.
So it takes in a table,
in this case aliens,
and will give you every single
alien, every single key,
and every single object within that--
every key value within that table.
In this case, all the values
are aliens-- alien objects.
So we do that here.
And then, we do the same
exact thing here, right?
Except instead of setting the alien x
equal to alien x plus the step length,
we want to minus the step
length so they move to the left
instead of to the right.
And again, it's mirrored to you to.
To the left, to the right, but I
have to think of it the opposite way.
I can't see that name.
It's in black text.
WinLogon, happy to be
here, taking 650 GD at edX.
Awesome.
Thank you so much.
Well, glad to have you with us.
WinLogon?
WinLogon?
I hope I'm pronouncing that correctly.
I'm probably not.
Colton's saying, cool.
Colton's sounding Jake Peralta.
I'm not entirely sure that is.
I should look that up.
Cool.
So this is how we move
the aliens on a timer.
And then, once we have
moved all the aliens,
we want to set alien movement timer
to alien movement timer minus--
what's it called?
The alien movement interval.
That way, in case we do go over
0.4 to, like, 0.41, or something
similar to that, it'll take
that into consideration--
that overlap into consideration,
and we won't kind of
get extra time added
to our loop, which is
what you do if you just set it to zero
and don't account for that overlap.
Alien movement timer gets alien movement
timer minus alien movement interval.
That's great.
So that'll roughly be zero, if not
0.01, 0.001, or something like that.
And that will move the aliens
to the right and to the left.
Now, if we run this, it should work.
They'll move.
So this is roughly what we
want things to look like.
It might be a little fast.
But notice that when they go to
the right edge of the screen,
they just keep going forever, because
we haven't actually implemented
checking for the edge, right?
So that's something we want to do.
We want to say, if
the far right alien is
at the right edge of the screen,
then downshift everything down
by a few pixels--
maybe even the step
length, we could do that.
Or maybe step height, maybe
we want to define step height
as a separate constant.
So I can say step height.
And maybe we want that to be
eight pixels, a little bit more
than the step width.
They're moving a little fast, so
I think the interval should maybe
be 0.5 seconds.
We'll see if that's any better.
That should be fine.
And this is a lot up to your
tastes more than anything,
so you kind of want to tweak it
depending on what you like best.
So we've done that.
We've got the aliens moving.
Now, we need to actually
figure out when the aliens are
at the edges of the screen.
And for that, what we
can do is, we're just
going to assume that all the aliens are
sort of-- even if they are destroyed,
they're there.
So we can always test
the far right alien
and we can always test the first alien
to be our left and right edges, right?
So our alien, or rather
our far right alien,
is 1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11-- our 11th alien.
So we can just kind of know
our 11th alien will always
be where we can indicate the
right side of the screen.
So I'm going to say, if aliens at 111-
so because we added to our table--
actually, I'm going to
use best practices here.
I'm not going to use 11, I'm going to
use far right alien and far left alien.
And aliens wide is also the same
index as far right alien in this case,
but we're going to separate them just
so that our code is more readable.
This is important.
You want to make variables that are
readable so we can, at a glance,
know that we're checking
the far right alien here.
We're not checking some random
11, which doesn't make any sense.
I'm just reading it offhand, if you're
not familiar with the code base.
So if aliens far right alien.x
is greater than or equal to--
and this is where we want
to check to see if it's
at the right edge of the screen, right?
So we're moving right.
So I want to say, if aliens far right
alien.x is greater than or equal
to virtual width--
that's the far right
edge of the screen--
minus alien size, and
minus alien size because we
want it to be when the right
edge of the alien touches
virtual width, not the left side of it.
Remember, everything in LOVE 2D is
based on its top left coordinate.
So if we check for just
virtual width, the alien
will get all the way to the right side--
like, past the right side of the screen
before it actually checks the condition.
We want it to check when the right edge
of it touches virtual width, right?
So I'm going to say, if aliens far
alien.x is greater than or equal
to virtual width minus alien size--
if the alien is right at the right
at the right edge of the screen,
this is where we want to
set the direction-- which
the variable is up here.
It's alien direction.
We're going to say, alien
direction is equal to left.
It's no longer right.
We're no longer moving to the right.
Now, on the next frame, this is going to
be false and this is going to be true.
And it's going to tick all
the aliens to the left.
And this process will
continue-- assuming
that we fleshed out
this if statement here,
this else statement-- this
will continue infinitely.
So what we can do is then say,
alien direction is equal to left,
and this will have the result of--
it could be possible that
the aliens move slightly
past the right edge of the
screen and we need a bit of logic
to sort of reverse that.
But we can solve that.
I guess it'd be fairly easy just to
figure out what that difference is,
and then subtract that
from every alien's x.
But we won't worry about that yet.
It's not super important.
What we're going to do first is just
get the loop working infinitely.
So in this case, now, we
want this to be right.
So in the else statement, so
when they're moving to the left,
we're going to-- we still have
it decrementing their position,
but now we don't want far
right alien to be tested for.
We want far left alien to be tested.
And we want it to be less
than or equal to zero,
which is the left edge of the screen,
not virtual width minus alien size.
Now, we're checking to see if
the left side of the left alien--
the leftmost alien, the first alien--
is right at the left edge.
So if aliens far aliens.x is less than
or equal to zero, than alien direction
is equal to right.
So then we're going to
go back to the right.
And we're missing one
last step, and that is, we
want every alien to move
down when this happens.
So what we can say is,
again, another loop.
4_alien in pairs of aliens, do
alien.y equals alien.y plus--
and then we just defined it--
alien step height here.
And then, I want to bring this same
exact code over here, just like that.
So if everything is according
to plan and my logic is correct,
when these aliens reach the
far right side of the screen,
they should bump down.
Let's test and see.
Yeah, it looks like it jerked a
little bit to the right there a little
funkily.
So yeah, that's the--
am I changing all of their
things to the right size?
I might want to do greater than,
not greater than or equal to,
so that it doesn't do that
weird sort of diagonal jump.
Let's see if that fixes it.
No, I don't like how that looks.
It looks a little bit weird.
This might be acceptable for
now, just in the sake of time.
6, 7, 8, 9, 10, 11,
12, 13, 14, 15, boom.
Yeah.
Another thing you could do is, you
could count the number of steps
that are ideal between each--
oh actually, do we want to do--
if we do this logic before the aliens
move to the left and the right,
it might look a little bit better.
And then, do an else, like
that, and indent that.
So this way, the aliens will move all
the way, and then set the direction,
and then skip that iteration so
that it looks a little bit more
like the original game.
Let's see if my logic is correct.
If not, I'll just revert it
and we'll accept it for now.
Mechanically, it needs to
be adjusted a little bit.
But the logic works, it
just doesn't look perfect.
But I worry if we spend too much time,
because we're already an hour in,
and we have collision and
game states to worry about,
we might not have enough
time for a three-hour stream.
But we'll see.
So let me go ahead and
do what I just did again.
OK.
Else, indent this, end.
Let's try and see what this looks like.
Hopefully this looks acceptable.
If not, I'll have to just revert.
Oh, that looks great.
OK.
Does it look OK on the left side too?
It's OK.
It could be a little bit better.
I feel like I need to maybe
change the left side so that it's
checking for, like, two
pixels instead of zero,
because it goes a little
bit past the left edge--
yeah like, four pixels.
So if I do single to four,
see if that fixes it.
While that's going, I will read
the chat, or at least try to.
You ever heard of [? byte ?]
paths, says OneDayMyLove?
I'm not familiar with what that is.
Oh, that looks great.
OK, now let's see, since now
it's going off of four pixels,
see if it screws up the right side too.
Hopefully not.
Nope, that's excellent OK, cool.
This looks awesome.
So let me read through the chat here.
So I missed OneDayMyLove
saying, have you ever
heard of [? byte path? ?] I'm
not familiar with what that is.
I apologize.
Bhavik Knight says, maybe we can make
it variable and make levels in the game.
With level up, we can increase
the speed by some amount.
Yeah, we could do that.
I recommend definitely taking it and
trying to do that yourself, if you can.
I don't think we'll have time.
I don't think I'll have time
to implement that on stream
today with the other stuff
that we want to implement,
but it's a great exercise, certainly.
And I think we've--
I mean, we've done something
similar to that before with Snake.
I think we did levels and stuff.
[? ISOTV-- ?] hello,
everyone, happy new year.
Sorry I'm late.
I was on vacation until today.
Did I miss anything special?
So good to see you [? ISO. ?]
Nothing too special.
We did a review.
I imagine you probably saw the
last Space Invaders stream,
I don't remember offhand.
If you did, then most of what we
talked about for the first hour
was review-- or not
first hour, 40 minutes
or so was review of the
code base, figuring out
what the next steps are, kind
of saying hello, happy new year.
Now, we've actually for
the last 20 or 30 minutes
got into the meat of the game.
We've added aliens moving,
so the next few steps
are to be collision
detection with bullets,
make aliens shoot, that sort of thing.
Yeah, and game states.
That'll be another thing, yes.
[? Suraton ?] kindly said, talking
about what we implemented last year.
Correct.
[? Aslee ?] says, why
is it doing that jump?
The logic was a little bit screwy.
It was checking to see if--
it was basically moving
before it checked every time,
and so it would move in a weird
position and then jump down.
And the logic was slightly messed up.
But we fixed it.
Everything's good.
So now it actually does the check
before it adds x, or subtracts x,
from the aliens.
And it does it with an if-else, so it
won't do them both in the same turn.
So we do get block
movement on the frames--
or not the frames, but on the part
of the interval where we actually do
move the aliens down.
We don't move them down and
then move at the same time.
That was the core issue.
It wasn't in and if-else.
Now it's in an if-else,
so that problem is fixed.
Yeah, essentially what Bhavik said.
When it reaches either left or right
extreme, pause for a time step,
go down that time step.
That's what we did.
Yeah, it felt. Awesome.
Cool.
So aliens look good now.
The movement looks good.
The next step is probably
collision, right?
So I can move around.
I can move the ship around and I can
shoot, but it doesn't do anything.
It actually just goes straight through
the aliens through the other side.
And that's not ideal behavior.
So a couple couple of
things we want to do.
As we mentioned earlier, we want the
projectiles to be only around as long
as they're visible.
So when they've gone past
the top edge of the screen,
if they haven't hit an alien,
we want to get rid of them.
We want to get rid of
them if they hit an alien.
And if we hit an alien, we want not
only to get rid of the projectile,
but also to get rid of the alien itself.
So a few steps, a few pieces.
There's a long block of code, too--
58 to 86 to do the alien movement.
We could separate that out, I guess.
So you can call this, like, tick aliens.
And then go down to the bottom here.
Just modularize our
functions a little bit,
see if it still works, which it should.
So it's a good habit to make your update
and draw functions kind of as small
as you can.
In this case, we have this big block of
code that does something very specific.
So take aliens.
We just took it out,
made it its own function.
Now, our code looks a lot more readable.
We can say, OK, in our update
function, we update the ship,
we update projectiles.
We probably can put
this in there, as well.
And then, we tick the aliens.
So we move the aliens.
So boom, that's done.
Cool.
Essentially, just a copy-paste
out of the update function.
It's not better engineered,
necessarily, but it's easier to read.
It looks a little bit better.
It's more easy to keep
track of what's going on.
You're not navigating through
a monolithic code base
to figure out what the functions do.
When you have functions that
you can reference by name,
you can also reuse them in
multiple places if you wanted to,
and mix and match them--
puzzle pieces.
It's useful.
OK so now, collision detection
with our projectiles.
So the projectile class, here.
What we can do is probably
define a collides function.
We want a collides function that
works with every entity in the scene,
because we want to check to see--
I mean, essentially, we only
want projectiles and ships
to collide with stuff.
So we could just define
a collides function
on a projectile that takes in something
that has an xy width and height.
The aliens-- do the entity
have a width and a height?
No, they don't.
So in this case, we can probably set
self.width is equal to alien size.
Is alien size the one in--
alien size, yeah.
It's always going to be 12.
But this way, each object
has a width and a height now,
and we can use that in
our collides function.
So we can go to projectile.
I'm going to create a new
function called collides.
I'll do it before update and render.
And it takes in an entity.
And this entity is
assumed by this function
to have an xy width and a
height, which is why we just
added width and height to entity.
The ship and the alien will
all inherit from entity.
And therefore, they will all
have an xy width and a height.
So this is aa, bb collision detection.
I think we covered
this in a prior stream.
I forget offhand which stream it was.
Maybe somebody in the chat knows
which stream we covered it in.
I remember drawing it--
or at least I thought we did.
Pong, yep, there it is.
OK.
Did we cover that in 3D Pong?
Did we really do that?
I thought for 3D Pong we did
Unity's collision to systems,
so we didn't need to do aa, bb.
We cover aa, bb in the
games course for the G50,
but for stream I don't
remember if we covered it.
Essentially, check to see
whether two rectangles--
whether one rectangle's opposite
edge is past the other edge
of another rectangle.
That means that they
impossibly cannot collide,
and therefore it's a simple check.
And it assumes the rectangles
are on the same axis.
They're not rotated rectangles.
They're axis aligned, so completely
orthogonal-- is that the right word?
Perpendicular to the axes,
or orthogonal to the axes.
And therefore, it's a very inexpensive
operation and a very simple operation.
So I can say if self.x is--
and so we're checking the
left side of the rectangle.
So if the left side of this
rectangle-- this projectile--
is greater than entity.x plus entity.
width, then that means that
this rectangle's left side
is beyond the right edge
of another rectangle.
Therefore, it's impossible for them
to collide, essentially, right?
And we do that same logic for
all four sides of the rectangle.
And then, if any of these are
true, then there's no collision.
Otherwise, there's a collision.
So if self.x is greater than
entity.x plus entity.width,
or self.x plus self.width
is less than-- that's
the right edge of the projectile.
If it's less than the-- oh this
doesn't have a width and a height.
So we need that as well.
So this needs to have a width.
So projectile-- do I not have
projectile width and height defined?
Projectile height.
Oh, that's not projectile
height, that's width.
My bad.
I screwed up the
constant in dependencies.
I called it projectile height.
It should be projectile width--
width and length, which is fine.
So self.width is projectile width.
Self.height is projectile length.
So it's one and five,
respectively-- small rectangle.
If self.x is greater than
entity.x plus entity.width,
or self.x plus self.width
is less than entity.x, or--
fairly long if statement--
self.y is greater than
entity.y plus entity.height--
so if this is below
the other rectangle--
or self.y plus self.height
is less than entity.y--
above the rectangle-- then we want to
return false, so it does not collide.
Else, we return true.
Both of these.
For a long if statement, it gets
kind of obnoxiously formatted.
But that's essentially it.
Check to see if opposite
edges are beyond each other.
If they are, it's not a collision.
If not, it is a collision.
Cool.
Simple.
And now, we can, in our
main.Lua, say, for every alien--
or I guess for every
projectile, rather--
projectile update.
Now, the thing is, if we want
to update each projectile
so each projectile takes in--
it updates, right?
So it ticks.
It says, if the direction
is up, move it up.
Self.y minus projectile
speed times all the time.
And the same thing goes
for moving it down, right?
So we can update all projectiles.
And maybe ideally this exists in the
update function for a projectile,
but it would probably
be ideal to do that--
from within projectile test a
collision against each entity,
say, for each entity in the
scene do this, blah blah blah.
What we're going to do instead is,
we're going to do that in here.
So we're going to move every projectile.
And then, since we have
access to all the aliens
and the player ship within this
function here, since we're in main.Lua,
we're just going to check collision
here in another four loop.
So we're going to say, for _alien
in aliens, pairs aliens two.
So we've moved the projectile,
and now we're going to say, if--
I'm trying to think.
Because if we do that, we also
need to get rid of the projectile.
So we would have to set
that projectile to nil.
Hmm.
But we can worry about that in a second.
So if projectile
collides with alien, do--
and then, OK, so P key.
So projectile key, and A key.
So that's what we're doing.
We're saying P key and
A key, because we want
to have access to the key in the
table from which we are calling it.
We're going to set that key
to a nil value in the instance
that we collide the
projectile and the alien,
and then we're going to also
add an if nil check here.
This will allow us to set
that particular alien to nil,
as opposed to--
do I want to set it to nil, or do I
set it invisible thing so that we still
have width and height access?
No, we don't need to set it to nil.
Invisible-- probably want to set
an invisible flag on the alien.
Defeat it, set an invisible flag.
And then, once we set an invisible
flag, the alien will still exist.
We can still update its position.
It'll still be hidden.
Yeah, that should work.
JPGuy says, everybody and
Colton, long time no see.
Thanks, JPGuy for tuning in.
Glad to see you.
Make sure that I didn't miss anything.
Hope you had a nice holiday season.
Talked about pineapple
pizzas and mentioned to you.
The audacity, says JPGuy.
Does Lua have an interface
support like Java interface?
It'd be helpful in
class, like projectiles
shoot by either of ships or aliens.
No, there's not really
a notion of interfaces,
Bhavik Knight Really,
everything is very dynamic,
so nothing has to adhere to anything.
You kind of define whatever functions--
like, the best thing you could get
is just by using classes, honestly.
There's not really an interface.
It's not going to, at
runtime or compile time,
determine whether you're
calling a function that's
valid on a class that
implements a specific interface,
because it's dynamic.
It doesn't have that notion.
Good question, though.
New Year's is old news now, says JP.
Yeah, it's getting there.
It's like, what, is today
the seventh, eighth?
What is today?
The seventh.
OK, so back to the code here.
So we're declaring P key as
the key for every projectile,
and A key as the key for every alien.
Normally, we just use
underscores, because we
don't use the key for anything
when we do it in iteration.
We just use the--
so this doesn't throw away the variable.
It's kind of idiomatic.
It's a paradigm to use
underscore in a lot of languages
where you just toss a value away.
You don't do anything with it.
So that's what we were using it for.
But now that we need the
value, and because we're
using a nested loop where
we were using underscore,
we need to differentiate these two keys,
because we will use both of those keys
here in just a second.
So one thing that we
are going to need to do
is aliens are going to
need a invisible flag.
So we'll just say entities
can have an invisible flag.
And it'll set to false by default.
And then, if not self.invisible,
draw it, right?
Because if it's not
invisible, we should draw it.
Pretty straightforward.
And then, what we're going
to do is, when we actually
check to see if the projectile
collided with an entity,
we want to check the invisible flag.
Because if the invisible flag is true,
then we want to ignore the collision.
So if projectile collides
with alien and alien.and not,
alien.invisible, then we want to
say, projectiles at index P key
is equal to nil.
And alien.invisible is equal to true.
And I think that should work.
I'm not 100%.
Oh, it looks like I messed something up.
Line 56, oh I put a
do instead of a then.
Whoops.
Arithmetic on global
delta time on line 99.
How did we do that?
Oh, I see.
OK, this needs to receive DT,
and we need to pass DT into here.
So remember, functions that you
declare don't have access to DT
unless they're called a
reference within main.
So we take DT and we
pass it to tick aliens
so that tick aliens can do whatever
work it needs to do with delta time.
So boom.
So now, if I do that, oh, it works.
Awesome.
No sound, so will it work
with aliens that are beyond--
yep.
That's so cool.
It works.
No sound effects.
I feel like sound is always a
super important aspect of the game,
so I want to make some sound effects.
[? Aslee ?] says, AWESOME, in all caps.
Yeah.
It's pretty cool.
I'm happy that it worked
sort of on the first try.
I mean, I had a couple of
syntax errors, but it works.
My logic wasn't wrong.
That's the more
difficult thing to solve.
OK, what sounds good?
Let's see.
Let's turn this up, turn
my sound up little bit.
So if you're not familiar, this is VFXR.
I'm not sure how you pronounce it.
It's a sound generation program on Mac
and Windows, and I think maybe Linux.
I'm not 100% sure.
It just generates sounds using simple
sound synthesis, different sound
waves that you can use, triangles,
sine waves, saw waves, et cetera.
They even have whistle
waves, which is interesting.
They have some pre-set settings
here that are all kind of randomized
and tweaked, so it's super nice.
I'm going to make a couple sounds.
So the first one I want to create
is, like, shooting, like a laser.
So they have a laser
shoot category here.
[LASER SHOOTING]
I couldn't hear that.
[LASER SHOOTING CONTINUES]
OK, that was a bit better.
That might be a little obnoxious.
I might turn down a little bit.
What do you think?
Does that sound obnoxious?
It sounds kind of nice, right?
We'll use that one.
Where are we at?
In here, Space Invaders.
Oh yeah, VFX doesn't let you create
folders within the interface.
So it's sounds, and then
export wave, sounds, laser.
Do the aliens shoot back?
Yes.
Not yet.
We will implement that.
Sounds, laser, and then
we want something for when
we defeat an alien, so explosion.
[EXPLOSION BOOMS]
No, probably not that one.
[EXPLOSIONS CONTINUE]
No.
That's a little loud,
but if we turn down--
a little harsh.
That sounds OK, right, maybe?
That sound is so much like
the original Space Invaders,
though, the first explosion.
This one?
I, for one, welcome our new
alien overlords, says JPGuy.
Not without a fight.
You need a short sound
for the explosion.
Yeah, I kind of agree.
[EXPLOSIONS CONTINUE]
Can I speed up the sound?
Yeah, it should be
sustain time to K time.
How's that?
They only eat pineapple pizza.
Yeah, nice.
OK, we'll go with that one.
That's fine.
So we'll just call
that one explosion.wav,
and we'll leave that open in case
you want to add some new sounds.
I'm going to go to my dependencies.
So this is where we
have all of our assets,
g sounds, this is going
to be a new table I'm
going to create-- a global table.
That's why we're using lowercase
g at the beginning of it,
because it's a global.
We're not doing that in main.Lua
with some of the globals we have,
because there is no
real fantastic reason,
I guess, because they're all used
within-- well, they're not globals,
technically speaking, actually.
They're all locals that are
highest level in main.Lua,
but they're not technically global.
You can't access them
from other modules.
You can access them from main.Lua.
So I guess that's maybe the reason.
g sounds equals-- this is a new table,
so each sound needs a string key.
So in this case, a laser
equals LOVE.audio.new source.
This is how I declare an
audio file, an audio object.
LOVE.audio.new source, sounds/laser.wav.
And then, I think it
takes in a word after that
to declare whether it's static
or streaming, and then explosion.
So static or streaming
just means whether or not
it will sort of load the
audio dynamically into memory
when it needs to get used,
and then sort of free it up.
Static means it's
always stored in memory.
So smaller sound files you can keep
in memory and it's not a problem.
For us for a game with a lot of music--
for example, Super Smash Brothers,
the new Super Smash Brothers Ultimate,
has 900 songs--
all, I assume, CD quality.
That's a lot if you keep all
those in memory at one time.
So those you would want
to stream from disk.
In this case, we're not streaming them.
We're just going to keep them static.
They're small sounds.
They're very few kilobytes.
LOVE.audio.new source sounds,
explosion.wav, static.
And then, in ship.Lua, this is where we
actually instantiate the projectiles.
So going to say, g sounds laser play.
And actually, right before I do
that, I'm going to call stop on it.
And what this does is, if the
sound's already playing it stops it
and then replays it instantly.
And this is to solve
the problem that happens
because if you try to fire a
sound or play a sound in LOVE 2D
and that sound's already playing,
it won't re-trigger the sound,
it'll just keep playing the
sound that was already playing.
So you can't, like, rapidly hit
laser and have a do, do, do, do.
It'll just play it only as fast as the
sound itself is going until it ends,
and then it'll trigger a new one.
But we wanted to basically, if
user presses space bar fast,
we want it to trigger every single time.
So we're going to call stop and then
play, and that will solve that problem.
And then, in main.Lua when
we actually do the collision,
we're going to say g sounds explosion
stop, and g sounds explosion play.
Just like that.
So this should work.
[EXPLOSIONS AND LASERS]
So a pretty easy game, I
would say, at this point.
We might need to throttle how fast
we can shoot the laser, but it works.
It's pretty cool.
The other thing that we need
to do which we haven't done,
which I mentioned we would do,
is get rid of the projectile
when they're off screen.
JPGuy says, got to go
for a while, hopefully
be back before the stream ends.
Good luck with the Space Invaders stuff.
Thanks so much, JP.
Appreciate having you on.
I'll see you next time.
Tune in on Friday for David's stream.
He's going to do some Docker stuff.
It's going to be super cool.
MKloppenburg, thanks for the tip.
Downloading VFXR now.
Awesome.
Yes, it's a great sound
synthesis program.
I love it for simple,
like, retro-style stuff.
Sustain, yep, did all that.
Death to them all, says JPGuy.
Death to all the aliens.
Normally, when you hear the word
love, you think of romantic stuff.
Now, immediately thinks of LOVE.update.
Yeah, I've ruined that idea
for a lot of people, I'm sure.
All right, awesome.
Everyone's thinking
that this is super cool.
I think it's super cool, too.
I mean, the sounds really
sealed the deal, right?
[LASER FIRING]
Now it actually feels
that we're playing a game.
It's not just a little
weird demo-y thing.
We're actually getting
somewhere with it.
So that's that.
Let's also take care of the projectiles
being at the top of the screen.
So we can say, if projectile.y is
less than or equal to zero minus
projectile--
or I guess we can just say,
negative projectile length,
then projectile P key is equal to nil.
And then, this four loop then
needs to be in an L statement,
because if we do this, this projectile
collide function will break,
because that's now a nil object, right?
Is that true?
I think the object will still exist.
We still have reference
to the object itself.
So I don't think that's
necessarily true,
but we should still put it
into an else just for good
measure, because logically we
don't want to check to see.
If it's beyond the
edge of the screen, we
don't need to check to see if
it's colliding with an alien.
It's impossible.
It's beyond the view.
So we'll do that.
So now, we can demonstrate
that this works.
In my LOVE.draw, if I say,
LOVE.graphics.print, projectiles,
two string, length of projectiles.
So this should print--
yeah, projectiles zero,
right, up at the very top.
[LASERS FIRING]
Yep.
See, they're all set to zero.
So we have no projectiles left.
They're all getting set to nil.
Pretty cool.
And it looks like it renders it.
I think it takes it a second to unload.
Like, the reference is
nil, but for some reason
it takes it, like, half a second
to actually update that number.
So it stays-- it'll, like,
instantly go to zero--
everything.
But it works.
It's good.
We are freeing our memory as we use it.
[LASERS FIRING]
So we don't need to
worry anymore about sort
of having a ridiculously long update
loop or running out of memory.
Those aren't concerns anymore.
We can check if the projectile is nil
or now in the loop before shooting.
Well, we're not going to--
oh yeah, I see what you mean.
Yeah, we could do that.
It's probably unnecessary and more
code than just doing an L statement,
though, to be honest, so that's OK.
Probably slightly more efficient, too.
Oh wait, yeah.
Yeah, slightly more
efficient, because it's
going to update iterate through
every alien if we did that.
So we're just saving some cycles.
It's not a huge optimization, but it
saves us, minimally, 55 if statements,
if not more logic than that.
Maybe it's more complicated than that.
So it turns that into just an else.
So it's pretty cool.
We have a special guest
today with us, here.
Who do we have on stream?
You might be cut off.
You might need to crouch
a little bit, there.
Hey.
SPEAKER 2: Hey!
Nice to see everyone.
COLTON OGDEN: Is this the first
time I've seen you since--
SPEAKER 2: In like, since 2018.
Yeah.
COLTON OGDEN: Yeah, I didn't
realize that actually, yeah.
SPEAKER 2: These streams are like
a tour of my childhood, it seems.
COLTON OGDEN: Oh yeah.
Did you want to play a
little bit of crappy--
SPEAKER 2: Yeah, how
far along are we now?
COLTON OGDEN: We've got projectiles,
and you can use the arrow keys and space
bar to shoot some aliens if you want.
SPEAKER 2: All right.
Hey, that's a lot of
diverse enemies up there.
COLTON OGDEN: Yeah, we
have it all-- they're
set to a random index on a spreadsheet.
We just implemented getting rid of
projectiles when they're off screen,
and so now the memory gets freed.
That was the most recent step.
SPEAKER 2: I still remember that sound.
There's a very prototypical laser sound.
COLTON OGDEN: Yeah, we
used a-- if you remember
from the games course, the VFXR tool
which we used to generate sound.
SPEAKER 2: Yeah for sure.
COLTON OGDEN: You started
a new course today, right?
SPEAKER 2: We did.
We're teaching a new
course on computer science
and the law at Harvard's
Law School, in fact.
COLTON OGDEN: How'd that go?
SPEAKER 2: Good, good.
Doug Lloyd will be co-teaching too,
so he takes over later this week.
So a few more lectures up this week.
COLTON OGDEN: Nice.
And then you're coming in on Friday?
SPEAKER 2: Oh, yeah, no
surprise this time, though.
What game are we playing on Friday?
COLTON OGDEN: Oh, yes.
It's called Docker.
SPEAKER 2: Oh, yeah, so
we actually use Docker,
which is a containerization
technology, which
is a similar in spirit to virtual
machines, if you're familiar.
So we're looking forward to
talking about how those works
and how they drive all
the CS50's infrastructure,
and increasingly a lot of
cloud-based services, as well.
COLTON OGDEN: Nice.
I'm excited to learn a few
things about Docker, as well.
SPEAKER 2: Cool.
All right.
COLTON OGDEN: Have a good time.
SPEAKER 2: Well, I don't want
to take away from the game.
Good to see everyone and see you back
in the chat, and thanks for tuning in.
COLTON OGDEN: Cool.
Thanks for popping by.
Appreciate it.
All right, so we did the
freeing of the memory
when the projectiles go off screen.
So the next thing that we should
probably take a look at maybe
is have the ships fire back at us.
Because currently, we can
fire at the projectiles,
but it's a one-sided battle.
The aliens cannot actually
give us any challenge.
Eventually, they will come down
towards the player and theoretically
collide with us-- well, they
will collide with us, certainly.
But yeah, actually having
an adversary fire at us
is probably worth implementing.
So why don't we take a look at that?
So in the alien class, this is
probably where we want that to happen.
[MUSIC PLAYING]
Aminebenamor, thank you
very much for following.
And actually, now that I think about
it, probably not the alien class,
because only a specific
subset of aliens are
going to want to be able
to fire at the player.
Do not give them weapons.
This is how it starts,
says [? Aslee. ?] Yeah, no.
Unfortunately, for the sake of having
an interesting enough game to at least
give us a challenge--
is what Bhavik is just mentioning now--
we probably want to make
this a bi-directional game.
So my initial thought was
do it in the alien class,
but that's not going to work
necessarily well, because we
need to have access to all the aliens.
We need that access to that information
because we need to figure out
which aliens are at the bottom row.
And the reason we need to do that
is because if we look at the game
and we see the aliens moving,
if we allow every alien to fire
and the projectiles will sort
of indiscriminately collide
with other aliens or the player's
ship, as you can imagine,
all the aliens that are above the other
ones will kill the aliens below them.
And this will therefore leave
only this top layer of aliens,
depending on how fast the shooting
interval is, once that has passed.
So what we want to do-- exactly,
Bhavik Knight is saying.
The bottom row aliens only, otherwise
the aliens will kill themselves.
That's correct.
So we only want the bottom row to shoot.
However, we want the
bottom row to shoot,
and that includes aliens that we
defeat from the bottom rows up.
So if for example, if we defeat
an alien there, the alien above it
should therefore be
able to shoot at us now.
And you know, the same
thing if we do it again.
Then, the alien in the
third row now should
be able to shoot at us, and so forth.
So we need to figure out--
I guess we could sort of
tweak the aliens that can
fire based on the aliens above them.
I guess whenever we defeat
an alien, we can then
trigger some code that gives the
alien above it the ability to fire.
That's probably the easiest
way to do it, honestly,
is to give each alien a flag.
So the alien--
I guess we just do it in entity,
because ship and alien can fire.
So self can fire is false.
We don't want every alien to
be able to fire necessarily
Now Colton is trying to build Skynet.
He is one of them.
I can neither confirm
nor deny such claims--
such accusations.
I'm going to, in main.Lua where
the ship gets instantiated,
I'm first going to let the ship fire.
Ship can fire is true.
This should be true.
And the aliens are all going to
be, by default, not able to fire.
They're going to be sent to
can fire is equal to false.
So let's go ahead and give the bottom
row of aliens the ability to fire.
So we generated all the rows and
columns, and then I'm going to say,
if y is equal to aliens tall, then--
So this is--
OK.
So I'm going to do this.
I'm going to say, local aliens
equal to this bit of code here.
So what I've done is, as I've taken
that constructor out of the table insert
function so that we can
store a reference to it here.
So local alien equals alien at xy,
math.RAM 256 for the random frame.
And then, I'm going to say,
if y is equal to aliens tall,
which means if we're bottom
row, then I'm going to say,
alien can fire is equal to true.
Right?
So this will only give aliens at
y level five the ability to fire.
And then, in our update--
did I do this in update?
I guess I can in alien here.
Yeah.
I want it to be on a timer, and I want
to give each alien a random interval
of time that it will fire.
Because I wanted to be kind of random.
I don't want to be on a consistent
timer, because if they are,
they'll all fire at the exact same time.
And it'll be kind of like a blanket
of fire, and it'll be weird.
And it won't be unpredictable.
It won't be fun.
So also, each alien needs to have a
reference to its own firing interval,
right?
So it needs to randomly
choose a length of time.
It will wait to fire, and then fire.
And then, when it fires, it
needs to reset that interval
to some other random value.
And we can set it between
maybe one and three seconds.
Or no, because that
would be pretty fast.
Maybe one and seven seconds.
Yeah, we'll do it between
one and seven seconds.
One and 10 seconds?
Yeah, one and 10 seconds.
This is all game design, game
balancing, that sort of thing--
again, up to taste more
than anything else.
You want to tweak values, mess
with them, figure out what works.
So we're going to say--
what makes the most sense, here?
Do we put it in here?
Because if we do that, we need
to create a new constructor.
Just brainstorming for a second,
figuring out what makes the most sense.
We can check if the alien
has another alien in front.
Yeah, we could do that, but that's
going to be a little bit less--
I think not as clean of a
solution as I want to look for.
I mean, it works.
That's solution would
work fine, I think.
We can set it so that if there are
fewer aliens, they shoot rapidly.
With more aliens, they shoot at a
slower pace, says [? Bhavik Knight. ?]
That's not a bad idea.
That's pretty cool, interesting
sort of game design idea.
Certainly would work.
Certainly would be good.
I'm just trying to figure out
how to structure this in my head
before we actually code.
So the aliens that can
fire need a timer--
a fire timer.
The fire timer is going to get a
random value between one and 10,
and that's the number of seconds.
Each one will therefore
need a timer, itself.
So an interval and a
timer-- the interval
will be the amount of time
to wait for it to fire.
The timer itself will
be the amount of time
in delta time in seconds that have
elapsed to wait for that interval
to have passed.
Will then fire, will reset the
timer, create a random new interval.
So yeah, we have the pieces.
So the best place to--
I mean, dynamically, we
could just do it in main.Lua.
We could just say, alien.timer.
Everything alien is going to need
a timer so that we can update it.
Well, it will have a timer.
Oh yeah, so alien.timer
is equal to zero.
Fire timer, we should
call it fire timer.
Try to be a little bit
more verbose when you can.
And then, fire interval.
And that's going to be math.random 10.
So this is the random length
of time that will pass.
Aliens in the back have less
pause, and aliens of the front
have more pause to shoot.
Yeah, that's a good idea,
[? Bhavik. ?] Not bad.
I would recommend implementing
something like that, for sure.
Try it out.
That's a good example of an actual
mechanic to influence the gameplay--
the difficulty of the game.
So fire timer is zero.
Fire interval is equal
to math.random 10.
So what we can then do
is say, in our update--
tick aliens.
It could go in tick aliens, too.
We'll write it in here, and then maybe
we'll make it a separate function.
So for _alien in pairs aliens, do.
And then we're going to
say, if not alien.invisible,
because the alien can't
shoot if it's invisible.
If alien.can fire-- if
it can fire, then that
means that it will have a fire
timer and a fire interval.
So we'll say, fire timer equals
alien.fire timer plus delta time,
if alien.fire timer is greater than
or equal to alien.fire interval.
So this means that the amount of time
has passed that's the same or greater
than the actual interval that we're
waiting for this particular alien.
It's going to be random for each alien.
So this alien is set to fire after five
seconds, and five seconds have elapsed,
then I'm going to set alien.fire
timer equal to alien.fire
timer minus alien.fire interval.
So set to zero, or roughly zero.
And then, we're going to spawn a
projectile, and it's going to go down.
So this is kind of what we did before.
Table.insert into
projectiles a new projectile,
which is going to get alien.x
and alien.y plus alien.height.
And then, it's going to be set to down.
So this projectile is
going to move downwards.
It's not going to move upwards, right?
So let's try this.
Oh, hey, whoa, nice.
OK.
So some of them are firing in tandem,
because it's not a floating point
value.
It's an integer.
So the random number--
oh, we forgot to change this
to another random value.
So alien.fire interval is
equal to math.random 10.
All right.
Aliens can't shoot
when they're invisible.
This is in line with Star Trek.
As far as I recall,
the Romulan spaceships
couldn't fire while they had
their cloaking devices on.
That's interesting.
I've not watched much
Star Trek, but that sounds
very apt for this particular example.
But the aliens are firing.
Now, the only thing is,
they're not shooting at us.
I guess I do want the alien to also
do the sound effect for firing.
So g sounds, laser, stop,
g sounds, laser, play.
Now, I kind of almost want to
have a different sound effect.
[LASER FIRING]
It's kind of cool, because it's
creating, like, these obstacle
courses that I have to fly through.
Whoa, like that.
Oh, it went right through me.
There's no collision for the ship yet,
actually, so it's not doing anything.
So it can just go straight through me.
I forgot.
I, like, instinctively
wanted to dodge it.
Yeah, the sound's not bad, because
it stops every one when it fires.
A different sound effect
would be better, says Andrej.
We can do that.
Different sound effects,
so let's do some lasers.
Not that one.
[LASER FIRING]
That sounds obnoxious.
[LASERS CONTINUE]
Even more obnoxious.
That's not bad.
I kind of like that.
I'll do that one.
Alien laser.
Different alien
projectile color, as well.
That makes it a little
bit more complicated,
because then we have to add
that as a field onto the class.
Maybe we can make that an optional
feature at the end if we have time.
For simplicity, we're going to keep
them the same color, just for now.
Dependencies-- it's a good idea, though.
Don't think that it's not a good idea.
I just don't think we have the time.
But no, it's an awesome idea.
I totally agree.
Alien laser equals LOVE.audio.new
source, sounds, alien laser.wav,
static.
Alien laser.wav does not exist.
What did I call it?
I called the alien laser.wav.
Oh, sonds.
That needs to be sounds.
OK.
Oh, so now we have to actually
call the right sound effect.
So in here it's alien laser.
[LASERS FIRING]
Yeah, I kind of like it.
It's a little bit deeper
than the regular spaceship,
so it kind of has a more
even, like, a weird, very
subtle, sinister difference.
So I kind of like it.
It's pretty cool.
Yeah, that's great.
We'll do it.
Ship it.
Now, the projectiles should
collide with the ship, as well.
So what we should probably do is--
updating it if it's less than
projectile length, or projectile.y
is greater than virtual height.
Remember, because these
are the projectiles that
go below the edge of the screen, we
have to keep track of this, as well.
Then, set it to nil.
We were only checking above
the top edge of the screen.
We also need to check the bottom edge.
And we need to also check to see
if it collides with the ship.
So we can say, if--
we can shortcut this, too.
Do I want to do that?
No.
We'll just say, if projectile
collides with ship, ship.invisible
is equal to true.
So because ship inherits from entity,
this should result in the ship dying.
Yep, perfect.
So I'm still moving the ship around.
I can still move it around,
but it is not visible.
And I want an explosion sound to play.
I want a different explosion sound to
play than when the ships die, though.
I want a more dramatic sound, I think.
So let's try here.
[EXPLOSIONS FIZZING]
Oh, that one was all right.
It's not quite long enough, maybe.
[EXPLOSIONS CONTINUE]
That one's very dramatic,
but it's kind of appropriate.
Cool.
We'll do that one, just for the drama.
Death.wav.
Let's go over here, we've got
got to go into dependencies.
Got to add death.wav.
Audio.new source, sounds,
death.wav, static.
Cool.
And then, here, set explosion.
We want death.
Lots of positive words.
All right, let's try and die.
[EXPLOSIONS BOOM]
There we go.
A very dramatic death sound,
and the player does die.
We do not go to a different
screen, unfortunately.
That is not yet implemented, but that's
probably the next step, honestly.
We've implemented movement.
We've implemented the ships firing.
We've implemented death
on the player's side.
There's no score, yet.
Score is important.
We probably want score.
The title screen, probably important.
Yeah, that's probably it for now.
The next part is going to be the
largest part-- the more complicated one,
I suppose.
So let's do that.
So the title screen.
Well, the state machine class first.
I'm going to create a new folder
called states within source,
and then I'm going to create a new file
in source called state machine.Lua.
And a state machine is a--
it's been a blast thus far,
have fun with the rest,
[? says MKloppenburg. ?] Will
have to watch the part later on.
Thanks so much for joining,
[? MKloppenburg. ?]
Glad to have you with us.
Hope you enjoy.
Be sure to tune in on Friday for David's
streaming It'll be super awesome.
The state machine class is a-- so a
state machine is essentially a finite
representation of what the state of
something is, whether something is--
it can only be in one
state at a given time,
and going back and forth between
states occurs during certain transition
criteria, right?
So you have a title screen.
When your game is in
the title screen, it's
in a state of being in the title screen.
You're not playing the game.
You're not at a game over.
It can only be in one state at a time.
You can't be a title screen and the
game over screen at the same time.
That doesn't really make sense.
You could maybe make
a game that tells you
you lost, but is also
sort of the game over.
But it's technically still going to be
a different state, more than likely.
A state machine is what allows you to
transition between the different states
of your game.
The state machine is kind of like a
thing that points at whatever state
is currently active and updates
it, checks for the transitions,
and then transitions when it needs
to do so and keeps track of that.
The actual states themselves
are how we modularize our game,
and we can then implement the title
screen separate from the play state
separate from the game
over state, et cetera.
So the state machine class--
first of all, it is a class.
So like we did before, we're
going to create it as a class.
The state machine has an
update and a render function.
Just like a lot of the
other stuff that we did,
we're going to call this from main.
It also has a change
function, which will allow
us to put it into a particular state.
And we want to actually
be able to create it,
so it takes in a state table which we
are going to store all of our states
in.
So when we call change
state, we want to do
is essentially say, self.current state.
We're always going to
have a current state.
And this state machine can
apply to creatures in your game.
It can apply to the game state itself.
You can basically make a state machine
for anything that changes state
and that you want to
modulize the state of,
or encapsulate into a
discrete file or module.
You can use a state machine for it.
It's pretty flexible,
and typically you use it
for creatures in the game
or game states, most often,
but you could find multiple uses for it.
The state machine will always have a
reference to whatever the current state
is as its current state field,
and that changes depending
on what we call a change, and so forth.
So I'm going to say
self.current state, exit.
So every state is going to have an
exit function and an enter function.
The exit and enter functions
function as the transition--
sort of an abstraction of the
transition of a state from one
state to another state.
When you transition from one state to
another, you call exit on the state
that you're in.
So it'll clean up whatever it
needs to do, do other stuff,
and then it will call enter on the
state that it's transitioning to.
And then, it will set the
current state to that state.
And so the enter basically lets
you clean up stuff in the state
they you're currently in if
you need to do so, free memory,
do whatever you got to do, and
then call some initialization code
in your new state with
the enter function.
And enter usually takes params, as well.
So that'll be this, if taking the param
is a second field of that function.
And then I can say, self.current
state is equal to self.states state,
and then, oddly, I do call it as
a function, which we'll see here
in a second as to why we do that.
You don't have to
necessarily do it that way,
but it's a nice, clean way to do
it when you have a game that uses,
like, fresh states every time
you do a state transition.
And then, I'm going to say,
current state enter params.
Self.states is equal to states.
Self.current state update,
and self.current state render.
So we have a state machine class
that will take a dictionary,
it will take a map,
take a table of states,
and they're actually going to be
in the form of anonymous functions.
And we'll see that.
That is why we call this
here-- this parentheses here,
like where you're doing a function
call, because each of the self.states
is actually a function.
[MUSIC PLAYING]
It's actually a function object.
Mirkyst, thank you very
much for following.
So if I go to main.Lua here, first
of all, go to dependencies.Lua
and require source
state machine, like so.
And in the LOVE.load, this is where I
like to initialize the state machine.
I'll usually do it after
all the setup stuff.
And back for another half
hour or so says, JPGuy.
Thanks for joining again.
Just doing some state machine stuff
now, so a little bit more meaty,
so to speak.
It's a simple class, but a very
flexible, very useful class.
What we're going to do is, I'm
going to create a g state machine.
Now, this g state machine
is global because we
might want to change our state
from within other states.
Well, from outside of
main.Lua, we are going
to change it from within other states.
Each state of our game is going to be
able to transition to other states.
So the state machine
itself needs to be global.
So I'm going to do this here.
And I'm going to say title is
equal to an anonymous function that
returns a title state object,
which we have not seen yet.
Play is going to be equal to a function
that returns a play state, which
we also have not seen yet.
And game over is equal
to a function that
returns a game over state,
which we also have not seen yet.
And I'm going to change our
state to the title state.
It takes no parameters.
And we want to make sure that
if self.current state, then we
want to basically put
this in an if statement,
because if we're starting the
very beginning of the game
and we don't have a current state
enabled, then this will not work,
because self.current state
will be equal to nil.
And we can't call exit on nil.
It has to be something that
has an exit method attached
to it, which is every state.
Every state needs to have enter
and exit associated with it.
That's part of the interface.
That's how we can get this to work.
It assumes that any state that we
implement will have that interface.
This is kind of like an interface,
as per what Bhavik mentioned earlier.
But again, this is all dynamic.
Nothing is going to enforce
that these functions exist.
It's up to you to enforce that.
So I'm going to go into states,
here, and create a new file.
I'm going to call this base state.
Not the most useful comment blocks
of all time, but better than nothing,
I suppose, arguably.
Base state is going to be the state
from which every other state inherits.
And the reason that we
want this to exist--
we wouldn't normally
need this, but this is
to prevent us from having to
write empty enter and exit
methods for our future states,
because not every state needs
an enter and an exit method, but our
state machine assumes they exist.
And because it assumes
they exist, we want
to basically make sure every
state has this interface-- it
adopts this interface.
And so we get this through inheritance.
So base state is then equal to a class.
And all base state is a
bunch of empty methods.
Base state, update, render, enter,
which takes parameters, and exit.
So this is all that base state
is-- just an empty class--
well, not a completely empty class,
but a class with five empty methods,
an init, update, render,
enter, and exit-- all functions
that the state machine assumes exists
on each state that it works with.
So now what we can do is, I
can say, new file in states.
I can call this title state.
And title state is going to
be equal to a class which
includes equals base state, right?
And so now this is
essentially the same thing
as copying over these five
methods into title state for free.
So if we just saved it
like this, titles class
is essentially the same
thing as titles state--
sorry, this should be title
state, not title class.
Title state will be the same
thing as base state, essentially.
They have the same methods.
We don't want that to
be the case, though.
We want title state to
actually have some behavior.
So what I'm going to say
is, any function now that I
define that has the same name as
these, it'll just override that.
It will just replace
it with our own version
while keeping the other
ones that we inherit
from base state, which is super useful.
So we can say it's title state, and
then this will be update and render.
We only need these two for now,
just to demonstrate how it works.
So I'm going to say I
LOVE.graphics.print, Space Invaders.
This is the title screen.
Actually, this is going to be printf.
Title screen, right, so this
is going to be Space Invaders.
It's going to start at zero.
It's going to be roughly in the middle
of the screen, virtual height divided
by two minus--
I don't remember how tall the font is.
I'm going to assume 16, so
I'm going to have minus 8.
It's going to use the width of
the screen as its wrap amount,
and I'm going to center it.
Oh, sorry, this should
be in the draw function.
And up here in the update
function, I'm going to say,
if LOVE.keyboard.was pressed, enter,
or LOVE.keyboard.was pressed return,
then I'm going to--
a bit of a brain fart.
g state machine change to play.
No parameters.
So maybe you see what's happening here.
It'll be clear shortly if not.
This is just a title screen
that's going to say Space
Invaders in the middle of the screen.
And it's going to check for input
when we press enter or return-- return
taking into consideration Macbooks,
which by default, the enter key is
return.
It's going to use that g state machine
we declared in main.Lua and transition
to our play state, a state
that we defined as play.
And again, the states are all
here-- title, play, and game over.
Those are the string keys.
And they map to anonymous functions.
These anonymous functions are
being stored in our state machine
in self.states.
So self.states is just a
reference to this table, here.
That's what this curly
bracket syntax is.
Remember, you can instantiate a class
with its name and curly brackets
if it just takes in a
dictionary or table or map--
however you want to look at it.
And these are anonymous
functions that are
stored there, assigned to these keys.
So key value-- the values
are function objects.
Those function objects
will be called here, right?
And remember, all they do is they
just return a state-- a title state,
of play state, and a game over state--
a fresh state, completely blank,
which we so set to self.current
state on line 13 here by saying,
self.states at state--
remember-- change to state.
And then, we call these curly brackets.
That'll actually call
the function object.
It'll execute that function's code.
And the function's return
value is that state--
depending on whether it's a play state,
a title state, or a game over state--
and therefore assign
it to current state,
so then we have a reference to
that state object-- a brand new,
clean state object.
MetalEagle says, hello CCSA TV.
Hello, MetalEagle.
Glad to have you with us.
Happy new year.
So that's how the state
machine is working.
That's how it's getting
the state object and it's
keeping track of whatever
state is currently active.
We still have a few pieces
left to implement, though.
Why don't we go ahead into--
well, we're already implementing the
different states, the title screen.
Let's add a play state.
The play state is essentially
going to be all the stuff that we
implemented in main.Lua already.
Play state equals class,
includes equals base state.
And so what this is going to do
is, all of this stuff here, right,
this is all going to be sent to
the init function of the state.
Play state.
Right, so that's what this does.
So we're using self now.
So we're no longer having
these pseudo-global variables
that are at the parent
level of our main.Lua.
Now they're actual member variables--
member fields-- of the object
itself, these new state objects.
We've essentially taken
all of our logic--
we've found a way to take the game
logic itself and put that into a class.
And so now we can modularize
how our game flow works and not
put every bit of important logic
into main.Lua, so it's super nice.
And that includes also
these variables up here.
We don't need these in main.Lua.
Those can be in the play
state, just like that.
And they're no longer a local, right?
So boom, boom, boom, boom.
They're self.
And now these-- the [? Aslee ?]
and Bhavik variables
were just kind of stored
temporarily they aren't really
ever referenced later on.
So we can keep those as locals.
We can keep the ship index as local.
We're reusing it here
and we're putting that
into self.ship, which is
actual ship variable that we're
going to use consistently
throughout the rest of the course.
Oh, it's JPGuy's birthday.
Happy belated birthday, JP.
I did not know, as well.
I apologize if he mentioned it before.
I did not know.
December 17th, nice.
Happy birthday.
So now, notice that self.projectile,
self.aliens, self.ship
is not necessary here,
because we assigned it here.
Yeah.
We declared it in advance
as a local because we
wanted to reference it later on.
And if you want a local
variable that you reference
from within a nested scope,
you need to define it
above that function, which
is why we did it before.
We don't need to do that
anymore, because we can just
call it self.ship here and assign
it, so super straightforward,
saving a lot of code, actually.
All of these we can keep in main.Lua.
We can keep [? kush, ?] we can
keep all the initialization stuff.
We can keep the state machine,
itself, being initialized.
All of this stuff, though--
this is all actually
update code for our play state.
So copy that, bring it over
here to our update function.
So this is cool.
Now remember, every time we
have a reference to ship,
it's no longer just a reference
to ship or projectiles.
We actually have to reference it
as self.ship and self.projectiles,
so that's important that
we keep track of that.
Let me make sure that I'm
not missing any of those.
OK.
And tick aliens-- remember, that
was a function that was in main.Lua
We no longer have access
to that from within here.
So what we want to do is, we
want to take tick aliens probably
out, and take that out of main.
So again, saving space in main,
allocating it to a different module.
Can I just copy it here?
Right, yeah.
OK, and self.aliens, cool.
And alien movement timer,
that's a self as well, right?
Yep.
So we want to set alien movement
timer as self.alien movement timer.
And alien direction, as well?
Yep.
Alien direction, that
also gets adjusted.
So alien direction becomes
self.alien direction.
Whoops.
Boom, boom, self.alien
direction, perfect.
So that should be good.
That looks good to me.
So our main.Lua is now 68 lines
of code, so we've actually
reduced it quite a bit.
The draw, as well, we can
get rid of that, right?
So we can do this, get rid of this,
go over here to make a draw function.
Or render, I should say.
And this should also be set
to play state tick aliens.
And this will be self tick aliens.
So here, we do this.
So pair is self.projectiles,
self.aliens, and self.ship.
So everything is kind of
coming into plan, here.
Code looks very good
and modularized now.
Yep, thank you.
Watched the video about
state machines 600--
so of course someone
has mentioned it before.
Yeah, super useful construct in game
programming-- ubiquitous, very useful,
very helpful.
OK.
We're going to keep if keys equal
to escape in our actual main.Lua
so it's accessible from within
every single game state.
Now, the missing
ingredient is, we actually
have to call g state machine render
and g state machine update, right?
Because if we don't do that, then all
that code that we put into play state
is just kind of lost in space.
It still needs to be triggered.
And we do that from
within main.Lua using
the state machine object
that we declared earlier
and that we initialized.
So assuming that I did
everything right, and actually I
know right now that I did not actually
require the states, themselves.
Very important that you do this.
Title, state.
And because I have a reference to it, I
am going to require a game over state,
and just add it really fast.
Game over state.Lua.
Game over state class.
Not functioning game over state.
Game over state is equal to
class, includes equals base state.
Cool, now assuming that
everything is in place,
this should at least
give us a title screen.
And as soon as we hit the title
screen, and we press enter or return,
we should get transitioned to
sort of the game as we saw before.
So let's take a look.
Cool, pretty nice.
We see we have the Space
Invaders text in the middle.
Because remember we're defaulting to
the title screen-- the title state.
So starting right off in the middle
of the screen, we press Enter.
OK, so it's not completely perfect.
Play state line 33,
bad argument to insert.
OK, play state line 33.
Let's see what that looks like.
Ah, aliens needs to be self.aliens.
Let's try that again.
Bad argument, two pairs, play state 66.
Probably the same bug.
Yep, self.aliens.
And play state 112.
Yep, self.aliens.
OK, boom, boom, boom, boom.
Remember, aliens by itself
is no longer meaningful.
It needs to be self.aliens because
it's within the play state.
So now-- OK, 78.
Still have a few that
we missed, which is OK.
Self.projectiles.
Yep.
I can't believe we haven't
missed any other one.
Let's see.
Yep, right here, self.projectiles.
OK, so hopefully that works.
Once again, 16.
Oh man.
Wait, what did I miss there?
What was that?
Oh, projectile line 16.
OK.
So projectile line 16.
OK, takes on an entity, which
gets an xy within a height.
So where are we referencing that for--
let's see if we have a stack trace here.
OK, that's from line--
oh, play state 56.
OK, play state 56.
This needs to be self.ship, not ship.
OK.
And this needs to be self.ship, as well.
A lot of these little
things you've got to tweak.
If you start this off from
the beginning in a state--
what was the key code to press when
changing two of the same names at once?
Oh, if you highlight something, you
can press Command-D or Control-D,
and that will highlight the next
thing of the same name in VS Code,
and a lot of other text
editors like Atom, too.
All right.
Is this working?
Come on.
[LASERS BLASTING]
OK, that looks pretty good.
Let's make sure that I
can shoot other ships too.
OK, cool.
One thing that we didn't implement,
actually, which is important
is that the ships above the
ships that die on the bottom row
haven't been given the ability to
fire, so we need to also do that.
But yeah, we've modularized everything.
Everything is cool.
We have two different states, now.
We have a title state and a play state.
And the last one will be a
game over state when we die.
So as soon as we die, we
should just do a change
to the game over state, which we can
very simply just kind of copy over
the title state stuff, here, making
sure to rename them to game over state.
And then, we want to set this to title.
And then, game over like
that, which won't do anything,
because we're not actually
transitioning to it yet.
So from within play state, what
we want to do is, when we die,
we want to g state machine,
change to game over.
Is that how I named it?
Let me make sure.
Yep, perfect.
So now when we die, we
should go to a game over.
Let's try it.
[EXPLOSIONS]
Awesome.
So we have a game over.
If I press Enter, we go
back to the title screen.
If I press Enter again,
back to a brand new game.
Now, do the ships regenerate?
OK, let's move firstly
the green one and then--
yep.
So the ships themselves also regenerate.
Sorry, that was a lot of sound at once.
MetalEagle says, why do you
have call stop before play?
So we mentioned this before.
What it does is, if you're
triggering in LOVE 2D a sound effect,
and you trigger it fast
before it's finished playing,
LOVE 2D will not refire it.
It will let it the sound effect
keep playing until it's finished.
And so if you want to
have a rapid fire laser,
for example, you will
kind of just be stuck
waiting for the first
sound effect to finish,
and it will kind of be
like a weird sort of--
sometimes it'll fire, sometimes
it won't, kind of sound.
So if you stop the sound effect and
then play it immediately afterwards,
you will be able to
re-trigger it instantly.
So you will get the effect
of rapidly triggering
that sound effect over and over again.
Good question.
You weren't thinking about
separating the entity class,
and with modularity, look how far
we've come with state machine.
Yes.
Yes, this is Bhavik And
that's a it's a good point.
Always try to modularize
stuff as best as you can.
I mean, I'm sure we could do a better
job of modularizing what we have now.
It's fairly well-structured,
but you can almost always
get better at organizing stuff.
If you see really large
functions, a good heuristic
is to kind of break those up
and make smaller functions.
Separating the states is very good for
game development, that sort of thing.
Yeah, it's good practice.
Always try to make the most
elegant code that you can.
It's difficult if you're in a rush and
you have deadlines to meet and stuff.
Sometimes you can't do it.
But as best as you can, as much as
is realistic for your hobby projects
or whatnot, it's good
practice, if anything.
The other thing that
we were going to do is,
we were going to make sure
that aliens could still
fire at us from the upper rows when
the lower rows are cleared out.
And we also want a score.
And we want to display the
score when we get a game over.
So what we can do is--
and also we would also want to
trigger a new level each time
we clear out the map.
I'm not sure if we'll
have time to do that,
but we could certainly talk about it.
But for now, let's do the
aliens being able to fire
when the lower aliens are cleared out.
So what we can do is--
where is the code located at?
[MUSIC PLAYING]
Balthazar_Bratt, thank
you so much for following.
Appreciate it.
In play state-- so in play state where
we actually check each projectile
against each alien, what we're
going to want to do there is,
when we do collide with an alien--
which is what we do here, when we
trigger the code for clearing it out--
we want to give the alien
above it the ability to fire,
give it an interval, that sort of thing.
So what we can do is, A
key will be the number--
the index of the alien every time.
That's what we have set to, here.
That's why we needed to declare it.
So we're going to say, if
A key is greater than 11--
and the reason that we're
setting a greater than 11--
and what we should actually do is
use a constant, so in this case,
aliens wide, right?
What this will give us is
true if it's anything greater
11, which is the length of
an individual alien row.
So any alien beyond the top row, right?
So if it's the case that that alien is
below the top row, then we want to say,
aliens at A key minus aliens
wide.can fire is equal to true.
And we have to do the same thing
that we do when we basically
figure out if the y level is aliens
tall when we initialize the aliens.
So we're going to say, can fire is
equal to true, fire timer to zero,
and fire interval to
math.random 10, right?
So fire timer equal to zero.
And fire interval, math.random 10.
And that should work.
Let's try it out.
[LASERS FIRING]
I'm going to clear out this alien.
Whoops.
Play state 55.
I forgot to do self.aliens
for all of these.
So make sure to do that.
OK.
Clear that alien out.
So pay attention to that.
OK, so he killed the other alien,
so that was an unfortunate bug.
Whoops.
Let's try that again.
[EXPLOSIONS]
So the reason that's doing that
is because the movement is fast.
It's too fast.
So we can maybe tweak that.
So if we set constants to the
alien movement interval to 0.75--
[LASERS FIRING]
OK, so it's still killing
the aliens below it.
So how did I fix this before?
I'm trying to remember.
I solved this problem once before.
The simple thing to do--
they're not spawning in the
middle of the ship, are they?
No, they're not.
That is a bug.
Yeah, the projectile is
spawning right at the alien x.
We don't want that to happen.
We want it spawn at alien
size divided by two--
alien x plus alien size divided by two.
That should fix it.
That should fix it.
Now it's right in the middle.
[LASERS FIRING]
There we go.
Perfect.
So it was spawning right at the
x, right at the edge of the alien,
and so it was instantly colliding
with the alien right below it
if there was an alien right below it.
So you don't want that.
You want the lasers to spawn
right in the middle of the alien.
So we fixed that problem.
Let's try and figure out a
[INAUDIBLE],, see what it looks like.
[LASERS FIRING]
Whoa.
They can still kind of--
they can still sort of kill each other.
Yeah, because they move in such
a way that the movement itself
might be slightly off of when
the projectiles themselves move.
So I think what we should do is set the
movement interval to 0.7 and not 0.75,
so it's not in between a second,
because all the lasers will always
fire at a second, I think.
Oh, I guess not, because when we
kill the alien, it changes the--
oh whoa, that was weird.
I got an alien to die
above another alien, which
is probably because I shot a
projectile and it moved right into it.
That's pretty cool.
[EXPLOSIONS]
So yeah, lots of fun stuff.
To do features where we ask a
player named, score, high score,
with the player name, levels, et cetera.
If the alien should check whether
any alien is in front of it
or not before shooting.
Yeah, that would be one way to
solve the problem a little bit more
appropriately.
The problem isn't that
there's an alien front of it.
The problem is that it
moves into it on the frame
after it's shot the projectile.
So anticipating that is
a little bit trickier.
I could just make all the projectiles
always spawn at the lowest y-level,
but that's not as convincing-looking.
We won't spend too much time
fine-tuning that, I don't think.
We'll kind of consider it a feature
for now, just for the sake of time.
I think if we had more time, we'd
actually implement perfect laser firing
from the aliens.
It's pretty good, though.
It works pretty well.
Andrej says, you could do what
the other, original game did.
With game creation,
I found some glitches
that you did not initially intend
to be a cool and unexpected feature.
Yeah, games are so crazy a lot of
the time that stuff that you try out
and mess up ends up
actually being interesting,
and demos you create end up being fun.
It's pretty cool.
And then, Andrej said, you could
do what the original game did.
But I'm not sure what that is.
Andrej, if you wouldn't mind
elaborating that'd be awesome.
Aliens' lasers would
be ignored by aliens.
Oh yeah, we could do that too.
I could just-- yeah, I could
say if projectiles down
and it collides with an alien, do that.
It's not super important
at this point, I think.
We'll just let it exist as it is.
It works well enough for the demo.
It's probably somewhat
fairly easy to fix.
And I encourage, actually, if you're
following along, to try to fix it,
because that'd be cool.
Let's implement a score
mechanic in the play state,
and then let's feed that
into our game over state.
So let's go ahead and set a score to--
here, I'm going to
set the score to zero.
And in the render function, I'm going
to draw the score after the ship.
So LOVE.graphics.print score,
two string, self.score.
Just like that.
So that'll print a score in our play
state, which it does, very top left.
Pretty cool.
It's not going to change,
though, because we don't
get a score any time we kill an alien.
But that's easy.
All we have to do is go
to our update function,
and whenever we collide with an alien--
which is here.
Say, self.score equals
self.score plus 100.
Pretty easy, right?
But we're not feeding the
score to our game over screen,
so we can't see what
our score actually was.
So we should probably do that.
So I'm going to, in our what we call
game over, which is-- where is that?
That is right here.
So game over, remember the change
function can take parameters.
So what we're going
to do is, we can say,
we're going to pass in an anonymous
table, and I'm going to say,
score is equal to self.score.
And what this is going
to do is, this is going
to pass this table to
the game over state
with a key of score that equals the
score as it is right now, self.score.
So in game over, I'm going to
game over a state, Enter, params,
self.score is equal to params.score.
So remember, that table is going
to get passed in our state machine.
This is what params is.
It's going to be fed into
current state enter, which
is the new state that we're getting.
And we're just pass in
that params as a table.
So then, game over state.
Here we have params.
We have access to it.
Self.xcore score is
equal to params.score,
and then here I can just say,
your Score Two string, self.score.
So go ahead and get some points.
Whoa, that's horrible, because
all I did was copy and paste it.
So what I'm going to do
is, I'm going to say,
virtual height divided
by two, plus 32, I think.
Try that.
OK, pretty good, not bad.
Not as much as I wanted it to be.
Let's do plus eight.
Cool.
That's nice.
That's pretty nice.
We can move it up a
little bit, probably,
to be more vertically centered.
We won't worry too much
about that kind of detail.
That's something that you can just
solve by messing around with it.
But yeah, that's pretty much it.
I guess one more message we
could add to the title state
would be the press Enter.
Just like that.
There we go.
Space Invaders, press Enter to play.
When we press Enter, we play.
We do this.
[INAUDIBLE] High score of 900.
Space Invaders, press Enter to play.
Bhavik Knight says, what does the
dot dot do in front of two string?
I forgot.
Dot dot is the string concatenation
operator, so what that does
is it will just add two things
together and just add one directly
onto the end of the other one.
So in some languages, like
Python, you can use use plus.
Two string just means
convert a non-string value--
a numerical value-- to a
string, and we're using dot dot
to concatenate your score.
And then, that string converted
value, just adding them together.
All right.
That was pretty much all I aimed
to complete for today's stream.
I do want to stick
around for any questions,
if people have some, before we adjourn.
In case I went too fast over
something, I would like to do that.
Maybe receive any ideas or
suggestions for future streams.
That would be awesome, as well.
I know that [? Aslee ?] did
say she wanted a typing game,
so I have been thinking about that.
We'll probably do something like that.
I might do hangman first
before I do the typing game.
We can use hangman, build
a hangman, and then use
some of that code to develop the
typing game, which would be cool.
And we could talk about
what kind of typing game--
just a generic typing game.
I've never made a typing test before.
I mean, a simple typing
game would be pretty easy,
just get the words correct, I suppose.
But yeah.
[? Aslee ?] says, yes, I'm
dying for the typing game.
Hangman would also be great.
That's also sort of a typing game.
I did debug it.
Oh, one thing that we do want to
do is push this to GitHub, right?
LS, where are we at?
This is in Pong, so out of there,
into streams, Space Invaders,
get status, OK.
Final stream version, cool.
So now, as of right this second,
the code that we did today
plus the code that we did
last stream is now on GitHub.
So if you want to clone it, mess with
it, try and add some new stuff to it,
definitely do so.
Definitely try to add new features.
It's a very good exercise.
Maybe use it to create your own
game in the future if you want to.
We have a full--
it's pretty much a full, I mean,
proof of concept, Space Invaders.
It doesn't have levels.
It doesn't have a lot
of bells and whistles,
but it will help you if you
want to start building a space
shooter of some kind, certainly.
Did a hangman game in 60 '01,
says Bhavik Knight, for a pset.
There was a typing game where you
got points as you typed correctly,
says [? Aslee. ?] Interesting.
Couldn't finish the story yet.
I'll share it here when I finish.
[? Aslee, ?] are you
building an adventure game?
I don't remember offhand.
If we talked about this on
a prior stream, I apologize.
Any questions at all about the
code today, or about anything?
Anybody have anything they want to
chat about for a couple of minutes?
We have a few minutes left, here.
Oh, text adventure in Unity.
Got it, OK.
Nice.
There is a Udemy course that teaches
how to do something like that.
If I'm not sure if you're using
that-- going through that Udemy course
by Ben Tristem, I believe.
That's a good course.
No, I got the code because I was late.
How was your holiday?
Says [? Aslee. ?] Holiday was great.
So I was in California.
I spent a lot of time with my family.
It was super cool.
Played games with my dad and
my brother and my friends.
Played some of the new Super Smash
Brothers, which is super fun.
Super good game.
Like I mentioned it earlier,
900 or something like that--
850 or 900 songs in that game--
insane.
Yeah, how about your holiday?
And anybody else in the stream,
what did we do over our holidays?
Certainly the weather was a little
warmer over there than it is here now.
It's in the 20s today.
It's a little chilly.
And again, more ideas
for games much welcomed.
I personally want to start getting a
little bit more into modern web dev
this year a little bit, so if folks
are open to more hands-on building
of websites using React
and other tools, that's
something that I think I
might want to do and learn.
So I wouldn't mind doing it on stream
if people are interested in that.
I think probably more people care
about web dev than they do about games,
so I'm not sure, but let me know.
Definitely that's what I'm using.
It's awesome.
I moved past the text
adventure to harder things,
but I'm a sucker for writing,
so I'm working on it.
Wow, 900 songs, that
sounds like a nice holiday.
Yeah.
It's a crazy game.
And yeah, it's a good course.
Hopefully you can make it through.
I would definitely stick through it.
Ben has a great teaching style.
I'm a big fan of his course.
[? Aslee ?] said, I
care more about games.
I personally think I care a
little bit more about games, too.
But I'd like to get good at web dev.
It'd be cool, I think it'd fun.
It would be good stuff.
Yeah, good stuff.
We made Space Invaders, everybody.
It took us two streams, but we did it.
So future streams-- so
probably realistically,
the next game stream for me is
probably going to be next week.
It's probably going to be Monday.
It's probably going to be hangman.
And then, another day
next week, maybe we'll
do the typing game, if
not the week after that.
David's on Friday doing Docker.
So that'll be really cool.
I'm going to try and get
some more people in here.
I'm going to try to get Nick in
here after a couple of weeks.
Nick is still on vacation, but as
soon as Nick gets back from vacation,
you'll probably get some
white hat hacking streams
from him on Kali Linux, and maybe
some other stuff maybe on Bitcoin
and who knows what else.
He's a jack of all trades.
For me in the future, hopefully React.
I have to learn it.
That's going to take me time.
It's going to be some late
nights and weekends in the future
before we can do that.
But next week, Monday--
anticipate Monday-- hangman stream.
Friday, David with Docker.
Tuesdays and Wednesdays are
going to be a little light,
because we have a class in
this room, but we might shift.
We might do a Monday, Thursday,
Friday stream schedule.
We may or may not still be
able to do Tuesday, Wednesday,
depending on how things work out.
It'll at least be about
eight weeks until we
resume Tuesdays and Wednesdays.
React, CTF, Docker-- yep,
that's all stuff that I
know that Nick was super interested in.
The CTF stuff was super cool.
Three days a week sounds great, says
[? Aslee. ?] Yeah, three days a week
is what we will strive for, if not more.
It depends on the room's availability.
This room is very popular.
Sorry if you have
already talked about it,
but is it possible to
generate an executable file?
Says-- I can't read that username--
[? CesarCXBR. ?] An executable file--
yes it is.
So on LOVE 2D's forum, their
wiki, Game Distribution
is the section you want to look at
here on their on their wiki page.
And it will tell you in detail
how to go about creating
Windows executable, for example, if
you're on Windows, or a Mac OS app,
for example.
And also, a .love file.
.love a very standard way that
people making LOVE applications tend
to distribute their games,
because it's pretty easy.
It's just a zip archive essentially,
but it will run with the LOVE framework.
Windows and Mac have all their
instructions here for making exes.
So this is how you would create, using
the command line, an exe using a LOVE--
sorry, this is how you would use the
command line to make an exe with a LOVE
zip having been already created.
It assumes that you've done that.
It assumes you've created
a super game.love thing,
or whatever your game is .love,
which is defined up here.
It's part of the process.
You have to do that.
And then, you need to--
essentially what you do is you
add love.exe on top of super game,
or whatever.
They say super game.love.
That's just the name of their game.
Whatever your game's name is .love,
you add the love exe itself--
the actual love exe--
so that it will run when you
double-click it on a Windows computer.
And then, it will receive the
name, whatever exe afterwards.
And they use this forward
slash b as well, which
I'm not entirely sure what that is.
It probably means binary, appending
that exe and the .love thing--
the .love binary on top of it.
And then, you can create a batch.
It has instructions for
creating a batch file to do it.
It's the exact same thing, sort of, for
Linus and OSX using the cat command.
So I do think that that slash b is
just adding them on top of each other.
But yeah, fairly straightforward.
Creating a Mac OSX application
is a little bit more complicated.
The Mac OSX application is--
yeah, yeah have a little bit more stuff.
They have, like, a package with contents
and some pllist files and whatnot.
But all the information is there on
that page, so definitely check it out.
One, two streams per
week, says Bhavik Knight.
Yeah, I don't want to
do only one to two.
I want to do at least three,
so we'll try to figure it out.
I have to figure out the
scheduling for this room,
make sure that we have the
time and the bandwidth for it,
make sure I can get enough
people to contribute,
which I know Nick's on
board when he gets back.
David's usually on board
once every week or two.
I can do one or two a week.
So we should have enough.
We'll work on it.
We'll get a schedule.
Am I teaching this semester
in the extension school?
Yes, online only, though--
online-only version of the GD50 course.
Andrej says, any Unity
streams in the near future?
Probably, yeah.
I would love to do
some more Unity stuff.
I'm a big fan of Unity, and I want to
get better at it and practice doing it.
So yeah, expect some Unity stuff.
I'll have to brainstorm and
think of some fun stuff.
I really want to make a 3D
platformer at some point,
as I mentioned on prior
streams, but that's complicated.
That's, like, a very large project.
So something smaller would be nice.
I'll think about it.
I have no concrete
games in mind just yet.
A stream to make an app would
be nice with React Native,
but perhaps [? Brian ?]
could help with that.
React Native-- that'd be interesting.
I'll see if [? Brian's ?]
familiar with React Native.
I've been asking him to get involved
in making some more streams of us.
He's doing a sort of mini
workshop course on campus,
so he's been a little bit
bandwidth locked, but maybe
after a couple of weeks have passed.
Any streams about functional
programming in the near future?
Says Bhavik.
Nothing planned, but I
would love to do something
like that, because I have
also been wanting to get
better at functional programming.
I'm a big fan of Closure.
And I've also wanted to learn Haskell.
So it would be fun to do
some of that on stream.
Solving the Rubik's cube in Unity.
Yeah, Bhavik Knight, that's true.
I had thought about--
brainstormed that briefly,
actually, last year after we
talked about it on stream.
And it would be possible, but it's
going to be somewhat difficult,
and I don't know exactly
offhand how I would do it.
So I would need to
brainstorm more, I think,
before actually committing to that.
I don't want to spend too much
time kind of umming and ahhing over
on the camera.
I kind of want to have a game plan
before I set up so that it's not
too awkward and quiet.
But we'll see.
I heard from a reliable source
that coding your own games
is easier than you think.
You know, you should take this
online Unity course on Udemy.
Online Unity course on Udemy.
Oh, that that's the
Ben Tristem reference.
Yeah, because he does that.
Did you finish the Udemy course?
I did not finish it.
I got through, I think,
a few of the games,
and then I got distracted
with something.
I have, like, 20 Udemy
courses I have to complete,
including some React ones that I just
bought, so we'll see how that goes.
I swear I see that 25
times a day, at least.
Last year sounds so weird.
Yeah, it was last year,
technically speaking.
I mean, it's been
almost a month since we
streamed-- it's been a month since
we streamed Space Invaders-- more
than a month.
The first part was November
26th, it was when I committed.
Udemy courses-- plenty in pending there.
Yes.
It is that.
So I use Udemy, too,
[? SisterDemure24 ?] I do.
Not a lot, to be honest.
The last time I really used
it was probably two years ago,
and I have recently bought some
new courses to try-- like I said,
I want to get good at web dev, so
I bought a couple of Udemy courses
on React and modern
web dev, so we'll see.
And some AI and machine
learning courses--
that is going to be a little
slower-paced for me, I think,
because especially if I want to
understand everything really well,
I think there's going to
be more math behind that.
We'll figure that out.
But AI and machine
learning are courses--
topics that would be cool to
have, like, Nick and someone else
talk about more on camera, because I
think those topics are really cool.
I was trying to finish the
Udemy course before Colton.
Student surpasses the teacher.
Yeah, definitely do so.
I'll be very proud of you,
[? Aslee, ?] if you do that.
I honestly don't know if I'll
get back into that Udemy course,
because Catlike coding is
a resource that I think
I would want to work through
first, and it goes into more depth
than the Udemy course does.
The Udemy courses a little
bit simpler, I think--
not that it's not very easy.
Not that it's not very easy.
It does teach you a lot and
there's a lot of good stuff in it,
but Catlike coding I think
is, like, pretty next level.
So if anybody is not
familiar, CatlikeCoding.com--
I'll pull it up here.
Really good resource for learning Unity.
Do it with the tutorial section.
I actually haven't checked it recently.
They might have some new stuff on here.
The person that does this is
really, really, really crazy smart--
does a lot of really good
stuff, really goes into, like,
the low-level side of Unity, which
is not something that you see a lot.
Like, actually has you digging
into the rendering engine
and doing, like, custom
shaders from scratch,
and all kinds of really cool stuff.
Yeah, really cool.
CatlikeCoding is a brilliant
website, says [? Andre. ?] I agree.
Udacity-- I like Udacity
courses more than Udemy.
Udacity offers nano degree
about some AI and ML courses.
Interesting.
I haven't looked too much
into Udacity, to be honest.
Coursera I have seen.
I think Andrew--
I don't know if it's Andrew--
I don't know how to
pronounce the last name--
Andrew Ng, but he does an ML course,
and his is very well-received.
And I think he's on Coursera,
if I'm not mistaken.
His was one that I wanted to get
through, but it's a little dense.
And I haven't had time
to really dig into it,
or the motivation to really go all-in
on Machine Learning, but sometimes soon.
EdX and Coursera are the best
because of the affiliation
with a great university.
Yeah, edX and Coursera are great.
Speaking of that, CS50 2018
materials are edX now, FCS50x, 2019.
So check those out if
you want to see all
of the latest lectures and
the latest course materials.
We got that shipped over the
winter break, so super cool.
Can everybody else take an even
vacation while I catch up on coding?
Says [? Aslee. ?] Oh, wouldn't that
be nice, an even longer vacation?
The landscape is always
changing, I guess.
But you know, as long as
you put in the effort,
I think, and you're
consistent about it, I
don't think you need to worry about
everybody surpassing you forever.
I think you'll be just fine.
Just go at your own pace.
Enjoy the process.
Enjoy the journey.
You don't want to spend years doing
something that you hate doing, right?
And eventually you learn-- or at least,
I learned to really enjoy the journey.
The destination is great--
being able to build stuff.
But learning stuff-- if
you can enjoy learning,
then you'll always be on top of
things, or at least relatively so.
You'll always at least
enjoy the process,
and I think go farther than a lot of
people that are less interested in it,
or maybe monetarily influenced.
You know, because
there's so much to learn.
If you don't enjoy it, then it's not
going to stick as much, in my opinion.
I don't enjoy everything I learned
in CS-- not every single thing.
But I enjoy.
I would say I can appreciate
enjoy the vast majority of it.
Certain things I don't
really enjoy too much.
I can't think of examples
offhand, but there are
certain topics that are less exciting.
I definitely agree on almost everything.
I wanted to learn stuff
like in The Matrix.
Yeah, that'd be really cool.
Everybody has their own time.
Some start a business at 25 but die
at 50, and some start life after 50
and work 40 years.
Yep, very true.
Very true.
It's all relative.
Like CS50 says, it's not
relative to other people.
It's relative to how you
were when you began, right?
How you are now relative to how you
are at x time ago is what's important,
not how good you are
relative to your neighbor.
Is the new CS50 stuff out?
Yes.
The new CS50 stuff is on edX now.
That's the one thing I love
that the professor says.
Yep.
It's a great, great expression.
Very true words of wisdom.
OK.
I think we're about ready to wrap up.
It's been a pleasure talking with all of
you, and a pleasure being back in 2019
on stream.
I'm glad that we were able to get
everything set up and working,
and everything was good.
We learned and completed Space Invaders,
so it's a very successful stream,
in my opinion.
It went smoothly.
There weren't too many great hiccups.
[? Thalamus, ?] it was great to see you.
You rocked at the Harvard Fair.
Thank you so much.
Good to see you as well.
Hope to see you on TV.
Timing for Fridays?
Says Bhavik.
David's going to be on at 3:30 Eastern
Standard time, so a little bit later.
[? Ichimasio ?] said,
thanks for tuning in.
And that means I will go.
Fun fact.
I will go!
Exclamation point.
Good to have you back, says
[? Aslee. ?] Thank you very much.
Cool.
All right, everybody.
I'm going to transition to
the bigger screen, here.
I'm going to bid all of you
a awesome rest of the week
until Friday, which I'll be
here with David co-hosting.
We'll talk about Docker.
Docker is super cool, super modern.
It ties in well with
all of the dev ops stuff
that [? Kareem ?] and I
have talked about before.
[? Kareem-- ?] I'm going to try to get
him on stream, as well, if we could.
It would be nice.
[? Caesar ?] says, it would be nice if
you could present a minimum Super Mario
game style.
For example, where it seems they
work with some kind of maps.
Yeah.
Yeah, we could maybe
do something like that.
We would do that in my
games course, actually.
We implement a version of Super Mario.
So if you haven't checked
that out, go to CS50.gg,
actually, should now point to the games
course, although it's old content.
Yeah, this is the games course for 2019.
Table of contents, Super Mario Brothers.
Is it still here?
No, it's not.
The URLs are all--
what's it have?
Sort of temporary before we
actually get the class fleshed out,
but if you go to CS50's YouTube channel,
our game development playlist, and type
in Super Mario Brothers, I show how to
implement a sort of basic Super Mario
Brothers style game.
And we could maybe certainly
take a look at doing something
like that again in the future.
It's a nice, fun demo.
Cool.
All right, everybody.
Thanks so much for tuning in.
See you all on Friday.
Enjoy the rest of your week.
This was-- this is CS50 on Twitch.
And I'll see you all next time.
Bye-bye.
