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