CRAIG LABENZ: Hello, everyone
and welcome to another episode of Observable Flutter. My name is Craig Labenz, and I
am your host so far, as always. I'm pretty excited
today because I'm going to get back into working
on my zombie shooter game that I have not touched
or really thought about in quite some time. Thank you, everybody,
who is tuning in. Man, I had a pretty
exciting morning because I've
recently been forced to update to macOS Sonoma. Thank you, corp update policies. And that broke pretty
much everything in OBS. And I discovered that
literally 10 minutes ago and was frantically googling and
looking up what I needed to do. And I'm currently running
a release candidate of the next version
of OBS, which has virtual camera that is
compatible with macOS Sonoma. And that was resolved a mere
40 seconds before kickoff. So it was never in
doubt, but it was. But here we are, ready to go. So let's-- oh,
and also, OBS is-- my scene switcher
is not working. So every time I want
to switch a scene, I have to look over
here and find the thing, and I don't just get
to press the button. It's a tough life,
let me tell you. Remember, folks, this is
the Flutter community. We've got just the
greatest reputation, so let's live up to it. Let's treat each
other wonderfully and enjoy discussing fun
Flutter topics today, as we do every day. OK. I think it's time to start. So where's the one that
I need to click here? I think I need to
click this one. Yeah. All right. Welcome, everybody. A lot of hellos. A wonderful hello
from me to you all. OK. Zombie shooter time. I have not done anything. I have not launched this code-- this project in VS Code since
the last time it was streamed. I don't really remember much. Oh, yeah. And I'm in the Flutter
directory because I was just updating to the latest build
from the master channel. Oh, that's not what I want. Please go to the correct
directory and Flutter run macOS. Our fortune teller here is
predicting upcoming questions, but I'm not going to answer
it until someone asks. So we'll see if the
name gets revealed. Should use puro to manage
your Flutter releases. So I have just the GitHub repo. My Flutter version
on my computer is the Git clone
of the repository. I'm not familiar with puro. I don't know if that
would continue to be applicable and helpful or not. All right. We're building this quite
slowly, probably because I just updated my Flutter SDK. Oh, yeah. I'm getting tons of
security pop ups here. Are you sure you want to
run this zombies game? OK. So yeah. The last thing I was
working on is the zombies get in each other's way. So they can't stand
on the same square. So they're-- yeah. One of them can share
the exact space with me, and then the others
just flock around. And they could probably pack
in a little more tightly than this, but I think we'll
leave it like that for now. OK. So what I was thinking we
could potentially work on today is swapping out sprites as the
characters move so that they look like they're
walking instead of our one-legged mountain
climber who just looks weird. And the zombies are all skating. So let's actually get our
characters walking today by utilizing other sprites. And then we'll have some
fun doing that, I think. Tal asks, why do you prefer
VS Code over Android Studio? So I came up in the TextMate,
Sublime Text, and now VS Code tree of editors and
not through the Eclipse-- I don't know what was between
Eclipse and Android Studio, but those heavier editors. So I've just never gotten
used to the heavier editor. It's not been my
jam at any point. I did try Atom for a moment. That was nice, other
than its really, really, really bad performance. Yeah, so I've just always
used really lightweight text editors. And VS Code, it has that very
lightweight, snappy feel. I didn't think I was
honestly ever going to stop using Sublime Text, and
then VS Code became a thing. And so it felt like the
best of all worlds to me. I also have honestly
never really done Android development. I came to Flutter through
being a web developer and then learned
some iOS development. And honestly, I do
Flutter development because I didn't want to
learn Android development. Don't tell anyone I said that. I wanted to have an Android
app without learning Android development. I'm probably not alone in the
Flutter world with that story. So that would obviously be
a pull toward Android Studio that I just never had. Oh, yeah, IntelliJ. Yeah. This family of editors,
this historical tree that is just not
my historical tree. So OK. Yeah, let's swap around
these icon-- or these sprites as they walk. I haven't even looked at the
sprite set since I picked it off of Kenney NL, a
really wonderful resource for getting these free assets. So again, I have to even
figure out which ones to use, and then I know we need
like a sprite group or something in Flame. And I have used that
over a year ago, but it will need to
remember how to do it. Eric says he uses IntelliJ
because his first boss told him to. Interesting to tell--
I mean, I guess there's some
scenarios where it's like, we've figured
out our development environment in this editor. Please use it. That's how you can-- your Git commits won't have
whitespace headaches with everyone else's. But that's a--
honestly, whatever editor you first
get used to, it's just so hard to change flavors. I've got a couple
other questions here. Oh, Amhar, welcome. First time watching
live content. Gosh, so happy to have you. Thanks for tuning in. A lot of folk stories here. Started as Android
native developer, and then in four months,
switched to Flutter. Android and now iOS. I'm curious, does that mean--
are you doing iOS development, or you're shipping your
Flutter apps to iOS? I did enjoy my little bit of-- it was back when it was Swift
4, was the language version. I did enjoy it. I just thought-- I had a little side
project I was working on. I was like, well, I know I
don't want to write this twice. So I guess I have
to write it twice if I start over from
iOS into Flutter, but then I'll still
only have one code base. Obviously, that's
the big attraction. Oh, gosh, there was another
question I wanted to see. Where was that? Scrolling up. Someone was asking something
about the-- oh, here we go. Oh, there it is. Yeah, can you explain
the new game tool kit? Yes. That's the question
I was looking for. Ramazan, thank you. Yeah, the new game toolkit is-- so I believe you're talking
about the Flutter Casual Games Toolkit, which is on V2 now. And it builds on V1, and I
think V1 was a misunderstood resource. So the Casual Games
Toolkit didn't really talk about building
games at all, which was, in hindsight,
maybe a bit of a naming error. But it was still
incredibly useful. And so the Casual Games Toolkit
had all the surround-- well, not all, but lots
of surrounding cruft that you might need while
building your own game. So ads integrations and
audio system integrations so you could play sounds in your
game and different monetization stuff so you could
have in-app purchases. And just a lot of the
boring, not fun, no one wants to think about this
aspects of game development. But teaching game development
itself is obviously an enormous headache
or an enormous task, and that was out of scope of
the first Casual Games Toolkit. So the idea was that there
was a bunch of boilerplate that you might need, and
you could uncomment it if you needed it. And then in the middle,
there was "your code here" kind of comment, and
that's where you would just write all of your game logic. And then the surrounding
stuff that you'd probably need was ready and waiting for you. That was V1. V2 actually has a
non-trivial game in it. So it now begins
to demonstrate how you would merge some of those
surrounding resources that were already in V1
with an actual game. And that's the Casual
Games Toolkit V2. I hope that was helpful. I've now missed a
ton of chat messages. Got some requests
to start coding. We're going to be right there. Oh, Danielle, great to see you. Hmm, hmm, hmm. All right. I've maybe missed some
interesting comments. And if so, I deeply apologize. Oh, here's an
interesting question. Can you do a
multiplayer single game? Well, all multiplayer
games, I would say, are necessarily not simple. I actually gave a talk
in Berlin this summer on how to make multiplayer
"Pong," which you might think is the simplest possible
multiplayer game. And my whole talk was
about how surprised I was at the lack of simplicity. I never found any
simplicity anywhere in that whole adventure. So how not to build
a multiplayer game. "A Year of Headaches"
or something, I think I cheekily called it. You too can learn about how no
multiplayer games are simple. OK. Let's do it. We just saw how the game works. I need to pull up the sprites
so we can figure out which ones are the ones we need to add. So in my Git directory, my-- where are you? Where's my own name? I think I'd like to do-- so I
have a Git directory in my dev directory. So all this other stuff
is just random nonsense. But then within Git, I
have a directory for all-- I just try to mirror GitHub's
structure on my own machine. So these are all
users or organizations from whom I have ever
checked out a repository for any reason. And then in each of
those directories is the repository with
the exact same name that it has in GitHub. It's not going to
change your life, but I find it
mentally convenient to remember where everything is. So I've got my zombies
directory here, and I think in the assets-- yeah. Here we go. This is all the unused
sprites that have just been waiting for their time. Oh, this is an
interesting question. Are you going to do a similar
series building an app? I actually thought
about that quite a bit. I think, basically, this
was the version of that that I went with first
because building a game just sounded really fun. All right. I think I have-- I'm on the Adventurer
here, and yeah. So here's the dude, and
we need to figure out what set of sprites
would constitute walking. OK, so these two seem-- but we need a-- I feel just toggling
between these two-- well, maybe that's walking. Does this seem right? Is this complete? Or do we need one
more somewhere? I guess this can be walking. I'm just pressing up and down
on my keyboard right now. So we'll toggle between
these for walking. Oh, I'm now finally
looking at their name. It says walk1, walk2. So that's definitely it. And then where's idle? Right there. OK, so this will be idle. And here's walking. And do we have-- what
would be an attack? Because we have to fight
these zombies at some point. I've been talking about
having a magic spell he casts, like a fireball or something. I'm not seeing any of these
that are going to really lend themselves to casting a spell. Maybe talk, and out of his
hand will shoot a fireball. Maybe that'll be this. Hamza asked, where did
you get the sprite from? I got it from Kenney NL, and
it's just such a cool resource. And I'm going to take a
second and show everyone. Oh, well, I'm not
remembering the domain. Kenney NL game assets. I will google it instead of-- oh, it is Kenney.NL. Isn't that what I typed? Weird. Please come back. Hey, where'd you go? Here we are. So this is a really cool
source of free game assets. They're all super royalty
free and wonderful. And they've got 2D, 3D, and you
can see right there Tiny Town. That's what I went with. But there's a
complimentary dungeon one that I was thinking of
maybe one day using. But who knows if
that'll ever happen. Yeah, pretty cool. So Kenney NL, that's
where it came from. All right, so we
need walk1 and walk2, I believe, to start
being incorporated. And that'll get us moving here. Oh, I should bring myself back. I'm still invisible. Got to click OBS today. My Stream Deck is lights
out after the Sonoma update. Probably just need to
update the software, which I didn't have time to do. OK. So I'm going to pull up
the Flame documentation, and we'll look at
the sprites docs. I'm going to hide myself again
so I'm not covering words. All right. Are we on the latest version? We are. So Components. Is that where it is? Thought it was here. Oh, this is a very long article. OK. Sprite. Sprite. Oh, here we go. SpriteComponent. Nice. SpriteAnimationComponent? That run in a single
cyclic animation. This will create a simple
three frame animation using three different images. That feels like what I want,
but that's only if he's walking. Interesting. I haven't done this in
an extremely long time. So I know there's a-- OK,
SpriteAnimationGroupComponent is a simple wrapper which
enables your component to hold several animations
and change the current playing animation at runtime. This looks like the winner. Since this component
is just wrapper, the event listeners can be
implemented as described in SpriteAnimationComponent. Its use is very similar. But instead of being initialized
with a single animation, Component receives a
map of generic type T as key and
SpriteAnimation as value. Nice. I bet you can make an enum
for what state you're in. Yeah, and that's what they do. So there's an enum, and then you
put those enum values as keys. And then you give those
sprite animation values. Wonderful. And so we store the
SpriteAnimationGroup as a variable in our component. And then we set its current
value to the [INAUDIBLE].. This is great. I love it. Very excited. I think we're about ready
to start implementing this. As this component
works with multiple, naturally, it needs equal
number of animation tickers to make all those
animations tick. Use animationsTickers
getter to access a map of containing tickers
for each animation state. I'm not going to lie. I don't know what
this is talking about. This can be useful if you
want to register callbacks for onStart,
onComplete, and onFrame. Gotcha. We were oh, so close. And then SpriteGroup, but it
is especially for sprites. Wait. Do I actually just
want SpriteGroup? Does anyone know? Do I want SpriteGroup, or do
I want SpriteAnimationGroup? I think I want
SpriteAnimationGroup. Let's go back and
look at the actual SpriteAnimationComponent. So this class is used to
represent a component that has sprites that run-- yeah. I guess because I want these to
be cyclic, this is what I want. And the one is if
they're not cycling. This definitely feels
like what I want. If you have a sprite sheet, you
can use a sequenced constructor from animated data. OK. Sequenced SpriteAnimationData. So that's this data, and then we
pass that to await images.load. But here, we're only
passing one sprite. All right. We're going to have
to play with this. Actually, this one
I think said more. So this is our sprites. So we're just looping. So there's obviously
three of them here because we're running-- calling map on this list. And then we'd load-- so, yeah, this is
a list of sprites. Well named. And then spriteList--
we Future.wait. This is-- oh, because
this calls load. So they're actually
futures for sprites. So this returns
all of the sprites, and then that gives us
this animation value. And then we pass animation
into the AnimationComponent. All right. I think I maybe
know what I'm doing. I'm going to put this
code on another window. No, I'll just keep
coming back to it. Can we use Dart Frog
instead of Node? Sure can. Yes, you can. Oh, yeah. There is a pretty
exciting release today, Super Dash made by VGV. And yeah, posting links
is really hard in chat. And I'm not really sure why,
but everyone should google that. OK. Yep. It's time to start
writing some stuff. So I have two things. I'm going to grab all of this. And this is going in
the onLoad method. So let's go to player. And in onLoad, here we go. So this is where we're just
loading our single sprite, and stuff is about-- interesting. All right. I'm not going to do
this cheeky loop thing. We just have two-- that's fine. And I use this
assets package to-- there's some code
generation that makes this. It scans the folders and
generates this big static class essentially. It's icons, icon.plus,
icon.camera, whatever the 1,000
different icons are in the material library. So we have this
for our own assets, and I'm going to use that. OK, so I'm going
to use a list here, and this will be asset.load. And I'm going to
bring this down, except the one I need
is walk1, walk1_png. Awesome. Oh, is it not asset.load? What was it? It's Sprite.load. I have the memory
of a Sprite.load. You need to be async. Can onLoad be async? Yes, it can. Wonderful. I guess it's a
future or situation. All right. So we need that. And now we need walk2. 2. Great, so this will be
the walking animation. And stepTime. Oh, boy. That looks way too fast. We're going to have a
jitterbug climber dude. Let's try a tenth of a second. All right. Now we save all of this
in an AnimationComponent. All right. We love to see it. Oh, that was
literally right here. So animation. We call this walkingAnimation. OK, size looks the same. And then this .player, that's-- where do I want to save this on? My old thing was called sprite. I don't know. I guess, maybe we just
still call it sprite. But sprite now needs to be
a SpriteAnimationComponent. OK, let's think here. Oh, no. Right, right,
right, right, right. Sprite is from the-- sprite is not a
variable that I set. That's not going to work. Sprite comes from Flame, so I
can't trivially overwrite this. So sprite is still going to
be when the character is just standing, I guess. That'll be idle, and then we'll
use this walking animation. So that'll just be called-- all right, fine. We'll go back to
animation for you, and then we'll call
this walkingAnimation. And then you're just animation. And that walkingAnimation
has to be set, and that's a
SpriteAnimationComponent. I'm not sure I'm
doing this correctly. And we'll make
this late, I guess. Just checking to make sure I'm
not missing anyone being like, Craig, you're doing it wrong. But I'm not seeing that. Aw, mystery_manager. Oh, shucks. So glad you're here. And you are managing the mystery
of your identity very well. We have no clues,
no hints, in fact. So whoever you are, you're
keeping it locked tight. All right. So we have this animation,
and now we need to use it. And I'm not going to lie, I
don't know how to do that. So SpriteAnimationComponent,
this.player. It's not clear,
where are we here? Are we setting basically the
sprite on the player component? Or is this on the game world? And then we're giving-- If you have a sprite
sheet, you can use a sequenced constructor. Yeah, I don't think
we care about that. I'm not actually sure-- the ticker, I don't
think we're going to create a callback for this. So we don't need to
worry about the ticker. Additionally, game sprite. Need to figure out how
to actually do this. I know that the component
just renders its sprite. That is a sacred thing. Do I need to set sprite to be-- so maybe I need to actually
save late Sprite equal-- or sorry. Yeah, late Sprite idleSprite. And then this can become idle. OK, there's only
one idle, I guess. So that's going to
be the idleSprite. So now I've saved-- oh, game.images.fromCache. I appear to have done
something earlier, and I could have
done that as well. So I cached the
old adventure pose. Also, I just changed what
I'm pulling out of the cache, so that's not going to work. Did I do that in the world? No. Oh, wait. I know what I can do. We'll go back, and we'll
search for this string. Zombie game. There we go. All right, so we're not
going to use action anymore. I'm going to use idle and
then also going to use-- what was it called? walk1 and 2. So now I can pull
those out of the cache. So this should work. And that means we don't need
to make this a future anymore. So sprite is just going to be-- we don't need to call load. You come from the cache. You're no longer a list
that we would future await. We just pass this list,
and then I need to put-- and get rid of
load here as well. Need a trailing comma. All right. I think that's better. I still don't know how
to do what I want to do. Oh, should I make this
code a little bigger? It's not that big. Tend to try to be
even larger than this. All right. I think that's better. I still don't know
the correct way to hold this in terms
of constantly setting the sprite value-- yeah, this needed
to be idleSprite. So now the magic
sprite value, which is what Flame renders
from a PositionComponent or a SpriteComponent-- I need to set that somehow
from this animation. Maybe that is where
I need the ticker? I hope not, though. Let's look at-- is there
a good docstring on this? Oh, no, there isn't
a docstring at all. From data. Does anyone know how this works? Nope. Nothing. Let's go back at SpriteComponent
and see what it says. A PositionComponent that
renders a single sprite at the designated position. So I wonder if SpriteComponent
is actually just not the right thing to use anymore. In components, is
there something next to SpriteComponent? There's Sprite-- is that
what was setting up? What did I make here? SpriteAnimationComponent. So yeah, I'm not sure
the right way to-- should I extend
SpriteAnimationComponent? And then-- what did that have? So it has its animation. Let's look at its update method. Maybe that'll tell us
what we need to do. There's this set animation. That's fine. Render, animationTicker. Oh, interesting. animationTicker
getSprite render. So in update, the
component calls the ticker. OK. I think-- I think I
want to change the-- I don't think I want to extend
the SpriteComponent anymore. I think I want to extend the
SpriteAnimationComponent, and then I'm not going
to make this as a child. I mean, I guess we could
just render this, right? I could add walkingAnimation. I wonder if this would work. And then this wouldn't--
see, that wouldn't be a SpriteComponent anymore. Now we'd just be a
PositionComponent. And then I could
potentially toggle between showing the walking
animation and the idle sprite. Maybe that's what I do. Maybe that's what we do. So in update, this is fine. All right. I don't think it's
going to make-- this isn't going to make
sense or be good or anything. But what I think, if
we refresh the app, we might see our character
just walking, no matter whether or not they're moving. And it'll be our job to then
remove walking animation when they stop walking and
instead add idleSprite. And then we start walking
again, remove idleSprite and add walkingAnimation. Let's find out. Maybe that'll work. Can you please restart? Yes. Do whatever you want. I beg you. Oh. OK. So asset does not equal null. Why? So opposes action1 does
not exist in the cache. Where did I use that? Assets? From cache. Yeah, action1. Oh, yeah, yeah, yeah. This is it, right? Oh, I think I know the problem. I don't have-- no, I have no
idea if this is the problem. No, I'm adding everything here. So my pubspec is bringing
in all the assets. All right. So wait. I've got-- reading chat here. We need a callback. That does seem to
be a way to do it. I think my current
strategy could work. Because it's just going to--
it has its own default callback to just toggle the
things and then render the one that's current. And that seems fine. And Randal L., I'm not going
to make this as a child. What's this? OK. So I don't think the problem
is that pubspec is not bringing the thing in. Oh, gosh. I almost closed-- I almost closed StreamYard. That would have been bad. Oh, no. Terminal. This is what I want. The enormous size
of everything here is becoming a little tedious. OK, so this was in player 34? Is that where that's happening? What's happening there? 34, action1. Oh, I changed the asset. Oh, you can't hot
reload code that changes what you read from the
initialization cache because that's not going to
rerun what is cached, now, is it? There's your problem. The old hot reload when you
changed the startup code problem. A timeless Flutter experience. Dude, how are you going to call
me an unidentified developer? I'm literally
sitting right here. Come on. OK, nothing's rendering
because there was an error. What was it this time? Same problem. Images.dart. Action1 does not exist in cache. Make sure to load an
image before accessing it. Sure. Adventure poses
adventure action1. So didn't we put
that in the cache? I swear we did. That was in "Zombie Game." Oh, my gosh. I'm so silly. I forgot to change
this back to idle. Boo. Boo. Who gave me this show? And what were they thinking? OK. Look at our walker. And we have to flip
him left and right. That's going to
have to be a thing. So he's moving his
feet, I think, too fast. This looks-- I think
this looks silly. So let's make things
smaller again. Oh, this is already fun. I've been annoyed
this whole time that all the characters
skate around. So let's half the time. I mean, five steps
per second is a lot. 10's obviously bananas. Let's see. OK, this-- oh, no. This is too slow. Too slow, right? Yep. 1.5 or 0.15. All right. We're going with this,
even though it's not that satisfying. OK. Very exciting. Now, let's handle the
stopping and the starting. So the initial thing--
we're not actually going to add walkingAnimation. I'm going to add idleSprite. Which is here. idleSprint. What is your problem? Argument of type sprite can't
be assigned to component. Indeed. Indubitably. This must now be-- so idleSprite will say-- we have our walkingAnimation. So now let's have our
spriteComponent idleComponent. And then in our
idleComponent, we will say, this is aSprite Component
with a sprite of idleSprite. Excellent. Can't add sprites. Have to add components. That makes sense. OK, so now we should go
back to our dude skating. Oh, I've been squished-- they
don't have a square aspect ratio? That's a surprise. They do not have a
square aspect ratio. No wonder they've
looked so squished. Wow. Wow. In the words of
Owen Wilson, wow. I was not ready for that. Oh, he's so stout. And now he's like a dwarf
from "Lord of the Rings." OK. That was good. So let's start walking
and stop walking. And we're going
to need the enum. What's your problem here? I don't remember
when that came from. OK. What enums do I have so far? Anything? Doesn't look like it. OK. Enum PlayerState idle, walking. Oh, yeah. Pretty great. So we'll now have
our PlayerState. We'll just call it
movementState so it's-- actually, I'm going to
rename the whole enum. PlayerMovementState,
and that's this. And you default to idle. And in our update method, we
can probably say, if state-- no, movementState equals
MovementState.idle. No, I don't think I-- so we can have a method
at the end of the update, or we can have some code at
the end of the update method that is like, if you're in
idle, remove the idleComponent-- or, sorry, remove
the walking component and add the idleComponent. But we can also just
leave the idleComponent. So we'd have to know, did we--
are we in the idleComponent because we just
transitioned to it? Or have we been here? Because if we've been here,
we don't have to do anything. So that's the headache
if we put this code at the end of the update method. The positive of putting it
at the end of the update method is it's all in one spot. The alternative is that,
when you start walking, wherever that code is, it makes
the change because it knows it needs to make the change. And when you stop walking,
it reverses the change. The downside of that is
the code is spread out. A real dilemma. I think I'm going to
start idle and then do it when we start moving around. That's what it's going to be. I have to detect when we
even know that's happening. So checkMovement. Movement this frame. Yeah. Oh, onKeyEvent. Here we go. Here we go. Oh, man. I do not want to put the code
in the onKeyEvent method. No way. No way. And then we could
do it-- yeah, right? On the KeyUpEvent, you could
change it, but that's terrible. That is not where
that's going to go. All right. I think it needs to just
be in the update method. So should we have-- could have Boolean
flags for whether or not the given component
is already mounted. That'll help us know whether
or not we've been in the state. Is this making any sense? Am I just rambling? Hopefully this makes sense. So our walkingAnimation--
can you check to see if a
component is added? Is that a thing? Does a component have
an API to be like, hey, is this component a child? Because that would be add child. So when we add a child-- yeah. No, that's right here. OK. Oh, we could see-- _parent means I can't access it. Oh, isLoaded. Nope. That's different. That's isLoad. Children. Oh, children is
not an underscore. That means we could say, if
children doesn't contain. Ho, ho, ho, ho. So if movement this frame-- oh, now I wonder if
we even need the enum. Yeah, what if we ignore the enum
and say, if movement this frame equals 0.0? What's your problem? Well, that's a Vector2. OK, Vector2.zero. Function Vector2. What are you talking about? That is a Vector2. What is the error here? Is it that I didn't do this? No. The type of the right operand. Function Vector2. Oh, I have to call this. I always misremember
this as a constant. Oh, I could also say
if length2 equals 0. That's what we want. All right, so if
we're not moving, then we need to be idle. So we'll say, if
children.contains walkingAnimation. Then we're going to remove it. And either way, we can
also say if children-- so if children does not
contain idleComponent, we add idleComponent. I wonder if we're going
to need that enum. It seems like maybe not. So here, we are the inverse. So this is if we're not walking. So down here is
if we are walking. So if you do not contain
walkingAnimation, add it. If you do contain
idle, remove it. What do you think? Is it going to work? Is it going to work? Let's find out. All right, not walking. I had the key-- oh,
look at us walking. Doo, doo, doo, doo, doo. Stop walking. Started walking. Stop walking, start
walking, stop, start, stop, start, stop, start. All right. That seems pretty good. Way more of a game. The zombie is going to be
a lot trickier, I think, because of how
they're staggering. And if if we can
get their footwork to make sense for their
lurching and their veering, that'll be very cool. But could be a little tricky. So beautiful. Ship it. Yeah, this is a great game. AAA. All right. I am going to commit this
actually because it works. It'll be really
interesting to see how this code ages when we get
to the part of the character casting a spell. That's when I think the enum
will start to be valuable. I'm going to remove it now
because I'm not using it, and so it's just confusing
to have it even exist. But I anticipate once
there's more than two states, this nasty implementation
is not going to age well. But I'm not going to try to
build a super abstract, really declarative machine
for a state machine, essentially, to move
through just the two. Figure it out when we
get to having the third. All right. So what have I changed
in all of ye lock? OK, I don't remember doing
anything, but whatever. Definitely did this. That sounds familiar. All right. All right. All right, all right, all right. So this was a move
player's legs when walking. Know what I mean? And now it's zombie time. So the zombies are
way more complicated. They're walking code is-- well, in past episodes,
I've already lamented. It's just already confusing. I've never seriously
made games before, so I don't really know
the patterns for how do you architect all these
different aspects that are influencing how an actor
in your game behaves in a-- because it's going to rarely
be true that the order you apply these is just
totally irrelevant. That'd be wonderful
because you just have a list of
things that modify the behavior of your actor,
and you just add a new system into that list. Maybe your update method
like runs through them and calls the thing and passes
the icon in, and whatnot. Or it passes in yourself
to this modifier, and then you get
back your new thing. But it's not obvious to me even
that that would necessarily be clean. I can imagine having really ugly
dependencies, like, oh, man. When I wrote system
five, I forgot that system two was applying
a multiplier because that seemed coherent at the time. But now it is
breaking system five. Anyway, I don't
know what I'm doing. So this code isn't
wonderful, and we're going to pay a tax for
that problem right now. Can we use Firebase AI
Machine Learning Kit to implement AI for zombies? In theory, I guess, yeah. I'm curious what AI behavior
you would want them to do. Right now, they just
track the player. I mean, zombies
are not very smart. So one of the things that-- an earlier realization was
that normal path finding didn't really make sense for
zombies because they get stuck on stuff, and
they can't find paths. So I think it'd be very
easy to not really make them zombie-like. Oh, interesting. What I want is the
flame_behaviors package. You're telling me that this head
scratcher-- oh, from very good? All right. This head scratcher that
I'm scratching my head over has tickled scalps before. Is that what you're saying? All right. Oh, here we go. Full documentation here. Conventions, event,
getting started. All right. So yeah. I've got behaviors. Define a custom entity
by extending entity. OK, and then we give
it a list of behaviors. This is looking promising. Later, we'll see how to mix
an entity and a component. Later is literally
right down here. So there are two types of
entities in Flame behaviors-- Entity and PositionedEntity. Has a position and size
based on PositionComponent. Love it. Oh, this is way too
small of code or text. And I need to hide myself so
I'm not covering the text. PositionEntity. These entities use
the EntityMixin. Great. So that's what-- yep. That's the power of sprites-- or sorry, components and
entities coming together. If you want to turn any
component into an entity, you can use this mixin. OK. Before I would know whether
or not I want to do that, I need to learn more
about entities, which I don't think I skipped yet. All right. Behavior. A behavior is a component that
defines how an entity behaves. Love it. It can be attached
to any component that uses the EntityMixin and
handle a specific behavior for that entity. Behaviors can either be
generic for any entity, or you can specify
the specific type. Yeah. Nice. I was wondering. And we can do this. That's great. Behavior composition. This is probably where
the money is coming. Each behavior can have
its own Components for adding extra functionality
related to the behavior. OK. That's fine. Sure. Note-- a behavior is a
non-visual component that describes how a
visual component-- the Entity-- behaves. Therefore, a behavior cannot
have its own Behaviors. All right. That seems fine. So we can give components, which
are those Flame components. Or are they something else? We can give some kind of
component to a behavior, and then behaviors
list up into entities. And then entities mix
with Flame components. What's next? Handling input, naming
conventions, code conventions. All right. This is interesting. This could be a really
interesting refactor. Might have a stream to refactor
the zombie class and use this. But that stream is not today. Thank you for that
hot wreck, Binh. Wonderful. Thank you. Hmm, hmm, hmm. Oh, that's an
interesting question. Do we need to make games
responsive for their screens? How does that work? Yeah. The camera component system
works really well for that. So I don't have a one-- not an
amazing implementation here. But the fact that this
behavior of the game-- oh, I'm still invisible. I don't know. Maybe y'all like that. The fact that this behavior of
my game as the screen changes size is obviously reasonable
is from the camera component system in-- can I move-- oh, I cannot
send input to the game while resizing it. The fact that this is
reasonable behavior is from the camera
component system. So that's what you
want to use for that. OK. The zombies need
to start walking, so we need to pick
their sprites. What is going on
with limbs here? Oh, so we can compose-- oh, that's pretty cool. Oh, yeah. I could change their face. Nice. Oh, and they could walk
upwards and look away. Do we have-- yeah, body
back instead of body front. Oh, that's neat. That's really cool. Yeah, that would also
be an interesting-- man, this would be a really
interesting adventure in its own, or on its own, to
compose a new sprite from this. OK. Zombie has also limbs. And where is-- the
zombies also climbing. OK, they're doing a
lot of the same things. They have walk1 and 2 as well. Oh, boy. Yeah, see, this
one's going to be super interesting to
explore how we make them look like they're lurching. I wonder if-- see,
there was this-- if we go back to player and
we look at the component that we made, the
AnimationComponent, right? The SpriteAnimation. It has this stepTime. I wonder if we can just increase
and decrease the stepTime on the zombies' animation
based on their speed, which is constantly going up and down. That will be my first attempt. So let's go back to zombie_game. We have zombie-- oh, they've
been cheering this whole time? That's what they're doing? Cheer1? They're walking around
with their hands up? Yeah, they are. How much of "PUBG Mobile" code
is implemented in Flutter? Well, not the game, but it's
the HUD around the game. They've been cheering
this whole time. That's pretty funny. OK, so we want walk. I think I'm going to
do the same things. I just want idle
and walk1 and 2. OK, that's good. And I'm going to copy
a lot of this code. Copy, copy, copy, copy, copy. Going to the zombies update-- or no. onLoad method. [INAUDIBLE] onLoad. Yeah. OK, so the old sprite is gone. The zombie is now just a
PositionComponent, just like our player. PositionComponent. And they are going to
need a walking animation and an idle component. And I'll put these with
all the other parameters, all the other attributes. So idleSprite, final. Oh, yeah. I don't really need-- storing idleSprite up
here was pointless. This will just be final. Make that change in
the player as well. As Michael Jackson
said, make that change. So idleSprite looks
good, idleComponent. You're not the adventurer. You are the zombie. OK, my double highlight here
didn't really work that well. Oh, yeah. Of course. Of course. A horse is, of course,
a course, of course. There's the idle. No. Have to pull it from
here. zombie_idle. OK. So we've updated all of those. Now, the zombie's never
actually standing still really. Is it? They're always walking
around, so maybe they don't need the idleSprite. Maybe we just add-- maybe we add the
walkingAnimation, and then we just start
tinkering with the stepTime. So they're going to step
a lot slower, I think, because they walk slower. So that'll be the
default stepTime. Actually, let's store
that in a value. Static const
defaultStepTime equals 0.25. defaultWalkingStepTime. Nope. OK, looking good. Now-- so veering
is just steering. So that's not, I think-- I don't think that's going
to impact our stepTime. But lurching-- that was the
zombie coming to a stop, and then stumbling
forward and whatnot. So lurching is maybe when
they should move their feet. So let's look at the lurchCurve. Oh, yeah. They had a random thing there. Lurch. So in the update
method in chase, let's apply VeerToPath,
moveAlongPath, and moveAlongPath so they
get unwalkable collisions. And then we
eventually applyLurch. Oh, boy. So in applyLurch-- so we
have our percentLurched. I don't remember the
range of this number. See, this is what
I'm talking about. This code isn't very good. Oh, this is going to-- I only want this to
be for one zombie. If printLurch, do this. And printLurch is going
to be a new parameter. I don't know if you all-- oh, I have debug. All right, perfect. So this is just going to
be if debug, then that. And now debug comes in as
false for all of these, so we just want to make one-- it's probably in the world. When I make all
the zombies, we're going to set debug to
true on one of them. And we can watch its lurch
value go up and down. Here we go, zombiesToAdd. Nice. So debug here is going to be
whether or not counter equals-- just pick one-- value. Restart. Here we go. So the lurch-- oh,
it's a standard curve. Of course. So it's going between 0 and 1. Right? Does it ever get up to-- yeah, really close to 1. Awesome. And then it goes back
down to 0, and then it climbs its way back up to 1. Perfect. Did it stop? Oh, no. Here we go. Oh, look at them. They're all walking. Oh, man that's way too fast. 0.25 for them is way too fast. So you go back down, and
let's get rid of debug. Go back to zombie, get rid
of that print statement that I just added. So percentLurched is
going from 0 to 1. So we could potentially
say walkingAnimation.step-- oh, no. Please don't be private. walkingAnimation,
please don't be private. Where's our constructor? Am I just misremembering
the name of the thing? What's going on here? Oh, it's on the animation
on the component. So animation.stepTime. That's what I need. walkingAnimation
.animation.stepTime equals defaultWalkingStepTime-- oh, no. Oh, no. Are you-- is stepTime
frame.stepTime? How does stepTime get set? frame.stepTime? Oh, boy. This does not seem
like it's going to be-- can we update the stepTime? Oh, Binh, you had a good idea. Debug true for one zombie. Yep. Oh, man, I don't know if
we can update the stepTime. I can also imagine
that doing so would launch the sprite animation
into an undefined state. Oh, here we go, stepTime. Yeah, we can do it. Assert stepTime greater than 0. Yeah. Oh, yeah. OK, here we go. That makes sense. So we can set the stepTime. And it takes a double. So what are you
complaining about? Property stepTime can't
be set unconditionally because the receiver
can be null. Read the error message, dude. So if they're not moving at all,
then they're not going to step. They'll also need
to slow down this. Let's try 4.5. I guess just 5 for now. Let's see how this looks. Game crashed. Awesome. stepTime greater than 0. Yep. The animation does get down to
0, so we just read that assert. There we go. So percentLurched
here-- this will be the max of that and 0.01. Oh. [LAUGHS] That is not
what I was expecting. Wow. So the default StepTime,
the most we would do is just keep the
StepTime at itself or-- oh, small values are big. I need to do 1 minus? This is Flame, Alex. Yes. Crashed. Good stuff. Super good stuff. Probably the same assertion. Yep. All right, I got to
think about this. So we're going from 0 to 1. And when the animation value
is 1, that's full speed. So at that point, I want to
have basically no modification on the StepTime. When the value is 0
in the lurching curve, that's basically
zeroing out their speed. And then I want to zero out
the zombies' StepTime, which would involve
increasing the StepTime. I need more time to pass
between the animation changing. So that's where-- small values
in that small amounts of time between animation frame changes. So I need small values
here imply high speed. Low values-- or
sorry, high values involve lots of time
between frame changes. So that's low foot
speed for the zombie. So I think what I-- oh. I'm not sure. So what did I type right now? I've got 1 minus
the percentLurched and the max of it. Oh, I think I want min 0.99. But let's still think
about this because I could have had 1 minus-- I could have had basically
0, subtract 0.1, and then that's why it got to below 0. And we had the assertion error. This will actually address
the assertion error, but is it actually
still what I want? So when the percentLurched
is really, really low, the zombie is
basically not moving. So let's call this 0.1. Whoops. So we're going to have 1 minus
the min of really, really low, right? And 0.99. And that is, of
course, going to be-- the minimum is the
really low value, and so we're going to get
1 minus almost nothing. So that's 1. So we essentially will say
the default StepTime times 1. So it's not going to change. So when the zombie gets really,
really low in movement speed, then that is just
when they'll have their normal StepTime, which
isn't actually what I want. Now, I could increase
the StepTime like crazy, such that a default
don't take steps. All right. We're getting some ideas here. Try max 1 minus percentLurch. OK, let's think about that. Where is the code? Here we go. So you're proposing the maximum
of 1 minus percentLurch. So when they are-- I think it might be percentLurch
minus 1, but max that. percentLurch minus 1. You might be onto something. If what I just
wrote doesn't work, I'm going to come back to that. I think there's some
there there, as they say. Oh, and then also, when
their speed is really low, it could put them on-- yeah. When their speed is really low-- no, this still looks
just ridiculous. I'm going to go
down to one zombie so we can focus on something. Player, zombie game. That was in the
world, wasn't it? zombiesToAdd uno. So the problem is, when
they're moving really slowly, they should actually still
toggle to the idle state. I think that's
part of the issue. And then when they're
moving faster, they could maybe take a
big step or something. In general, I think
this is OK, other than they do still some skating
that's not very satisfying. And they stay in the walking
state even when they're barely moving at all. And they should definitely
be not moving their leg. We don't want them like doing
the splits when they're just standing still. That's not very zombie-like. So I'm going to return to
the zombie class and restore. I think there's a real
chance, by the way, that my like whole lurching,
swaying algorithm just isn't very good. I think what
potentially should be the case is they have this
own little inner state machine where they spend 500
milliseconds maybe not moving anywhere, and then they have a
very distinct 100 milliseconds of taking a step. And we could just show them
taking a step during that time. And then they go
back to not moving, and then they take a step, and
then they not move for a while, and they take a step. Oh, man. I do feel like that's a lot
better than what I'm doing. And I think that I'm
going to do that. I think I'm going to make that-- I'm going to do that refactor. That's how this
is going to work. OK, so they're
going to need idle, and they're going
to need walking. And then we're just
going to move them through alternating
states of not moving to quickly taking a
step, not moving, quickly, taking a step. And that, I think, will be good. All right. Let's do that. That would be an
exciting win, to get the zombies walking around
and looking pretty decent. Oh, can they have
the Michael Jackson-- do a moonwalk? That's actually a
really darn good idea. Not very zombie-like, but
that would be pretty funny. OK. All right. Still have the idleComponent. Thought I might
have deleted that. OK, idleComponent
is still there. Great. So the zombie has two
states, and that's like wander and chase. But then, there's going to
be another type of state that they have, right? Because within wander
and within chase, they're going to be looping
through standing and stepping. So this zombie state is
actually more like zombie-- I don't know. It's almost like their target
state or their goal state, or something like that. I guess we can
call it GoalState. It's funny to think about
zombies having goals, but that is what I'm
going to call it now. ZombieState. OK, so it's ZombieGoalState. And now, we'll also have
ZombieMovementState. And here, it will be
standing and stepping. Pretty excited about this. Pretty excited. So in our initial-- where do I have my
variable for their state? There we go. ZombieMovementState. So this is going to be
movementState equals .standing. Also rename state to goalState. Again, giving zombies goals is-- it just feels-- I'm seeing-- oh, I got to go
further down for the red lines. There we go. goalState,
goalState, goalState. Looking good. Looking good. All right, applyVeer? No. Lurch. Lurch, lurch, lurch. That's what it was. So let's remember
how this works. I wonder if I can just
reuse this machinery. applyLurch. So their speed was
this continuous wave that was irregular. It wasn't a perfect sine wave. That's why they've got these
different curves, I think. Yes. Is that lurch? Does that change the curve? Yeah, yeah, yeah. Yeah. So is this irregular
but still analog wave. And now it's going to be way
more stepwise, where they just start stepping forward
and then stop completely. And that, I think, is going
to be a lot more satisfying. So in applyLurch--
yeah, yeah, yeah. So I don't think we care
so much about the curves that we were picking between. Don't think that's
a big deal anymore. Yeah. So setLurch, I'm going to
get rid of this method. Fare thee well, method. And we had our curves. So setLurch, you go away. The curves-- this goes away. There's some red at the bottom. That's probably in the
method, right? applyLurch. Oh, wait. Maybe I want this. What does this do? Difference. lurchStartedAt. So we are still going to
need something like that. I'm just going to
comment you out for now, and then not doing this. percentLurched. Don't care about that. Walking time animation--
we're probably going to do something with this. OK. So I can say here,
we're going to need like a time of the
last lurch change. Going to need that. So duration wanderLength,
wanderStartedAt. Where's my lurch things? Oh, I have all these
variables that are going away. lurchStartedAt, I
do want this one. There we go. Yeah. Commented out all these
variables and got no red code. Nice. We're deleting stuff. So lurchStartedAt--
oh, and then I'm going to have to set
that in the onLoad. Sorry, I'm just scrolling up and
down like a crazy person here. lurchStartedAt is going
to equal DateTime.now. OK. Nice. Now in applyLurch, we can say-- oh, and then we need to know
how long it's going to last. lurchDuration. Yeah. Some shared concepts. So lurchDuration-- this is
how long at the beginning of the start of the game
the zombie stands still before taking their first step. So let's have a method that
does that. getLurchDuration. And this will return
of a random duration within some
established confines. Oh, I wonder if we
can end up using the-- hey, let's go with that again. So getLurchDuration is
going to return a duration. getLurchDuration,
and you are going to return a duration with
an amount of milliseconds equal to random.next integer. And you are going to get some
number between the minimum and the maximum. So this is the maximum minus
the minimum plus the minimum. How does this work? nextInt is the max. So the max for us-- we
want basically 0 to 1,200, and then we add 300 to it. So we take the maximum
minus the minimum. Maximum minus minimum, and
then we add to it the minimum. OK, and we can decide
later whether or not those values are still good. They may not be, but that's OK. So now we have our
getLurchDuration. So this is how long the
zombie is going to-- oh, we need our step duration
and our idle duration, don't we? So this will be
getIdleLurchDuration, and then I'm going
to need another one for getStepLurchDuration. Because those are
not necessarily going to be the
same length of time. Checking chat real quick here. Oh, Brainiac, what'd you miss? Well, I'm trying to make the
characters move their legs when they walk. Yeah. ZombieMoveStrategy. That's what we're working on. Oh, we got some other folks. Making games in vanilla Dart. Awesome. Are folks working
on-- are people going to do the challenge? The game jam from Flame? I'm really, really
strongly thinking about it. I want to participate. Someone's making
Wordle with Flutter. That's a really good
learning exercise, for sure. All right, so got our
StepLurchDuration and Idle. So it's going to be get
the idle duration here. And our initial
state we already said is walking or standing, right? Standing. OK. So in applyLurch, we can
say, if movementState equals ZombieMovementState.standing. There's just a couple
things we want to do here. If DateTime.now.difference
lurchStartedAt is greater than
the lurchDuration, then we know that it's time
for us to change states. So we could say movement
state for the next frame is going to be
ZombieMovementState.stepping. And our lurchStartedAt
is going to be now. And our lurchDuration is going
to be getStepLurchDuration. But at the end of this, we were
still standing for this frame. So our speed is 0. OK. Else. If the movement state is-- oh, I could have
done a pattern here. I'll worry about that later. So we have a similar question. Has too much time
passed, basically? If so, I think we're going
to basically-- we're going to want all of these methods. So then we move
back to standing, and then we get our standing
duration-- getStanding-- getLurch. Go getIdle, I called
it, for some reason. getIdleLurchDuration. I'm going to rename
that to standing. Standing. And then we return the
speed, and that is the-- this is the actual thing here. So the speed is,
I guess, just 1. Return speed? Maybe. We could also-- in here, we're
going to switch to stepping, so we could remove
the idleComponent. And we can add the
walkingAnimation, and we can do the
inverse down here. So this is remove
the walkingAnimation and add the idleComponent. And I have to be adding
the idleComponent initially to fully set up
the initial state. So we have our idleComponent,
and is it added? No. There we go. Does anyone think
this is going to work? Oh, I skipped your question. All right. Scrolling back up. Oh, thoughts on Project
IDX as a Flutter ID? I think it could be quite
cool if they can really stick the landing of
efficient emulator. If the friction to
emulators is strong, then I think it
has real potential. To me, that's the
value proposition for a Flutter developer. So the Android emulator is
just always bedeviling me. Oh, hey, Philip. Howdy. Elegant management
of game patterns. Yes. That book is so great. Bob is such a wonderful writer. Holy smokes. Yes. Complete endorsement
of that as well. I've read most of it a
really long time ago, and I think I'm probably showing
right now that I've forgotten all of it's important lessons. I think what we wrote
just now may work. After I get rid of this
last bit of dead code. All right. One zombie. He stands still. He walks. OK, so the step speed
needs to be way higher. Yeah, yeah, yeah. Step speed needs
to be way higher. Oh, also, his speed
itself needs to be lower because I used to
be constantly reducing that speed with the lurchCurve. So the speed-- where's
the step speed? defaultWalkingStepTime,
this is now really, really cartoonishly high. I don't know what a good
value is, but maybe that. And then where is your
speed, good friend? Here we go.
worldTileSize times 4. That's way too high now. Let's cut their speed in half. Restart. Here we go. Zombie. Where are you? Oh, there we go. OK, so it stops. It lurches. It stops, walks, stops, walks. That's-- I don't know. I don't actually know. Is it good? Is this all right? It seems all right, I think. I think its legs need
to move a little faster. They definitely
need to move faster. But we're kind of in the-- I think it's in the right pace. There we go. Let's move-- let's try this. I think they do stand
still for a long time. I think I'm going to shorten
how long they stand still. So here, I had the lurch. OK, so let's rename
this to stepping, minimumSteppingLurchDuration. Lurch isn't really the right
word anymore, now is it? minimumStepping, and then
let's do the same thing here for standing. So StandingLurchDuration
is going to go into the standing method. Those are long variable names. And SteppingLurchDuration. Is going to go into
the stepping method. And now what did I
say I wanted to do? Make standing shorter? Let's try that. OK, this seems OK. This seems all right. More zombies at once? Yeah, yeah. Good, great. Good call. World. Let's go back to-- the feel of the horde
is what really matters. I think I might take debug
off as well, so we can not-- yeah. That's just
obscuring everything. Where is that set? Debug. Is that in zombie_game? I think that's where it is. Yeah. debugMode. All right. I think it's OK. What do folks think? How does this look? I wonder how they look
when they get caught. They're not super convincing as
zombies, but I've seen worse. All right, vaguely
positive reviews here? All right. Yeah, let's look at 100. It's going to be a real mess. It's going to be a
real traffic jam. Oh, gosh. [LAUGHS] Also, we are
getting some dropped frames. Wow. Yeah, this isn't good at all. The frame rate is
really quite bad. This could also be a
pretty fun-- obviously, Flame and Flutter, there's
nothing preventing them from rendering 100 zombies. But I'm doing no
optimization at all. Literally nothing. Wow, the frame rate is so bad. Oh, that's fun. Some zombies spawned
in the house, but they're in
unmovable terrain, so their movement speed
keeps getting zeroed out. And they're stuck. Game logic. Actually, now I want to have a
whole stream with either Wolfen or Lukas or anyone
from the Flame-- any expert on
Flame to-- how do I get this to still render
60 frames per second? Because obviously, this
has got to be a thing. I think probably my pathing
algorithm for the zombies is too expensive,
and it's just-- yeah. I guess the first question would
be, where is the time going? But that'll be its own question. All right, back to 12. As they say, you build the game
first and then optimize it. And obviously, this will need
quite a bit of optimization. All right. Yeah, probably the path finding. Dishank. Uh oh. Did I ignore another person? Positive feedback from my side. Great. Is there something
else that I missed? Oh, show more zombies? We did that. Dishank, are you
feeling ignored? I hope not. Are you running debug? Oh, release would be faster. That's a great point. I'm definitely
running in debug mode. Yeah. All right, well,
this is pretty good. I feel good about how this went. And there's definitely some
opportunities for improvement in future streams. We can try to
optimize the planning. I'm sure Lukas
and/or any other-- I mean, anyone who's
just made games before would have ideas on
how to make the algorithm more efficient. I have been pretty
interested in using-- called Rive now, but not yet. I've been talking with
them behind the scenes to come on the show, and both of
us have just been really busy. So having-- oh, looks
cute, not terrifying. Yeah, I agree with that. And I was going for terrifying,
so that is a funny miss. Yeah, they do look
cute, don't they? Those little buggers. Yeah. I think Rive would
be pretty fun. Flyweight pattern? I don't know what that is. Another thing to look up. OK, well, team, thank you
so much for tuning in. Thank you for all
the good ideas. Thank you for helping me make
sense of the sprite animation API and brainstorming
code to write in the applyLurch method,
and all the things that we accomplished
today as a team. I appreciate it. Am I going to be here next week? I think so. I may have a guest
next week, and I don't remember the schedule. But I might have
a guest next week. And if so, I'm pretty
excited about it. If not so, then
I can't remember. Whatever. There's a guest at some
point in the future that's going to be great. If there's nothing, then
I'll be back on this. And who knows? Maybe I'll work on optimizing
having 100 zombies on screen or something else in the game. But there's still
a lot to be done. All right. As a reminder, this is where
you can find the code online. I'm going to commit today's
work as a new branch and push that up. In fact, I'll just do it right
now, and then we can call it. Oh, yeah. Need to actually
add these things. So this is move zombies' legs. What did that say? Oh, I didn't save files? Thank you, VS Code. But no change? Something's weird. Yeah, OK. Commit. Thank you. And Git Checkout Branch
Episode 07 Zombie-- oh, no. We'll call it Sprite
Animations and Upstream. Here we go. Origin, Episode 07
Sprite Animations. OK. There it is published. Folks, I enjoyed
having you all here, and I can't wait to do this
again probably next week. All right. Have a good one, everybody.