SPEAKER 1: All right,
welcome to GD 50, lecture 7. This week we'll be talking about one
of my favorite franchises of all time, a core part of my childhood-- Pokemon as shown by the Poke
Ball on the screen there. So back in 1997 I think
it was, the first Pokemon game was released, Red and Blue. I believe it was released a
year earlier in Japan where it was released as Red, Blue, and Green. And the overall goal of the
game was fairly straightforward. You were a Pokemon trainer. Your goal was to go out into
the world and try and capture any number of 151 different
types of these creatures called Pokemon that were based on a whole
bunch of different types of creatures. Shown in the screenshot here, there's
a Weepinbell fighting and Geodude. A Geodude was a rock type,
Weepinbeel was a grass type. You had different types of Pokemon. When they fight each other, some
types were better than other types, like this sort of very large rock,
paper, scissors relationship. And it was just a very
addicting formula. You'd have a team of these creatures
that you had caught and raised and battled, and you'd
fight other trainers. And the awesome part of
this was you could go and you could actually
fight your friends, or trade Pokemon with your
friends that they had caught. And you would often share
stories back and forth about the different rare creatures
that you would have encountered, and all sorts of things. You'd have a customized party
that was sort of a part of you. And so this is Pokemon Red. The series has evolved over time. This is a screenshot of Gold and Silver,
which was released a couple of years afterwards for the Gameboy Color. Again, this was released
for the regular Gameboy. Gold and Silver introduced a bunch
of new features including breeding, and a day, night cycle,
and a lot of other things that became part of the core series. Here is Ruby and Sapphire, which
was for the Gameboy Advance and got a significant graphical
update, but the core formula stayed much the same. Here is Diamond and Pearl,
which is for the DS, which it made use of two screens, as
seen on the top and bottom there. Here is Black and White,
which was another step forward in that it introduced three dimensional
graphics for the over world, so you could actually see some sort of
3D for the first time in the franchise. And then more recently, we've seen
for the 3DS, games like X and Y, which is shown here, and
Omega Sapphire, Alpha Ruby-- Alpha Sapphire and Omega
Ruby, and Moon and Sun. And so this is a great
illustration of why the RPG genre of video
games, role playing game, even though it's sort of its
own unique take on the formula. But it allows us to, we can sort
of dissect this and take a look at what makes an RPG and what
makes a Pokemon game altogether for a nice cool demonstration. So today, we'll be talking
about a few new things. We'll be doing things a lot
differently in this lecture example relative to other examples, because
we're transitioning away from the state machine and talking about a construct
called the state stack, which is effectively a more advanced
version of the state machine. Whereas before, we had a state machine
that was in one state at a time, whereby we could be in the play
state or the start state or what not, we can now actually
have multiple states that exist in parallel that are on a
stack of data structure, which you've seen in CS 50 if you've
taken, where we can have, for example, the field state, the
play state at the very bottom, which is always there, and then
we can push states onto the stack as we need to for
example, a dialog state so that we can actually
display some dialog, some text to the screen without
getting rid of the play state that we had there before. It allows us to render multiple
things at the same time, and then also return
back to prior states, rather than completely
create new states every time we want to make a transition. We'll be talking about
turn based systems. So an RPGs like Pokemon
and others, there are often battle
systems that are usually turn based in this particular genre
where you're fighting-- you have one team or one character fighting
against against one other team or one other character, and you take
turns fighting each other. And you have an indefinite amount
of time to make your decision and then form some sort
of strategy as to how you want to approach the problem. We'll be taking a look at a very
primitive turn based system, but a fully functional one today. Another huge aspect of this genre
is graphical user interfaces or GUIs as they're shortened. Things like panels, and scroll
bars, and text boxes, and menus, all sorts of these things that allow us
to get a more visual sort of look at our data, and allow us to navigate
a much more complex game ecosystem more efficiently. And to tie it all together,
RPG mechanics at large, we'll be looking at things
like leveling up and experience and how to calculate the damage that
one party does to the other party throughout the course of a battle. And so it will be a fairly
complicated set of examples, but fairly illustrative
of the genre as a whole. So I'd like to demonstrate sort of
the example that I put together. If I could get a volunteer from the
audience to come up and take a look. Tony that'd be awesome,
thank you so much. So this is my simple but fully featured,
more or less, demonstration of Pokemon. So if you want to enter or return. So this is a-- so here we have a-- we can see right off the bat, we
have a text box and a play state like we did before. So this text box is actually a state
that's layered above the place. So you can see it has some instructions
about, if you want to press P, you can heal your Pokemon. You can press Enter to dismiss. So if you go ahead and
press Enter, you'll be able to actually move around now. And so something to note is
before, input was actually halted while the dialogue was on the
top of the screen for the play state. You're actually not allowed to
access or update this bottom state, because the state stack is only
allowing input to the top state. And so I have limited the
play here just to this box, but if we walk in the tall
grass down here, in Pokemon, in order to actually
initiate an encounter with another Pokemon or another
wild Pokemon, you walk in the grass. So here we've walked in the grass. There's a random chance for this
to happen, there's a 1 in 10 chance basically. So it's saying that a
wild Bamboon appeared. So a wild creature appeared. He's level 5, we're level 5,
should be a fairly even battle. So you can press Enter,
and it will say go the name of your Pokemon, which
is an Aardart in this case, and it's randomly
allocated at the moment. So go ahead and press
Enter one more time. Now we can see on the bottom
right, we have a menu. So we can fight or we can
run, so those two choices. So we can go ahead and fight. So we fight, whichever
Pokemon has the higher speed will go first and do damage. We obviously, do a lot more damage,
but he's a little bit faster, so he's going to go first. So we fight one more time. We should be able to knock him out. So as soon as we do, we get a victory
message, we get a victory song. If we press Enter, we'll
actually get some experience for defeating that enemy. So we've got quite a bit of
experience, we got 65 XP. In that bottom bar, we can see
we have all these GUI elements, we've got a panel here, we have
text boxes, we have progress bars, all these pieces are coming together to
give us sort of this turn based system. And so after this, we may
level up just to demonstrate leveling, which is part of the
RPG mechanic side of this game. So we have to press this one more time. We did, perfectly. So they leveled up. And so now we're level 6, so we can see
the 6 changed above our progress bars. So now will be a little
bit stronger every time. And the stats aren't shown here, it's
actually a part of the assignment is to create a menu that will
actually show you how you leveled up, what stats actually increased. But underneath the
hood, behind the scenes, you actually are getting stat increases. And so here we can see that if we
our HP goes all the way down to zero, we faint. And when we faint, the screen
instead of fading to white will actually fade to black
to illustrate the difference between the two transitions. And so that we can keep
playing indefinitely, the game will restore your
Pokemon back to full health. And so this will go on forever. This is effectively what
the simulator is, it's just a simple series of infinite battles. There are random Pokemon in the grass. There's five total that you can fight. But all the core pieces of
what make the game are here. So actually, let's
illustrate running too if you wouldn't mind, just so that
we can see if there is a difference. So we can actually flee, and then
battle we'll get cut short there. We won't get any XP, we
won't faint, but we still get taken back to the play state. So that's all for the Pokemon demo. Thanks so much Tony for
coming up to demo it. Well, there's a lot of pieces
involved here, but as we will see, once we have a lot of these sort
of foundational pieces implemented, it's not too difficult to start
layering more and more of these onto the game to make it even more rich. In fact, the assignment is to-- and we'll see this at the end,
I'll recap this at the end-- but the assignments goal is
for you to implement a menu-- similar to the menu that we saw
where fight and run were shown-- that will show you what your current
stat is for each of your stats, there's attack, defense,
speed, and your HP. It will show you what your stat
is before leveling, the amount that it will increase by,
and then the final amount, which is sort of similar to how
the actual Pokemon games work, so you can see rather than just seeing,
oh I increased my level from 5 to 6, 6 to 7, you can see, oh, my
strength increased from 12 to 14. I'm a little bit stronger, I'm going
to do more damage on the next play through. So our goal here, we're going to take
a look at the field state, the play state, and the battle state. And there's a common dichotomy
in most of these sorts of games, be it Final Fantasy, or
Dragon Quest, or Pokemon where there is a field,
where you are walking around, you're character interacting with a game
world with NPCs, going through towns, and what have you. And then a battle mode,
sort of a battle state where you're actually fighting
against some sort of enemy, or a series of enemies, a
party or a single creature. And so we've implemented simple
versions of both of these to illustrate and also the transitions between them. Before we start, I want to
make another sort of plug for this howtomakeanrpg.com, this book,
I actually learned a lot from this and about using LUA in the
context of game development. And I pitched this I think in
one of the earlier lectures, but if you want a deeper dive
into a lot of these constructs, and to sort of get a sense for how you
might do something like cut scenes, or more complicated battle
layouts, and a lot more like-- it goes into a lot of detail
about a lot of awesome things, definitely check it out. It's not free, but if you're
interested in this genre, which I am, it's definitely worthwhile. Here's what the sprite sheets
look like that we'll be using for this sort of demonstration. The Pokemon aside, which
are individual textures. Here we're using a simple
sprite sheet, which just has a bunch of tiles,
most of which we did not use. Note that the bush, the tall grass
is not on any sort of background. And therefore, we need
to layer, basically have two separate tile maps as opposed
to one, which we didn't do last time. And we were reusing the sprite sheet
that we used in the Zelda lecture. Before, we used it for all the
enemies, the skeletons, ghosts, and slimes, et cetera. But now we're actually using it
for the PCs that it contains. Specifically, just the male NPC
here, which is our main character. The foundational class that
we're using in this lecture that sort of everything else
revolves around and makes this work is the state stack. And so before, what we had
was a state machine, right, where we were in one state at a time. So you can almost think of
it like, we have a box here, and it just has one socket. And then we're always
looking at this one socket, whether it's the play
state, or the battle state, or a transition of some kind. And now we're transitioning
into the idea of, instead, of just one state that we can only
see at once, we'll make it a stack. And so what we can do with this
is, rather than just having one, we can therefore render multiple
states at a time, right? So let's say this is like the
field, right, or the play state. And then maybe this is like a
dialogue, or something, right? Like we saw before in the
field, we had a text box. We can actually layer
things on top of each other. And then maybe this is like a
fade out, right, or a fade in. So we start with the play state
maybe, and we're walking around, and we interact with in NPC. Rather than transition the
play state to a dialog state, which would, in our previous model,
completely eliminate the play state, because there's only one state
that could be active at a time. Now we just render, however many
states we have in our stack, we just render them sequentially based
on the order that they were popped in. We would rendered this first, we
basically render from the bottom up. Render the play state, then
the dialogue, then the fade in. And this will have the effect of doing
a whole bunch of different things, which we'll see in the distro. But we only really ever need
to update one state at a time, right, because if we have the play
state active and the dialog state active and the fade in state active,
in this order, as a stack, right, we were pushing the operation
for pushing for getting something onto the stack is called
a push, and getting it off is called a pop if unfamiliar. If we're pushing all of these
states on, then usually, we only need to update
whatever's on top, right? If there's no fade in
for example, and we only have a dialog state active,
or a dialogue and a place state in that order top
to bottom, then we usually don't want him update what's
going on in the play state. We're only concerned with the
dialogue that's taking place. We only want that to take input. And when we press Spacebar,
Enter, or whatever button clears that dialog
state, we pop it off, right, and then we're back to the play state. Then we're just updating the play state. And so being able to update
just what's on top while being able to render
everything that's on bottom. And this doesn't hold true
for all game formulas, there's certainly some games where you
can have a dialogue and a play state both get updated, but that's still
using a state stack of sorts, you're just then updating
things in a top down way. But this allows us to do all
kinds of things like transitions, and preserving-- like for example,
the fact that we have a play state and we can pop a battle
state on top of that, where we don't see the
play state underneath it, we only see the battle state,
and that's all updating. But it is we pop the
battle state off, we're right back where we just
were in the play state. It's preserved its state, for lack
of a better word, from before. And this is something that this
model affords us comfortably. And so that's sort of the
foundational class that's implemented in this
distro, which will allow us to do all kinds of awesome things. So let's go ahead and take a
look at what that looks like. I have state stack open here. So state stack has just
a set of states here. And then whenever we
want to insert a state, it's going to be at the
end of the state stack. So it's going to be whatever the last-- so if we're looking at it as
a series of states in a table, it'll be whatever the last
index is in the table. That will be our, the top of our stack. And you can implement this either
way in reverse if you wanted to. It's just easier, because you can just
do a simple table.remove to get rid of the-- table.remove on that table
to get rid of the last state without having to shift back everything. So if we did it starting at index one,
you'd have to shift everything back. And it would also be a little bit
weird, because you would start at one, and then things would go left. But basically, in order to
update whatever our end state is, we just do at #self.states, which will
be however large our state stack is, we just call update on that state. And then process AI is here,
although we're not using it. But if you had AI, it
would be the same thing as basically an update for artificial
intelligence for your state. Rather than rendering
just one state at a time, we iterate through
all of our states here using ipairs, which will
iterate through them numerically starting at one going to the end. So we call for istate, and
ipairs of self.state, render it, so that will render everything
back to front, or bottom to top, and allow us to get
this layered look where we have a play state going
on underneath for example, and then a dialogue on top. Or we have a battle state
going at the very top, and maybe that battle state
itself pushes a dialogue state to the top of the stack,
or a bunch of other states, a transition, whatever you would like. To clear it, we just reset
the table to an empty table. To push a state, we just do an
insert on that state, and then we call enter on that state. As we did before, sort of similarly with
state machine, when we changed a state, we would call enter on it
and exit, but now we're just calling enter when we push. And then to pop, all we do
is call exit on whatever the last state is in our
state stack, and then just call table.remove on self.states. And by default, when you call
table.remove on a table, just a table with no other arguments,
it'll remove whatever the last index is of that table. Does that makes sense? Anybody have any questions as
to how the state stack works? All right, awesome. So let's take a look
then at the start state. So this is the start date. Fairly simple, we just have
a couple of text labels, and then we have just a randomly
assigned sprite going left to right. How do we think we're
achieving the movement? Yep, timer.tween, and
then we're just drawing an ellipse, right, pretty simple. And then when we press
Enter, note the transition. Notice that there's a fade to
white, and then fade to transparent. And so if we recall from when we looked
at match three, how do we do this, do we remember? AUDIENCE: [INAUDIBLE] SPEAKER 1: Exactly, and the
rectangle was stored where? Sorry, to repeat for the
camera, we had a rectangle that filled the entire screen, and we
just tween the transparency for it, which is true. The rectangle there
before though was stored in whatever state was active at the
time, which was like the start date, or the I think begin
game state was the name. The actual state that wasn't
necessarily relevant at the transition. But using a state stack, we can
actually decouple this idea. We can take the concept of a
transition, and because imagine if we wanted to make a transition
between every single state that existed in our game, right? If we wanted to transition
from the battle to the field, or the field to the battle, or whatever
else we might want, like the start to the field, we would need a
rectangle in every single one of those that has an opacity
that we're keeping track of. And that's not necessarily germane
to the purpose of that state, right? So because we now have a
state stack, we can actually abstract out this idea of a transition,
and turn it into its own state. We can have a transition state. And recall that since we're
just layering everything [? off ?] all these states, and
we're rendering them sequentially, having a state that possesses it's
own for example, opacity rectangle, we can just layer that, push that
onto the stack, and render that, and it'll give us the illusion
of having this transition. But we don't need to actually
have it be part of the state that we're trying to
transition out of and into. Does that make sense? So let's take a look,
for example, at the-- let's take a look at
the start state first, just so we can see where that
actually gets kicked off. So the start state, we kick off
some music, we have a sprite, and a sprite x and y. These are values that are relevant to
the sprite that's moving, actually, we only have one sprite
ever moving left to right. It just gets teleported to
the right edge of the screen as soon as it gets taken to one edge. So we only have one sprite, one x and y. And then every three
seconds as we see here, we have a callback function that will
tween the sprites x to negative 64 over 0.2 seconds, so really quickly. And then on finish,
teleport it to the right. And then do the same
exact thing, but tween it to the center over 0.2 seconds. And then as soon as we press
Enter or Return, note this here, we have gStateStack, not
a gStateMachine anymore, and we're pushing on
to it a fade in state, which takes an RGB, a duration,
and a callback function. Now if you look at
main.lua, this is relevant, because now we no longer
have a state machine, right? We previously had a global
state machine, gStateMachine. We would give it a list of indexes
into functions, anonymous functions. Those would return the
instantiation of a state. And then when we called
change, the state machine will index into its list of states,
and call that anonymous function, which would have the result of
changing the state to some state that we've implemented
as a class, right? Now we just create a
state stack and we just push a new start state onto the class. And so what this will do is
effectively the same thing, only now we can layer things onto the
start state, right, or play state, or whatever we want to, and we're
not going to ever get rid of it. I mean, we can get rid of
it, but we don't have to. For the play state, especially, we
want that to pretty much never get popped off the stack,
because that's going to preserve all of our information. We're going to default back to that to
store all of our character information, our Pokemon information,
whatever else we might want to add onto this shell
of a game in inventory, et cetera, a world state at large. We want to preserve that and keep that
consistent across all of our battle states and so forth. And the battle states will just
pull information from that world, from that play state, and
construct a battle as needed. Does that makes sense? OK, so here we're effectively, the
only real changes we've made to main are no longer a state machine,
now we have a state stack, going to push start
state, that start state has some behavior just like any other
state that we've implemented before. And what I was about to get into before
was here on line 36 of the start state, we're pushing another
state onto the stack. So there's already a start state, and
it's from within the start state itself actually. We're going to take that stack,
which is just one level deep, and then we're going to
make it two levels deep now. So now we're going to
add a fade in state. And the fade in state as we
can see, takes in an RGB. Does anybody have a guess as to
what the RGB is relevant for? AUDIENCE: [INAUDIBLE] SPEAKER 1: To whether you want
to fade in black or white? Yes, any color. We can make this, we can make it
a fade to red if we wanted to, or fade to blue. But we don't have to create two
separate classes for a fade in white, fade in black, fade out
black, fade out white. We can just give it a color. And then I mean, we could even
go a level further with this, and make it take in an opacity as well,
so that we don't need a fade in state, or a fade out state, we just
need a fade state, right? And the fade state will
determine, based on whatever last opacity parameter we give it,
the right way to fade in and out. But in this case, the difference
between the fade in state and the fade out state is one knows
to go to 0, one knows to go to 255. That's really the only key difference. And then this 1, the duration, right,
we need to tell it how long to fade. And then this last bit
here is a function. We're giving it an anonymous
function, this is a callback function, because the fade in state by
nature is an asynchronous state. It does its behavior over time. So we need a way, we
need to tell it, OK, when you finished doing what
you're doing, call this bit of code here, so that we can do
something not immediately, we can sort of defer its
execution till later. And this is something that we'll
see common throughout this lecture, because we have this implemented
also in like dialogue for example, because we don't know when the user is
going to press Spacebar on the dialog state and clear the window. But what if we want that window,
the clearing of that dialog to trigger some sort of event, right? For example, if they press Enter
when they're in the battle, we want it to go to the next action. We don't necessarily know
when it's going to happen, so we'll just pass in an anonymous
function to that dialogue state that the class will call whenever the
close function is called on that dialog state. It says, when closed, execute
this anonymous function. And then that anonymous function
can do whatever you want to do. It could pop another other
several states onto the stack. But this is what allows us to
chain asynchronous behavior. That's the key here. So this anonymous function-- so
we'll take a look now actually at the fade in state, just so
we can see what this looks like. So we see here, fade in state,
right, takes in the color. We saw before, that will
be the color we fade to. The length of time that it'll take us
to actually perform the transition. And what are we using for the transition
do we think, timer.tween, right? So most everything
that we'll do actually throughout the course of this lecture
that has asynchronous behavior, we can implement it with
timer, which is nice. It allows us to fairly
succinctly and declaratively tell right out what exactly we
want to have happen over time. In this case, we're going to
tween over the course of time the opacity of our self to 255. So the fade in is going to fade
into the full color of whatever we have given it. So it's going to go from 0, which
is shown here by default, to 255. And then as soon as
we finish that tween, that is when we pop the fade in state. We're going to pop ourselves off
the state effectively, off the stack effectively. And then here, we're
calling on fade complete. And that's where the
anonymous function is. On fade complete is passed in here. So by putting that function into the
finish function of the tween operation, we've allowed ourselves to defer that
function that we've written up in the-- it's in the start state. We defer the execution of this function
until after that tween operation takes place. Does that make sense? OK, awesome. And that's effectively what it is. And that's a common theme that we'll see
if you're looking through the distro, you'll see it in a lot of places. Anonymous functions
or callback functions rather being passed into things
like the dialogs, and the fades, and a few other places. In the take turns state
for example, there's a function that takes in at
a callback function as well. And that's effectively how you can
chain asynchronous behavior that executes over time, rather
than it being blocking. Does anybody have any questions so
far as to how this works, at all? All right, so when the fade is done-- we're still the start state here-- at this point, the fade is done, we're
executing this anonymous function. We're going to pop the start state
off of the stack in this case. And then we're going to push a-- we're going to do two pushes here. One is to push a play state, which
recall is where the NPC [INAUDIBLE] character walking around. And another one is to
push a dialogue state. And so what this will have the
effect of doing is rather than us immediately going into the play
state and being able to walk around, we're actually put
right into a world where there is a message waiting
for us that we have to press Enter on in order to continue. And when we press Enter, because
we're pushing the play state first, and then the dialogue
state, the dialogue state is at the top of the stack,
right, because things get pushed onto like a stack of plates. You put a play state plate on the
bottom and then another plate on top, and that plate is the
dialogue state in this case. And you can only interact with the top-- we're only updating the top
plate at once in this model. We could obviously make
a more complicated state stack that allows us to have several
layers of states being updated at once, but for simplicity, we only opted to
allow the top layer to be updated. The dialog state is going
to be the active state, it's going to be receiving input. All of them are going
to be rendered, so we're going to render things
from the bottom up. We're going to render
the play state, then we're going to render the dialog
state, but the dialog state's going to be active. We're only going to be able to
press anything on that state. And then lastly, actually,
even beyond the dialogue state, we're pushing another state,
we're pushing a fade out state. And in this case, it's the
opposite of the fade in state, it just takes in an RGB, and
we'll go from 255 opacity to zero opacity in that case. And so what that allows us
to do for playing right, we're here in the start
state, pressing Enter. That's our fade in state was there. And then we pushed to the play state
and the dialogue state and the fade out state at once, so
you would almost think that we push a fade in
and then the fade out, but we have to lay that
foundation before we put the fade out state
on top of the stack, right, because the top
layer gets updated. So we have to push the fade out
state on top of all of those. That will get updated,
that will fade out, and then we're back to the
two states that we push before we pushed the fadeout state. Does that make since? OK. Does anybody have any questions
as to how that sort of flow works? Cool. All right, so that's
the gist behind, I mean, that's essentially the core
of what we're doing today is the state stack
pushing multiple states. And then just figuring
out the right order the need to push them in to get the
desired appearance that you want, right? We push the fade out state
while we're in the start state, or fade in state rather. That will take us to
white, and then like sort of, almost like underneath the-- behind
the curtain, we're popping everything, and then we're adding the play,
dialogue, and then another fade out state. And so you sort of have to balance the
order that you put things in in order to achieve the desired results. It may not necessarily be
exactly as you intuitively think until you think about just
how we're updating and rendering things on a stack. And so that's the ultimate hurdle I
think in really getting comfortable with the distro, but once you've gotten
that, everything else sort of falls into place. That and the sort of abundance of
asynchronous functions, as we'll see pretty shortly when we look
at GUIs, and how we've implemented a lot of basic GUI functionality. A lot of that is very,
very call back driven, just because of the nature of it
being based on user input, right? You don't know when the
user's going to do any input, so defer whatever happens
with that GUI code with the triggers involved
when the user presses Spacebar, Enter, and then call that function that
you've passed into that GUI widget. All right, so we've taken
a look at the state stack. We've taken a look at the
start state, the fade in state, let's take a look now at the play state. So the play state-- a lot of this is actually
very similar to what we did back with Zelda, which is a
very similar type of game top down. View, the only difference really
with that was RPGs of this nature-- Final Fantasy, Pokemon,
Dragon Quest, they're tile based to the degree of even
your movement is tile based. And so we've striven to
implement that with this lecture. So when we move our
player, our character, it doesn't have free motion like
we did with Zelda for example. So I'll demonstrate this. So I can go to the field state
here, the play state, sorry. And then when I move,
if I press right, he moves in that direction at
a perfect grid interval. So if I move up, I'm taking
my hand instantly away, he's going to keep moving, and he's
going to stick hard set to this grid. And that's just a sort of trend
that these games have implemented. It allows you to stay perfectly
aligned with the grid, and helps you I guess certain game-- I don't think it's strictly
necessary for probably most of the games that
choose to implement this. I think it was a symptom of tile
based games from the NES and Gameboy era being easier to
design and implement, because they're very tile based systems. But I mean, even as an aesthetic
choice, I suppose it makes sense, because everything
aligns very perfectly. So that's the core difference really
with the field state in this game. So how can we go about implementing a
grid aligned movement system like this with our player relative to how
we did it in Zelda for example? How do we think-- sure yeah? AUDIENCE: So we don't x and y's,
we just have the tile positions. SPEAKER 1: So you don't have x and
y's, we just have the tile positions. Close, I would say it's more
focused on the tile positions, but you still do need an x
and a y, because you still need to draw that sprite
at that exact position. Right, yes Tony? AUDIENCE: Well, when you need
to move the sprite, instead of moving at every update, you tween
it between the two tile locations. SPEAKER 1: Exactly, so
rather than moving the sprite at exact pixel positions per update, you
tween the sprite when you receive input to a specific location. And then we actually stop
input at that point as well. There's no use for us having any input
when we're not exactly at a given tile, so we disable input while
he's walking effectively. And so this is implemented, if we're
looking at the distro in the entity class, there is a-- I believe it's in here-- maybe player, hold on. Oh sorry, no it's entity
walk state, not the entity. Entity is just a container for
the information that's relevant. So here in the entity walk
state, we have attempt move. And so what attempt
move does is essentially it looks to make sure that we're
within the bounds of the map, right? And then if we are-- every entity in this game now has a
map y and x, and a regular y and x. And so the regular y and
x, we still need in order to draw our sprite at a
specific location on the map. We still need to draw it going between
240 something and 230 something, right? But we need a map x and a
map y to basically say, OK, the sprite should be at
this position on the map. And then we'll just tween it
between that position times 16, and it's the position
plus or minus x or y times 16, which will give us
the exact x and y value that we need to draw it onto the map. And so that's what we're doing here. So were going to call
attempt move on input. So anytime we do any input-- and this is done in the player
like idle, or player, yeah, player idle class-- player idle state. We change the animation
to write animation. And then we get it's
current map x and y. And then based on whatever
direction the player is looking, or the entity is looking, we could use
this for an NPC class, or the like. We just modify our 2x and 2y. So to 2x and 2y is going to be the value
that we're tweening towards times 16, right? And so if we're trying to go
outside the map boundaries, just changing us back to
idle won't let us do that. Otherwise, set our map y and map
x to that position immediately, right, because that's just a
minus or plus one operation. And then over the course of 0.5
seconds, actually tween to that value. And we can see here, we're
tweening to the tile size, and actually to the tile size minus
self.entity.height divided by 2. Do we know why that is? We do that, because if we're looking
at the field, we can see here, notice that we're not perfectly
lined up with the grass, right? It's kind of like
we're halfway above it, because it looks just a little
bit more natural this way, this is how most sort of games look. And if you're in a game
like this and you're like walking up against
a wall for example, this will allow you to
sort of look as if you're up against the wall
rather than sort of being at the edge of where the bottom of the
wall is, and kind of looks unnatural. Hence why we minus 1/2
our height right there. And then when we're
finished, we actually test to see whether we're still
pressing a key, and if we are, then change our state
to walk again, which we'll just repeat this process depending
on which direction we're looking at. And that's effectively it. And that's what allows us to
get this grid based movement. Any questions as to how this works? Cool. Let's take a look then at the
play state, let's go back to it. So we have a level, the
level contains our entity, and it can contain all of
our entities, and whatever objects you want it to contain. In this case, when we're
in the play state as well, we're going to check
to see if we press P, because that's recall, where
we can heal our Pokemon, just a little game hack just to
make demoing it a little bit easier. But if we press P, we play
the heal sound, we take our-- and we'll look a little bit more detail
as to this, all this in a little bit. But self.level.player.pa
rty.pokemon@index1.currenthp equals
self.level.player.party.pokemon@1.hp. So the difference is current HP
is whatever you currently have, you could have taken damage. HP is whatever your max HP is. And this is like in a
nutshell how you get like stat changes in games and
RPGs, and health and mp differences. You've got to keep track of a max and a
current value for all of those things, and then depending on whether
you're buffed or debugged, or whether you have taken damage
or not, or used spells or not, you can have an accurate
reflection of where your character is and then
always return back to that state whenever you need to. The interesting thing here, the
slightly more complicated thing is when we press P, we want
to show a dialog that says, and I'll demonstrate this,
we want to show a dialog just like this one that says, we press P,
your Pokemon has been healed, right? Now I can't move. I'm pressing the arrow keys. I can't move my character at
all, because this dialog state-- we're in a new state, well, we've
pushed a new state onto the state stack. And that's the dialog state
here, which has taken a value. And because it's the top layer of
the stack, it can't get updated, or it's being updated, and we
can't update the play state, right, based on how we've modeled our state
stacks operation, or how it works. And then as soon as I press
Enter, it gets popped off, we've just popped it off. Now the place states at
the top, I can move again. So that's what's going on. So the dialog state then is actually
very similar in a sense, to the fade in and fade out state in that, notice
that it takes an anonymous function. When does this anonymous
function get called? Do we know? At the end of what? AUDIENCE: [INAUDIBLE] SPEAKER 1: Yeah, well, when the
user closes the dialog box, correct. So let's take a look at
the dialog state then. And we can see, it's actually
pretty simple, it's pretty small. We have a text that it
takes and a callback, right? The text is used here. We instantiate, and this we'll see in
detail when we start looking at GUIs, and all the widgets
they've implemented here. This text box gets put at a hard coded
position, and it receives this text. And then we set our self.callback
to that callback function. If we have closed the
text box, meaning, we're looking to see at
self.textbox.isClosed, which is a function of the text box class. If it's closed, then execute
self.callback, and then pop this dialog state of the stack, right? So it's similar in a sense,
to the fade in and fade out, and then it takes anonymous function. The only difference is
in how it gets executed. With the fade in state,
the anonymous function was called at the end of
the finish function, which is part of the tween object. In this case, we're executing the
callback function explicitly when we've closed the text box. So we're waiting for user input versus
waiting for some asynchronous operation to finish. And then of course, we
call text box render, and then we'll see all of these methods
shortly as part of these widgets, but at a glance, this is all that's
really happening with the dialog state. Very simple, using the
same pattern that we've seen of deferring future
behavior to anonymous functions. Any questions as to how this
works, or anything so far? Cool. All right, let's take a
look back at the play state, I believe we're getting close to
being finished with the play state. Yes, so everything, that's basically
what the play state is in this game. And then a lot of what's going on
takes place in a level as well. So in a nutshell, we have
two maps, two layers, right, because the grass in the tile sheet
is its own sort of [? alphaed ?] out object, it's got
transparency around it. We keep a layer of the base, a
layer of the grass underneath, and then a separate
layer for the tall grass. And then we can just
look and to see when we're walking in the player walk state
when we've walked over tall grass. And then what do we need to do
to start a random encounter? Yes? AUDIENCE: [INAUDIBLE] SPEAKER 1: Yes, how do
we initiated though? That what are we looking for? We do push a battle state as
soon as we've triggered one, but how do we trigger one? What are we looking for? AUDIENCE: [INAUDIBLE]
player is in the grass. I don't know if it's on
moving to a new grass, or if it's time spent in the grass. Yeah, we do a random chance
whenever the players on grass. And it's whenever they start to walk
and there on grass in this case. But you can do it either
way, you can do it when they're leaving the
grass, walking into the grass. In this case, it's whenever
you press the button, and they happen to be on grass,
it'll do a random chance, one in 10. And if it's equal to 1, 10%
chance it'll trigger an encounter. So that's the gist behind
triggering a random encounter, and a lot of these games really-- some games do it differently. They'll sometimes make it more
likely the more steps you've taken, they'll like sort of keep a counter
to say, oh, I've taken 100 steps, it should be a lot more likely now. Some games will just be completely
random, 1 in 10, 1 in 5, depending on how the developers
decided to implement their game. The former is a bit more robust. But for simplicity, we just
chose, math.random10 equals 1. So yeah, we create the tile maps
here, pretty straightforward. And then the actual random encountering
takes place in the player walk state. So here we have check for encounter. And so what this does is whenever
we enter the walk state, which is we press the button
to enter, or to walk, this entire function
gets called, because we do the transition to the player
walk state in the state machine. All of the entities are still
using just a regular state machine not a state stack. Wasn't necessary for this
demonstration, though I'm sure there are some used cases for
using a state stack for an entity. In this case, we're just
using a regular state machine. So when we change to the walk state,
we are calling enter as we've seen. And then we call self,
checkForEncounter. And so self, checkForEncounter
will set a flag if we have not started an encounter
basically and will allow us to move. And if we have checked
for an encounter, it will, or if we have triggered an encounter,
it will push in checkForEncounter, it'll actually push a
battle state onto the stack. So checkForEncounter just
basically does what we said before. If the grass layer,
because we have two layers, right, we have the base
layer and the grass layer. So if the layer at yx where yx
is are entities map x and map y. If the ID of that is
equal to tall grass, and we have just a global
constant table called Tile IDs, which has all these IDs. And math.random10 is equal to 1,
OK, change the entity state to idle, so don't let them keep walking. Pause the field music
rather than stopping, so that way when we come back to
the field later and we press play, it will be at the exact
point that it was before. Triggered the battle music, and then,
we've seen this already, fade in state, push to the stack, right? So over one second, we're
going to fade to white. So this will have the effect
of the music starting, but we're fading to
white right away, which is very sort of similar
to how most RPGs do it. And then we have our
callback function, which will execute as soon as the
fade in state's done, right? In this case, push to battle state. Battle state takes an
entity, and the entity has all of our Pokemon
information, that's why we're passing that
into the battle state. So the battle state can say,
oh, what Pokemon do you have? OK, I'll be able to look at your
party and say, OK, your first Pokemon is this, send him out to
battle, et cetera, right? And then lastly, push a
fade out state, right, because now we've got the battle
state on top of the play state, but we want to fade into it, right? So we're going to fade, we're going
to put the battle state first, and then because we're
using a stack, we're going to put the fate out state on top
of that, and then fade out to that, pop that off the stack. And then we have our battle
state that we just pushed, right? And then self.encounterFound
get's set here. And that's creating an
encounter, checking randomly, pushing the right things
under the stack, battle state, fade state, fade in, fade out. And then you're set to go. So that's effectively what the-- it's known in RPGs as the field
versus the battle or encounter state. Even though we're calling it play
state here, we've left the field, we've gone into the
battle at this point. And so now we've seen
basically everything that the field has to offer us. And we've covered everything
that's relevant there. So we're going to take a break
now for five to 10 minutes, and then when we get
back from the break, we'll talk about GUI
elements, panels, text boxes, and then we'll dive into the sort
of mechanics of the battle state. All right, welcome back
to lecture 7, Pokemon. So before the break, we
talked about the play state, we talked about the states
stack more importantly, and then we talked about how
a anonymous functions are sort of the backbone to how we
get a lot of this asynchronous and deferred behavior for our
game, which is very common in RPGs, and I mean, a lot of genres, a lot
of complicated genres of this sort. Another big key part of games
like this are the graphical user interfaces, or GUIs as
they're shortened to. Things like panels on the
screen, things like labels-- text labels that move around, things
like lists, text boxes, scroll bars, and you can get a lot crazier with it. In this particular lecture, we'll be
talking mostly about panels, labels, text boxes, and scroll bars-- progress bars rather, not scroll bars. But the sort of the
first I think corner-- or the first sort of
like keystone GUI widget that we should take into
consideration is the panel. So a panel is [INAUDIBLE]. So if we look at this in a game-- just pretend this is a panel I guess. So this is effectively
all a panel is, right? It's just sort of a rectangle. It allows us to-- if you're
looking at most user interfaces, like text boxes on your screen,
or if you're on Facebook and you're looking at almost
anything, like your little message window, a lot of those things at
the very core, the very bottom, the foundational part is just a panel. So any guesses to how in Love2D,
we can make a simple panel? AUDIENCE: Two rectangles
of different colors. SPEAKER 1: Two rectangles
of different colors, that's exactly what we end up doing. So that's effectively
how we can make a panel. There's another way of making a panel,
which we won't do in this lecture, but it's called-- we use as a construct
called a nine patch. So a nine patch is-- imagine taking this little image
here, and it's of some arbitrary size, but it's very small. And this is very similar
to how a lot of games implemented their panels
or their graphical user interfaces back in the 80s and 90s, I
mean, to a lot of games till this day. But back when hardware was
fundamentally tile based, you could take a image like this,
split it up into nine pieces-- nine patch is where the
terminology comes from. And sort of similar to how we actually
constructed the Zelda dungeon, recall, where you have corner pieces, and then
a top, bottom, right, and left side. You just layer this,
one of each of these, first off, right, of the corner pieces. And then however many you
need of these on the sides to create this rectangle, right? So imagine we've created-- these are all, if we can visualize
these as being a bunch of tiles, right? So just imagine that we've
taken these corner pieces, these are the corner pieces,
we've taken one of each of those. And then we take these side pieces,
and we just like draw a bunch of them like that. And then we take this
centerpiece, and then we can either layer it, or tile it a
bunch of times, or just stretch it. And stretching it has a bunch of nice
bonuses associated with it depending on how you've set your filter mode,
love.graphics.setdefaultfilter, if you set it to
bilinear versus nearest, you can actually get a nice gradient. And if you set it to nearest,
you get a nice pixelated look. But you'll see this often, and
Unity has nice support for this. Take an image that has maybe
more complicated than you could get with just two rectangles, right? Something that actually has a design
and maybe a gradient color, and actually layer-- I mean, create a arbitrarily
sized text box to fit your needs. And if these aren't even increments
or whatever your tile size is on your 9 patch, you could just scale
the top, bottom, left, and right side as well just to keep it
scaled, the centerpiece. So does that makes sense. So this is common, we won't
be using that in our lecture, but it's a very, very common piece to a
lot of graphical user interface design. In a lot of games, you'll see it a lot
if you get more into game development, so it's definitely worth talking about. Another piece that we'll be talking
about today is the text box. So I mean, what's a guess
as to what the text box, how we can implement a text box, and
how we will implement a text box? So what foundational
piece can we start with? We already have-- yeah? AUDIENCE: You just
put use the love print to the screen over one of those boxes. SPEAKER 1: So use the love print to the
screen over one of the boxes, exactly. Yep. So maintain a list of
text items, right, text. And then just draw them inside
a panel, and there's a text box. You've taken two ideas, and
sort of mix them together. A selection is kind of the same thing. It's a the only difference
being that with a selection-- so a selection is another
thing if we think about, for example a menu where we
have fight, and like run, and it may be in a more fleshed out
game, we have like an item thing, right? So that's a menu effectively. It is very similar to what
we get with a text box, but it's got a set of ingredients
here, fight, item, run, which they aren't set to wrap, they're
not one like contiguous set of text. It's just a bunch of items. And then nice thing about
a selection is that you can have a cursor on your selection, right? And then what do we need to
associate with like, for example, if we want this to
actually do something, and if we think about what
we've been doing so far, how do we go about
implementing functionality with a selection like this? Like what needs to get associated with
each of those entries in our selection? Callback function, right? Just as we've done with everything else. If you have a fight item here, each
of these, if we think of the selection as being just this part
of what we're looking at, right, because this background part is
just a panel, we don't care about that. We care about the
selection at the moment. The selection is the items
and the arrow, right? When as we'll see in the
assignment, your goal will actually be to take selection and get
rid of the arrow functionality, because for the
assignment, you don't need or want to have a selection
active, a cursor active. You just want a list of things. But based on what the cursor is pointing
at and when we press Enter or whatnot, we should index into
the selection, and then execute a callback that's
associated with each of these items. And that's how we can get
behavior out of the selection, rather than just being a list of
things that we render to the screen. If we have fight, and we
click Enter, a callback is set to maybe push a
state onto the stack that will trigger an interaction between
the two entities on the screen, right? The first one will
attack the second one, the second one will
attack the first one. And that's sort of its own asynchronous
set of states that do its own thing, but it's kicked off via
an anonymous function that we've associated with
each of these things, right? An item pushes another state, which is
like an item mini state, where then you open up a brand new set of menus that
you can look through all your items, and each of those items has a
callback associated with it, right? Your potion has a callback
associated with it that says, when I click on
this, either by default, just restore the HP
of my active Pokemon, or let me choose who to restore. So therefore, push another state,
which is like a select Pokemon screen with its own set of callbacks
associated with each of those. It's just in order to get all of
this sort of complicated behavior that you need to, it's really
ultimately just pushing states and adding callback functions to
all of these different options that you can select. And then run, push a fade
state, and then pop this state, and then push a fade out state. And that's really all we're doing. And so this look at all
of these GUI widgets here is just sort of a conceptual look,
but we'll take a look very shortly at some actual implementation. The last one that I want to
look at is the progress bar. So a progress bar for
example, the HP that we've seen in the actual battle where when
we take damage, it goes from right to left. Any guesses as to how we've
implemented a progress bar? Yes, Tony? AUDIENCE: Once again, two rectangles. SPEAKER 1: Two rectangles, yes, exactly. One, and then the nice thing
about rectangles in Love2D is you can set the edges on
them to be rounded or not via an optional parameter. So without anything more
complicated than a rectangle we can just create these sort
of almost ellipsoid progress bars, very simple progress bars. Ones the red, right, the
red that's the background. And then ones the outline, the black. And one is set to fill
with the first parameter, one's set to line with
the first parameter. Now how do we go about
animating whether or not, how do we animate the decreasing
amount of health when we take damage? Yes? AUDIENCE: Between the width. SPEAKER 1: Between the width, exactly. And what are we tweening it by? How are we tweening it? How would we calculate how
much we need to tween it? AUDIENCE: Well, you could just have
your width equal your health remaining. SPEAKER 1: If your width is set
to equal your health remaining, then your health is maybe 10. And you want your health
bar to be like 100 pixels long, how is that going to work though? AUDIENCE: Multiply it. SPEAKER 1: You could
multiply it, but if you know the width that you want
your progress bar to be, you can just multiply the
width by the ratio of the max value of your HP, or sorry, the ratio
of your current HP over your max HP, right? So if you're missing-- if you have
50 HP, and you're missing 5 HP, your ratio is 45 over 50. And if you multiply that by your width,
you get the exact amount of width that you need regardless of how
wide you want the bar to be, if you want to be 1,000 pixels,
if you want it to be 50 pixels, as long as you multiply current health
over max health times the width, you'll get that ratio no matter what. Does that makes sense? Cool. So that's a look at all the GUI widgets
that we're looking at, how they sort relate to what we're doing. We'll take a look at
their implementation here. So I'm going to go ahead
and open up the panel. And I'm going to move
a little bit quickly so we can get into sort of
the meat of the battle here. The panel is as we've said before
just two rectangles, right? It takes in an xy within a height. And then we would just
draw two rectangles. One is larger than the other. The bottom rectangle is slightly
larger than the top rectangle. So the first rectangle gets
drawn and it's whitish. And then-- oh, I'm
sorry, sorry about that. We have a xy within a height. And then we're drawing two
rectangles to the screen. We have the background
rectangle, which is drawn first, which is going to be the full xy
width and height of the panel. And then we're going to draw that
at a white color, and then draw-- in the context of this game-- we're
drawing everything at the same color, but we can change the color. If we wanted to parameterize
it, we could do that. We could set, we could have a color
option here in the constructor. We're not doing that, we're just
drawing everything the same color. But that's how you would
get like customized menus, some RPGs let you do that. And then what we're doing here is we're
just within a small slightly smaller boundary. So just two pixels
smaller on the x and y. Where you are going to
draw the second rectangle, which is a kind of dark shade of gray. And that is a panel,
that is all panel is. And then we could just
have a function called toggle, which sets it to
visible or not visible. And if it's visible, get rid of it,
or if it's visible, sorry, draw it. Otherwise, don't draw anything
when it gets rendered. So that's a panel in a nutshell. Any questions? Cool. So the next thing that we
should look at is the text box. So a text box-- so the text box is a little bit
more complicated than a panel. A text box in a nutshell needs to
take in some arbitrary body of text, and it needs to chop it up based
on how wide your text box is. And if it surpasses the
height of your text box, right, ideally, you should page your
text so that you can press Space bar, Enter and go through pages of text
until you've exhausted all of your text. And you press Enter one last time,
and you get rid of that text box. And so we have a panel here, which
we have an xy width and height in our constructor for the text box. And we have our text as well. And then we have a font if
we want to explicitly decide what font we want to use. In this case, or at
large, we're going to say that we instantiate a panel at xy
width and height, nothing too fancy. And then the fancyish part, the
slightly more complicated part is here on line 20
where we say, underscore self.textChucks gets self.font,
getWrap, self.text, self.width minus 12. So anybody know what this function
does or want to take a guess? Yes? AUDIENCE: Is that the page
thing you were talking about? SPEAKER 1: Exactly, it's
the paging of the text. Is the chunking of the text rather. Not the paging of the text, so much
as is the chunking of the text, which we will use to page the text. So we take some you know
arbitrarily large body of text, it can be as large as we
want it to be, and given-- this is actually a function
of to Love2D font object. So this is given to us from Love2D. Get wrap will return two
values, the second of which is all of the pieces of text that
the main big body is divided into based on the width. So this self.width of
minus 12, that's how wide it's going to divide our
text into chunks of up to, it could be slightly smaller
than, because it divides it based on the word. But no piece of text will ever
exceed self.width minus 12 width. And this will allow us to then render
several lines of text within our text box, and they will never
exceed the boundary, right? And so the paging functionality
is actually in next chunks. So we call self next here at
the end of [? knitt ?] function. And then self next basically checks to
see, OK, are we at the end of the text? If we are, then we're not
going to display any text, and we're going to close the window. We're going to close the panel. But if we are not at the end of the
text, like we still get text left, what we want to do is new table. And then we're going to,
up to three iterations, we keep track of where we
are in our chunks, right? We get self.text chunks equal
to all of those chunks, right. And that could be an arbitrary number. It can be only one chunk, there
could be like 30 chunks, right? We need a counter to
keep track of where we are in terms of like based
on what page we're on, right, and however many lines we
rendered to the screen thus far. So starting at I, and
I get's chunk counter, and chunk counter will
get incremented by three every time we call next
chunks, which is every page. We could have easily just
called this next page as well. It's going to insert
into that chunks table that we just created,
self.textChucks at i. And once we've reached
the number of chunks total that we returned from get wrap,
we're going to flag end of text as being true, and then
we're going to return it. And so what this will
do is, eventually, we're going to be equal to the number of
chunks that we got from font get wrap, right? And once we are, that
will signal with next that it's time to close the
text box, because end of text will have been set to true at the
end of that last chunking process. And then we can see here,
when we update text box, and that whenever it's
on the top of the stack, remember, we're looking for
a Space or an Enter press, and then we just call self next. And that will have the effect of
eventually closing our text box. And then is closed recall, we
looked at that earlier, we checked to see it is the text box closed? And that's just a flag that we set here. And then for rendering purposes,
we render the panel first. And then for each of our displaying
chunks, so we only have up to three displaying chunks at one time, which
gets set by the next chunks function. We just print that to the screen
using i as a multiplier on our y. And so that will render up two or
three lines, i, i plus 1, i plus 2. Any questions as to
how the text box works? It's a little more work
than the panel for sure, but it's fairly straightforward. We're just keeping a list
of a bunch of text things, and then we're just chunking
them based on how wide the text box is, the dimensions thereof. And then let's take one
look at this selection. So a selection is basically, a list
of text items with a cursor, right? And as I said before when we were
looking at the screen over there, each of those text items has a
text value and a callback function. And the callback function
is what allows us to assign behavior to this selection
object beyond just displaying things, right? Because when you have a menu,
when you have a selection and you select something, you
want behavior to happen, right? So each of these items indef.items will
expect to have a callback function. And then here, when we update
the selection, what we're doing is we're updating whatever our
current selection is, which is just a number between 1 and the number of
items in that selection making sure that if we're at one and we go minus
one, that we go back to the bottom. And if we're at the bottom
when we press, and we go up, we go back to the top. And we play sounds,
cutesy, things like that. And then for each-- and for our selection here,
from one to number of items, we calculate how much
padding that we need. And we draw the cursor
at our current selection, and then we draw each item based
on i and whatever our gap width is of our panel, which we assign it to. So we divide our panel up,
and then basically just keep track of where
current y is and draw the actual selection
and the cursor if that's the current selection to the screen. Any questions as to how a
selection sort of works? Notice here, if we press Return,
if our selection is being updated, self.items at
self.currentSelection.onSelect. So it's expected that that item will
have an onSelect function, which is that callback function. OK, and lastly, we'll
take a look at the menu. And then we'll finally
take a look at the battle, which is where sort of everything kind
of comes together with all of this. And that'll be it. And then we'll talk
about the assignment. So the menu is a panel
and a selection together. That's the gist behind what
a menu is in this game. You can define a menu
to be a lot of things, and you can get a lot more complicated
with a menu, but in this example, in this implementation, we're
just saying a menu is a selection and a panel put together as one item. And we've seen it in the game,
[? if we're ?] going to run it. That's just a text box. Going to look for a battle. OK, so here's a battle. That's just an empty panel
at the bottom, regular panel, but now it's a text box. We push the text box onto the stack. Push another text box onto the stack. And so this is a menu right here. Notice that there is
a cursor and there's a selection embedded within a panel. And each of those items,
the fight and the run, those have a callback
associated with them. The purpose of the fight callback
is to trigger a new state where the two Pokemon asynchronously
attack each other, in chain behavior one after the other. And then run pushes a dialogue,
then pushes a fade state, then pops both of them, and
then pushes a fade out state and puts us back to the play state. So that's effectively
what's going on and that's an example of what the menu looked like. And so a menu, just a selection
with a panel put together. When we draw the menu, we draw
the panel and then the selection. And then when we update the menu,
we only update the selection, because that's all we care about. And that's basically it. And so the menu itself
will get a def, that def should have items, that items
will get passed to the selection. That's pretty much, that's it for the-- oh, progress bar as well. We'll look at progress bars when
we get to the actual battle state. So now, let's take a look at a few
of the classes and data structures that are pertinent to
the Pokemon themselves. So if you look at party
as are first class, very simple class, literally just this-- self.pokemon is def.pokemon is
just a container at this point. You can take this-- I mean, even in I think
a fully fleshed game, you wouldn't really need
much more than just this. But if you needed to expand
upon this idea at all and you know preserve metadata
that exists for the party, this would be a perfect way to do it. The actual pokemon class itself is
not a whole lot more than effectively a bunch of stats. And that's a lot of what an RPG is. This genre is-- it's
mostly just numbers. You're just comparing
numbers against numbers and then adding a roll of the dice. That's effectively, that's what
Dungeons & Dragons, a lot of it is. And that's-- yes? AUDIENCE: Would it make
more sense to store just delta per level and your initial
one, so you can have fewer variables? SPEAKER 1: Say it one more time. AUDIENCE: Wouldn't it
make more sense, instead of storing your HP and
everything for each level to store your initial
stats in each area, and how much you would go up per level. SPEAKER 1: Would it make more sense
to store the amount that you go up per level for your Pokemon? Yes, that is what we're doing. So we have a base-- so here's how the split works for
the stats in this case, right? We have base HP, base attack,
base defense, and base speed. A level 1 Pokemon
has-- a level 0 Pokemon has these stats of this species, right? Every Bamboon or whatever
Pokemon that we choose will have whatever we've
allocated it to be it's base HP, base, attack, base defense, base speed. And then the thing about Pokemon
and I mean, a lot of RPGs will sort of do this
thing, but we need some way of leveling up the Pokemon in an
necessarily non-deterministic way. Like two Piggies that level up may
not have the same stats, right? One might have slightly
higher attack than the other, one might have slightly
higher defense than the other. We do this using what's called an IV,
and that's what Pokemon itself does. And it's short for individual
value, this is sort of like the DNA of your Pokemon, right? So this HP IV is separate from your
base attack, base speed, base et cetera. And this basically, it gets compared
against a dice roll every time you level up three times. And this is how I've programmed
it, it's not necessarily how Pokemon itself does it, but
you will roll a dice six times, or three times, one through
six like a normal die. And you'll look to see if that roll
is greater than your IV, right? Or it'll check to see whether your IV
is less than or equal to that dice roll. And if it is-- or sorry, if it's greater than
or equal to that dice roll. And if it is, it will increment that
stat by 1 for those three dice rolls. So you can get up to three more,
or you can increase the stat by up to three times per level. But you can only have an IV up to five. So you're rolling against a six, and
you will occasionally not roll a 6. It checks to see
whether or not the IV is greater than or equal to the dice roll. And if it's not greater than or
equal to the dice roll in the event that it is a six, or if
the IV is up to a four for example, which means a
five or six will go against it, then it will not get a stat increase. And this is a sort of simple way of
implementing this DNA based system. It's randomized, but
it's a weighted, right? If you have a higher IV,
you have a higher likelihood of being greater than or
equal to the dice roll. And so that's how we
implement stat increases. And then we need a way of keeping
track of what our stats are, like our actual stats. So our actual HP, our actual
attack, our actual defense, and actual speed that's been
calculated level by level, we need a way to keep track of that. We need level, we need
our current XP, and then we need our-- and the
amount of XP to gain a level, which will get higher and
higher per level, as you can see here, because it takes in the
self.level times self.level. And then it multiplies
that by five times 0.75. And then your current HP. So we're really not storing
our value level by level, we need the base because we
need to know what our base was. I mean, we could effectively
globally reference these variables, but it's minor efficiency
gains at that point. But we need the IVs and we need the-- I mean, we need a reference to the IVs,
we need a reference to the base HP, and we need to keep track of
whatever our actual stats are, and then our current HP
always, because our current HP can differ from our actual HP. And in the actual game, you
can have your attack, defense, and speed also vary match
by match, because you have moves that lower your
speed, lower your attack, lower your defense, et cetera. In this case, we haven't
implemented that, so we don't have a current
attack, current defense. But in a more complete implementation,
you would have that sort of thing. Does that sort of answer your question? Is that in the right vain? OK. And so here's the level up code. So like I said, three
dice rolls, one to three. If six is less than or equal to our IV,
so it could be a six, in which case, it would be greater than what
are max IV could possibly be. IVs range from one to five, but if
it's less than or equal to that IV, then we're going to consider
that a stat increase. It's a weighted odd to determine
whether or not we get a stat boost. And it does this for every stat, and
then it returns all of the increases. And this is relevant, this line
95 for a return HP increase, return attack increase,
defense increase. This will be relevant for
assignment 7, because your goal is to take these increases and actually
display them to this user in the battle state when he gets a victory,
or he or she gets a victory and has gained a level. You will display a menu with a
selection that has all of these things, and you'll need this value. So it returns these values here, and
you'll be calling this function any way from your battle state stats level up. Or we'll be calling level up rather,
which returns self stats level up. And that's all a Pokemon is. It's effectively mostly
a data structure. And we use this in our battles to
throw dice effectively back and forth, and have a victor and a loser, and
then gain XP and gain levels that way. So any questions as to how a
Pokemon object class works? Cool. We'll take a quick look at what
the actual definitions look like, which you can probably take a guess. It's very simple, just key names. And then we have the actual
name, we have the sprite names, we have the HP, attack, defense-- all the things that get
put into the actual object, they need a reference
to in the definitions. And so Pokemon ultimately
are just this, they're just data, right, which is what we
talked about in a prior lecture, data driven design. The more you can take
all of your Pokemon and make them into, or
anything, Pokemon or any object, and turn it into an easy to
write data structure like this, the easier it is for you to add more. We could easily add, it wouldn't
take too long to create 150 of these. I mean, they wouldn't
be all that interesting, because we don't have
moves implemented yet. But in an ideal world, we'd find
a way to also model moves as data, and therefore, you can just link
moves to your data structure, to your Pokemon object like this. Yes Tony, did you have a question? AUDIENCE: Well, I just wanted
to mention that the paradox games are very good about that. [INAUDIBLE] SPEAKER 1: Oh, like Crusader Kings? AUDIENCE: Yeah. SPEAKER 1: The comment was Paradox Games
are very good about data driven design. I'm assuming you've dug
through their files? AUDIENCE: To some extent,
and also it's just if you play their games for awhile,
it's everywhere, like to the extent that sometimes on the Wiki,
they put the source code up. SPEAKER 1: Oh, yeah. Yeah, no, it's just good game design. Ultimately, if you want to-- and their games are large,
they have a lot of content. If you want to have a lot
of content in your game, you need to find a way to take
the burden off the programmer and put it onto the
designer, or at least make it easier for the programmer,
because making source code and debugging source code all day long,
especially for very complicated things is not easy. And it's ultimately not a
desired thing to do, right? It's a lot easier for me to whip up
a new creature in 10 lines of code here and feel good about it than hard
coding a lot of these sort of things, right? So shifting as much of it to data as you
possibly can should be your end goal. So that's what Pokemon defs look like. Before we get into the
actual battle, we want to take a look at what
a battle sprite is. So a battle sprite is what was
rendering onto the screen, right? So we take a look here. That's not a battle sprite,
but almost a battle sprite. That was just a texture. So if we get into a
battle, slowly but surely. All right, so these are battle sprites,
and they don't look much different than a regular sprite,
and they're not that much different than a regular sprite. But they have some functionality that's
important, mainly that functionality where one is flashing, and then
one was being opaque, right? So in order to do both of those things,
we need to store some sort of data within our sprite, right? Yes? AUDIENCE: Zelda for the
invulnerability flashing. SPEAKER 1: Yes, exactly. For what we used in Zelda for
the invulnerability flashing. For the enemy, or I should say, for
whoever is getting attacked, yes. They are getting an opacity flag stored. They have an opacity flag stored in
their object that we can tween, right, we can tween on and off
over the course of time. That's what we did with the entity
in Zelda when it took damage. And we set it to invulnerable,
and while it was invulnerable, it was flashing on and off. But we can't necessarily do that with
the sprite that's blinking white, because there's not really
a like white flag, right? We can't make something
completely white with just a flag. That's something that we actually
need to use a shader for. And so a shader, and we're not going
to get into too much detail about this, shaders are pretty complex,
a little arcane at first. But what they are is effectively
a little program that runs on your graphics card, and that
looks at when you're drawing something, it looks at every pixel depending
on what kind of shader you're doing. But for the sake of this demonstration,
we'll look at every pixel that you're drawing to the screen,
and perform some sort of function on that pixel, and produce
a new value, right? And this is how you get a lot of
really crazy awesome things to happen, but it can be pretty insane. Shader Toy, I think is the website
that has a ton of really cool-- I'm not going to pull it up now, just
'cause I don't remember the name, I believe it's shader toy. There's a website where people post
all the shaders that they've written, and you can see a lot of
really crazy stuff, things that you would never imagined were
possible with just code like this effectively, looking
at positions of pixels and [? pictures ?] on
the screen and whatnot. But effectively what this does, this is
a white shader, the goal of this shader is to just turn a
sprite completely white. That's all the goal of this shader is. So it gets a float called white
factor, which [? you'd ?] say here. And then white factor
effectively is just going to be summed onto
whatever the RGB is of that pixel, whatever pixel that
we're drawing when the shader is active. What that has the effect of doing
is, white factor, if it's equal to 1. Here's the thing about shaders
and a lot of this stuff, a lot of the data
structures within shaders are based on floats that
are from zero to one. So if we assign the RGB of something
to a vec 3 that's 1, which is 111, that's going to be 255, 255, 255. Therefore, that pixels RGB
is white, pure white, right? And so what we're doing here is on
our battle sprite, self.blinking and one or zero, remember, that's
the LUA [? turnerri ?] operations. So we're saying, if self.blinking
is true, one else zero. So send our shader white factor based
on whatever value self.blinking is. And so that will have the effect of
the shader getting a one or a zero, and adding a one or a zero
to the RGB of that sprite. And if blinking is set
to true, the sprite's going to basically be drawn
every pixel at 255, 255, 255. Otherwise, it'll get drawn with
whatever that image's pixel value is at that position. Does that makes sense? OK. The syntax is a little
bit weird, but that's what's happening here in this shader. And there's a link here
where I found the shader, but it's a very simple,
very simple shader, probably like one of the
simplest shaders you could write. But it's a great example of what you
can do with a shader, and pretty simply. And it's nice, because you can take
like texture coordinates and do math based on that, or pixel coordinates
and do math based on that. You can pass in like a sine
function for example, in your file, and have that sine function perform work
on like RG or B value of your sprite and do all kinds of cool stuff. It's really neat, like the possibilities
are limitless with shaders. But that's how we get
it to blink, because you can't do that outside of this-- I mean, there's probably some weird
way could get it to work as well, but this is probably the simplest way
we can get our sprites blinking white. And so self.blinking just gets
a timer.every0.1 or whatever. We'll actually see that
in the attack state. But that'll flick to self.blinking
between true and false. It'll negate itself over and over again. All right, so that's the battle sprite. Last thing we'll look at is another
extremely simple class, opponent. All the opponent is is it
has a party, that's it. But in a fully fleshed
game, your opponent might have a like trainer sprite. A message that it
says, like a full party of Pokemon, a gold value that
will give you when you defeat it, all kinds of things. But it's here just as
a simple illustration. Yeah? AUDIENCE: [INAUDIBLE] put
a method for on defeat if you want to maybe
have it kind of collapse the room or something like that. That would be another
thing that you could do. SPEAKER 1: Oh, a method? Yeah, we can associate a method
with an opponent called on defeat, or whatnot that will do arbitrary
things, collapse the room, or otherwise. Yes, absolutely. Or even push a new state,
like to like teleport us to a new location in the world map. Maybe we like cleared
the elite four and we want to get teleported to
like the end credits, exactly. Limitless possibility. So let's go ahead and take a look now
while we have just like 20 more minutes or so left. We'll take a look at the battle
state, because the battle state and the states that they're in
are probably the more complicated side of how this works. So a battle state, we have a player, we
have a bottom panel, the bottom panel for when we start the
state just for that part, but otherwise, we're always
pushing things onto it. Whether we've started the battle or
not, because when we are fading in-- sorry, yeah. Because when we initialize this state,
we also push a fadeout state onto it. But we don't want to trigger
the tween of the Pokemon sliding from left to right until
after that state gets popped. So we have a flag here, which will get
set to true on the very first update iteration. And then when that
gets set to true, we'll actually tween the Pokemon
going left to right, and kick off all the other
sort of asynchronous processes that exist thereafter. But let's look at the battle one more
time just to see what's going on. So I'm going to walk
until I get into a battle. OK, we got a battle. So notice here, the fade
in happens as soon as the-- the slide in happens as soon
as the fade starts, right, as soon as the fade
finishes, I should say. We get a message popped
onto the screen, right? It says a wild X appears. Right, that's the enemy Pokemon. We hit Enter. Turn this down a little bit. We hit Enter, and then we pop another-- push another state onto the stack,
another battle message, which is very similar to a dialog state. Says go our Pokemon. And then we push a menu
onto the screen, right? We've got a menu that says,
fight or run, a selection. It's a menu, which has a selection. And then now, this is the
top of the stack, right? So it's the only thing getting input. Everything else is rendering beneath
it, but nothing's getting input. So we have the option
to either fight or run. Let's say we fight. We fight, we got a new state
now, we're in an attack state. Several things just happened. So what happens as soon as
we kick off the attack state? Yeah? AUDIENCE: You get a text
box saying, x attacked y. SPEAKER 1: Yep, so the
first thing we have happened is, a text box that
says, x attacked y, where it could be either us or
the opponent, because it's based on whoever has the higher speed. And then what happens next? AUDIENCE: [INAUDIBLE]. SPEAKER 1: Well, it does. So let's take a look at
it right now and tell me what exactly happens as
soon as the text box pops up. So what were the pieces
that happened there? AUDIENCE: Flash. SPEAKER 1: OK, so the
attacker flashes white, right, which is the
shader that we looked at. That's the shader blinking on and off. There's some timer that says,
every 0.1 seconds, blink on or off. And then what happens? AUDIENCE: Then the damage
is dealt. [INAUDIBLE].. SPEAKER 1: Well, damage is dealt,
yes, but what happens visually as soon as the white blinks? AUDIENCE: The other one blinks. SPEAKER 1: The other one blinks. What's the other one blinking? AUDIENCE: I'm not sure. SPEAKER 1: So it's opacity, right? So remember, we're doing the exact
same thing we just did with that white, with the blinking, but we're
tweening every 0.1 seconds the opacity of the defending Pokemon. And then we take damage. Then what happens when we take damage? AUDIENCE: The reverse basically. SPEAKER 1: Well, what gets animated
when the thing takes damage? We've animated the blinking,
we've animated the opacity. AUDIENCE: [INAUDIBLE]. SPEAKER 1: The health bar drops, right? So we're chaining
several things together. We're chaining-- first, we're doing
them every 0.1 seconds for six times, blink white. Then blink the other
thing opacity, right? And we're playing sound
effects at the same time too, we're playing a sound effect for the
attack, sound effect for the hit. And then once that's finished,
tween the health bar, right? So we've modified the health
of the defending Pokemon. And then what happens after the first
one, after that process is finished. AUDIENCE: Repeat for the other side? SPEAKER 1: Exactly, repeat the exact
same thing, but for the other side. But what are we doing in
between each of those? We have to do something. AUDIENCE: Checking if somebody dies. SPEAKER 1: Checking if
somebody dies, exactly. And if somebody dies-- let's say we die, what happens? AUDIENCE: [INAUDIBLE]. SPEAKER 1: Well, we yeah, we
go back to the play state. We fade out to black, and then
we go back to the play state. What happens if we knock out the enemy? AUDIENCE: Go to this screen. SPEAKER 1: Exactly, and
what happens on this screen? So what's the first thing that happens? Well, so recall, what happened
when the Pokemon died? What happened? AUDIENCE: It fell off
its platform thing. SPEAKER 1: Exactly, so that's a tween
probably, right, on his y value. Then what happens? AUDIENCE: [INAUDIBLE]. SPEAKER 1: Exactly, we've pushed a
battle message state onto the screen. And then what happens
when we press Enter? AUDIENCE: [INAUDIBLE]. SPEAKER 1: What just
happened right there? AUDIENCE: [INAUDIBLE]
text box that says, you earned whatever experience points. Then you get your XP goes up. And presumably, it
checks if you leveled up. SPEAKER 1: Yes, correct. AUDIENCE: [INAUDIBLE] to level up. SPEAKER 1: Exactly, so
when push a dialogue to the screen that says you've earned
x experience points, the XP bar tweens, right? We've gone up to however our ratio
of current XP to next level XP is. We animate our text bar that
way, or progress bar that way. Then we push a fade in
state, right, to white. And then we have to pop
everything off the stack, and then push a fade out
state to the top of the stack, and then we're back to the play state. But if we do level up, we need to play
the right music, play the right sound, and then part of the assignment will
be actually, in that exact function, you're going to need to add
some behavior that will do what? AUDIENCE: [INAUDIBLE]
display the change basically, and what the new one will be. SPEAKER 1: Yes, and what
are we going to need to do. What will we need to do in order to? AUDIENCE: [? Explain, ?]
what was it called? The selection box, but without
the selection part basically. SPEAKER 1: Yes, so once we've
taken-- once we've leveled up and we're in that victory state
of the battle state, right, we need to push a new state,
a new menu state, which has all of those stats and the
amount that they've increased. And then when we press
Enter, presumably, we should pop that off, and then pop
everything else back to the play state, and then do the fade in as normal. And that is the battle
state in a nutshell, a lot of pieces that sort of are waiting
on each other and input and stuff like that. But fairly easy to understand, just
because a lot of it is very simple things that are just chained
together over and over again to produce this sort
of interesting behavior. So here we have sprites,
recall the sprites are what we're going to need to animate those. We have health bars, which
are progress bars, which are just two rectangles that are-- ones a line, a black line, and ones
a fill that fills beneath the line, so that we get a sense of
how much is missing, right? We get the width, the height, a color. We can give our progress
bar any color we want to, which is how we get the difference
between, say, a health bar and an XP bar. We just make one red and one blue, and
we draw them in different spots, right, but they're both equally progress bars. And then they get a value. Their value is whatever sort of
determines how much of the rectangle is scaled. And the max is how much that
should be divided by in order to produce a ratio for the total width-- a scaler for the total
width, which will allow us to get the sense of an amount missing. And then a player circle
x, opponent circle x for the ellipses, just the graphical
details for the actual Pokemon, so that we can get their stats, so that
we can actually do dice rolls, or not really dice rolls in this case,
but so that we can add or subtract HP based on attack and defense. And so here was the update,
so trigger slide in. So what trigger slide in
does, is a one second tween, which you talked about, right? The Pokemon going left to right,
or left to right, right to left. There x values, just
tweening in over one second. As soon as that's finished, we're
going to trigger starting dialogue. So the starting dialogue is push a
battle message state onto the stack. The battle message state is just like a
dialogue state in that it gets a string here, so a wild something appears. It gets a callback function for
once we press Enter on that. And the callback function is itself
another push of a battle message state that says, go, and then our Pokemon. So notice that we're referencing the
self.opponent.party.pokemon there, and self.player.party.pokemon
there to get the actual name. And then once we've popped that off,
then we push a battle menu state here, right? So let's take a look at
the battle menu state. So this is interesting, because
this is where we actually define the behavior for our menu works, right? Recall, we need something to tells
us what happens when we press Fight, and what happens when we click Run. So when we click Fight,
notice here items, right, self.battlemenu gets
menu, and menu expects items. This items key, this table gets fed
right into the selection, right? And the selection, it
expects remember, a text, because it has to know what
to render at that index. And then an on select function. And that on select
function is the callback that gets executed when you press
Enter at that particular location in the menu. In this case, fight, what that does is
it pops this battle menu state where we no longer need the menu, so pop it. And then push a new take turn state. And then take turn state in this game is
the Pokemon fighting each other, that's what the take turn state is. And it could have been called
fight state, for example, but take turn state is
a little more versatile. If we wanted to maybe make,
maybe one Pokemon wants to run, the enemy wants to run and
we want to fight, right? But you can't always run,
so they should try to run, and then we can still fight them. Or they can use an item, or
we can use an item, right? There's a lot of different
things you can do. Or we want to throw a Poke
ball at them, and if we fail, then they should fight us, right? Take turn is just a
general purpose state that we could repurpose
for whatever use we want to with any interaction
between us and the opponent, whether it's fighting, running away,
or using items, catching them, any of these combinations of things. But in this case, for the sake
of this example, for simplicity, we've only implemented fighting. The we and the opponent fight each
other during this state, which is, one attacks the other,
and then we check for deaths in between both of those. And then go to victory or feinting
depending on which of those holds true, if either. Running is slightly different. So if we run, I've programmed it to be
100%, it will 100% of the time work. In Pokemon, you actually
have a chance to run based on what the delta is
between you and your enemy. So if they're stronger than you, you
actually aren't guaranteed to run away. So what we do here in my implementation
is, we just pop the battle menu, so it's gone. And then we push, you fled successfully
to the screen, this battle message. But there's a difference here versus the
other battle messages that we've shown. I mean, it's not really different,
but it's something to keep in mind. So I'm going to get into a battle. And so first of all, with that message
that you just saw on the screen, I had to actually press Enter, right? I discarded it explicitly
by pressing Enter. And that holds true
also for these messages. It won't do anything
until I press Enter. So I press Enter, and then I press
Enter, and it does it's thing. But notice the difference
between when I hit Run. I'm going to hit Run, I fled,
and it does it on its own. It's not waiting for input, right? So how have we implemented that? Yeah? AUDIENCE: Using timer,
you'd automatically do it the same way you would afterwards,
instead of waiting for you input, you just wait for the timer to end. SPEAKER 1: Exactly, so we use a timer,
and then when the timer is finished, we pop the battle message just like we
would have popped it by pressing Enter. This false flag is what
allows us to do that. We press false and false
is, can we input or not? And we can't. So actually, if we didn't do
any timer thing after this, and we just did that false flag, the
battle message would be there forever, and we could never get rid of it, ever. It would get stuck forever. So we got to be responsible and
say, OK, we're going to put a timer, we're going to call timer.after
0.5 seconds immediately after that. We're going to push a fade in state. And then we're going to do these two pop
operations here as soon as that fade in happens. This first pop will pop the
message, right, this message here that we didn't pop through input. So this is actually garbage
collecting, in a sense, for us. It's discarding the message that
we couldn't discard automatically. And then we're going to pop
the battle state, right? So running will push the battle
message, trigger a timer tween for our timer.after five seconds,
sorry, push a fade in state. And then after the fade in states
done, then pop both of those states. The message and the battle state
take us back to the play state. And that's where we'll be
as soon as that's all done. And that's all that's in
the battle menu state. Any questions as to how the battle menu
works, the difference between fight and run and sort of how those operate? OK. So let's take a look
then at the take turn state, which is the last piece
and the largest piece I would say. This is the most relevant
to the assignment. So we maintain a reference to which
Pokemon is first or second to go, which sprite is first or second
to go, and which progress bar is first or second to go up here. And we do that, like I
said, based on speed. So whichever Pokemon is
faster, and we could have also made this a little bit shorter, just
by keeping the sprites and the progress bars as members of the
Pokemon object, or the class, but since they're kind of
separated, like we don't necessarily want a Pokemon to have a reference
to it's progress bar at all times, or I mean, you could. There's nothing preventing
you from doing it. It would only serve the purpose
of shortening this code here. But we need to keep a reference to
this so that we can call attack here, which is this large bit of
code twice, without needing to duplicate all of that code twice. Does that makes sense? So Tony, did you have a question? AUDIENCE: Well, I was just
thinking, you could probably put that into a helper
function where you just change the order you pass it in. SPEAKER 1: Sorry? Say it again. AUDIENCE: I just kind of feel like,
I guess you could take the code, and you could avoid
duplicating that I guess. 'Cause it's just reversed, so
what you could do is you could-- if you passed into a helper
function, which you would just, instead of passing it first-- instead of passing it, opponent
Pokemon, player Pokemon, you would pass it, player
Pokemon, opponent Pokemon. And that would probably work I think. SPEAKER 1: Well, you also have
to take into consideration-- so the comment was, you could pass in
the player Pokemon and the opponent Pokemon into a function, and then
you reverse them in that function, I'm assuming, have reverence
them and reverse them. But the sprites are
decoupled from the Pokemon, and the progress bars are also
decoupled from the Pokemon. So we could shorten this by making
these four things here fields of the Pokemon objects,
but they're not strictly pertinent to the operation
of the Pokemon object. And it sort of kind of makes
the Pokemon objects a little too, not basically abstract
or lightweight enough, and it only serves the
purpose of this point, of just shortening this bit of code. There's probably a more elegant
way to do it, but it's hard to say. If this code were to get larger, maybe. But the gist of this
is basically to have a pointer to whatever Pokemon, progress
bars, and sprites should operate first in the attack versus what
should operate second. And then the two will trade blows in
order based on who's first and who second. So when we enter the
take turn state, we're going to trigger that attack, here this
function attack, which we'll take in first, second, first, second,
first, second for the Pokemon sprite and progress bars. And then anonymous function,
which get's executed as soon as the attack is finished, right? So this is a code that will pop a
message that gets pushed in attack, and then this is where we
actually check deaths, right? And it will determine whether we go
to victory or faint screen or not. If not, and we return if so. If not, we're going to do
another attack, but see, everything is reversed now. Now it's second, first,
second, first, second, first. So we have the same
function, self attack, which just takes in the attacker. And it's effectively, attacker,
defender, attacker, defender, attacker, defender for the Pokemon
sprites and progress bars. And so the attack function
here first pushes a-- well, OK. What does the attack-- let's go over it one more time. What do we think the attack
function does in order. We covered them just
a moment ago, but what was the order that happens when
something attacks another thing? Yeah? AUDIENCE: The attacker blinks white. SPEAKER 1: Attacker blinks white. AUDIENCE: Then the
defender blinks opacity. SPEAKER 1: The defender
toggles it's opacity. AUDIENCE: And the health bar shrinks. SPEAKER 1: Health bar shrinks. Exactly, and then that's
basically it for attack, right? Blink, play a sound, blink,
play a sound, shrink the bar, and also we're doing damage
in that function as well. We actually have to change
the status of the Pokemon. So this is effectively
where it starts, right? We place a battle message
state onto the stack that says, the attacker name attacks
the defender name. Notice that it gets false
just like the run message did, because we're not
accepting input here. But it's up to us actually, it done
up here at line 42 of the enter state. But we're going to after 0.5
seconds, play the attack animation. So power up sound every 0.1 second. We're going to member the
blinking flag on the sprite, we're going to toggle it by
setting it not to itself. So if something is not itself, if
it's a truthy value, it becomes falsy, if it's falsy, it becomes truthy. So basically, toggling
between true and false. Limit of six, right,
because remember, every will do something every amount
of time indefinitely, unless you pass in a limit of some
value, in this case, a limit of six. So we're saying, only execute this code
six times, only blink six times, right, only toggle six times,
blink three times, right, because it has to go on and off. And then as soon as those
six iterations are completed, we call the finished function
on that timer object, which takes an honest function. As soon as that happens, we
do the opacity bit, right? We blinked the attackers, so now
we've got to blink the defender. So we play the hit sound. We do the exact same thing that we
just did for the blinking, only now, every 0.1 second, we are setting
its opacity to either 64 or 255, depending on what the value
of its opacity is, right? So we are toggling between 64 and 255. Limit of six, take a
function, calculate damage, which we've just very simply done it,
attack minus defense, right, up to 1 though. So if the defense is actually
higher than the attack, which will still do at least one damage. And then over 0.5 seconds,
we take the defenders bar, and we tween the value equal to
their current HP minus damage, right? And then that will set in the bar, in
the progress bar, it'll set its value. And even though the progress
bar is behind state wise, right, it's on the bottom of the stack,
because it's on the battle state. And we're in currently
the take turn state, but because we're still manipulating
the values of that state, and we're rendering every state,
we're actually still manipulating that state regardless of the fact
that it's not on the top of the stack. So that allows us to shrink that
Pokemon's progress bar regardless of it being on the top of the stock or not. Then once that's finished,
once the tween is finished, actually set the current
HP to that amount, because we're only tweening the
progress bar's value, which is independent from the Pokemon's value. And then that's the end of the attack. The attack is completely
finished at that point. So any questions as to
how the attack works? Just a chain of tweens basically. So we do an attack, then check
deaths is the next function. And we're almost finished, I'm going to
kind of go quickly here, it's at 7:30. Check deaths is the player Pokemon
current HP less and equal to 0, or is the opponent Pokemon
current HP less and equal to zero. If the former's true, we need to
faint, and if the latter is true, we need to go to victory. So faint is effectively a battle state,
right, when it says, you fainted. And then what? Remember what happens when we faint? AUDIENCE: [INAUDIBLE] text
box, and then it leaves. SPEAKER 1: It leaves,
do you remember how it leaves as it differs from
like running away, for example? AUDIENCE: [INAUDIBLE]. SPEAKER 1: Well, beyond
that, aesthetically, do you remember how it's different? AUDIENCE: [INAUDIBLE]
differently to black, I think. SPEAKER 1: It does. It fades to black instead. So that's how we can
differentiate when we're fainting versus when we're running away. And so that's what we're doing here. Notice that the fade in state
RGB is zero, all of those. So it's going to fade in to 000255,
as opposed to 255, 255, 255, 255. So it's going to be a black
fade in versus a white fade in. And then once we've-- this it just sort of a
thing that I implemented so that we can keep playing indefinitely. But once that's finished, restore
the player Pokemon to full health, resume all the field music stuff. And then once we've pushed
a fade out state, 000, and then we've gone back
to the field, let's push. Notice that here it takes a function,
right, after the fade out state's done. Once the fade out is finished-- so as
soon as we're back to the play state, push a dialogue state
that says, your Pokemon has been fully restored, try again. Which will take the context,
and we'll [INAUDIBLE] to press Enter to get past it. That's fainting. Victory is a little bit more robust. So victory is-- do you remember
what happen when we get a victory? AUDIENCE: Well, it has to
check leveling up as well. It says, you've defeated your
opponent, then your XP bar increases. Then if you've leveled up, it
tells you that you leveled up, and then it leaves. SPEAKER 1: So it tells you you defeated
your opponent, XP bar increases, checks for a level up, and then leaves. After displaying the level
up message or not, it leaves. It pops everything back to
the play state, exactly. So remember, the very
first thing that happens though, the opponent sprite gets
tweened over the course of 0.2 seconds, it's y value to virtual
height, which means, all the way to the bottom
of the screen, right? The typical defeated your opponent
from Pokemon sort of animation. Once that's finished,
play victory music, push a battle message state
that says, victory, right? Once that's popped of the
stack, calculate the XP, which is, I just chose arbitrarily
sum all the IVs of that Pokemon times it's level, and that's the XP you got. Push a state that says,
you earned x XP, right? It's false, so that means
it doesn't take input. So that means it's up to us in
order to pop that off the stack. So after 1.5 seconds, we play a sound,
and then we tween that XP bar going up, right? So that's what's going on here,
self.battleState.playerxpbar, we're tweening of the
math.men, of the XP plus XP, or XP to level, because if we don't, it
could go past the edge of the XP bar, because we could go over
our XP to level, right? Let's say we have 10 XP till we
gain a level, we could gain 20 XP. We'd be 10 XP overboard. So we don't want to tween our XP
bar past the edge of the XP bar, it would be a graphical glitch. So a math.men our XP plus
XP, and our XP to level, which will take the
lesser of the two values. Once that's done, it's tweened,
we're going to pop the message off, and then we're going to
actually add the XP, level up. So this is where we level up if
the XP is greater than XP to level. Play a sound, set the XP to the
current XP minus our 2 level XP, which will mean that we'll
have some carry over, right? And then actually call
the level up function. Now here is where-- oh, and also after that,
congratulations, you've leveled up. Fadeout white, which is
just a white fade out here. I used it twice, so I
made a function for it. Just pushes a fade in state. Stop the victory music, play the field
music, pop, push a fade out state. So either way, when we've got a victory,
we're going to push a fade out white, or we're going to call
fade out white, correct? So push a battle message
state, and then as soon as we press Enter, because we
leveled up, fade out to white. And if we didn't level up,
but we still got to victory, we still need to fade out white. And so this is where your
assignment is, assignment 7. Assignments 7 is, notice that we
have self.playerPokemon level up. The key thing that we are
going to need to do here is add a menu that shows
us how we leveled up. And if you recall, playerPokemon
level up returns all the stats that you've increased this level. So you can show a menu that just
says, your HP plus that amount, right? You're going to get all four values. It's going to explode to
all four of those values. And then you're going
to create a new battle-- or not new battle menu, but a
new menu of whatever you want, but probably on the right side of
some vertical height for items. The only difference
here, the only key thing that you're going to
take into consideration is, and I'll go back to the
slides, because we're actually done at this point going over the code. But the selection items, you won't
be able to actually select anything, it's just going to be purely visual. So you're going to need to edit
selection to have the option to not have a cursor. And this is detailed in the
spec, which was actually released before lecture today. So you can take a look at that. But you'll need to make
a change to selection. But all the pieces are there. It should be a fairly
easy assignment as long as you understand how the
states work, how the menu works, and how to create a menu
based on those values, and how to actually get
the values from level up. So some missing features that we didn't
talk about, which we didn't implement are, for example, the
detailed level of screen, which is your assignment,
monster catching, right? We only have a party of one Pokemon
throughout this whole entire thing, but one of the arguably main appeals of
the game is to be able to catch more. So that would be something to add, to
prioritize probably adding to the game. A field menu so can actually look
at all the Pokemon you've caught. That would be nice, so you can
actually see how much HP they have. In item inventory, because the
game, the regular games have items. You can use potions, you
can find gold nuggets that you sell for a ton of money. Different abilities, currently we only
have basically one fight operation, which is like a tackle. And the game itself,
the regular game has like over 100 different moves
that have elemental attributes, and do different things,
and cause status effects, buff you or your opponent. So adding those is
appealing, and maybe being able to represent them
as data is nice to. Trainers that you can encounter in
the game that have their own preset or randomized Pokemon for to fight. Monster evolution, because that's
like one of the funnest things is taking a really weak
Pokemon, and like raising it to become really strong, and
evolving it at a certain level. Towns, routes, other levels
beyond just our basic square area. Monster breeding, which is
introduced in the second series, so that you can take two
Pokemon and have a chance to get an egg with really good stats
or a really rare Pokemon from it. And then like a day night cycle maybe
where different Pokemon come out at different times of the day. So you are incentivize to
play at different times of the day for that purpose. But that was it for Pokemon. Next week we'll actually
be diving into Unity. So we're actually done with
LOVE 2D, which is a lot of fun, but now we'll be going
into how to make 3D games. So this is a screenshot from the
game we'll be making next week, which is a 3D sort of side scrolling
Flappy Bird esque helicopter game based on a famous web
game called Helicopter Game. And it was sort of one of the
early ancestors to Flappy Bird. On the Wikipedia page, it
actually says that too. I remember playing, it was back in like
2007, or 2006, or something like that. But your goal in this game-- this
is a modified version of that-- your goal is your-- everything is 3D,
but it's a side scrolling perspective. So this is called 2.5D for that reason. You're controlling a helicopter,
you're the purple helicopter. And your goal is to in an
infinitely scrolling world. So we'll revisit infinite scrolling,
but in 3D, avoid skyscrapers. So you can see there is a green
skyscraper, crudely modeled. Collect coins, so you can see
there's a coin there, it's a 3D coin, it will always be spinning. Your coins are up at the top right. You'll see a background
that's infinitely scrolling. And then you'll have
jets that will randomly fly above you to sort of give you
another sort of layer or dimension of obstacles to watch out for. And this will teach us a lot of
the basics of how unity works, so we can start getting into even more
interesting things like a first person like sort of core exploration game. And then lastly, when we end
the semester with Portal, we'll look at a couple
of fancy things there. But that was it for Pokemon. Thanks for coming, and
I'll see you guys next time.