CRAIG LABENZ: Hello, everyone,
and welcome to another episode of Observable Flutter. My name is Craig,
and I am your host. And, today, we're
continuing my adventure-- our adventure, our collective
adventure, building an infinite zombie shooter game. And in the previous
two episodes, if you've not seen them, but are
tuning in today, first of all, welcome. And in those previous
two episodes, first one just set
up some scaffolding, got that all going. And in the second one, actually
uploaded an actual game map and got camera mechanics
working correctly. The only problem with the game
map was that nothing in it was real. So there were houses and you
could just walk up the walls and on the roof and whatnot. And I really was
tickled last week referring to that as climbing
mechanics in the game. [LAUGHS] But, today, we're going to
fix that and make those houses and objects like trees
real so you can't just walk through them. And then, if there's time,
might even add some pathfinding. But I don't have any
zombies in the game yet. Honestly, pathfinding
might have been a little bit ambitious of
me to put in the card title. But we'll see how far we get. All right. Well, let's get started. So I think I'm going to begin
with a little tour of the code that we have from
these previous weeks. So let me blow this
up so it's legible. Maybe this is too big. What do you all think? Is this too big? You can only see a couple lines. I'll leave it here for now. Let me know in the chat if this
is cartoonishly big, or just right, or some such. NameTopSecret asks, what is
the Flame version you're using? Well, the latest, 1.8.2. So a little tour of the code. In main.dart we have
very standard stuff. I'm initializing a single
instance of the game, passing it into the root app. And in that root
app, basically just not even using a scaffold,
just going straight into-- I don't even know if
we needed the Material app, to be honest-- but basically going straight
into this game widget. This comes right out of Flame. And then, we're excited about
our game, which we just saw up here was called a zombie game. So the zombie game
has in it a world. And this is not the movie. [LAUGHS] But this zombie world
contains all the stuff, so all the terrain, all of any houses,
fences, doors, eventually maybe zombie spawn
points, stuff like that. And this is all going to
be specified in the world. And last week, we got
the camera component working to follow the
player around the map. And in here,
onload, we're adding some sprites and, honestly,
not really using many of them. We are using the
adventurer, but I don't think we're using
this town one anymore. That's being loaded separately
through a program called Tiled. And where is the Tiled
implementation here? What's going on? What's happening? I thought that was in this file. Isn't it? I know I committed all the code
at the end of the last episode. So it should be there somewhere. Yeah, land is a file we're
not even using anymore. This was from week one. Can get rid of that. Yeah, world, it
should have been-- oh, I was looking at the game. I thought I was
looking at the world because I'd started
talking about the world. There we go. So, in the world, this is
what contains all the stuff. Here we set up just
the size of everything based on how big our tile map
is and then how large we're scaling it in the game. This land map, or a list,
also just unnecessary. That's from week one. It's obsolete because
we're now, again, using this Tiled component
that I was talking about. And it's pretty fun. You use this other software
called Tiled to build a map. You put down sprites
where you want them. And then, this
exports an XML file that is an industry standard. And so, Flame also supports it. And you can look right
at the file itself. And it's just a bunch of
really fun [LAUGHS] XML. But, actually, the fact
that it is plain text here is pretty handy, because
I was doing a little research for the first part of this,
and you can figure out what the API is to
consume all of this data by being able to read the XML. So that's actually quite handy. So in the world,
we load up the map. We add a player. And we put those both
in this world component. Again, in Flame, there's
a component tree, just like in Vanilla Flutter
there's a widget tree. And, ultimately,
we tell the game-- we tell the camera component
to follow the player. And that gets us to
where we were last week. I added a little
extra stuff here to make sure the camera
couldn't go out of bounds. But that got our game
feeling close to-- started to resemble a game. So it was pretty exciting. And I'm going to just run
it real quick so we can see where the last stream ended up. And then we'll carry on. Oh nice, there's an error. [LAUGHS] What is this? Asset doesn't exist. Oh, yeah. I've been making this
one modification. Every time I export the asset-- I have to figure this out-- it's exporting
with a certain path that the Tiled program
thinks is correct, and it's not the correct
path for Flame and Flutter. So I've just been
making this edit every time I export the map. Not a very robust build
system, you might say. So now we have our map and
our player can walk around. Nothing obstructs the player. Oh, hey, here we go. I was looking at this earlier
and I wasn't seeing the castle. But now the castle is there. So that's great. So this is where the
last two weeks got us. We can see the camera's
following the player. When we go to the
edge of the map, the player can go all
the way to the edge, but the camera doesn't
clip into nothingness. And you could design
it differently. You could put half the camera's
width worth of mountains or, obviously, impassable
terrain so the player always stays centered. But we didn't opt for that. And here we are. So now we're going to make
it so that you can't just climb this house, and you
can't walk through trees, and stuff like that. So let's get started. Now last-- yesterday, I was
doing a little prep for this. And because I knew
I was going to have to read documentation and
watch YouTube videos maybe. I ended up just watching-- or
just reading documentation. But I didn't think that was
going to make good television. So I did a little
preparation to get us-- to get myself set up
on the hitboxes stuff. And so, I've added,
in this Tiled program, an objects layer here. If you've not used Tiled,
then much of this jargon isn't going to make
a lot of sense. But I learned it
in a couple hours just by watching a
YouTube series about it. So it's not too
hard to get into. And you've got
different layers here. So we've got the ground layer
that also, apparently, not all my ground is in
the ground layer. I found it very easy to put
tiles in the wrong layer. [LAUGHS] And then, I
have my objects layer. And, well, these
are object sprites. And then, there's an
actual objects layer. And this is for stuff. And here, this is where you
designate certain spaces on the map and assign
properties to them. So we can click on
this object here. And I'll right click-- object properties? No. What do I want? There's a-- wait, how
do I edit the object? Maybe we just add a new
one if I can't figure out how to select it. OK, I think it's selected now. No. It just wants to delete it. I'm not going to lie. I do have a hard
time with the menus. [LAUGHS] I'm not very
good at navigating this. Where is the thing? What's going on? There's a way you can
add custom tags to these. I was doing this yesterday. Yoopi asks, can I
have the YouTube link or the title you want to
learn about the objects? Yeah, I will share that. It was really good. Seven years old. So a couple of the buttons
have changed places. But it is still quite good. Also, by the way, at the
end of every episode, I just commit
whatever code I have done and push it to this
repository, which is open. And there's a branch
for every episode. So, right now, there's
two branches other than main, stream one, stream
two, chapter one, chapter two, I forget what I call them. But, yeah, this is where you
can check all this stuff out. We might just have to add a new
object here because I'm failing to remember how to actually
edit out the darn thing and see it's-- yeah. The biggest learning
curve in this app is just figuring out
what buttons to click. Let's try to add a new object. That'll be good. So I added both of the
existing ones as a polygon, so a polygon around the house. And then, there's this little
polygon at the base of the tree because I want the player to be
able to walk behind the tree. So let's add a new one. I've got the polygon
thing selected here. Nope, almost had it. Let's add another one
around this tree, I guess? So basically just click here. Click. Decide what space should the
player not be able to walk in, or what space should be whatever
we're worrying about right now. So I've added this polygon. Right now it means nothing. But, here, I can click
object properties and not have the thing show up. It was showing up yesterday. [LAUGHS] I don't know what-- yeah, none of these are it. Honestly, I don't know
what these screens even do. It was just a pop-up. Oh, it's on my other monitor. Goodness gracious. Here we go. So you can name them so that you
can find them in code that way. I haven't really
thought too much about how I would
super organize these. All I've done is add
custom properties. So I'll add another
one right now. And the thing I was naming
this was blocks movement. And it's a Boolean. And then, of course,
this does block movement. And now, I think-- oh, I'm
about to draw another one. I don't want that. Now, I bet I can click another
one-- object properties, there we go. It's on this menu again. So we see that
that is actually-- this was set up yesterday. So now, both of these
trees and this house have a polygon defined around
them with the tag blocks movement set to true. Pretty exciting. Now I'm going to re-export
this because, in my code, in what's in the app right now,
this last one that I've added isn't there. So I'll say file export. Is that window going
to another thing? Maybe I need to do export as. Here is the window. World TMX save, replace. Looking good. Now we'll go back to World TMX,
make this edit again, assets. So we're ready to actually
use these objects. And, again, I did a little
homework on this yesterday. So I kind of know what to
type for the first part of the stream today. So we have some polygons that
are now defined in this map. And we can actually see
them in the XML file, which is just really fun, I think. So we've got our
different layers here that we were just looking at
in the Tiled program itself. Here's layer one ground. We saw that. And this encodes
all the squares, which asset is in them, I
guess, is what this all means. And then, there
was object sprites. So this specifies
where the houses are and where the fences and
that castle are or whatnot. So most of this
layer is empty, which we can see a lot of zeros
and then some other assets. And then, if you
keep scrolling down, we get to the objects layer. Maybe you could name
these better or something. But the objects layer just has
the stuff that we were just talking about. So we've got a blocks
movement object here. And it has its
starting coordinates. Actually, it took me a minute
to figure this out yesterday and it might take me
a second to remember because it just deleted
everything that I wrote. [LAUGHS] And then it
has all of the points. And I believe, yeah,
all of these points are relative to this point. So this specifies
this whole polygon. And then there's just
these other ones. I didn't give them classes
or names or anything. That actually would have made
this probably easier to read. But for now, I only have
one kind of polygon, and that is unwalkable terrain. You can't walk through
a tree and you can't-- we're deleting the climbing
mechanics from last week. You can't just
climb up the house. So this is the XML file. And we need to get this into
game components in our Flame code. So let's get on with that. So it all exists in map. Then there's an object. So map is a Tiled component. But components are the
primary thing in Flame. And then, all of the
actual Tiled goodness sits on this attribute
called Tiled map. So in Tiled map,
we can see, there's a whole bunch of
different things. But we want to look at its-- I think there's a
get layer method. Yeah, get layer-- what was it? [LAUGHS] Get layer-- oh, what are you--
what were you called? Let's see. I already don't remember. Tiled component. Yeah, blah, blah, blah. I wonder if I read this in
the documentation yesterday. Render. I should pull up
some documentation. That's always really helpful. Let's go here. And Flame Tiled. And let's actually
just read Flame titled. It's amazing how hard it is
to remember what to type when it's not in front of you again. So, like always,
Google takes you to-- well, not like always. But, often, Google takes you to
the original version of Flame which, Flame folks, y'all's SEO
game must have been on point for that initial release. All right. Flame Tiled. This is what I'm using. And layers, yeah,
this is what we want. Oh, get layer. Is it just a floating method? Well, that's weird. Doesn't seem right. At Its simplest, layers can be
retrieved from a tile map by invoking get layer. I wonder if we don't even
have to do the .tileMap thing. I'm going to close this. So map-- .tileMap dot-- it wasn't from here, right? Get layer? No. Tiled map. Oh, tileMap. OK. Get layer. Oh, I think I wrote
the wrong thing. So, yeah, the layer
that we want to get is in object group, which
I deduced yesterday, as I was doing prep,
by seeing that they're called object groups. So I thought, well, I
bet that's what we want. So object group. And then we give it a
name, I think it was? Yeah, my blabbity blah. So for us, that name is objects. So we'll grab that. And there we go. Well, I guess they should
be single-- single quotes. So we now have all of the stuff
that we've defined in our map. So this will be
the object layer. And the type of this
should be object group. Yeah, and it's nullable. But we know it's there. So I'm just going to put
this bang to get rid of it. Now, object layer
has an objects array. So we'll say for object
in object layer.objects. And objects itself is a list
of Tiled object classes. So we can start to look at what
is available on our object. Now, here, there's a
bunch of different things that this could be. And I've already explored
different parameter types here. So is polygon, of
course, is a Boolean. And it tells us whether
or not this is a polygon. Polygon itself is a list
of points that presumably is null or empty or
something if this is, in fact, not a polygon. And then, the other thing
that we need is x and y here. And x and y are basically
the global coordinates of our first point. So 0, 0 here, this first
point, this is relative. It's zero pixels to the x
away and zero y pixels away from this point. And these are our
global coordinates. So we can say, for-- oh, wait, actually, we
have to make a few checks. We'll say, if object
is not a polygon-- so if you're not a polygon, then
we'll continue and not examine this one in the loop. And we can also say if object-- now, there's the
properties as well. Where were the properties? Oh, properties right here. And, remember,
properties in Tiled itself, wherever
that app is, looks like it's in this same window-- properties were where
I said, can block-- or, no, blocks
movement equals true. So we should find that in
the properties array, so properties. And for this part, I did
a little bit of digging because properties
is just a list. And it's kind of annoying. But there is some really nice-- this was what, called a Tiled-- what are you objects? Tiled object. So Tiled object here has-- there's some nice little helper. All the properties
are indexed by name. What was that called? (SINGING) Doo, doo, doo. Hmm. OK. I don't know where I
found that yesterday. Maybe it was--
what's in properties? So what did we say this was? This is a Tiled object. So now it should be able
to tell us what properties is-- custom properties. There we go. Oh, yeah, we have to click on
the custom properties class. There it is, by name. So this will allow us to
not loop through the list ourselves and whatnot. Yeah, Clint Purser is on
it, custom properties. Thank you. So, yeah, custom
properties there. So this means we have
a by name object. And-- oh, wait, it
didn't name them. What am I talking about? I think I just have to do first. No. Oh, should I give these names? Was there by tag or something? How did I do this yesterday? What was my thinking. Back in the TMX.dart file. Oh, the property has a name. Right. The object itself
does not have a name. The property, of course,
does have a name. Duh. One day. So we'll say, by name. And this was a map. So it's not a method. That means we can ask, do you
have the key blocks movement-- which we should spell correctly. And if you do not have
that key blocks movement, we will return. I'm assuming here
that this will never be set to false because
then I would just not have the property at all. So in some sense, this doesn't
even need to be a Boolean. It's actually just its mere
presence indicates truthiness. But we'll worry
about that later. So if you don't
have block movement return because you're
some other property, and right now I'm just worried
about the unmovable terrain stuff. And now, I think
we're ready to start thinking about the points. And the first thing
that I did yesterday was I just wanted to make
sure I was understanding where they were and that I
was measuring stuff correctly. So I started to make-- I was adding these really simple
components to the game and-- oh, one second. I got to send someone
a message here. So I was adding really
simple components to the game and just drawing a little
red square in spots to be like, yeah, those are
where I put the components in the Tiled map. So let's do that again. So remember, object.polygon
is a list of points. So we can say for final
point in object.polygon. And these are points. And a point is basically
like a vector two, similar to a vector two,
in that it has two doubles. In fact, I don't know why
it wasn't a vector two now that I think about it. So we can just add
a component here. So we can say, add. And I was playing with a
rectangle component yesterday. And the position of this is
going to be a vector two, where the x value is the point
.x and the y value is the point .y. And then, I wanted it to
just be a little red square. So we can add a paint for that. And this is the same paint
that comes from Flutter. So we'll add-- I mean, I could add
just painting here. But I'm about to
use a color as well. So we'll add material. And this paint is
going to have a-- I think we just say color
equals, like colors.red. So let's test this. I remember having more
lines of code yesterday. But I don't know what they were. So we'll see here. And let's run it. Let's see what we get. So hot restart. It's thinking. We're back in the game. Now, we don't see
any red squares here, which could be because
I did something wrong, or it could be because
I did it right, but I'm at the checkpoint
that I expected to be at, which is that
all the red squares are up in the corner. Nope, they're not even here. Oh, I do remember one thing. I had to give them
a higher priority so they draw above the map. So priority-- the map
has a priority of one, if I remember correctly. Actually, I think
it's literally-- nope, don't remember
where I specified that. Back to the game. Do we have red squares now? Oh, still no. Nice. Great, our first
bit of debugging. What am I doing wrong? Vector square. [SIGHS] Oh, you probably
need a size here. So this would also
be a vector two. Oh, yeah. For some reason, I thought I
had the size specified just from this position, which
is, of course, bananas. So we've got a
little thing there. Back to the game. Up to the corner. Aha. So we have some red squares. Clint again. You are really paying attention. You've eaten your Wheaties
this morning, and it shows. So we have our red squares. They're all in the
wrong spot though, which is a little annoying. So they're all zero indexed. But that makes sense, remember. I mean, they're really
shoved in the corner here. There's two problems
that we have to solve to correctly
position these in the game. And the first one is that we
observed that the first point always said 0.0. But it was relative to the
actual global coordinates. So we're not yet taking
into account object .xy, we have to do that. And that's one of two
things that we have to do. So back to world.dart. Our position here is now going
to have to be plus object .x. And for the y value,
it's plus object .y. Now, if we reload, it
will still be wrong, but it'll be less wrong. So I scroll over here. And we've got our points here. You can see that the
house is outlined here. Then we have our
two trees outlined. And they're positioned
relatively to each other, but they ain't in
the right spot. And that is because
we are scaling up the entire rest of the game. And there was a file we
set called constants. And there's three important
values in here right now. One is the tile size. This is the actual physical
amount of pixels in the tiles that we're using. Then the other one is how big
we want them to actually look on the screen. So here, everything is just
being blown up times four. But the Tiled program
doesn't know about that. And so, that means
that this file has no idea that
all of these values should be multiplied by four. So we'll go back to world.dart
and multiply all of these things by the world-- what is it called? World scale constant. So times world scale. Then here we'll say parentheses
again and times world scale. And it'll format for us. And now, when I
restart, we should have red dots on all the corners. Except they're still kind of
not right because, you can see, they're just all offset. But that is a matter
of their anchor. Now I'm remembering why I
had more lines yesterday. [LAUGHS] So, by default, Flame
positions things-- things have however
much size they have. But then, their position
is one coordinate. So that, of course,
begs the question, well, where in the object
should the coordinate be? And, by default, it puts
that in the upper left. And you can specify that
via the anchor property. So if we say anchor
here is a anchor.center, which is the type
of anchor that makes the most sense for my brain. And I was certainly clicking
on the center of things when I did that. When I drew these
polygons in Tiled, we can see that all of the
polygons go back into place. These two are nicely
on the corner here. And we're drawing stuff. Now, these polygons
don't mean anything yet. Of course, the existence
of these red squares will not prevent us-- they will not disable our
climbing mechanics just yet. [LAUGHS] The climbing API
is still alive and well. So, now, things
already get pretty-- they get more fun. And I implemented this one
way yesterday with hitboxes. But as I was thinking
about it last night and this morning while
I was walking my dog, I had another idea. And I don't even think
we need hitboxes. And I want to talk through why. So if you've made games
before, you probably are familiar with hitboxes. If not, then quick
crash course, a hitbox is basically an amount
of space around something that can be used to measure
whether or not this object is colliding with something else. So if we had a hitbox around
this house and a hitbox around this person, we
can measure whether or not they contain any
of the same pixels. And if they are, we can
activate some collision logic. This is great for
shooters and whatnot, where if you're playing
"Space Invader," you want to know when your
laser collides with an enemy or when their shots
collide with the player. And when that happens,
you blow up the enemy, or you take some health away
from the player, or whatnot. But movement is different. Imagine I had a hitbox here
and I walked onto the house. And so, the hitbox-- the collision detection
detected a collision. And it calls a method on
either the player or the house. And it's just like
hey, by the way, you're colliding with
this other thing. So the player gets its
on collision start method called in the house. The house's hitbox is
passed into that method. But what good does
that do me here, because I've already walked
in some unwalkable terrain. So I've already gone
in an illegal space. And what I would have to do
in that logic is eject myself. You'd have to move
the player back. And what I really want to
do is prevent the player from ever walking
into the place-- ever walking onto illegal
terrain in the first place. And so, I don't think we even
need to use hitboxes here. But we do need to
add more components and check for those components. So this is what I-- I cooked up some version
of this yesterday. And then I'm going to
try to do maybe a more streamlined version of it now. So we know that
these are polygons. All of these points,
in object.polygon, these constitute specific
enclosed polygons. I've got some
stuff in chat here. Let's see. Why not position it
randomly on the map? Billu Boy, why not position
it randomly in the map, excluding popping on the
objects such as house, trees, et cetera. Can you say more about what
you mean by position it? What's it? I'm curious what you're
thinking here, Billu Boy. Billu Boy? Billu Boy. So we need to have polygons
registered with the Flame component tree, just like
we've now registered all of these individual rectangles. So let's, instead of
adding these rectangles, let's start to store
a list of vertices. So I'm going to grab the way
we grabbed-- we got this point. We'll say here final vertices
equals a list of vector twos. And I'm not going to add
these rectangles anymore. I'm going to, instead, say
vertices .add this point. So now, we are building
up the information that we need to add an
actual polygon to the game. So after-- where is it? Yeah, so after this
loop, this is where we would add a polygon component. And now I'm freelancing. I'm improvising off the
homework that I did yesterday. I presume there is
a polygon component. Great. I knew there was
a polygon hitbox. That's what I used yesterday. So this has an extremely
similar signature to the polygon hitbox, which is great. So we need to give
it its vertices. I don't remember if it's
important to give it its position or size. Those feel mutually exclusive. I don't actually understand
how that would work. But, anyway, we're just going
to pass in the vertices. And I-- oh, yeah, there's
one thing I really want to point out. In the polygon component-- oh, actually, no. I read this in the
polygon hitbox. Nope, it's here as well. Cool. So the polygon hitbox
and the polygon component have this really
interesting note. Always define your polygon
in a counterclockwise fashion in the screen coordinate system. And, you know what? I actually think I
forgot to do that-- oh, no, I did
luckily define this in a counterclockwise fashion. I'm remembering how
I drew these blocks. But I also defined
these two yesterday in a counterclockwise fashion. So I have not dug into the logic
of what breaks if you don't do this, but you have to do it. Oh, you're talking
about the red squares. So you said, why not position
the red squares randomly in the map excluding popping
on the objects such as house, trees, et cetera? So I think we may have had-- I may have
under-communicated about what the purpose of the
red squares were. I just wanted to
make sure I was going to be able to correctly
translate what I defined-- oh, I keep thinking Tiled
is on that other screen. Maybe I should
just put it there. I wanted to make
sure I was going to be able to correctly
translate all of these points to actual stuff in code. So I just wanted to draw
them to make sure that they were in the right spot. But if you have
another idea, or you think I'm still overlooking
something interesting, let me know. So we've added a component here. Now, this component is not-- this is completely meaningless. If we rerun the game now, the
fact that component exists, a, nothing's going
to show up anymore. The red dots are gone. And we're obviously still
able to walk around. But this is where
I had my ideas. So let's add a new file. Why did-- did I save
this as untitled one? So let's [LAUGHS] actually call
this unwalkable terrain dot-- or, no. I guess it's a component.
unwalkable_component.dart. And this will-- we need
a cursor down here. Also, let's just add to
the barrel file real quick. Yeah, components.dart. So we'll need
unwalkable component. All right. Class unwalkable
component extends-- I guess it extends the polygon
component now, doesn't it? And in our constructor here, all
we care about is the vertices. So this is super.vertices. Oh, it's interesting that you
can use the underscore outside of the file. I guess they probably
had-- this is a really interesting exception
to the private variable rule. For a super constructor, you
have to be able to see it. I'd never thought
about this before. Interesting. So this unwalkable
component has no purpose other than to identify the
role of this polygon component. So now, we are not going to
add polygon components anymore, we're going to add an
unwalkable component. Now, the unwalkable component
still doesn't block movement. So, again, this is pointless. But now we get to
the exciting stuff as I close all
these other files. In player.dart,
where we move around, this is our movement
code that we were working on last week or two. And, oh, yeah. So I guess we can
revisit this real quick. When you press different keys,
you update the movement vector. And then, when you
release keys, you again update the movement vector by
either zeroing out the value or setting it to
the other direction if you're still
pressing the other key. And then, in the end, you
have a movement vector set. And the movement vector
actually always exists. It starts at zero. Every tick of the
game we figure out what direction are you moving,
how fast are you going, and how much time has gone by. And we add that vector
two to your position. Then we clamp the
position to make sure you can't leave the game area. So this is it. And I reasoned yesterday and
did wire up, but with a hitbox that I now think I
don't need, that this is where we can prevent
moving into an illegal square. So imagine-- remember I
was talking about here, if I wait until there's a
collision detection that gets triggered, that means I know
I'm already in an illegal spot. And what would I do in-- it
would be on collision start. And then you get your
intersection points. And then the other hitbox
that you're bumping into, and it'd be like, OK, cool. I'm colliding with something,
and it's a house or whatever, and what am I going to do? I'm going to have
position.add, and I'm going to have some negative
movement vector of what I just did or something. And I hope that this is correct. And I hope that
it's called in sync with this or something nasty. And I'm definitely
not sure that I'm not going to stutter against
unwalkable boundaries. So I don't want to do this. So, instead, let's see if
we can prevent movement in the update method
if you're trying to walk into an illegal space. And when I say,
let's see if we can, I already did it yesterday. So I know that we can. So this is-- I'm going to try to remember
what I typed yesterday. And my thought was,
[SIGHS] back to the game. Oh, Bobek. Finally caught it live. Oh, you watch all of the
videos and just wanted to say, thanks for
the cool content? Oh, shucks. Well, you're very, very welcome. And I'm glad that you had a
free Thursday morning, evening, afternoon, or night-- or I guess it could
be-- could it be Friday? Is it Friday for someone
in the world right now? I think so. Maybe it's Friday. I'm glad you're here. So what I thought we
could do is decide-- well, if I'm heading
up, then let's get the top center anchor of my
character and query the game-- the component tree for what
components are at that spot. And this is a thing that
you can do in Flame. And if any of the components
are unwalkable terrain, then I will simply zero
out any of the up movement from my movement vector. And the first thing
that I was doing was zeroing out the
movement vector entirely. And that would mean
if you were heading toward impassable terrain,
like this diagonally, as soon as you hit it, you'd
just stop and be stuck. And you wouldn't
slide to the left like you'd intuitively
think should happen. So that, immediately--
yeah, that was a really
terrible experience. And then I realized,
Oh, right, we don't zero out the
movement vector. We just zero out the
up component of it and allow the player
to keep sliding across the impassable terrain. So this is just what
we're going to implement. And I think it's going to work. So we'll say, if position-- or sorry-- if movement.y
is less than zero, so this means we are moving up. Oh, you know what? We could add maybe a
little helper on this. Oh, no. It's just a vector two. I'm not getting into that. So this means, we
are moving up so we can grab the position of-- what's it, position of-- and then we give this
an anchor, I think. What is position take? It takes a vector two. That's not what we want. So we probably want
position of anchor. And this takes an anchor. Cool. So this is going to give
us the global coordinates of this component's
anchor.top center because we're heading up. And so, this is our final-- we'll call this new top. Oh, you know what? I actually need to add-- I wonder if this is
going to not work. Oh, this might move
one Pixel into-- OK, sorry, I'm thinking about a
bunch of different things here. Let me slow down. I have not actually
updated the position yet. So for the first frame
that the player is clipping illegal terrain, I
think this may not calculate-- this is the old position. And so, if we're just barely
clipping into illegal terrain, this won't know about
it yet because we're about to move into
the illegal terrain. This change to
our position value is what's going to move
us up another one pixel into the illegal terrain. So this line won't find
it on that first frame, then this line will move you
into the illegal terrain. And then, the next
frame through, we're actually clipping now
at the start of the update function. So then this method
would detect. And this is, by
would detect, it's the code we haven't written yet. So then, everything
would kick in and you wouldn't be able
to move more than one Pixel into the illegal terrain. I think we can live
with that for now. Actually, let's do it better. Let's do it better. So let's update our position. I think this will just do it-- this will prevent
clipping one pixel. We're going to remove
the value-- actually, I think, yeah, we don't
need this anymore. We're going to remove
the movement speed if-- oh, maybe we can save
the original position. Yeah, original position
equals vector two. Or maybe we can
say position clone. Is there a clone method? Wonderful. So we have our
original position here. Save this to use after
we zero out movement for unwalkable terrain. And then, here we will
fake update the position. So our anchor calculations
take into account what movement we want to do this turn. I didn't do any of
this last night. I'm just thinking of this now. Hopefully this is correct. Let's find out. So we're getting the position,
the top center of us. And now, this is where
we use this cool API that I hoped Flame
would have, and it did, where we have a
reference to the game by way of has game reference
as a mixin on our component. So that allows us to say game,
and then every component, it turns out-- so the game is a component,
the world is a component-- all components have a-- I don't remember
what it's called-- have some method
components at point. There it is. And the point that we
care about is the new top of where we're going to be. And so, here, we'll
say for final component in all of these components. And now we just check, are any
of these components unwalkable terrain? If component is unwalkable-- oh, yeah, it's a
component, not terrain. I think I called it
unwalkable terrain last night. Yeah, let's import
all the components. If the component is an
unwalkable component, then our movement vector
can't go up anymore. Oh, you know what
I've just realized? I don't want to actually
zero out the movement. I want to have a value that
is a movement this frame. Because the player is
still pressing the up key. And I don't want to
zero out that value because I'm going to get
to the edge of the house. They should keep
going up immediately. But if I zero out
their movement here, they'd have to release the key
and press it again to move up. So we're only going to
manipulate the movement this frame variable. So movement this frame will now
have its y-value set to zero. So we were going up
negative y-coordinates. Remember, 0, 0 is
always in the top left. So we were going up. But we concluded that
our new top center position was going to intersect
with an unwalkable component. And, thus, you cannot
walk up on this frame. But we are not
setting the movement this frame to zero because
we want to still be able to slide along the house. We want to be able to slide
along the house is just a pretty funny sentence. [LAUGHS] Let's quickly make
these other changes. So this is, we are moving down. So our position of anchor is-- we'll call this one new bottom. And this is the bottom center. And here, again, we set
the y-value to zero. Now, this is, if the x is
less than the zero, that means we're going left, so new left. Suddenly our codebase is
sounding a little political. Center left is the new left. And here we'll just set
the x-value to zero. And x is greater than zero,
that means we're moving right. New right. That's not how you spell right. Though it doesn't
actually matter. You could put asdf here. Center right. And we set our x-value to zero. Then, for our last
thing, we'll say that position equals original
position plus movement this frame. I think this is going to work. Stalker Prod-- it's
an interesting name you've chosen-- maybe a break when collision to
not go through the whole loop? Hmm, interesting. So-- oh, yeah, absolutely. Great point. This is an excellent point. Just an excellent point. Nice. Thank you. I'm pressing
Command S on Chrome. It's like, do you want
to save streamyard.com? I do not. I'm continuing to press
the wrong buttons. All right, restarting,
switching to the game. Let's see. Can we move into this spot? We can't. [LAUGHS] Look at this. Can't enter over here. Can't enter from the bottom. We just cannot enter. And the tree, we should--
yeah, blocked on the tree. Try to enter from this side,
from the left, cannot enter. But we can go around the tree. I have not yet programmed
a way to have the person be rendered behind the tree here. That's going to be important
for any amount of realism. And you can play with the
hitboxes and make sure, here is-- it's kind of unrealistic that
the person can't come closer than this. This hitbox actually
isn't great. Why can't you even
get up to the door? So let's actually just
fix that right now. Let's go look at our-- oh, please stop. Actually, I don't know if
I'm good at editing tiles. Oh, here we go. We're editing objects here. Let's bring this hitbox
up to here, and same, and see if it's good? File export. Maybe I don't need to do
the export as world.tmx. Did you get the change? You did. That means it was exported. Restart. The game is going again. And I don't-- maybe
that was high enough? Maybe not. But you can see how cool it is
to build your game world not purely in code, which is how
I would have always done it in the past. Yeah, anyway, all right. We have added hitboxes. And now, we can think
about pathfinding. And pathfinding is going to be
really quite a bear, I think. So this, I've done no prep
on, but I have thought about while walking my dog. And that is some form of prep. Obviously, for
pathfinding, we're going to use a
conventional pathfinding algorithm, like Dijkstra's or,
of course, actually A-star. And, oh, Clint has another idea. Let's do it, because I skimmed
it and I like this idea. If you enable debug
mode in your Flame game, you can see the borders
around the components. Nice for debugging
this kind of stuff. Yeah, I've never
actually done that, but I've read about
it many times. Debug. Is that on the game? Is that where we do it? In your Flame game. Yeah, this is-- oh,
yeah, of course, it would have to go into
the super constructor. Doesn't look like it's there. Is it just a property
that we set as opposed to pass through the debug? Nope. Nothing. Clint, I want to do this. Oh, set it onload. OK, we will do that. Debug mode. There we go. True. Restart. Oh, interesting. Why is it only on the player? Huh. Clint, why is it
only on the player? Oh, that's our only-- but these are also components. Maybe it's positioned
components. It's probably
positioned components, because these other ones
are, I guess, not positioned components. I don't know how the Flame Tiled
extension is positioning them, but they seem to not be
positioned components. Anyway, yeah, this is cool. I'm just going to leave this
on for, basically, ever. It also is suggesting
that we may want to trim the image because
there's a lot of empty space here, or do
something about that. Pathfinding, so if you've
done pathfinding before, then this little crash course
in how pathfinding works won't be very exciting. If you've not done
pathfinding before, then this crash course
in how pathfinding works might be confusing. Anyway, pathfinding is
done through a graph. It's a set of nodes that can
connect to some other nodes but not all other nodes. And this is how
pathfinding works in games. It's how pathfinding works
in navigation software. Obviously, the nodes are
like corners on the streets, and the connections
are the streets. And so, pathfinding is a way-- pretty clever algorithms. I once tried to write
a pathfinding algorithm myself and just-- I'd never researched
it or didn't know how they worked, and tried
to reason about it myself. I was not successful. And then I looked up A-star and
was like, oh, I wasn't close. [LAUGHS] I'll just use this. But if I'm remembering how
A-star works correctly, it starts at your
destination and starts to crawl through the
graph, through the nodes, back to your origin. And it's keeping
track of weights and how close it is for one
node to get to the next node. It's like, you've got
your starting-- you've got your destination node. So all the nodes around
that have a weight to get to it of one. Let's say all the terrain
has the same weight. So there's a weight of
one from all of those. And then, you crawl
out from those. And the weight from
the next line of nodes says, well, to get back a layer,
that's an additional one step. So my weight, I guess, is two. It's your weight plus
my weight to you. How long does it take
for me to get to you? And you've already calculated
how long it takes from you to get to the end goal. And so, you just scan
all the nodes like this. And then, once you get to the
player, then you'll have-- and you scan
exhaustively because you could miss a shortcut maybe. Oh, no, yeah, you always expand
through the lowest weight as well. All of our weights here
are one, so it's not going to be a huge issue for us. But, anyway, once you have a set
of nodes that you've explored, you pick which one
has the lowest weight, and you look for its neighbors. And you keep going until you
eventually find the player. And then, the player is going
to have nodes around it. And it will just walk into the
one with the lowest weight. And then it walks into the next
one with the lowest weight. And it walks into the next
one with the lowest weight. And it eventually keeps going
until it finds the player, or it finds the target. So, for us, that's
going to ultimately be a zombie that's going to
be heat-seeking the player. Now, I was talking
about this graph. And this is the
component-- or this is the aspect of
pathfinding that I'm not really familiar with
in games because we don't have an obvious graph. You could say every
single pixel in this game is a node in the graph. And that would be a graph. But that is probably not
a very performant way to calculate pathfinding,
because our world is bigger than it needs to be. And, also, and lots of
games have big worlds. So I think there's got to be
some clever tricks that game developers do for this. And this is where I'm really
just freelancing and guessing on the fly. But what I presume makes sense
to do is pick a granularity and, I guess, playtest
different granularities. So we have all these
different tiles. If we go back to the tile map,
we can see the tiles better. We have all these different
land tiles coming together to make our game. Well, what if we said-- oh, Randal, what do you got? Maybe weighted vectors
toward vertices? Yeah, I think-- so, Randal,
brainstorm with me here-- and anyone else. The question is, where are
the vertices in this game? Where are the nodes
in this world? What I'm thinking-- and I'm open
to better ideas, because I'm certain there are
better ideas out there-- but I'm thinking we could
say, for each square, like this, in actual
pixels of the asset files there are 16 by 16 pixels. So the maximum amount
of nodes in our graph, our pathfinding
graph that we could have in every single square,
is whatever 16 times 16 is, or 256. But, oh my gosh, that's so
many across this whole thing. So we can't do that. That's wild. The minimum granularity
that we can have is simply one node, like in
the center of the square, or maybe top left corner,
or whatever we pick. That would also be
a possible node. That's a possible
granularity we can have. It's a possible graph. But then, our players are going
to move in really weird ways. They're going to go-- if you're here, and you can't
go through this spot maybe. Oh, we moved the
hitbox, so you could. But let's say you
still couldn't. All the zombies would
just maybe be taking these really staggered steps. And they're not going
to move fluidly. It's not going to feel
natural in this game. Everyone's going to be moving
in really regimented ways. So we don't want one
node in our pathfinding graph per game square. I don't know what
the right number is. Maybe it's four per square. Maybe it's 16 per square. I'm not sure. Randal, what do you got? You could look for corners
instead of lines in your source data or any non-walls. Yeah, so I feel like
that would be a very-- if we could do that correctly,
that would definitely be an elegant solution. And one thing that I
was imagining, as I was walking my dog, let's say-- I'm going to add some more. Oh, no, I don't have
them added to this. I'm going to get real
silly and creative here. Oh, this is a perfect one. Ground, no, we'll put
this in object sprites. So we've got an arrow here. So this is the target. [LAUGHS] This is where
you're trying to get to. And there's nothing here that
really represents a player. So I'm just going to be
whatever this thing is. So this is a zombie. And this is a player. This is the target
the zombie is seeking. If we had pathfinding at
four by four intervals, it would say something
like, maybe there's four nodes per block. We could have-- it would
go to the middle here. Then it might go up here. Then it might go here. Then it might go here. Then it might go here. And it starts walking
over like this. And it would be again
still regimented. You'd see the zombie taking a
bunch of 90-degree turns, which is just not going
to be satisfying. But what you could do
is say, how many turns-- how many of your steps
have the same trajectory? And then, actually, just skip
and make that your first point. So we're actually going to seek
here, if our first thing is up and to the left, and
then up and to the left again, and then up
and to the left again, we'd say, well,
don't go up and then don't go to the left
and up and to the left again, just-- actually, no,
that algorithm is going to be really tricky to implement. I'm definitely not going to be
able to do it in the next hour. Hmm. So Randal, you're
saying, look for corners. So another way we
could do this is, you could draw a line
between your target and the-- again, I feel like this
is just going to be nuts. You could draw a target
between your line, your actor and its target,
what it's seeking, so the zombie and the player. And you could say,
do we intersect any unwalkable terrain? If we do-- if we
don't, that's easy. Just go on that line. If we do intersect
walkable terrain, then this is where
that algorithm would come in where we'd
need to figure out, OK, which of our corners is closest to-- some mixture of closest to
us, or maybe it's just-- no, wouldn't be
closest to the target, because that would, of
course, involve just cutting through again. Maybe we'd say closest to
the target that doesn't involve any more intersections. So this doesn't
involve any more-- if the zombie gets here, going
to this point doesn't involve any more intersections. So that's cool. But at this point, which also
involves no more intersections, doesn't-- it's closer to the target. So we'd actually say,
the algorithm could be, draw a line from the
zombie to the player. Oh, no. We clipped on walkable terrain. How far can we get? We get here. Now figure out all
of the nodes, which one is closest to the
target but has the least-- or has no clips,
doesn't take us back into the unwalkable terrain. And then, what
we'd actually do is we don't even walk to this
spot and then cut over. We start here. We do this whole calculation. We figure out this corner. And we just walked
to that corner. Is that what you're saying? The Bresenham algorithm
takes a diagonal and turns it into a
horizontal and vertical steps. Is that what I want? I mean, I kind of want the
thing to move diagonally where appropriate. Michael Schwartz says, step
one, go directly to the target and find out if a box prevents
the next or present move. Yeah, that sounds like
what we were saying. So we draw a line from
the target to the player. And then we would
calculate intersections, which I hope there is an
algorithm for in Flame. And then, yeah, we look
at all those points and figure out which one
is closest to the player. But getting to that
point doesn't involve intersecting the polygon again. And we actually just walk to
that point as our first step. I think it's reasonable. Should we try it? I never thought I just
wasn't going to use A-star. But maybe I'm not
going to use A-star. I have no idea. I've got a little
bit of time left. Definitely not enough. Let's see how far we get. This is going to be fun. So the question that
we need to know-- oh, I don't even have a zombie. Oh boy. Not a good start. Oh, I will add-- yeah, I'm going to--
we gotta add a zombie. Here we go. Let's go to Finder. And in characters,
there is a zombie. Oh, I wonder if this is already
in that asset that I made. Zombie cheer, that's what
we're going to go with. That's pretty fun. Oh, cheer two. Oh, what a festive
little zombie here. Anyway, cheer one, that's
what we're going with. Assets? Yeah, we had this
generator here. Cheer, so adventurer. Do we have zombie? Yeah, zombie poses cheer one. Here we go. Great. So this asset, this is
also from episode one, I just used the
asset gen package. I think that's
what it was called. Pubspec.yaml, what
do we have here? Assets gen, really good. Love it. In our game, this is where
we load all the things. So let's load assets. So we're going to cache
our zombie pose one again. Now we need a new character. Did I just put-- oh, put
player in components. That's interesting. Anyway, whatever, zombie.art. And I'm going to copy
some code out of player. What do I need here
for my constructor? It's a sprite
component for sure. So this is a zombie. Please leave. Great you're gonna take
a position, and a size, and an anchor, and a priority. So I'm going to grab all this. And you are the zombie. And you take a position instead. So this is required
this dot position. Nope, super not position. And we have to import
sprite component so this stops yelling at us. So we don't need
to do this anymore. And I'll keep this size, keep
this anchor, keep the priority. Looks great. Oh, this is closed. All right, there we go. We've got a zombie. And now, I think
back in our world, we can add our first zombie,
world.dart, of course. I'm going to get rid of
this commented-out code. Let's add a single zombie. Oh, sure, we'll do
it up here, I guess. Zombie, oh, yeah,
probably need to make this like an actual instance
variable and not just local to this method. Late final zombie,
zombie, import. Oh, yeah, components.dart. This is the one I want. Let's add zombie. Now we can get rid of
this redundant import. We've got a zombie. It needs a position. So let's-- I'm going to change
the positions to what I just drew here. So our player is going
to go in this spot. That's two pick-- it's one, two,
three, four, five, six, seven, eight, nine, 10, and two down. So where does the player-- that was in the
player's thing, yeah. So you're now 10 and 2. I don't know if we
need the 0.5 stuff. We seemed to before,
so I'll leave it. And then, for the zombie-- so that was 10, 11, 12, 13, 14,
15, 16 and two, three, four, five, six. I don't remember what
numbers I just said. Where's the world? (ENGLISH ACCENT)
Where's the world? Where's the zombie in the world? Oh, I'm in the wrong file. Here we go. So position is going
to be a vector two. And how did I do
that in the player? I should have a really-- I should have a convenience
constructor for this. Yeah, it's like all of this
shenanigans that we need. So this was 16 and five,
something like that. All right, I don't know. Let's start. Do we have a zombie? Players on the house,
zombie doesn't exist. But that's probably because
I need to build properly. We also see that
the player can-- I guess needs to be nine. So I have to rebuild
I think because of the new asset or something. Oh my goodness. Harshit, you don't
know how lucky you are with the person
below you in chat. [LAUGHS] Said, can I ask a
question regarding AdMobs and monetization
of the application? I'm stuck, and can't seem
to find any solution for it on the internet, or Stack
Overflow, or any other place. And your question has been
seen by ex-AdMob devrel, now Flutter devrel,
Andrew Brogdon himself. Oh, I didn't add the zombie. Thank you, [? Fadi. ?] [LAUGHS] Yeah, man. That's like the old-- in the early days
of Flutter, folks would change values
in their widgets and not call the
please render method. And that's why it was all
wrapped in the set state method. So people would
stop doing things like updating some values
but forgetting to press the please render button. And I just have that
all the time in Flame, always forgetting to
add the component. Nice, we have an error here. We're moving forward. Sprite does not equal null. Is the assertion that failed? Indeed. So the player, where did I
set the sprite in the player? In onload, yeah. So when the zombie-- here we go. Except, oh, you
need the game ref. Does the player
have the game ref? Yeah, it does. I'll add you to the
zombie with has game ref. Probably have to import some
stuff, like experimental, because has game reference
is replacing has game ref. And we add the zombie game. There we go. Now we have the correct one. So let's go back to zombie
game and grab this thing, make you not look
like the character. Bring in the actual
assets file itself. This might work. That's not the right one. Hey, we got a zombie. It's too far over. Let's bring it back to the
left and down more on the y. So our zombie was
added in the world. And this is way too much. Let's go 14, and then lower. So 6.5, refresh. This is going to be good. Ah, ha. All right. We are ready to have this
zombie try to seek the player. Oh, it's getting exciting now. Also, Harshit, I'm
just so happy for you. This is some high quality
attention you're getting. We need to draw a line from
the zombie to the player. And we need to figure out
where does that line intersect any unwalkable terrain. Oh boy. Don't know if these
APIs exist in Flame. Don't know what I'm doing. Void onload. Here we go. And it's an override situation. Let's Google it. Flame calculate line
intersects components. And I'll add Flutter
Flame so Google knows what I'm talking about. How to calculate the
relect of a raycast? Oh, raycast, yeah. That seems like a word I want. Clint, you are right. That is correct. Magic happens when
you tune in live. I think if you're reading,
my English is not good. I bet your English is great. The formula for calculating
a reflected vector-- that's already not what we want. But we might see some methods
that we're interested in. So test game, create boundaries. What does create boundaries do? La, la, la. Oh, a lot of stuff here. Looking good, Mr. Game. I'm going to actually scan
through these [INAUDIBLE].. So we do have a raycast method. Oh, this is a raycast
method on a component. Does that mean we can
raycast from our cells? Raycast, no. We don't seem to
be able to do that. Where did this come from? Is it a mixin maybe
that we have to add? Forge 2D game. Oh, that feels like a--
that is a 2D physics engine. It feels like a place
that would add raycasting. Let's go to the bridge
packages and click on Forge 2D. Nope, I don't think this is
going to have what we want. Joints-- is that all
that the Forge duty does? I thought it was a
whole 2D physics thing. All the docs are about joints. Maybe I'm actually just not
clear on what Forge 2D is. Interesting. Yeah, I don't see
anything else here that's going to suggests it's
going to add the capacity to do raycasting. Actually, let me go to
the packages documentation itself just to make sure. Forge 2D, this is basically
the same page I was just on. [LAUGHS] Oh, the
publisher is Flame Engine. So I don't know if we're going
to have any extra docs here. More docs can be found
where we just were. All right, we're
back to-- let's just look at our-- look
at the API ourselves. Let's look at what can
components-- oh, I'm re-adding onload. That's really smart of me. Zombie, I think
it's going to be-- I mean, probably in
position component? Let's see what we got here. This is probably where anchor-- position of anchor was. And then-- oh, where
does the components at? How did I query? How did I do that? How did we figure out when
we were clipping the house? That was done in the
player's update method. So in the player update method
I said, oh, just components at point. Yeah, so the question
would be components in line or something. Absolute position of anchor
to local, absolute to local, position of anchor. A lot of anchor logic
here, very stable. Angle to, that could help us. Flip horizontally,
flippity, flip, flip, flip. Render debug mode. Oh, nice. Render tree stuff. Absolute rect. Set by rect. No lines. It feels like it would be in
a position component thing. But let's try component because
maybe components at point-- yeah, components at point
is actually defined here. So it could be in this file. Let's scroll through these
functions, see what we get. From the top,
component, looking good. A lot of docs, wish I
had time to read them. Scrolling down, scrolling
down, loaded, mounted, removed, parent, children,
children factory. I love when programming
yields funny names. Find parent, first child, last
child, ancestors, descendants, propagate to children. Man, we need an
estate tax in here. Find game, contains,
on game resize, onload, on mount, on
remove, parent resize, update, update tree,
on children changed. Well, they are going to
grow older, you know? Render, render tree,
add, add to parent, add all, add child,
remove, a bunch of remove. Change parent. Whoa, man. That's cold. That's real cold. You can't just swap
parents like that. Contains local point. Contains point. Yeah, if what we want exists,
it's going to be here. Components at point. Oh, no, now we
get into priority. Hmm, interesting. Interesting. You know, I have an idea. I have an idea. The polygon component has
collision detection mixin. Ooh, interesting. I'm going to-- I'm
keeping that in mind. And I might combine
it with what I'm thinking right now, which is
that yesterday I was looking in the polygon component. And for that, I'm going
to go back to world, because this is where I can-- oh, yeah, unwalkable. Now I can click on
Polygon component. So this, I think, has its
own contains point method. And the contains point involves
lines, global vertices, render, render debug mode, contains
point, get edge, is outside. Oh, no. They didn't go with the
raycasting algorithm. I thought we might get to
see some raycasting stuff. So the has collision
detection mixin, that's quite a good idea. That's what we're going to do. Let's look there. Zombie world has game ref. I'm just going to say
has collision detection so I can click on it. I'll have to remember
to get rid of it. Actually, let's
read a little bit. Keeps track of all
the shape hitboxes in the component's tree
and initiates collision detection every tick. Hitboxes are only part of the
collision detection performed by-- you know what we could do? You know what we could do? Would this be silly? What if I added a
line component from-- every update cycle, I
add a line component from the zombie to the player
and give the line component a hitbox. And then, when the hitbox
collides with a polygon-- which, of course, it would
also have to have a hitbox-- then we would change
our navigation. Is that silly? [LAUGHS] I don't know. You can experiment with
non-standard collision detection such as has quad
tree collision detection, sometimes bring
better performance, but it's not guaranteed. This mixin is useful. So it has generic
collision detection. Oh, wow, there's not a lot here. So we need the other mixins. Yeah, let's look
at shape hitbox. So this implements hitbox. A little mixin, OK. What are we in here? In the shape hitbox class? What other things are here? Collision pass through. Is that for tunneling? Collision callbacks,
collision detection. Collision detection, add on. Doo, doo, doo. Hmm. So, yeah. I mean, another
thing we could do-- oh, what? Moderator? Why? Can we unmoderate
Andrew, please? Andrew is helping out. Oh, no. Andrew, did you
lose what you typed? Ah, that's so painful. Mm. And the messages are gone? They're not even there? Oh, no. Moderator, can you
unhide any messages? Is that a thing? I don't know if
it's going to work. What we could do
is we could cache-- there's no way this is going
to happen in this episode. The rest of this
episode may just be planning how we're going
to handle collision detection and then implementing
maybe a tiny bit of it. But let's think about this. Every time we add one of
these unwalkable terrains, we know all of its points. So we could cache a list. Oh, we could cache
all the vertices. And then, I can
look up an algorithm for calculating if
two lines intersect, because I definitely don't
know it off the top of my head. And then, every time the
zombie wants to move, we could draw a line
from it to the player. And we just loop over all
those vertices ourselves and run the check to
see if they overlap. If they do, we'll cache
them in such a way that it tells us
we can immediately find which unwalkable
terrain we have clipped. And then we'll run that check. That's what we're going to do. That's it. Great. So I'm closing a bunch
of stuff, going back to the world.dart file. And, yeah, this is
what we're going to do. So every single point we're
going to add the last point. So we'll have a
point last point. And it's nullable. And at the end of this,
we say, point equals-- this is the next point. I don't know if this
is going to work. I think it's going to work. I'm fairly confident
it's going to work. Point next point goes here. I guess we should call this
last point or something. Oh, I did call it last point. What are you complaining about? Vector two, what's
the problem here? Vector two can't be
assigned to type point. Indeed. So we had our last point. Yeah, we're just going to
do this in vector twos, actually, now that
I'm thinking about it. Vector two, blah, blah, blah,
blah, blah, great, great, great. So every time here, we will
say, if there is a last point, or this is the end
of the list, we have a line to add to
our cached list of lines. So there's probably
a line class. But I'm just going to
make one up real quick. And it's going to have
a start and end vec two. So a final start
vector two and final-- oh, that's not how
you write code-- vector two, and
final vector two end. And then we add the constructor. This dot start, this dot end. very. Simple. So if there is a last point,
or this is the end of the list, then we have a line to add. So-- oh, we need
a list of lines. We don't need this
list of land anymore. So this will be final list. This is unwalkable
component edges. And that's going to
be a list of lines. Now, we add. So this says, lines.add. And the line is different
points depending on what's up. So if we are in a-- oh, let's add-- let's make
next point go up here too. That'll help us
get the last one. Next point. So, in the list, we just say, if
last point does not equal null, then we add this line of-- it doesn't really matter
what the start and end is. Oh, maybe it will. We can figure that out when
we look up the intersection algorithm. But last point and next point. Oh, I didn't call it lines. I gave it a better name--
unwalkable component edges. And then, at the end
of the list, we add-- yeah, end of the loop-- we add another one, which is
the last point that we had. So that's here. This is the end. Oh, no, it's the start. Last point. And then, this line ends
at the very first point. So vector two first point. I'm just being lazy here
because I don't want to calculate all of this again. Can you unselect, please? So the first point is
going to be assigned only if it's null to next point. So the first point
will be saved there. And then, this will allow
us to add the first point. I'm gonna have to call add. And you have to be
a line, of course. What's the problem with you? Oh, these are nullable,
but they're good to go. So we have the lines. DrDraco says, can you show
the performance of this game? Well, I can show walking around. I don't have an
FPS overlay though. I do think Flame
supports such a thing. There's not a lot
going on right now. So the performance is stellar. You'd be very impressed. And did I create Flutter? No. [LAUGHS] I actually found Flutter
around the 1.0 release. And I immediately
realized I liked it a lot. And then I applied to Google
so that I could work on it. And here we are. Andrew, I presume you will
retype that stuff up and share the info, maybe on Twitter,
so everyone can benefit. But I haven't really
followed the questions. So maybe Twitter is not
the appropriate place. So we have our lines. And I actually want to add them,
just so we can see them drawn. Let's do that. Every time we add a line,
let's add a component for it. And this will be add
a rectangle component. Oh, no. Maybe we need a line component. Is there a line component? Hmm. So I need a rectangle component
but skewed, that's the thing. Yeah, I'm not
going to add these. Pretty sure the
positions are correct. Now we need to look up calculate
if two lines intersect, intersection of two lines. Let's consider the
following case. The equation a1x, b1x, plus c1. What is c1? Where did c come into play here? I don't see a c. What is c? 8x plus b2y. What is c? Does anyone know what c is? Does anyone know how to math? I don't know how to math. Let's consider the
following blabbity, blah, evaluate the point,
intersecting, solving two simultaneous linear equations. Also, we do let the
equations of the two lines be written in general form. So, yeah, I mean,
they're the same, but we've got one and
two in each of these. Now let the intersection
point, x0, let this be-- wow, I'm really just
not following this. Plus I have no idea what c is. Am I missing something? It doesn't say what c is. I don't think this is
the tutorial for me. Let's look at another one. Is Bard-- oh, Bard's not
joining the party here. Bard? Where is old Bardy? Hmm, hmm, hmm. Again, c. Oh, wait, this one
actually said what c was. Why did it jump to
some other page? Line, line, intersection. One of the most common
line intersection. Despite the fact that it's
so common, a lot of coders still have trouble with
it, like me, right now. First question is, what form
are we giving our lines in and what form would
we like them in? Good question. Ideally, each of our lines
will be in the form ax plus by equals c, where a, b,
and c are the numbers which define the line. Is this like standard geometry? You're just supposed
to know what c is? I think of a line as
two points, xy1 and xy2. Definitely don't want this. Do you use chatGPT
when you develop? No, but do I use Bard a lot. And it wasn't showing
up on this search. c may mean the center
of intersection. Yeah, but I'm calculating that. I guess we solve for
it, is the idea maybe. Here-- may be sir c
is a pure integer? Hmm, I'm not sure
what that would mean. I'm not going to lie. Anyway, regardless of how
the lines are specified, you should be able
to generate two different points along the line
and then generate a, b, and c. ax plus by equals c. What is c? Yeah, I don't know. Finding a circle
from three points. Yeah, there's a bunch
of fun stuff here. But it thinks I know what c is. So the only Bard tool that
I use is an internal one. I don't actually know where
to find Bard publicly. Maybe I'll just Google that. How ask Bard a question? Oh, see? This is what I wanted. Why didn't you pop
up when I was asking about intersecting lines? Use Bard on Android. No, I just want to
use it on Chrome. This is really funny. I don't know where to
find Bard because I always use the internal one. [LAUGHS] Do any of you
use Bard and know-- so it's just a constant. OK, great. And I guess we're solving
for it or something. What is the constant? OK, someone else is
continuing to help here. For example, 3x plus
2y plus 7 equals 10, where we want to
find the x and y value. 3x plus 2y plus 7 equals 10. That's a line? I mean, that's an equation. That can represent a line? Man, my high school
math is so rusty. I actually had-- I got a
math minor in university. And [LAUGHS] it feels like
it needs to be revoked. Standard linear equation format. Hmm. Open the Google
app on your phone. Tap the chatbot icon. How to ask Bard a
question on Chrome? Where do I go to
ask Bard a question? Try Bard. There we go. Yeah, this is what I want. I use the internal one. Then I don't--
where's the link, man? Sign up to try Bard. Bard.google.com. Is that literally it? Oh my goodness. Oh, this is the internal one. Can't show it. There was no new UI there, it
just gives different answers. It's the next release. All right, so we can't
ask Bard because-- oh, I guess I could ask
Bard on my personal. Bard.google.com. There we go. This looks promising. Try Bard. I don't know. Sure, whatever. Just [INAUDIBLE]. I know Bard's an experiment. Everything is an experiment. Please write a function
in Dart to determine if two lines are intersecting. I'm pretty confident that
the next release will-- oh, Bard was just updated. Oh, nice. Here we go. Are lines intersecting? Check if the lines
have the same slope. If they have the same slope,
they are not intersecting. A list of doubles is line one. So this is line one's double,
but you can't-- a double is not a line. You need coordinates. I don't know if this is right. [LAUGHS] Let's see here. So line one, two, three, four. This isn't a line. Is this a line? This is a point. Am I missing something? This is not a line. I don't think this
code is working. View other drafts. I would like to
view other drafts. Well, this is kind of neat. This is the same. Oh, this is a line. This is a line. So what does this do? This is slope one. Oh, I like it. Check if the lines
intersect by finding the point of intersection. Oh my goodness. This is absolutely impossibly-- there's no way a human brain
could ever make sense of this. But I will bring
it in and test it. Are lines intersecting? I think I'm just going
to write a test for this, and then that'll be
the end of the day. I might not even
have time for that. So let's say, as-- so this will be
a list of double. And as list, I'll
call this, so it's friendly for that component. So our as list here
is going to say, yeah, we just return start.x,
start.y, and end.x, end.y. And then let's get
our slope as well. And what is the slope of a line? Line one-- mm. OK. This is real, real lazy of me. In theory, that's the slope. Let's write a couple tests. We're [INAUDIBLE] this is
going to have to be filled-- finished next week. So components, lib, test. Let's call this line test.dart. Import package, test, test.dart. Why is that-- why
is that not love me? Package test doesn't exist. Do I not have it in my pubspec? Flutter dev dependencies,
Flutter test. Oh, right. This is Flutter not Dart. Flutter test. All right, here we go. We're going to import
the game itself. This was in components, right? It's in the world. So we'll just put it here. Here we go. We need to find out if
this line class works. Line, slope should
test return zero for-- we can't-- oh,
yeah, for flat line. Yeah, we're going
to have to check things like not having infinity
slope not dividing by zero. Pretty sure there's no
divide by zero check here. Return zero for a flat line. So we should say, just
expect a line of-- let's have it start at 1, 0. So this is a vector two of 1, 0. Wow, can't type. Vector math 64. So we got our vector two here. This is x 1, 0. And it goes to 2, 0. That is flat. So that slope should
have a value of zero. Oh. Can't type. So Flutter test? We've got one test. Does some generated code work? Two lines can be
defined by four points. So the intersection
will need four inputs. Yes, I agree with that
100%, which is why-- yeah, each of these lines
needed to have two points. The tests failed. What happened? Negative 0.5. Let's look at the code. So 0.1 minus 0.0. This is the starting y
minus the starting x. Oh, no. This needs to be
three and two, right? Yep. Let's try it again. Classic-- oh, infinity. Divided by zero. One minus zero-- zero
minus one is negative one, divided by zero minus two. I don't know. I'm going to do this later. This will be next week. Actually, it might
be a guest next week. I'm not sure. But, folks, pathfinding is
proving to be very interesting and involves a lot of geometry
that I am not good at. But I think we have
a good strategy. I'm actually pretty excited. I'm going to do more
homework before getting into this next time. I'm pretty excited about
this fundamental strategy of draw a line from
a zombie to a player. This log represents a zombie
because they have a similar IQ. And the target, of course,
represents the player because the player will
be the zombie's target. So the-- draw a line
from the player-- or from the zombie
to the player. Check out, does
that line intersect any edges of unwalkable terrain? If it does, just
visualizing this, I think the correct next
part of the algorithm will be find the corner of
the unwalkable terrain that is reaching from this spot that
we're currently calculating. Reaching does not add
any more intersections and is closest to the
target of all such edges. So the two-- the line from
the zombie to the player is going to intersect here. Of course, we have to go back
through the unwalkable terrain to get to this one, this
one, this one, and this one. So this algorithm
would produce these two as the eligible
corners to track. And then, this one is,
of course, closest. So we're not even going to have
the zombie walk to this square and then go over. We'll just skip
straight to having this be the first target
for the zombie to get to. Once it gets here, it's going
to run the same algorithm the whole time. Once it gets to this
corner, assuming the player hasn't left, then the
same algorithm will continue. It will draw a
line to the player. And it will finally
just not have-- it will not intersect
any unwalkable terrain. And so, the zombie will
be able to go straight. I feel like that seems right. Oh, someone is also
telling the actual line-- the actual thing for slope. That's great. So y1 minus y2. y1 is-- so now using this
points just feels silly. So y1 is end.y minus end-- no, minus start.y. And this is over x. So n.x minus end
dot-- or start.x. Thank you. Negative one. Do I know how to think of lines? This is sitting at one
on x and zero on y. So it's on the x-axis
and the next one is two on x and also zero on
y, so still on the x-axis. Shouldn't that have
a slope of zero? What? End.y minus start.y. End.x, start.x. And that, I mean,
this looks familiar. So end.y is zero. Minus start.y is zero. So this is zero over some
value, zero over negative one. x-- I don't know. I'll do it later. It feels like it
should have worked. [LAUGHS] Can't you
debug the numbers? I seem to not be able to. [LAUGHS] All right, everyone. Got decently far today. And I'm going to also
talk to the Flame folks, just see if they have any
raycasting stuff in Flame. They've had everything that
I've wanted in the past. And they probably will
today as well for this. So who knows. The next time I pick this
up, there might be just-- we might be pressing an easy
button that basically tells us the answer. But if not, we're going to do
some line intersection logic. I'll probably enter
the next stream with that part figured out so we
can just get to the good stuff where we know if lines
are intersecting, and what slopes are, and
boring things like that. But that will be an adventure
for another Thursday, another Observable Flutter. And until then,
everybody, have fun.