[MUSIC PLAYING]
COLTON OGDEN: All right,
welcome to GD50, Lecture 5.
Today's topic is The Legend of Zelda,
as you can see on the screen there.
A very iconic game.
Last week we did Super Mario Bros, which
is arguably the most iconic video game
of all time.
Legend of Zelda is a close contender.
Even now, just like with Mario,
they're producing very wonderful games.
I think last year Breath of the
Wild took all the, I forget which
ceremony it was, or award sort of show.
But it took like every award possible.
Just last year, the latest
Zelda, the Breath of the Wild.
So it feels sort of apropos to talk
about Legend of Zelda in that context
as well.
Here's a screenshot of the
original Legend of Zelda.
It was an NES title, just
like Super Mario Bros--
just like most games of
its time, tile based.
You can sort of see how the surroundings
are sort of layered tiles bit by bit.
The goal of the game overall was to
sort of explore this open world, which
is kind of a first of its kind.
You controlled Link, shown
at the very bottom there.
You had a sword, yo had
bombs, you had arrows.
You want to go find rubies,
you went through dungeons.
You slayed monsters and bosses,
and then ultimately the goal
was to obtain the Triforce which is
shown at the very top left and right
of the slide here.
Here's a screenshot, another
screen shot of Legend of Zelda,
inside an actual dungeon where
you can see at the very top,
there's got like a map layout.
Maps are sort of arranged in a grid.
And you could go in a room by room,
whereby each room was the entire width
and height of the screen, looking for,
solving puzzles, looking for items,
that sort of thing.
You can see there's a monster there.
You have hearts at the very top right.
So when you take damage,
hearts sort of decrement.
Will see both of these, the hearts
and the dungeon aspect of the game,
today in lecture, as well as
the width and height of the room
being a dungeon tile.
And controlling an avatar.
But this is sort of a
representative screenshot of what
the game looked like back in the day.
Some topics today that
we'll be discussing in order
to implement sort of the foundation
for what a game engine like this
might look like are things
like top down perspective.
So as you can see, in
this screenshot, we're
looking at Link from the very top
in a bird's eye view, as opposed
to how we used to look at
Mario, from the side, where
it was sort of like a side scroller.
Here we're actually looking
at things from the top down.
So we have a view of the room in
a sort of a different perspective.
There's not really a z
axis, technically speaking.
But there sort of is.
You can implement things like
gravity in a game engine like this
by jumping over holes
and things like that.
But there's no gravity like
there was in Super Mario,
where you walk left to
right, you jump over gaps,
and it's very easy to see in that sense.
We'll be talking about
infinite dungeon generation.
So in the context of
today's example, we'll
see how we can go about implementing
a dungeon that sort of you
can go through forever and ever,
and how to sort of model that
and make it look as if you're traveling
through a dungeon over and over again.
And going through different screens in
different rooms that are all different.
But in reality, we'll see it's
actually just an illusion like we've
seen before in prior lectures.
Well talk about hitboxes
and hurtboxes, and what
the difference is between the two.
Hitboxes being rectangles on
the screen that inflict damage
upon other entities in your
game world, and hurtboxes
sort of being the rectangle
that models where your player,
or where an entity in the game
can be hurt by other hit boxes.
Well look at events.
So events are a way of broadcasting
some key message that tells
the game world oh this thing happened.
And let me register a function to
call when that event is processed.
So on some event, dispatch
an event, and then
upon that event being received
by whatever is listening for it,
perform this chunk of code.
It allows you to sort of
decouple aspects of your game
engine a little bit, and makes for
a little bit more readable code.
And allows you to do some
interesting things with achievements,
for example, where you don't necessarily
want to pull every single frame.
Oh, did I do this sort of arbitrary
list of things this frame?
Rather, you can just process
all of that with an event,
and have a listener and a function that
every time you broadcast pick up coin,
maybe I have an
achievement that's like oh,
how I picked up 50 coins in this level.
Your achievement callback function
in your event can then look for that,
and in your game loop, you
don't have to say every frame,
if a player has 850
coins, and you sort of
take out bloat that would exist in your
sort of overall game loop that way.
And we'll see an example of how
we use that in today's lecture.
We'll look at screen scrolling.
So a very iconic aspect
of Legend of Zelda
is when you're going from
one screen to another,
there is a sort of transition period as
one screen loads and the other screen
goes away.
In hardware, in the NES,
this was sort of the only way
you could actually render more
than a screen width of tiles.
You actually had to dynamically
load in tiles and sort
of overwrite tiles that
existed before that in memory.
But today, we'll see how
we can sort of create
the illusion of doing that
by just drawing a room
and then having our main room, and
then just sort of tweening over,
and then setting everything
back to the origin at 0.0.
And it makes it look as if we're
moving back and forth between all
these rooms, when in reality all
we're doing is just doing a shift,
and then putting everything back to 0.0.
So I'll illustrate that
on the screen, if I can.
And one of the last things we'll
look at is data driven design.
In the context of a lot of types of
games, particularly RPGs and action
games, it's often very valuable to
be able to model all of your items,
entities, sort of abilities,
anything you really
can, as data rather than logic, in order
to make it easier for you to write one.
And two, for you to sort of allocate
the design aspect of your game
to other people, not just programmers.
And have an engine
that's very versatile.
And model and we'll take
a look at how we implement
sort of a foundation for that later on.
But first I'd like to illustrate
a demo for today's lecture,
a sort of implementation
of Legend of Zelda.
Is there anybody that would like to
come up and sort of demo this today
in class?
Awesome.
Thank you so much.
So whenever you're ready, go ahead
and press the Return key there.
[MUSIC PLAYING]
And so we see on the screen, Legend
of 50, which is our Legend of Zelda
you rip off.
So you press Enter.
You go into, this is the
play state of the game.
So you control an avatar.
You can walk around.
He can interact with
switches, as we saw here.
When he presses a
switch, the doors open.
And once the doors are open,
you can walk through them
and it does, as I alluded to before,
it transitions the screen, one
entire screen height or width, depending
on the direction, up above and below.
We see here now where we're pressing
spacebar to actually swing our sword.
It's destroying the
entities in the game space.
So there's a hitbox triggering when
you press the space bar that collides
with other entities in the game world.
If that hitbox hits their hurtbox,
then they are flagged as dead,
and they disappear from the game world.
And so this goes on ad infinitum.
It's just an infinite dungeon.
So he can go in-between as
many floors as we want to.
All we're doing is every time we go
through a doorway, spawn a new room,
delete the old one, and just keep
going forever, and ever, and ever.
In a game like Legend of Zelda, it
typically doesn't work like this.
There is a hard set number of rooms, and
they all exist in sort of a 2D array.
And when you generate
your dungeon, you sort of
have to take into consideration
things like where do I put the keys?
Where do I put the boss?
Where do I put treasure?
That sort of thing.
We won't get too in-depth as to how we
can implement a complex algorithm like.
But we'll touch on it a little bit,
and talk about maybe some ideas
that we have.
And then lastly, if we
just-- if we want to demo--
if we can see the top
left, we have hearts there,
which is iconic sort of Zelda.
When we do take damage from an enemy,
notice that we flicker a little bit.
So there's some rendering
behavior triggering,
and we become invulnerable
when we take damage.
And then lastly, when we
finally take the last hit,
we get a little game over
screen, using the Zelda font.
And that's the game in a nutshell.
We press Enter and loop back.
So pretty simple altogether,
but there's a lot of pieces
here that we haven't really seen before.
So thank you very much for
coming up to demo the game.
All right.
So that was a demo of Legend
of 50, Legend of Zelda.
It has a lot of the
pieces that Zelda has.
It's not as fully
fleshed out, of course,
as a full game like Zelda, which
would be monstrously large.
And there's a lot of things
we need to factor in.
But the foundation is there.
We have the dungeon foundation upon
which we can build an actual generator,
if we wanted to.
We have entities.
We have hit boxes.
We could easily model, because
we have switches in the game,
we could model things
like treasure chests that
open that have different states, just
like the switch had different states.
And have different objects in our game
space interact with other objects.
We have a lot here, and we'll
talk about all of it today.
So here's a few screenshots just
to show what we just looked at.
Our goal mainly is the
second screen shot,
on the top right, which is
the play state, which has
all of the little pieces we just saw.
But just to tie it all together,
we do have a start state here,
and a Game Over state
which is relevant in regard
to the hearts on the top left.
So the first thing that I'll talk about
as we sort of get the engine built up
is, where do we go for getting
our assets into the game?
And just like in prior
lectures, prior to today,
we just have a sprite sheet,
like we did with Mario,
where everything is laid out in a
fairly even sequence of tile segments.
In this case, the segments
are 16 by 16 pixels.
Here, I've overlaid a grid just to show
that the picture is indeed perfectly 16
by 16.
And this is something that you
should take into consideration.
And consciously do when you're
building your game assets.
Make it easy to chop
up into little pieces,
so you can then index into this
sprite via some table of quads.
And then assign an ID
to whatever object,
or tile you want to
render to the screen.
And so we see if you look
here, things like the doors,
they're not perfectly
modeled by one tile.
And so when you do have things
that are larger than 16 by 16,
it's not quite as easy as oh have an
entity or an object with this frame ID,
and draw it to the screen.
You actually need to a little bit
more complicated render logic.
So just offhand, if I
wanted to draw a door,
does anybody have any suggestions
as to how I would model that?
And or draw it to the screen?
So if we have a door that's not
perfectly a 16 x 16 pixel tile,
offhand, what we need to do is
just basically store all four
of those tiles in this case, or at
least keep track of however many tiles
your object is.
And then just draw them
based on some offset.
Have an xy that represents
the top left of that object.
Place it in the right position, and
instead of drawing just one tile,
we draw four tiles.
And then instead of having just one
collision boss that's 16 by 16 tiles,
you can do a couple of things.
You can either check collision
on all four of these tiles,
or at least all two of these door
tiles, or model your own custom hit
box that then maybe that object that
represents a doorway has control of.
So you can say doorway collides
player, and then the doorway
has a height and width of however
many pixels it is wide by tall here.
Just this door part.
And then, you are able to then build
upon not just having one tile that
models an object or
entity in your game space,
but now you have more
artistic flexibility.
You can do things like have doorways
that are more than one pixel wide.
And sort of have a more convincing
gameworld as a result of that.
You'll see a lot of things
like that in games like RPGs
where you have full houses that
are obviously not just one tile.
You can model things as
one tile if you want to,
but from an artistic perspective, and
from just a game engine perspective,
it's a little bit easier
to programmatically
be able to cut up all your assets
into tiles and draw them as such.
So being able to build
upon just the single sprite
and be able to do
multiple sprites represent
an object is the key to things
like houses, things like big trees,
things that are just more complicated
than single tiles as we see here.
The character sprite sheet is, as you
can see, a little bit more complicated.
So this is an example of a
sprite sheet that has padding.
Sometimes you will get sprite
sheets that aren't neatly divided
into even segments.
And for good reason.
Because sometimes you have
tiles that are not perfectly
the width and height of whatever
your game engine's tiles are.
In this case, the player is actually
20 pixels tall, and 16 pixels wide.
And on top of that, he's got
different frames of animation.
We can see here at the bottom left,
he's got a sword swinging animation.
The sword swing animation is actually
stored in a 32 by 32 pixel frame,
because sometimes depending on
which angle he's looking at,
his sprite can get a
little larger or smaller.
So when you have a sprite with
padding, what's sort of a way
that we can draw this
to the screen reliably?
How do we take this into consideration?
How would we render a
screen sprite with padding?
So let's say this sprite here, our
character swinging a sword, is in a 32
by 32 pixel box.
Right?
This sort of box around
here, this white space.
But he's only still maybe 16
pixels wide by 20 pixels tall.
Or maybe he is some
amount similar to that.
In order to draw him to the screen,
which you can look at in the code,
all we really need to do is
assign that sprite an offset.
Just say, OK, this sprite's
offset x and offset
y are some value that basically
lets us draw the sprite
to a negative x and a negative y value.
And that'll shift the sprite up,
such that it aligns perfectly
with wherever his xy are.
And you can take a look at the
code to see exactly how that works.
Lastly the actual entities for our
game world are different creatures.
And this is a sprite sheet-- this is
a more ideal sprite where everything
is 16 by 16 pixels wide and tall.
And all we really did here was
just divide the sprite sheet
using the regular utility
function generate quads by 16 16.
We don't have to worry about separate
widths and heights for everything,
and we can create animations
very simply because of this.
As a side note, something that
I like to do, when I'm parsing
or when I'm piecing apart a sprite sheet
that's got a lot of individual frames,
particularly if I'm creating
animations, or I just
need to know for a tile, a particular
tile to draw to the screen.
It's kind of a pain to manually look
through each and every tile, one
by one.
And say OK, this is one but OK.
But if I want to find out
which one the slime is,
I got a count, OK how many
pixels wide does this?
OK, and then it's times 4, it's 12.
And then it's on the fifth row, so
12 times 4 plus 1 will give me, OK.
So it's at index 49.
So I've spent a non-trivial
amount of time sort of hand
calculating what all these do.
I wrote a simple Python script
that will just go over a file,
and just add a digit to
each individual quad,
to just show you at a
glance what each sprite is.
And this is including the distro,
so you can see what it looks like.
Saves a lot of time.
And I recommend trying, when
you're sort of working with assets
and you find yourself doing
something that takes a long time,
and it's sort of tedious
and mechanical, maybe
try to find a way to sort of
automate that, or at least
make it easier to do things at a glance.
In this case, just simply imposing a
numerical grid solves that problem.
I don't have to spend a
significant amount of time
figuring out which frames of
animation the ghost facing left is.
I know instantly it's 67, 68 and 69,
and allows me to just crank things
up that much faster.
So the first thing we'll take a look
at is sort of top down perspective.
And we mostly talked about
this before, earlier.
But all it really is is a tile
map, which we've seen before.
The only difference is now, instead
of looking at things from the side,
we're just looking at
things from up above.
So what's probably the
most obvious consideration
when designing a top down perspective
versus a sort of side scrolling
point of view?
Looking at particularly at
how the tiles are drawn?
What stands out?
So the thing that stands
out to me is that we
have things like shadows on walls here.
We have also corners,
and things altogether
are skewed such that they are almost
like rotated slightly as if they're
simulating an angle of rotation relative
to the camera facing from up above.
You can see this on the
player, for example.
It looks like you're looking
at him from backwards and up.
When you're modeling your assets
that way, it's more convincing.
And Zelda has always done
this, to make your assets look
as if they are slightly tilted,
and that you're looking up above.
So when you're designing
a top down game, just
for convincing the sake of being more
convincing, try and emulate that.
The entities here, like
the skeleton, and such,
are a little bit more straight on.
The bats and slimes and
whatnot, even though they still
have a little bit of that appearance.
Like the spider it sort of looks
as if it's from the top back.
But modeling your assets from a top
down perspective, mainly the thing.
Pay attention to shadows
and highlights, which
adds a lot in terms of convincing us
that we're in this room with lighting.
And also make sure that you're
doing things like corners and stuff
and making, it look as if things
are slightly skewed rotation wise.
The first thing that we're going to look
at in terms of the code in the distro
is Dungeon Generation.
So in Legend of Zelda,
dungeons are fixed.
They're completely set in
advance by the designers.
And in most games, this
is actually the case.
In our example, and in a
couple of other examples,
a primary example of them
being that I can think of,
that I have in the slides, a famous
game called The Binding of Isaac,
dungeons can also be generated.
So what's the sort of like
the main unit of a dungeon?
At least in the context
of Legend of Zelda?
If you had to distill
what comprises a dungeon,
what's the most fundamental unit?
Tony.
A room.
Yes.
A room.
So we can almost look at this, if we
picture it in terms of a 2D array,
right?
We have, assuming that
this is like index 1.1
in Lua, 0.0 in other languages,
going left to right, top to bottom.
Basically we have on or off,
relative to each of these.
Off, on, off, off, off.
Each of the indexes in
this 2D array holds a room.
And so the room has connections
implicitly between the other rooms.
If you wanted to go, let's say,
from this room here to the room
up above it, what's the offset,
in terms of the 2D array?
How are we going from
this room up to this room?
So we're just going up a y level, right?
So this is x level 3, y level 3.
If we wanted to go up to the
next room, we need to load in.
If we're doing it the Zelda way, right?
And we're just going--
we're doing a transition
between one room
to another, what we need to do
is load in the room at this room,
minus one on the y, and
then perform the transition.
And then set that to
the current room, such
that now we know we're
at y level 2, x level 3.
That's our dungeon.
In the context of 2D dungeons
in The Legend of Zelda,
that's as simple as it really is.
You have a 2D grid of dungeon rooms.
Each room has its own collection
of entities and objects
and connections to other rooms.
But really, all you do to fill a
dungeon is fill an array in a smart way
such that there is no rooms that
are, for example, left by themselves.
Notice that every room in the
dungeon has at least one connection
to another room.
And that when you're
maybe doing your algorithm
to create a convincing dungeon,
let's say this room here has
a door on the right, that has a lock.
Right?
We want to make sure that
the key isn't in that room.
Because if it is,
we're never going to be
able to get to it, assuming that
we come from another direction.
So when you're designing
dungeons procedurally,
you want to take these sort
of things into consideration.
And then for example, the boss room.
Let's say this is the
boss room, the boss room
should have maybe a boss
key, or something like that.
But the boss key should not
be, obviously, in that room.
It should be somewhere maybe where
there's a couple of rooms before it
that have a lock or a key, so that you
know there's some sort of challenge
involved in your dungeon.
It's not just random
as we've done before.
There has to be a little bit of
sort of conscious design on behalf
of your algorithms.
Today we're doing
things completely random
for illustration, just because a system
like this is fairly robust and complex.
But with some effort, you could create
a simple dungeon generator just using
those mechanics.
Just make sure that
you have locked doors.
The locked doors can only
open when you have a key.
Make sure the key exists in
a place that's accessible,
and sort of create a
chain of a control flow.
Model maybe via graph of some
kind that represents your dungeon,
and the progression thereof.
So that's what a Zelda
dungeon looks like.
That's what a 2D dungeon in this
sort of perspective looks like.
And it looks-- it will look similar
to this in other game engines.
It doesn't necessarily have
to be perfectly modeled
as a screen width, screen height room
going into another screen width, screen
wide room.
You can have arbitrarily complex rooms
that have arbitrarily complex sizes
and shapes.
But you still need to make sure that
the connections going out of the rooms,
like if you can still model left,
right, up, down, if you want to.
You can model arbitrary numbers
of connections between rooms.
Just make sure that you have
puzzles that can be solved.
That's the main sort of obstacle
in generating your dungeons.
Here's a game that I really like that
uses the old Legend of Zelda formula
to very good effect now.
It's called The Binding of Isaac.
Notice already we can instantly
see that it's top-down perspective.
It's the entire width and height
of the room is the dungeon of--
the entire width and height of
the screen is the dungeon room.
You have a map up here that shows
you, OK, I'm in this room right here.
I can go up, I can go left.
I can go right, I can go down.
This room up here with
the yellow crown, that's
going to be locked behind
some door with a key.
So we need to have keys that
spawn in any of these rooms that
are just blindly accessible.
And Isaac does things a
little bit differently also
in that it generates
keys and bombs randomly,
so that you can actually get--
you don't necessarily have to plant
your keys in very specific locations,
if your algorithm is sufficiently
accommodating and complex enough.
You can just at, the end of every room,
have a chance to spawn a key randomly.
And if you're lucky, or if
you're not lucky, and assuming
that the end of the dungeon doesn't
exist behind your locked doors.
You have the opportunity to stall
and lock those doors, or not.
And just go on through
your dungeon as needed.
In this case, they don't
lock the boss doors.
So you can go through the boss door
regardless of whether you have a key.
So they've accommodated for
this purely random approach,
and that just goes to illustrate
how you can still take randomisation
using very complex
principles, and produce games
that are extremely addicting and fun.
And you don't necessarily
need to be super elaborate.
The first thing, OK.
So hitboxes and hurtboxes
will be the next topic.
So I want to touch on, in the
code, how we sort of do dungeon
generation in the game engine here.
So I'm going to open up--
there's a few files.
So doorway, notice in the source
distro, there's a World folder.
We've sort of categorized
all of the files that
have to do with world generation
within that World folder.
We have a doorway file, we have a
dungeon file, and we have a room file.
So the dungeon file models the dungeon.
All of the rooms that form our
dungeon, just as a very high, level top
level, data structure.
It's actually very simple.
The room is, as we saw before, the
individual unit of the dungeon.
So a dungeon is effectively
like a table of rooms.
And then it holds the code for how
we can transition between them.
But that's sort of how
we can think about it.
When you a game level in
general, in this case dungeon
is sort of our game level.
You can model the
different subset aspects
of your level via some data structure.
In this case, we've
decided to make it room.
But if you have just
a platform or level,
maybe you have regions
or zones, or anything.
Just little subsegments of your level
that you can transition between.
It's useful to think in terms of
that, because from an efficiency
and performance standpoint, you
want to dynamically probably load
certain levels one at a time.
Certain aspects, certain components,
sub areas of your level, one at a time,
rather than just the
entire level at once,
because depending on how sufficiently
complex and large your level is,
you can get to exercising your
computer's memory constraints.
So don't want to do that.
So in this case, our algorithm is
we have a dungeon, we have rooms.
We have one active room that's
only visible at one time that's
loaded in memory.
And then whenever we
transition between rooms,
we want to have another room
that gets temporarily loaded.
So let's look at the dungeon class here.
Dungeon.lua.
So 14 self-taught rooms, empty table.
We're going to fill that with
rooms, just room objects.
Self.currentroom.
Room, and we're going to
pass it in the player.
So that the player has access to all
the entities and objects therein,
and can do things like collision
detection, which is very important.
And also so that the entities in
the room can look at the player
and then decide what they want
to do with their AI, which
you can model arbitrarily complex.
The whole init function of room.lua
is going, we're going to show it here.
So in room.lua, in
that same world folder,
we've instantiated the
dungeon at a high level.
We know the dungeons, it's
basically a table of rooms.
So what's a room look like?
Well a room, as we saw
before, what are the pieces
that a room needs in order to function?
What does the room have to
sort of keep control of?
AUDIENCE: Doors.
COLTON OGDEN: Doors.
Anything else?
So doors it has to keep track of, right?
Because when we touch a door, we
should transition to the next room.
It should keep track of the player.
So the player can update.
It should keep track of objects
in the room like switches,
as we saw in the example, such
that they can update those.
And see has the player
stepped on the switch?
If he has, open the doors.
And then entities, right?
It should keep track of all
of the creatures in the room
so that they can update, they
can interact with each other.
The player can hit them,
or they can hit the player.
And you can model whatever
interactions you want to.
So we can see here.
Oh, and another thing too.
Probably one of the more
important visual aspects of it,
we need to also have a set of tiles
that model what the room looks like.
It's a container.
So we're going to draw, we have a
corner, a corner, a corner, a corner.
Walls on the sides.
Walls on the top and bottom.
And then a floor.
And so we need to
obviously draw the room
before we draw all of the other things,
and update all the other things.
So tiles, doorways,
entities, and objects.
So we can see here.
You have tiles, soft-lit
tiles against an empty table.
We have a function called
Generate Walls And Floors.
Entities, empty table.
We have a function
called Generate Entities.
And then objects equals empty table.
We have a function
called Generate Objects.
And lastly, doorways.
And then here because
it's not necessarily
as complex as we need for a entire
function, we just have four doorways.
And this is static.
I've chosen in this example to
have all the doorways always
be in the same place, and sort
of behave in the same way.
But you could create a more
complex system, whereby especially
if you have an algorithm that
generates a dungeon dynamically,
and maybe your rooms aren't
necessarily hard set.
Maybe you have a broom, right?
If we go back to our slides earlier.
And let's say we're looking at this
room right here in the very center.
We can see it has a doorway
on the left, a doorway up top,
and a doorway to the right.
But there's no doorway on the bottom.
And that looks like an arbitrary design
decision on behalf of the designers.
But if we look at this
room up top here, we
can see that it sort of
behaves in the same way.
There's a doorway going
into this room to the left.
There's doorway going
to this room down below.
But it doesn't have a doorway up
top and a doorway on the right,
because there are no rooms going
between that room and those directions.
So when you have a 2D
array of dungeon rooms,
and you want to model your
doorways, it can be as simple
as is there room in that direction?
If there is, and you will have
your data structures sort of
laid out in advance before you generate
these doorways, if it does not exist,
don't make a door.
If it does exist, make a door.
And then make sure that
when you transition
between from the right to the left,
or from the bottom to the top,
that you go to the correct rooms at the
correct indices in your 2D room array.
Does that make sense?
Anybody have any questions so far as
to like how this works at a high level?
OK?
Awesome.
So these doorways here are
all because of this dungeon
is completely random, and every time it
always has doorways going top, bottom,
left and right.
We're always just going
to put four doorways,
going top, bottom, left and right.
Note that they take a
string denoting which
direction they are, and
this will become important
later on in the doorway
class, which we see here
is modeled as a separate class.
False here just means is the door open?
So by default, we're
going to close the door.
And then self, so that we have
access to the room from the doorway.
The room should have a
reference to the player,
so that it can model interactions
between the entities and the player,
as well as the objects and the player.
Notice that it has a render offset.
So if we look at the
game here, the tiles
don't completely match up to the
width and height of the screen.
And that's mainly a function
of the virtual height
not mapping perfectly to 16
tiles, divided evenly by 16 tiles.
So what I've done is I've made
the dungeon a little bit smaller.
And also the fact that the doorways take
up a couple of extra tiles of padding
on their sides, it's like
sort of blank space up here.
We've shifted everything
inwards a little bit.
We've made the dungeon two tiles smaller
width and height wise than the screen.
And then we've just rendered
offset by a certain amount
such that it's completely centered.
We calculate how much padding exists
between the fully rendered dungeon
and whatever blank space is left, and
just shift by half of that amount,
and that's our render offset.
So that's why render offset
is important in this case.
When you're trying to center
anything, you typically
do it just by calculating an offset.
Calculate the width and height of
whatever you're trying to draw,
calculate whatever your width and
height of your screen is minus that,
divide it by 2, that's
your render offset.
And then these two fields here are
interesting. self.adjacentoffsetX,
self.adjectentoffsetY.
Does anybody have a guess
as to what this is used for?
Yeah.
Drawing itself when
it's the adjacent room.
Yes, exactly.
So drawing itself when
it's the adjacent room.
So when you load the
next room, you're going
to instantiate a room just like this.
But by default it's
going to draw at 0.0.
However, if we want that room
to draw not right where we are.
Obviously, if we're in
the current room, we
don't want the next room
drawing right on top
of where we are, because then
it's just going to layer right
on top of the room we're in.
And we want to draw it--
if we're going to the right, we want
to draw a screen width to the right.
If we're drawing it to the left, we want
to draw it a screen width to the left.
And same thing on the y-axis.
A screen height above, or below it.
So adjacentoffsetX or y, we just
add to when we draw the room.
And that'll have the effect of
rendering it separately from the room
that we're currently in.
I can try and draw an illustration here.
So if we have our room here,
this is our current room.
So self.currentroom.
And let's say we have a doorway here.
Right?
That's a doorway object,
the player is here.
He collides with that object.
It's going to trigger a
transition to a room up above.
So what we do is we load
in a new room right away.
We always have a pointer
called self.nextroom.
And this is all kept track
of in the dungeod.lua file.
But self.nextroom by
default is going to be nil.
But when we transition from the
current room to the next room, right?
We should set that to something.
So self.currentroom is
going to be the same.
But when we trigger this
collision, self.nextroom,
this is going to be
equal to just a new room.
And then we get which doorway we're in.
We figure it out.
We figure out which direction
we're moving in, technically.
And then if we're moving
up, then we just pass in a--
we said it's adjacent offset y in
this case, to negative screen height.
If it's below, we set it
to positive screen height.
If we go here, it's going to be
negative screen with the adjacent x,
will be negative screen width.
And then positive screen width on
the x if we're moving to the right.
And so this sort of adjacent
we could see, if do xy,
it will be that's basically
the adjacent offset.
And so we end up when we draw our
transition going from bottom to
up in this case, we just tween
the camera to this value.
So we have a camera, right?
This is our camera.
And it's going to be looking here by
default. So camera x and camera y,
those are values in our code as well.
When we trigger this
collision on the doorway,
and we have current room
here, which is at 0.0,
and then we have this room here,
which is at 0 plus our adjacent
offset y, which is
negative screen height.
So it has the effect of making it
negative screen height on the y.
Our camera x recall
love.graphics.Translate is our camera.
So all we do is we just tween that.
We say, OK.
Here's our Cam x is going to be here.
And Cam y, it's getting
a little bit messy.
I apologize.
But our camera x and y are here.
And then over time, we're going to
tween that up into the next rooms
x and y, which is a just x plus adjacent
offset x, y plus adjacent offset y.
And then once this camera has
shifted from here up to here,
or whichever direction we're going,
whether it's up, down, left or right.
Once we've completed that, we can
normalize everything again back to 0.0
by doing self.currentroom
equals, what do we need to do,
if we're going to put
everything back to?
Let's say I want to make this
room the new current room.
What do I need to do?
So we have current room,
and we have next room.
If I want the current room
to become the next room,
all I need to do is say self.currentroom
equals self.nextroom, right?
And then once that happens,
what happens to the adjacent
offset x and y of the next room?
They get set to 0, right?
I want to take this room
that we've offset up here,
and I just want to make it the
center of the game world again.
I want to just put it as 0.0, so we
can do this exact same calculation
by just setting adjacent offset x or y
to a negative or positive screen height
or screen width relative to 0.9.
So what I'm going to do is
just set the adjacent offset
of x and y of the next room,
which is now current room, to 0.
And it's going to draw
it right back at 0.0.
And then camera x and camera y
are also going to be set to 0.0.
And this is going to have the effect
of looking as if we're going up
and staying there.
But in reality, we're
just going up, and then
instantly shifting
everything back to 0.0,
including the player, entities,
and switches of that room.
So it's purely an illusion.
But it allows us to simulate this sort
of infinite exploring a dungeon effect.
Does that make sense?
Does the overall flow of
how this works makes sense?
OK.
So it's a little bit messy there.
Hopefully I was able to illustrate
the overall algorithm for how
the infinite dungeon generator works.
Where did we leave off?
We were in the init
function of the room.
So let's take a look at a few of
the functions that comprise that.
So generate walls and floors.
This is very similar
to what we've looked
at before with tile maps in Mario for
example, where we just go from y to x.
And then we just pick a
random ID, or our random ID.
Well, it is random ID for some of it.
But sometimes we need an explicit ID.
So remind me, what does
the ID actually map to when
we're you drawing tiles to the screen?
If we want to give a tile an ID, what
does that, what should that map to?
Yeah?
AUDIENCE: The frame in the sprite sheet.
COLTON OGDEN: The frame in the
sprite sheet, that's correct.
It doesn't have to for all game
engines, for all implementations.
But it's the easiest thing to do,
is just to give your tile an ID
that you can then just
draw, you can index
into your sprite sheet at that ID.
It's just very, very simple,
lightweight clean approach to modeling.
And we can see here, ID gets ID.
After we've figured out
what ID we want, now how
do we determine like
let's say I want to draw--
let's say, for example,
like this tile here.
Notice it's a corner tile.
What do I need to do
to basically assign--
I'm going to die before I even
have the chance to show you.
While I avoid enemies, if I wanted
to draw that top left corner,
what am I sort of looking for?
I'm still taking damage anyway.
Relative to x and y,
what am I looking for?
What x and y does that
tile need, assuming
everything starts 1.1 on
the top left, and goes down
to height and width of
the overall dungeon?
What is the xy need to be
of that top left corner?
It needs to be 1, right?
So if that's 1.1, basically
the top left corner,
that ID should be equal
to the corner sprite.
The top left corner
sprite, specifically.
What about the top right corner sprite?
What should the x be?
Sorry?
AUDIENCE: Width.
COLTON OGDEN: Yes, width.
Exactly.
What's the y?
Still going to be 1.
What about the bottom left?
Width, comma height.
And then the bottom right?
AUDIENCE: But it's [INAUDIBLE]
COLTON OGDEN: Sorry, bottom
left should be 1, comma height.
Bottom right should be width, height.
And so basically checking
the position of each tile
is how we can infer its ID, which is
what we're doing in the source code
here.
If x is 1 and y is 1,
OK, top left corner.
And notice that we've
put these into constants,
that we've put in the
constant.lua, just for readability.
So we can instantly see OK,
I should set ID tile top left
corner, not some arbitrary number
that's our index into the tile sheet.
At maybe like 40 something,
or whatever it is.
Bottom left is 1 and height.
Top right is width and 1, and then
bottom right is width and height.
And we can see, it's readable.
We can sort of see at a glance what
we're doing, where you're conditionally
generating all of our tiles.
If none of those are true, and
then x is 1, what's that tile?
If x is 1, but it's not a corner?
It's a left hand wall.
And the same thing is if it's the
width, it's a right hand wall.
And if y is 1, it's a top wall.
If y is height, it's a bottom wall.
And then if it's none of those,
it's got to be a floor, exactly.
So that's basically how
we generate the overall--
it's a very easy, simple
generation algorithm.
What I've done is for
randomization here,
we have actually a table of potential
left walls, potential right walls,
potential top and bottom.
And then we just ascribe it
a random value within there,
based on however large that is.
And that gives us
variable, we can see it.
We have random-- every
time I generate it.
So take note of maybe
some of the tiles visually
that you can see there on the map.
Notice that they change.
Notice that the switch has also changed.
Notice that they changed again.
So everything is variable.
We have a little bit of visual variety.
This is what we did,
essentially, with Mario.
We changed the tile
set, and the topper set.
Only now we're just changing,
we have only one tile sheet,
but several variations at
the same tile they're in.
So all we need to do is pick a
random variation of whatever tile.
And that involves us
looking at the sprite sheet,
picking out which individual
tiles, which separate tiles,
map to the specific type of tile,
and then just picking a random value
from that table
That's how that works.
So a couple of things, I
think, left to show relative
to the dungeon generation.
So 48, we have generate entities.
So entities are here.
We have their names, right?
Some of the entities we saw, skeletons,
bats, slimes, ghosts, spiders.
It's a very lightweight function.
It's not like 15 or 20
lines, but we're generating
a bunch of different kinds of entities.
Because what we've done,
we take, we basically
take a random type from that table.
We create an entity, and
then using what we've
defined in a global
table called Entity Defs,
we take out what sort
of matters in terms
of generating each individual
entity, the characteristics thereof,
and we just put it in simple data form.
This is sort of like the segue into
data driven design for your game.
If you look at Entity Defs here, we
can just see that all of our entities
are a sequence of data.
So up here we have player, on line 10.
Player gets a table.
We give him walk speed.
We have a table of animations.
So he's got a walk left
animation, a walk right animation,
a walk down animation.
All of those have their frames, their
interval, their texture, everything
is just clean data, right?
There's no logic here, really.
It's just flags, or
values, simple things.
You could give anybody who has sort
of the basic knowledge of what we're
doing this file, and maybe
some textures, and say, hey,
I want you to design--
I want you to lay out
basically all of that's
involved in what makes a
skeleton render to the screen.
Create all the animations for them.
Give those animations their timing.
Give them the exact frames.
Maybe skeletons should have health,
and maybe health should be equal to 10
on a skeleton, or something like that.
There's not really any
programming going on here.
But we're describing
everything in our game as data.
And that's important thing in complex
games that have fairly complex systems,
but that can be modeled
via some attributes.
You can just ascribe your entities and
data, and then let your engine parse
this information, and then create
your entities programmatically.
You shift the burden from the programmer
to the designer, a little bit.
And you afford your design
team, you and maybe other people
who aren't as comfortable
with programming,
the ability to modify the game
engine, add things to the game,
without having to go through
the bulk of your engine code
and do anything too fancy.
In this case, it's simple.
All we're doing is just we're creating
animations, and assigning a texture
to each of these individual things.
But I alluded to this last week.
You can have a file that maybe
describes something like a goblin.
We'll see this at the end of a
lecture, which has maybe like a flag
for is it flammable?
How much health does it have?
What are its animations?
What skills does it have?
What's its attack strength?
What's its defense?
Where does it spawn typically?
By putting all these attributes together
and having your engine sort of parse
this, you can create, depending
on how many fields you have,
you can create very complex potential
list of things in your game.
Whether they're entities, whether
they're weapons, whether they're items,
abilities.
Anything you want to.
Whether they're levels, even.
Based solely on just data.
So this is a holy grail
of design and development
when you get into especially
very complex games like RPGs,
where you can have skills
that have particle effects
and do different damage
to different things.
And you have entities that are-- maybe
you want them some to set on fire,
some to be electrocuted.
Maybe some you don't.
Maybe some melt when
they touch something.
Create a bunch of
flags, create a function
that parses this, and
generates entities as a result.
And you just allow yourself an
incredible boost in productivity.
No longer do you need to create a spider
class, a ghost class, a bat class.
It's all unnecessary.
All you need to do is define what
sort of attributes does a bat have?
What attributes does a ghost have?
What attributes does a skeleton have?
And anybody can therefore mod
your game as a result of this.
All they need to do is know what
attributes a potential entity can have.
And your design team is all the
more productive as a result.
So that's a spiel on
why this is relevant.
We've implemented a very
basic version of this
just for the sake of
modeling animations.
But we don't need a separate class for
all the different entities in our game
world.
We just need, basically, and
in the context of this game,
what are their animations?
So that's entity defs, that's
how it's working if we're
looking at room.lua at line 48.
All its doing is getting animations.
This is, by the way, your function
that takes in these definitions
and creates your entities.
They should be looking for--
looking into that.
They pull that definition
and then just parse out
each individual relevant piece of data.
And then just construct
some relevant information
or attach a relevant flag to that entity
that your game engine can then later
parse.
The flammable example, if you do an
attack and that attack is of type fire,
and it collides with an entity,
and that entity.flammable is true,
that should trigger some behavior.
But you don't need to do
anything terribly complex.
And you can assign this to any
arbitrary entity thereafter.
So just a simple way of getting
very complex behavior for your game
objects, entities, whatever you want.
Modeling them as data rather than
thinking about it in terms of classes.
So that's that.
Does anybody have any questions
just like sort of how that works,
or why it's viable?
Or why it's useful?
OK.
Couple of last things we'll
look at really fast in room.
So online 82.
Generate objects.
So we did this very,
very similarly last week.
Where we just had a game object class.
Notice that it takes itself
a definition for a switch.
That definition is in gameobjects.lua.
Switch.
Here's a little bit more sort of data
modeling something like a switch.
It's got a type of switch, frame is 2
by default. So width and height of 16.
It's not solid.
Player can walk over it.
Its default state is upressed.
So when it gets spawned into
the game, it's unpressed.
And it's got two potential
states, unprecedented and pressed.
And each of those states have a frame.
So all we need to do to render it
is say, what's its current state?
Render this state.frame.
And then now, we don't
need to do anything--
that's basically all we need to do in
order to render it at the right point.
Your game engine, therefore,
needs to look at your object,
and look at its state and look at its
potential states, index into that state
and then pull the frame from that.
But it forwards you sort
of infinite flexibility.
You can now have maybe
of a glowing state,
and maybe there's a frame in that
sprite sheet that allows it to glow.
And you could just change it
to glowing whenever you want,
and it'll just render appropriately.
So very flexible approach
to modeling data.
149 in room.
So we're almost done
with the dungeon here.
So 149 is our update function.
All it does basically is
iterate through everything
and update it, including
basically all the entities.
So for every entity if its health is
less than or equal to 0, it's dead.
If it's dead, then don't render
it, which we see down in render.
But if it's not dead, and
self.player player collides with it,
and the player's not vulnerable,
then we should damage the player,
go invulnerable.
And if the player's health
is zero, change to game over.
So notice how readable this is.
When you model your entities like this
as well, all you really need to do
is just check for certain
flags or functions,
and you can do arbitrarily
complex tests like this.
Because entities can be dead, or not.
Because entities can be
invulnerable, or not.
And then because entities are all xy,
with height based things in our game,
and collide just expects
them to have that.
All we just do is play
sound, damage the player.
What does damage do offhand?
What do you think it does?
AUDIENCE: I'll take a wild guess
and say subtracts from your health.
COLTON OGDEN: Exactly.
Just take a wild guess and
say it subtracts from health.
It does.
So entity damage just takes in a
number, and subtracts that from health.
That's all it does.
Go invulnerable.
What does that do.
AUDIENCE: Means you've been taking
damage for that many seconds?
COLTON OGDEN: It does.
It prevents you from taking
damage for that many seconds.
All that really does is just set a
flag, which I've alluded to quite a bit
already, an arbitrary flag that you
can model via data or some other means.
Every entity has an invulnerable flag.
And if it's invulnerable, then
the engine should look for that,
and change the rendering, and the
mechanics of the entity accordingly.
And then lastly here, just simple
if the player health is equal to 0,
which it might be after we take
sufficient damage, change to Game Over.
That's all it does.
And then for every object, just as we
do with every entity, update the object
and if the player collides with it,
then trigger its On Collide function.
And this can be a function
that you arbitrarily create,
depending on the needs of your object.
If you look up here, for example,
every time we create a room,
we generate objects in the room.
We insert into objects a switch, which
we saw before, in gameobjects.lua.
And takes an x and a y.
In this case, we just
make sure that the x and y
is a random number between
the top left of the map
and the bottom right of the map,
within the padding that it has.
We get a reference to that object.
And then we define are
On Collide function.
So what we do is we
say if it's upressed,
so recall that we have a state
in the game object definition.
If it's unpressed, change it to pressed.
It's all we really need to do.
And then notice here, for k, a doorway.
So for every doorway in doorways,
self.doorways, set that doorway open
is true.
And then play a door sound.
So our On Collide
function just interacts
with other things in the game
world, and just very simple things
but has a pretty interesting
sort of mechanic.
Like we have a--
I took damage off the bat there,
because my spawning isn't perfect.
But it changed from-- notice that
it changed its state from unpressed
to pressed.
Because it went from the one sprite to--
I'll go into another
room so we can see that.
I'll take some damage on the way.
Go to another room.
Notice the sprite.
It sort of looks like
it's unpressed, right?
Because the game object's
default state is unpressed.
And then On Collide triggers
when I go on top of it.
It changes state, which has an
effect on what gets rendered.
And it's On Collide
function is called, which
opened up every doorway in the room.
So simple like 15 lines of code.
But pretty compelling, interesting
behavior for the sake of our dungeon.
Like, it adds a lot,
as simple as that is.
Now we sort of feel
like we're interacting
with our game world a little bit.
So that's how that works.
And then lastly, rendering on line 188.
Just go through all of our
tiles, render all the tiles.
We've seen this before.
Render all the doorways,
render all the objects,
render all the entities,
if they're not dead.
Right?
If they're dead, then don't render them.
And then this bit here, we'll take a
look at the end, which is stenciling.
So notice that when
I walk through doors.
Well first of all, notice that I can't
walk through doors if they're closed,
which is important.
So when you collide with your doorway
objects, if doorway.Open is false,
it shouldn't trigger
the room switch, right?
But if I open the doors
they're now all set to recall.
Door.open is now equal to true.
So they've changed their renderings,
and now they're rendering open doorways.
If I walk through it, notice that
it looks as if the player walks
underneath the tiles.
But I'm drawing the
tiles before the player.
And one approach that
you might think to do
when sort of creating this
believable appearance of walking
through a doorway, is say OK.
I'll just render the player after I
render before I render the doorways.
Right?
So render all the doorways last.
But it doesn't quite work out, because
the sprite actually starts right here.
So what does somebody think
is going to happen if I were
to draw the doorway after the player?
AUDIENCE: Your default player disappears
if you cross that line or [INAUDIBLE]
over that line.
COLTON OGDEN: Exactly.
The player would disappear as soon as
he gets to this little bit of line right
here, which is not very convincing.
And actually I'll try right now
to take away the stenciling,
so we can see what that looks like.
So if I just take away the stenciling
here, and then I just render.
Can't type today.
So I'm going to go ahead.
Do that.
Well, first of all, the rendering order
is such that now the doorways render
after before the player.
So he just walks right over them, right?
So I'm just walking, I was
walking over the walls.
That's not compelling.
And if I were to do something as
simple as change the rendering order.
So right now, the doorways render first.
I'll just render the
doorways after the player.
So right here, I took that out, right?
No.
So right now, they
render before the player.
I want to render them after the player.
And I'm going to just run it.
And then going to step on the switch.
And then, yep.
Notice we get some weird behavior, too.
Like sees his head is getting cut off?
And other entities as well, right?
In that case, I couldn't really tell.
But yeah.
Very weird rending behavior.
And what we do to fix that is we
create what's called a stencil.
So basically I'll try and get
a screenshot here I can show.
First let me fix the
changes that I just made.
Right.
And then if I go back
into the code here.
So a stencil is just-- and we'll
see a slide on this in a little bit.
But a stencil is just basically any
sort of arbitrary shape that want,
that you draw onto the screen.
It's invisible, but
it determines whatever
gets drawn on top of that stencil,
it determines whether or not
that thing gets rendered.
So I have a stencil going basically
from here onwards to the next room.
So right about where the
doorway hits the archway,
because that's where we
want the player's head
to look like it disappears.
Stencil going here.
I have a stencil going right here.
And right here.
And on the right side as well.
And what that does is
I've set the stencil
to say whatever passes through
this stencil during the sort
of stencil testing period which
is, well all we do is we just
draw the player during that time.
But basically if it's on the
stencil, don't render it.
And so it what that has
the effect of is we still
draw the doorways before the player.
So the player walks in and
he's drawn above this part.
But as soon as he hits the
stencil, he's not drawn.
It just-- all those,
basically what it does
is it draws the character to the
stencil, and not to the actual canvas.
And so we get if you want convincing
layered, weird, visual effects
like that, stenciling is an approach.
OK.
And then that's all this.
So this stencil function,
love.graphics.stencil,
takes in a function.
This is what's going to run during
the actual stenciling process.
It's going to draw--
I draw four rectangles.
All those rectangles
are just those archways.
Right?
Perfectly layered over them such
that it goes into the next room.
And then we do.
Notice that it says Replace, and then 1.
So we replace any pixel that
gets drawn to that stencil
with the stencil value of 1.
And then we only draw things that are
less than 1 during the stencil test.
Which means anything that didn't
get ascribed to value of 1, which
means that if the player
went over the stencil,
got a pixel value of 1,
that's going to be false.
He's not going to be
drawn to the screen.
Yes?
AUDIENCE: So you are stenciling,
if you created a different shape,
you could use that, for say, like a
light system, some areas are dark,
some areas are light?
COLTON OGDEN: Good question was
if you have a stencil, because you
can do with an arbitrary
shape, you could
create lighting systems where some areas
are dark, and some areas are light.
I want to say possibly.
The thing is, I'm not 100% sure
whether stenciling allows you to do,
whether love 2D stenciling allows
you to do arbitrary numbers.
I basically am not sure if
it's on or off, or a gradient.
Typically, if I were to do
our lighting system like that,
I would probably draw
a faux lighting system.
One, you can use a
lighting kit, like box
2D lights, which does really
compelling, cool lights for you.
Or sort of a cruder way to do
it, but possibly realistic,
would be to draw a shape that fits
whatever you want to be your darkness,
and then render it at an
opacity that's less than 100%.
So you'd have a room, and
let's say maybe to sides of it
are kind of shadowy.
You draw black rectangles there, right?
But instead of drawing those
black rectangles at 255 alpha,
you draw them at 200 or
150, or something like that.
And so you can still see
what's underneath them,
but it looks as if they are a shadow.
And you can use gradient
effects to do the same thing.
If you want to have shadow
that's darker going lighter,
I believe you can draw rectangles
with a gradient effect in Love 2D.
I'd have to look into a little bit more.
But you would look to
do something like that.
Draw a gradient of shadow via some
rectangle, or some arbitrary shape,
to simulate lighting in that case.
And you could accomplish
something similar to that.
And there are a lot of other crazy cool
ways I've seeing lightning done in 2D.
But that's probably offhand the simplest
way that I could think of doing it.
Cool.
So that's stenciling,
and that's basically it
for the dungeon generation, which
is arguably the most important part.
And also we looked earlier at
the sort of transition mechanic.
And we'll look at the transition
mechanic a little bit more
in a little bit.
But let's take a five minute for
right now, and then come back to that
and see some more stuff.
All right.
Welcome back to Lecture
5, Legend of Zelda.
So before the break,
we talked about a bunch
of different things, dungeon
generation being foremost among them.
Now we'll actually start talking about
things like hitboxes and hurtboxes,
as we can see here on the screen.
So a hitbox is a rectangle, basically.
And it's how we've
implemented it in the distro.
But we can see here there's
a few different sort
of rectangles overlapping.
We have the green rectangles here.
These are hurtboxes.
These are where you can get hurt.
And then this is a hitbox.
This is where you can hit something.
And so games, like especially
with fighting games,
in a lot of games of fairly
complex interactions,
and complex entities that
have weapons or particles,
things like that all do damage.
You'll see a lot of
complicated overlapping,
and arrangement of these
rectangles that sort of bring about
how things interact with each
other in terms of collision,
and doing damage, and
affecting other entities.
In this case, he's doing an attack
that goes from the left to the right.
It's a low attack.
Only his foot, essentially,
a little bit inwards.
But essentially his foot does damage,
whereas the rest of him is vulnerable.
If someone were to come up to
him from up above and attack him,
it would do some damage.
And there's Minecraft, and it shows
you how the same sort of principle
applies to 3D games as well.
In this case, what you see
there are all hurtboxes.
Those are all aware things can get hurt.
Even though those are few
of those things are items,
so that only really affects whether
or not the player collides with them
and picks them up.
But that's basically the difference
between hitboxes and hurtboxes.
The distro I think I accidentally
called the file hurtbox.
So when I push it up, it's
going to be renamed hitbox,
because that's what we use it for.
But what is offhand in this game,
what do we need a hitbox for?
AUDIENCE: [INAUDIBLE]
attacks from the swoord.
COLTON OGDEN: Attacks with a sword.
And the reason that we need
a hitbox for that is why?
AUDIENCE: To [INAUDIBLE] the hit.
COLTON OGDEN: Yes.
And why can't we just use the player's
position x-y width and height?
AUDIENCE: Because it
has a direction as well.
COLTON OGDEN: Because it has a
direction as well, and also it needs--
that's essentially its hurtbox.
Right?
So those two reasons.
It has that direction that we need
to sort of act as its reference point
for generating a hit box, to
inflict damage on other entities.
And we need to use the
player's main hitbox
and hurtbox that it already
has to see if something
hit it, the player, maybe from
another side, or something like that.
So let's go ahead and take a look here.
The main bit of code that deals
with the hitbox, in this case,
is one going to be hurtbox.
Should be hitbox.
But we can see this is literally
just a rectangle class.
xy, a width and a height.
self.x, self.y, self-taught, self.width,
self.height equals all those things.
That's all you need for a hitbox.
Literally box, just all
you need are those fields.
And then you can do simply collides.
Just like you do entity.
Entity collides hurtbox.
Yes, no, true or false.
Because recall, collides
expects xy width or height.
And it's defined an entity.lua.
So if we go up to the player states.
So recall, last week
we introduced the idea
of having the player maintain
its own collection of states,
in a state machine.
As opposed to just the game
world having a state machine that
influences whether we're
at the Start screen,
the Play State screen, whatever we
want to divide our game up into.
We have entities states as well.
Now one of those is the-- so we
have the idle in the walk state.
Those are so very similar to last
week's, where the idle state, they're
just standing still.
And the walking state they're moving
and their animation changes accordingly.
The swing sword state is a
new state, and what this does
is the player presses spacebar.
It triggers this swing sword state.
There's a new animation.
So we go into the swing sword animation,
relative to which direction we're in.
And we get that direction and then
we calculate the xy width and height
of whatever our hurtbox.
Should be hitbox, is
going to be for the sword.
When the sword hits something.
So that hitbox, if it's
facing the left, it's
going to be 8 pixels
wide by 16 tall, which
is roughly the left side of the player.
And then we just calculate the x and y
depending on which position we're in.
It should be roughly centered based
on whatever direction the player is
looking at.
And so we then instantiate
that hurtbox here.
We call it a self.swordhurtbox.
And then all we really
need to do is what?
In our game loop to check to
see if we've hit an entity?
We just need to loop over the entities
in our room, the current room right?
Here.
So every entity in the
dungeon's current room.entities.
If the entity collides with our
sword hurtbox, sword hitbox,
then, we saw this method
before, entity damage one.
And then hit enemy play.
That's as simple as it is.
And recall, in room.Lua, if an entity's
health dropped below one, dropped to 0,
it would just trigger it to become dead.
Right?
And so that's all we need effectively.
And then we have some
additional logic to make sure
that the animation only plays one time.
And then once it has played one
time, change its state to idle.
And then we can press spacebar
repeatedly within that same state,
just to keep swinging if we want to.
And it will just restart
the animation, and restart
the hitbox being instantiated.
And so that's all we
really need to do for that.
Now I have some lines of code here
at the bottom of the sword swing
state.lua.
And player swords, swings swords state.
And what these do is
sometimes it's useful
when you're programming to sort of see
where your collision boxes are, right?
Because you don't necessarily know
if everything's lined up perfectly.
When you're detecting collision
between different entities,
you want to just check to see are
the rectangles actually overlapping
when this triggers?
So all you need to do in order
to visually see this at a glance,
and you may have seen this before, in
other games or debug modes of games,
is just draw a line rectangles.
And then just give them the xy width and
height that you want to look at, right?
So here, I'm going to
go ahead and save this.
And then I'm going to run the--
I've basically uncommented it.
Because by default, I
don't want it showing.
They're pink rectangles, I don't
want them displayed on screen,
unless I want to do debugging.
So I'm going to
uncomment them down here.
Notice it sets the color to 255, 0 255.
That's magenta.
I'm going to run this.
[MUSIC PLAYING]
Turn that down a little bit.
Nothing looks particularly different.
But when I swing my sword, notice
that there's a little rectangle.
And the rectangles aren't
100% perfect necessarily.
They give the player
little bit of an advantage.
Like up above, like his
hitbox is barely anything.
Up above, in the actual
animation, but in the collision,
I give it quite a wide berth.
And so that's how you
can, at a glance, sort of
see whether your things are
interacting appropriately.
There's lines of code.
I did the same thing for the
other states, the entity, walking,
and idle states.
If you want to sort of look at
those, and see not only the player
swinging the sword, but also the other
entities, and the collision boxes they
have, and just to check whether or
not they're overlapping appropriately,
or to change them, or do
whatever you want with them.
But that's a nice way to do--
sometimes it's hard to
debug collision if you
can't see exactly what's going on.
Because it's often just in terms of xy
width and height, so on and so forth,
with offsets and such.
It can be kind of a pain
and/or sort of difficult
to track down certain bugs that way.
So just draw to the screen.
You can apply that same logic to
a lot of things in your game world
that may be hidden, but you want to
see sort of visually just draw shapes
or draw different things for them.
So you can see what's going on.
So that's how the hitbox and
hurtbox work for our player.
And you can easily just spawn
more hitboxes for other entities
if you want to.
If you wanted to give
them states, or maybe
you want to create projectiles
that are offensive.
Maybe just create projectiles,
and then shoot projectiles
in a specific direction, but
assign a hitbox to that projectile,
if you want to.
Or just use the projectiles
xy width and height,
and then you can calculate
a collision that way.
Just treat it like a game
object, or an entity.
It's up to you.
Or a separate class all together.
But that's hitboxes and
hurtboxes in a nutshell.
Anybody have any questions
as to how those sort of work?
OK, cool.
So let's go on to the next topic.
So events.
So I spoke of this earlier.
Events are just a nice way
to sort of say when something
happens, do this block of code.
And you can do this anywhere.
And you can decouple it from
like maybe two different objects
interact with each other.
But you don't want them
to sort of pass references
back and forth between each
other, and bloat your code.
Maybe you want the code for that check
to happen not inside your main loop.
You want to sort of abstract it
out to some sort of other function,
like instantiated events.
And then have your main
rendering and update logic
be free of all this conditional stuff.
So you create event.
You say on some event, so
maybe like on player walk,
and then you just update
some, maybe a label
on the top right of the screen,
that gives the player's xy.
And you just say that xy is
equal to that player's xy.
I mean, aside from the fact
that you could just literally
draw the player's xy.
But it's the same principle.
You can update some value somewhere
separate from another entity.
And then just wait for an event,
arbitrarily defined event,
that you then broadcast later.
So let's say I want a
broadcast swing sword.
I have a swing sword event.
So whenever the player
presses space, not only
do they do all the code that we
saw before, but they swing sword.
And then we say when you swing sword,
pass in the xy of wherever they swung
the sword.
And so then you can say OK, on swing
sword, look at all the entities,
and see whether or not the
hitbox at xy collides with them.
And you can sort of take out the logic
from where you had it before, and put
it in some other centralized location.
If you want to.
More representative of this model is
the idea of an achievement system,
where instead of every
frame, and you can
have-- games have a ridiculous
number of achievements.
Some games have like 1,000 achievements.
You don't want to put the test
for every one of those 1,000
achievements necessarily inside
of your update logic, right?
You can instead just
broadcast an event for all
of the different things that influence
whether those achievements are met.
So whether a player needs to get
100 kills in a game, or 100 coins.
Whether they need to
jump off some ledge.
You just have events that
model all these interactions.
Event on, pick-up coin, do this.
Increment some time, some counter
that's stored somewhere else.
Event.on, kill creature.
Or kill creature
triggers every time you,
literally a creature is sent
from as false to dead as true.
Maybe whenever the player jumps,
you do an on jump function.
And then you can test to see
whether or not-- in that code,
you can test to see whether
they jumped off that ledge.
And then if that happens, you
have your achievement system,
but you don't have all
these if statements,
and all these tests happening inside
your sort of imperative game loop.
You can just pseudo asynchronously
check for all of them,
given a sufficiently detailed
event system in your game.
And so the library that we'll use
just to show this a little bit
is in the knife library, that
we saw before with timer.
Timer.on, timer.every, timer.tween.
In the knife library,
there's a submodule
called Event, which allows
you to call event.on.
Give it a name and a function.
So on player jump, off player jump.
You know?
Function.
If Player.Position is by some
cliff, or some area that's relevant,
then call this block of code.
And increment, or set
the achievement to true.
Event.dispatch.
Dispatch an event when
something happens.
So if a player jumps, so when they
press the spacebar, event.Dispatch jump.
And then just whatever values
that your callback function needs.
Let's say you want to check
to see what is on the cliff.
Well, if you want to check
to see whether they jump off
that specific cliff, you need to
check their xy position, presumably,
so you can just pass in optional
parameters via event.dispatch.
You can say OK, dispatch the
event that the player jumped,
and say that the player jumped at xy.
And so this function call back is
going to have access to the xy.
It's going to say OK,
on jump, I say I got,
I'm getting my callback called with
player.x player.Y. And it happens to--
oh, it's not at the cliff.
So nothing happens.
Or it is the cliff, so the achievement,
jump off cliff, unlocked is true.
And test now is not
happening every single frame,
which otherwise would potentially.
And it's not blocking up your update
logic for your player jumping.
Right?
The player, the jumping logic inside
player, the player jumps state,
doesn't need to know--
doesn't need to ask whether or
not we've jumped off a cliff.
That's something that we should just
delegate to our achievement file,
or whatever you want.
And that's sort of the flexibility
that events afford you.
So we use event.
I'll touch on this a
little bit briefly here.
But basically, if we go to the player
walk state on line 21.39, first of all
we're checking for input here.
Right?
We're just saying if we press
the left, right, up, or down.
The direction is that walk left,
walk right, so on and so forth.
And then what we do is we
call entity walk state.update.
So this base walk state code that
exists in entity, so that we can
let every single entity have this code.
All it does just check
to see whether or not
the player or entity that
it belongs to hit a wall.
And if it did, then set
there bumped equals true.
And what it does is just a flag that
says, OK, the player bumped a wall.
OK?
Why is that relevant?
Because then, after we call that,
we can say, OK, if we bumped a wall,
and this only happens when
the player walk state,
if we're looking to the left,
temporarily adjust our position.
Because when it bumps you, it knocks
you back out back into the world.
But we're going to re-bump our
position into wherever we collided.
We're going to look at
every doorway, and then
we're going to say if we collided
with that doorway, and it's open,
then we're going to shift it
to the center of the doorway
so that it doesn't look as if the
player is walking through the wall.
We're going to set it
to wherever the wall is,
or wherever the door is, the
very center of the doorway.
And then we're going to
call event.Dispatch shift,
and in that direction.
So shift left, shift right,
shift up, and shift down.
And so all of these four different
cases are going to say OK,
the player has hit a doorway.
I have a function somewhere.
Event.on, shift down, shift
left, shift right, shift up.
That's going to trigger what?
The next room.
It's going to trigger us moving to
the next room, and also the next room
spawning, and the camera
sort of shifting position.
So the code for this is in dungeon.
So we're going to go
ahead and look at dungeon.
So right here, on line
29 through 43, event.on,
shift left, shift right,
shift up, shift down.
And what that does is we have another
function called begin shifting,
which takes in an x and y.
Negative virtual width, virtual
width, negative virtual height,
virtual height.
Does anybody recall why we need
to pass those numbers in there?
If we're shifting left, and we're
passing a negative virtual width,
what do you think that
number is going to be?
So recall every room has
an adjacent offset x and y
that renders that room with that
offset basically added to it.
Its x and y.
So if we begin shifting to the
left, negative virtual width
is going to be the x offset.
Because that room needs to be rendered
a screen width to the left, right?
And so on for the right, virtual width.
It adds a virtual width to the x offset.
The adjacent x offset.
And then virtual height, negative and
positive for shift up, and shift down.
So begin shifting, basically the gist
of this is it's a tween operation.
So we start here, recall timer.tween,
just takes a value and interpolates it.
So self, we have a camera
and a camera y, right?
Going to equal shift x and shift y.
Eventually.
So shift x and shift y being
the adjacent offset, that we're
going to need to shift the camera by.
So camera x and camera y, they
both sort of zero, always.
When the room gets completely finalized.
So if the shift x is virtual
width, negative virtual width,
cause we're shifting
left, then it's going
to tween the camera going from right to
left over the span of a second, right?
And then the player, the
player x and player y,
those are basically set up
here to the opposite end
of the room, the next room.
So if the player is going
from the top of the room
to the bottom of the next room,
it needs to get its x and y
put in that position, the
bottom part of the next room.
Or the left part of the next room, if
they're moving from the right, right?
Or the right part of they're
moving from the left.
And the bottom part
if they're moving up.
And the up part if they're moving down.
So that's all that does.
Puts them in the right
position, on the next room.
Tweens both of them, the
camera and the player,
because the player needs to
keep moving to the next room.
And the camera needs to shift up.
When it's finished, we
call finished shifting,
which is just a function that
says basically everything to 0.
It sets the current
room to the next room.
It sets the camera x and
all that stuff all to 0,
and then all the entities and
objects there, it all gets set to 0.
So they are offset to 0.
So that everything is
basically right at 0.0
again, just like it was
at the very beginning.
And then last but not least,
when we get into the new room,
we start with the doors all open.
And then when we get into the
room here, because this is all
done in the finished
function, recall, this
will only happen after
the tween is completed.
Every doorway is going
to get set to false.
Open equals false.
So all the doors will close as
soon as we get into the next room,
after we've performed
the tween operation.
And then we'll play a door sound.
So that's the code involved in
actually doing the shift operation.
So we were able to trigger it.
We don't have to necessarily do that
code from the player walk state.
We just say if the players
collide with the doorway,
then dispatch an event
that is handled by dungeon.
Because dungeon has access to current
room, next room, and all the stuff
that it needs to actually
perform that transition.
So we have the two that are sort
of decoupled but they work well
in tandem with one another.
So anybody have questions as
to sort of how this works?
OK.
That's essentially
the dungeon scrolling.
It's a tween operation.
It's a camera, a temporary
room gets rendered.
We move to a temporary room,
everything gets set back to 0.
And then it's just a wild loop of
that sort of behavior, effectively.
And we use the event library to
sort of clean up that whole aspect.
And sort of screen scrolling
here, just another demonstration.
We went over it in detail,
but this is a nice GIF
I found that sort of
encapsulates what that means.
Stenciling is something
that we saw earlier.
And if we look at this
as a stencil, so we
can pretend that these are
the circle of the heart,
and that rectangle are a stencil.
And then we want to draw this gray
rectangle on top of those stencils.
We can use the stencil to
mask out specific parts, which
would otherwise be pretty tough to do.
I mean, it's hard to
necessarily, maybe individually
render pixels, or create a shape
that sort of looks like this.
Much easier for a lot of very
interesting visual effects
to use a stencil of
arbitrary shape and size.
And you can use images, too.
You can use images of stencils, to
create some pretty cool effects.
We went over it before, when
I talked about the archway.
So I won't go over
into much more detail.
But again, you can look at that
in the, where was it exactly?
It was in the room
function, room class, sorry.
Down here at the bottom.
So lines to 213 to 229 recall
they're a stencil function.
The couple functions in
particular that you need to use
are love.graphics.stencil which
draws the stencils, and then
love.graphics.setstenciltest
which compares
the stencil values to
some value, and draws them
whether or not that condition is true.
So we have our function here,
which draws four rectangles.
Those are all the archways.
It replaces any pixels that get drawn
to those rectangles with the value 1.
The stencil value 1, so
stencil value is just
like a hidden value that
determines whether a image will get
drawn if it's being stenciled or not.
Set stencil test means that we're
looking for anything less than 1.
And we'll draw that.
And because the player
is going to be 1 if it's
on any of those stencil
rectangles, it will not
draw the player if it overlaps
with those stencil rectangles.
And so that's in general how it works.
And you can do, there's a
lot different comparisons.
You can do greater than 1.
You can do iterative stenciling so
that you can increment of value.
You can have simple values
go between 0 and 255.
There's a lot of interesting
effects you can get with that.
But the general use that we just
saw is set the values to one,
and check to see whether they're less
than 1, if you want to draw them.
If you want to not draw them.
Does anybody have questions
how stenciling works,
and how these functions work?
Yeah?
AUDIENCE: So like having stat
stencil cover up the door?
So when you draw the
player, you won't actually
draw on the stencil, [INAUDIBLE].
COLTON OGDEN: Yes.
So it's like having the stencil
rectangles go over the doorway,
so that the player will not get
drawn when it's underneath them.
If I go back to a slide that has it.
The stencils are here.
Right?
I had to go through and figure out
the exact xy that I needed for them.
And there is a--
what you can do is you can
take that stencil function that
draws the rectangles, and you can
just take it out of the stencil test,
and see.
You will actually be able to see
where the exact rectangles are.
Because then it will just
literally draw the rectangles.
But here, basically right between here
and where it would be on the next room.
So that when we go from here over
into the next room, it's still
stencils them.
Right here, the player
is going to go over them.
It's going to set its
stencil value to 1.
It's checking for anything
less than 1 to render.
So it's going to fail
the test, and it's going
to not draw that player at that point.
So it's effectively just
masking out the player,
because we're assigning it
to the right stencil value,
and we're doing the
right comparison for it.
But yeah, that's effectively it.
Just using it to, because
we were in a situation
where our tiles didn't cleanly
lend themselves to drawing over
the player, which you could do.
You could draw the shadow part
underneath the archway part,
and then just change
the rendering order.
But the spreadsheet that we had
didn't afford us that flexibility.
So we went with a little more
interesting stenciling approach
to getting it done.
We looked at this earlier.
So game design via data,
rather than a bunch of logic.
So the more you can sort of get towards
modeling your anything in your game
like data, like this, the
more flexible your engine is,
the easier it is for
people to mod your game.
Which is hugely important.
And the easier it is
for you to let designers
take charge of actually
creating assets for your game.
So here we've created an
arbitrary collection of data,
and you can get as complex
with this as you want to.
The only thing that really matters
is whether your game engine
supports those fields, and acts
on those fields accordingly.
But once it does, I
mean, the more things
you add, the more possibilities
you have for adding new things.
So goblin, we have health, strength.
So strength should be accounted
for in our game engine.
And this is, we're talking in just
the general abstract sense now.
Let's say we're like
making an RPG or something.
Or maybe this is part of our Zelda game.
If we got a little bit
more detailed with it.
But goblin is going to be a
creature that has 10 health.
It's got two strength,
so these values will
get loaded when it's
created as an entity,
and strength is some
value that our engine
needs to apply to anything
that it decides to attack.
Right?
You could do the same
thing with defense.
You could create arbitrary things.
It's got a goblin texture.
So that's the sprite.
We've defined some
animations here, which
you've already seen how to
instantiate those in our game engine.
Maybe it starts off with a club weapon.
And you can also reference
other sort of Lua files
that have these definitions, if
you have maybe a weapons defs file.
You could index into weapons defs
club, and then that club item
will maybe have a bunch
of its own characteristics
that when your game engine
is loading this goblin def,
it loads in the club def as well,
and instantiates a bunch of qualities
onto that entity, or
that item, that then have
behavior relevant to your game engine.
Aggressive is true.
So if something is
entity.Aggressive, then maybe it's AI
will seek out the player.
Right?
And you can set it to false.
So now you can differentiate between
entities that chase the player,
and entities that are sort of docile,
and just do their own random thing.
Sleeps at night.
Maybe your game has a day/night cycle.
Maybe some entities stay awake, and
some go into a sleep state, right?
So it will look and
say OK, it's nighttime,
does the entity sleep at night?
If it does, OK.
Then entity change state, sleep, right?
And then during the day,
when the day happens,
you'll do the same thing in reverse.
You'll say if the
entity sleeps at night,
then you say entity.awake is true.
Or whatever it is.
Change state awake, or idle, or walking.
And then flammable.
Maybe you have weapons that
shoot damage, or shoot fire.
Or maybe you have torches
that shoot fire, or some--
maybe you're in a
level that's like lava.
And you know, if you touch one of
the tiles, it has a flammable effect.
And if it's flammable, may be
your status equals flammable.
You take damage over time.
And you change your animation.
Maybe you'd get tinted red,
or something like that.
But thinking about all these
different possibilities,
like what your engine is capable of,
you can create pretty much arbitrarily
complex data structures
and entities, and items,
and whatever you want in
your game, without having
to create a goblin class.
Or a skeleton class.
Or a arbitrarily complex creature class.
You just compose your creature
with attributes and model
its behavior that way.
Composition over inheritance,
which we talked about last week,
is very valuable in game design.
And that's what Unity's game
engine sort of approaches.
Because entity is a purely
entity component system,
whereby entities are modeled
as collections of components
that each do something.
And we can sort of almost think
of these as components, too.
Albeit in a much simpler
sort of representation.
Does anybody have questions of
this, or like why it's useful,
or anything like that?
OK.
So the last thing I wanted to talk about
today, and I saw one or two students
were mentioning how they
wished they knew, or wish we
talked about a little bit more how
programming was done back in the day.
So I'm not an expert, necessarily,
in programming 6502 assembly,
which is what the NES is written in.
There are a lot of people
that are very good at it.
But there's a few links here.
So in the slides, if you wanted to
look at the slides on the website,
Homebrew is actually quite a popular
thing amongst certain communities
online.
And what it is essentially
getting compilers
that will compile source code for some
arbitrary processor, or development
environment from some
arbitrary computer system.
Like the NES, which was a 8-bit
microprocessor based machine
with its own set of
interesting hardware.
It allowed you to compile
the assembly for that
and actually run it via a
program called an emulator.
And an emulator just allows you to
run ROM images, or just arbitrary data
that maps to the machine
instructions of some system
that the emulator has
emulated via virtual machine.
And so if you wanted to look into
it a little bit more in detail,
there's a couple of links here.
So NES.wiki has got a
bunch of great links.
There's a Programming
Guide that kind of goes
over the basics of how to do some
6502 assembly as well as with the NES,
and its particular hardware.
And then the compiler that most
people use for this is called CC 65,
which is a 65 02 assembly compiler.
And so this is an example
of some source code.
It's a little bit tough to read.
But I can't zoom in, I don't think.
But the gist of it is
you have a lot of--
and here, actually I
could pull it up online.
Le me try.
Super Mario disassembly.
So go here.
This is on Github.
So somebody decompiled,
basically, or disassembled rather,
took the bytes that represent all the
machine code and the ROM image of Super
Mario Bros and then just converted
it back to assembly language,
and then added comments to it.
Because I won't go into too much
detail about what assembly is,
and how it works, but
essentially it's just
a layer directly above machine
code where the keywords
map to essentially what
are bytes in memory
that are actually program instructions.
And that influence the behavior of
your CPU such that you get programs.
And that's what c is layered on top
of, an in every language thereof,
thereafter.
So if I zoom in here, see if I can.
It's being a little slow.
But it's letting me--
so here we can see.
I think it's lagging because
the file is just so large.
It's like 14,000 lines of code.
Because the thing about assembly
is assembly is a very, very long.
Because there are so many--
you need to have, just
like really slowly.
Zooming in infinitely.
There are just so many steps
you need to do in order
to achieve the same thing that you
get in a high level language like C,
or even languages higher than that.
Taking a lot of things for granted,
especially things like loading
registers.
So every CPU has got a series of
registers that can store values,
and then the CPU has logic
that you can basically
say OK, if the value in register a is
greater than or equal to some value,
then branch to this sequence
of the assembly code.
And often you'll have to do things
like and/or operations on byte level,
just on bytes.
Like right here, we can see we're
ending the value 3 on whatever
is in register a.
Like I said, don't intend to get
too much into what assembly is.
But for those curious as to how games
were implemented in the '80s, and even
the early '90s, it was
all in assembly language.
Because assembly language
is you're literally
programming the CPU of whatever
machine you're trying to program.
And that's how you get as
much efficiency as possible.
If you know your computing, environment
you know what your CPU is capable of.
The thing about compiling a
language like C, or C++, or Java,
which is even higher of
an abstraction than those,
is you're allowing sort of
algorithms to do the work for you.
You're allowing algorithms
to take your source code
and turn it into this, basically.
A layer lower than this, effectively.
This is an intermediary
level that it does generate.
But suffice to say,
in the '80s and '90s,
C compilers were not as good as
humans were in creating games,
especially to do a lot of the tricky
things that they needed to do,
in order to get them working efficiently
on processors that were, at the time,
one to three megahertz in speed.
Which now we have like three gigahertz
processors, and it's no big deal.
But that is effectively,
that's sort of a window
into what it was like developing
games in the '80s and '90s.
And then in the '90s, with like
things like N64 Playstation 1,
going onto Playstation 2, and so forth,
it was typically done in a language
like C or C++.
Sometimes with some variants.
Certain consoles, like the PS3 has
a notoriously difficult graphics
processor to program.
So a lot of PS3 three teams
needed to program in assembly,
even at the time, which was 2007, 2009.
But that's, in a nutshell, what
it's like programming the 65 02.
So if you want, those
links up above, it's
a pretty enlightening
experience actually digging
into that sort of thing, and trying to
make sense of what the assembly does.
It's quite difficult, and quite
burdensome because of how long
these programs are, and just how
minute each individual instruction is.
Like just checking, just
loading a value into a register,
just to do a loop often will just
be iteratively loading a value
into a register.
And then like performing some
operation, or calculation, off of that.
And then branching to
some other loop of code.
A lot of that can be condensed into
like just a couple of lines of C,
or even fewer lines of Lua.
So a lot more of a burden
but there is insight.
Definitely some insight
into digging a little deeper
and sort of looking there.
So once again, those are the
links on the NES dev wiki.
Wiki.NESdev.com if you're
interested in looking at that.
In Assignment 5, like
you did in Assignment 4,
you can create a game
object that's consumable.
And feel free to use code
from Assignment 4 to do this.
When like you define
your on consume function,
if you're going to adopt the same model
as the last assignment, then any ideas
as to what we need to do?
Probably just add the opposite
of damage the entity, right?
We want to add hearts.
We want to add HP to it.
And you'll see in the distro that
health is modeled as an even number,
because every one digit is half a heart.
Yes.
AUDIENCE: Can you give a
negative number to [INAUDIBLE]??
COLTON OGDEN: You can.
And that will effectively
be the same thing, too.
So.
The second part is including pots.
So there are pots in the sprite sheet.
So these should be solid.
So when the player
interacts with them, he
should be bumped out of where he was.
Allow the player to lift them.
So there's an animation in the sprite
sheet that will actually allow--
that shows the player
lifting up the pot.
So you probably need a new
state, player lift pot state.
Walk with pot state.
Whatever you want to define it as.
But you'll need basically to have
those two in order to get this to work.
What's one thing that we'll need
to do in order for the player
to lift the pot.
Any ideas?
Besides the rendering aspect of it, when
the player walks around, for example,
what needs to happen?
If he's holding the pot.
AUDIENCE: He's slower.
COLTON OGDEN: You can make him slower.
Yeah, that's possible.
That's not required for the assignment.
More fundamental than that.
The pot needs to track the
player's location, right?
It needs to be relative
to the player's location.
So keep the pot probably above
the player by some amount.
I won't be too picky as
to how you implement that.
But the pot needs to track the player.
Which is this next point, actually.
And the walking animations,
like I said, should
change while they're carrying it.
Allow the player,
lastly, to throw the pot.
So when you throw the pot, basically
turn the pot into a projectile.
If it hits one of the walls, break it.
You can either just despawn it
instantly, or have some animation.
It's up to you.
But just, if it hits a wall, despawn it.
Trigger it to-- maybe just
trigger it to some values, false.
Render is false,
whatever you want to do.
If it hits an enemy, it should
damage the enemy by one,
just like we've seen already.
And if it travels
farther than four tiles,
in addition to also hitting one
of the walls, then destroy it too.
So those are the main
components for Assignment 5.
And the ways that which you achieve
most of it's fairly flexible.
But yeah, that's Lecture
5, Legend of Zelda.
So thanks for coming.
I'll see you next time.
