CRAIG LABENZ: Welcome back,
everybody, to another episode of "Observable Flutter." my name is Craig Labenz. And I am your host-- so far, as always. Who knows? Maybe one day there'll be an
adventurous other primary host. And today I'm pretty excited to
talk about an extension built on top of Flame. And it's called Bonfire. And Bonfire is specifically
a bunch of helpers that you need to build an
RPG-style game in Flame and Flutter. Think if you were trying to
make "Stardew" or some other-- "Vampire Survivors" or some
of those really super-popular indie games, you might
start with Bonfire. And I'm joined today-- oh, wait. I always get into
this too early. One second. First of all, remember,
code of conduct. Folks, this is the
Flutter community. Kindness always-- I really-- I actually do enjoy,
on "Hump Day Q&A"-- if you don't watch "Hump
Day Q&A," you should. They have this whole thing,
spells out all the stuff. And the one thing
they talk about is, if you're unpleasant in the
chat on "Hump Day Q&A," then they share that around
with other people. Same is true here,
though I don't think we've ever had anyone
rise to that level of earning themselves the banhammer. So that's always good. But yes, I am joined today by
Trey Hope, a Flutter YouTuber, who has made some content
on Flame and on Bonfire, specifically. So of course, I had to reach
out and collaborate on that. So let me switch back here. And Trey, welcome to
"Observable Flutter." TREY HOPE: What's going on, man? Thank you for having me. I appreciate it. CRAIG LABENZ: Yeah, appreciate
you taking the time, and not only the time
to be here today, but the time to make
all those YouTube videos that you've made. For folks who haven't seen
Trey's YouTube channel, Trey, how would they find you? What would they search? TREY HOPE: Yeah, you can find
me on pretty much all platforms, YouTube, TikTok, Instagram. But just type in Trey,
T-R-E-Y, dot codes, C-O-D-E-S. And then I should pop up. CRAIG LABENZ: All right, yeah. I'm just grabbing the link to
your YouTube channel right now. And I'm going to make a
banner with it, Add Banner. There it is. There you go. TREY HOPE: Thank you. CRAIG LABENZ: So this is where
you can find today's Flutter content. That's where I found it. And Trey, you came
recommended to me. I'd seen some of
your videos before. But when I got started on
the Flame series recently, someone was like, have
you seen Trey's things? And I was like, the time is now. So your reputation precedes you. TREY HOPE: I'm
glad to hear that. CRAIG LABENZ: All right,
and it was Steph, actually, who did the recommending. And I'm glad-- TREY HOPE: Hey, Steph. CRAIG LABENZ: --that
you're here, Steph. Yeah, Steph, the
author of the Flutter-- or technical reviewer of
"The Flutter Apprentice." TREY HOPE: I've actually
talked to Steph before. Yeah, she's good people. CRAIG LABENZ: Nice, nice, nice. God, I love all of this. I love folks in the Flutter
community collaborating, here. OK, so Trey, you're
the Bonfire expert. So I'm going to
be driving today. You're going to be coaching. But I'm going to lean
on you for expertise and a little bit of
know how, in terms of getting things wired up. I've so far run-- actually, let's switch to
the correct settings, here. I'm going to have to
do this real quick. I want this one. Here we go. Get in there. OK, so I have created
a fresh Flutter Create. I've added Flame and
Bonfire, and Flame Tiled, which I presume I'll need. And then I have downloaded
an off-the-shelf map, and added it to Assets. But I haven't actually
gotten this to load yet. So I think I still have to
do some tweaking to something in this. Last time I ran it,
it was a black screen. But so here we can
see, pretty darn fresh. And how do you get
started with Bonfire? TREY HOPE: Yeah, so I guess
the first thing, I guess, would be to get the
actual map loaded. So it sounds like you
have the assets in there. So if you go to the
main.dart, where you have the Bonfire widget. Let's see, here. CRAIG LABENZ: Oh,
so I'm not even using the Bonfire widget yet. I suppose I need
to do that, huh? TREY HOPE: I'm trying to think. CRAIG LABENZ: I'm pulling up the
Bonfire docs on another slide. And then I'm going to bring
it into the main screen so everyone can look at it. All right, so looking at
the Bonfire docs here, yeah, I always like to
start from total ignorance, so we can all learn
together here. So let's look at
their getting started, which is linked out here. Here we go. I would like to--
yeah, know more. And now we need to
make this text bigger. That's interesting. Why did this load in a new tab? Oh, I didn't Command-click. That's what happened. All right, so created
with the purpose-- that creating Flutter games
is easy, objective, and fast. So built on top of the
Flame, as we see, dah, dah, dah, there's examples. Let's look at getting started. Oh, right, the joystick. Yeah, yeah, yeah. That's such a great-- all right, so let's get to here. So WorldMapbyTiled is-- is
this a widget from Bonfire? TREY HOPE: I believe it is. Yeah, I believe it is. Or it's either from Bonfire or
from the Flutter tile package. But that is-- I think that
what you have right now, you have a world object,
being set to the map. CRAIG LABENZ: Correct. TREY HOPE: --Bonfire world-- CRAIG LABENZ: --but if we're
going to scrap all this-- Yeah. TREY HOPE: Yeah, so yeah,
we just replaced that with the Bonfire world. And-- CRAIG LABENZ: I need
one more, I think. All right, so let's add some
imports here, Bonfire widget and then we need this
Joystick, I guess. Oh, this is also from Bonfire. Great. So this one's
probably redundant. So we're bringing
in all of bonfire. All right, what
do you need here-- TREY HOPE: So they're here for-- CRAIG LABENZ: --for the map. TREY HOPE: Yeah, that's where
the map would equal that world map by tile. CRAIG LABENZ: OK. So we got that going. Now this is where I will
use my actual path, which is assets/dungeon/tiled,
and then the map file is sampleMap.tmx for
me, sampleMap.tmx. And this is the last thing
that I was playing with. In sampleMap.tmx, it
points to this source, which I think needs to
also be the absolute path to sampleSheet.tmx. And in sampleSheet.tmx,
we get the source to the full, actual
tile map, the sprite. And this, I was just editing. But I haven't run it yet. So this may work, but
it probably won't. TREY HOPE: I don't
think that you need the assets/dungeons
path, or part in the path, when specifying it in
the main.dart file. CRAIG LABENZ: Oh. TREY HOPE: I think
you can just do-- CRAIG LABENZ: Really? TREY HOPE: Yeah, I think
it's just sampleMap.tmx, if you have it in
the right directory. That could be--
I could be wrong. I know usually when I
set it up, I just specify the actual name of the file. CRAIG LABENZ: All right. Well, let's dive
into that as well. Where do you put it? Because I've right
now got it in Assets. So this is my project
bonfire example. Folder name is Bonfire. In the assets
dungeon directories, there's all of these assets. TREY HOPE: Right. So I noticed-- it's weird. I noticed-- well, I guess you
can use either JSON or TMX. When it's a JSON file, it would
be in the Images directory, but then for TMX files it would
be in the Tiles directory. I think that's right. So either/or, just
depending on what file-- if you're using TMX, we'll
put in the Tile directory under Assets. CRAIG LABENZ: So you basically
just skip this dungeon layer. TREY HOPE: Yeah. CRAIG LABENZ: Well,
we're getting a-- TREY HOPE: You may be able
to put it in there still-- CRAIG LABENZ: [INAUDIBLE] error. TREY HOPE: --but I'm not sure. Oh, only supports JSON. CRAIG LABENZ: Oh, it
only supports JSON files. Oh. TREY HOPE: Yeah, I think,
if I'm not mistaken, Bonfire only does
JSON and then Flame lets you do TMX
and the other one. CRAIG LABENZ: Ah. Trey, do you potentially
have a map with a JSON file that I could borrow? [LAUGHS] I didn't
anticipate that. TREY HOPE: Yeah,
let me see here. I probably have one for
Mario that you could use. CRAIG LABENZ: OK. TREY HOPE: Let me see if
I can get this populated. I have to redownload it. CRAIG LABENZ: Yeah,
so world map I tiled-- let's see here. So this does come from Bonfire. Now, the other thing I could try
to do is load this up in Tiled and see if I can then
export it as JSON. I don't know-- well, I haven't
used Tiled since I ran-- since I was building the
world for the zombie game. So let's make a new project. I've got to say, the
tiled UI is a little-- TREY HOPE: It's a little weird. CRAIG LABENZ: Leaves a
little to be desired. [LAUGHS] TREY HOPE: Yeah. Hold on, I'm getting
it right now. JSON. And grab that from here. How should I send this to you? CRAIG LABENZ: Do you
want to email it? TREY HOPE: Yeah, I'll
do that right now. CRAIG LABENZ: All right. Folks, thanks for
bearing with us here. I'm going to pull this out
of my email real quick. TREY HOPE: All right, just sent. CRAIG LABENZ:
You're a hero, Trey. This is when keeping
it real goes wrong. [LAUGHS] When staying ignorant
leads to big surprises. All right, waiting for-- there we go. Got the stuff. Love it. Now, this is just the JSON file. I'm going to need your
assets as well, right? TREY HOPE: I'm trying to think. CRAIG LABENZ: Can you
zip up the structure that you have with those? TREY HOPE: Yeah, I guess
that would make sense. Let me do that. Hold on. CRAIG LABENZ: All
right, well I'll still bring that in for now. Who's got a either interesting
Flutter joke or just thing that you learned or
something to share during-- from the last week? Put it in the chat, and we'll
pop it up and talk about it while Trey and I fumble
around with these maps. All right, I'm
going to Downloads. That's what I'm doing. There it is. TREY HOPE: Let's see. Audio, no. Images. I think I can just send you
the tiles, but I'm not sure. Let's see, Share. Compress. All right, I'm going
to email the files that come along with that JSON file. I may need to send over
another directory as well, but maybe this is all you need. But we'll see. CRAIG LABENZ: New
release of Flutter yesterday shortly after Hump
Day ended, Randall says. Oh, it was a hotfix? Or was it a new beta? Probably a new hotfix. Do you know what
was fixed, Randall? All right, awaiting the
second email over here. Minor fix on stable. TREY HOPE: One second. CRAIG LABENZ: For
more stability. Ah, a few iOS rendering things. Were they in-- what's the thing
that I'm thinking of here? Maybe Impeller related? Is this the Boring Show? Well, it is boring right
now, so in a way, yes. TREY HOPE: I just
sent it over to you. CRAIG LABENZ: OK, great. This is a-- TREY HOPE: Let me
know if that works. CRAIG LABENZ: --similar thing. Awaiting that email's arrival. If this has an attachment,
it might take a second longer to get to me. Yeah, this is a live stream that
has Boring Show vibes for sure, including today where our-- my desire to come-- TREY HOPE: I like
the Boring Show vibe. CRAIG LABENZ: --in here not
knowing anything about Bonfire has meant I don't
have the right map type, which is
delaying our launch. OK. Come on, internet. Get me this email. I'd love-- oh, there's a
website with the release notes. What do you think? What do you think I'm
working on the team supposed to know about that? A lot of pressure. I love that character
in "South Park," Tweek. Oh, it's a lot of pressure. He's just always freaking out. All right, got this
Images directory. Do you have the Images directory
next to world map JSON? TREY HOPE: World map JSON
is in the Images directory. CRAIG LABENZ: In the
Images directory, OK. And I see you've reincluded
that wonderful "OK." TREY HOPE: I've also sent
over a Tiles directory, but I'm not sure if
that's necessary or not, but it shouldn't be. CRAIG LABENZ: OK. So we're bringing
the screen back in. Screen, get in the game. Pubspec. OK. So my new setup
is in Assets we've got this images directory
that has all the stuff. So I'm just going to say
assets/images, and that's it. Then I'm going to press
Command-M and minimize instead of open main.dart. Now in here, we
can actually use-- oh, what was it called? It's got a full
name, world_1_1_map. This is great naming. So you believe we can
do this, and then I am not using this at all. And I can begin. We could run this. TREY HOPE: Right. Should work. CRAIG LABENZ: OK. In the words of-- what's his name-- Samuel Jackson in "Jurassic
Park," hold onto your butts. I love this giant red error. Oh, no directionality
widget found. Let's wrap it in a material app. TREY HOPE: Oh, is
that what that means? CRAIG LABENZ: Material
app will add that, yeah. So this is, what, Home? Is that what we give this? And then OK. Restart. All right, we got the joystick. That's good, but there
is another error. Wait. Why does it still say it
can only support JSON files? We are super duper
using a JSON file. Does that make any sense to you? TREY HOPE: Do you need-- I'm trying to think. You weren't debugging it, so
that shouldn't be an issue. I was wondering if you had
to restart it completely, but you already did. CRAIG LABENZ: Yeah. I'm trying that now, but
I'm not optimistic this is going to help. Wow. Invalid tileset source. Only supports JSON files. Let's look up that
error and figure out where it's happening. It's not in-- oh,
tiled world builder. I'm just going to
search in here. JSON. No, it's not in
this file either. I think I'm going to
clone Bonfire and-- [LAUGHS] I'm going to
clone Bonfire and just search for this error. Do you have any other thoughts? TREY HOPE: What if you
search in the search bar-- CRAIG LABENZ: Oh, here we go. This is actually it right here. Well, this isn't going to
search in the dependencies I don't think. So we found one here. It says print(TiledWorldMap)
Error error. And so that's what
we're getting down here. And then it says-- so there's an exception coming
from probably this read map method. My guess is that's it. Oh, is it just having a JSON-- this sure seems like valid JSON. Weird. Oh, let's try giving it-- I have an idea. Let's try Assets, Images. Let's try that. Maybe the error is
not that precise. TREY HOPE: Right. I see what you're saying. CRAIG LABENZ: Oh, OK. The asset doesn't exist. No, we're moving. This is good. So it loaded the file, and
now-- oh, I keep moving this up as if that's going
to raise this text. So it loaded the
file, but now this is getting double concatenated,
so that's not really what we want. TREY HOPE: So we don't need
the assets images on that. CRAIG LABENZ: Well, we
seem to have needed it on-- so it was able to actually-- oh, wait a minute. No, you're right. Now it says unable to-- no, you're totally right. TREY HOPE: I'm not sure why the
JSON isn't popping up, though, for the map. That's weird. CRAIG LABENZ: Yeah. So Yefri says, it's
possible that there's a syntax error in
the JSON, which is looking more and more likely. But boy, I don't-- oh, there's-- so look at this. There is also--
there's other errors that we're going to get to. This isn't right. Oh yeah, you were mentioning
the Tiles directory that you sent that you weren't
sure if I was going to need. I think I am going to need it. TREY HOPE: Yeah. So I sent over the
Tiles directory. I'm glad I did that, OK. You should have that
in your email too then. CRAIG LABENZ: All right. Pulling it in. Thank you. Download. TREY HOPE: It
should be two files. CRAIG LABENZ: OK, grabbing that
out of my Downloads folder. And Tiles. Unzipping. Yeah, there it is. Oh, this is so funny. All that's in here is the TMX. Oh yeah, that is literally
what it specifies. OK. Screen back in the game. TREY HOPE: Hopefully,
that works. CRAIG LABENZ:
Let's try it again. So what I did-- because this
says it wants to go up a level and then into the Tiles folder,
I put it up a level and next to Images. I hope that's appropriate. TREY HOPE: Yeah,
that's how it should. CRAIG LABENZ: Anyway, we're
back to the same error, only supports JSON files. So that is coming from--
we saw that was here. And I presume it's in read
map, so I'm going to print and we'll just see. I think we're going to get
flag one but not flag two. And while that's happening, I'm
going to go back into read map and poke around some more. So from server, that's not it,
so we're not in that statement. Otherwise, it's
just reader.read. Yeah, we got flag
one, not flag two. So we go into this reader.read
method, and that's here. So this says route bundle load
string path file, so let's see if we get print data. And then JSON decode the data. This is already going to be
enough to be interesting, in all likelihood. Yeah, I know. [LAUGHS] ApllArte
Tutoriales says, I thought I was the
only one who got errors. No, no. I am an error machine. So which was the last thing? It's obviously loading the file,
and a lot of looks like Base64 encoded stuff there. So result. So we're even
JSON decoding the result, and then there's
just going to be-- there's some exception in here. Oh, here we go. This is thrown literally. So if the tileset.source
doesn't contain JSON or TSJ, this is the specific problem. Tilset.soure. TREY HOPE: Do you still have
the TSX fileset somewhere? CRAIG LABENZ: Well, I haven't
gotten rid of anything that you've sent. TREY HOPE: OK. Well, I mean in the code
where you're calling the path. Is that still referencing
TMX or something by chance in main.dart? CRAIG LABENZ: I don't think so. Yeah, I've gotten rid of
basically everything else. TREY HOPE: OK. CRAIG LABENZ: So if we go
back to this read method-- so tileset, where are you? So map, tilesets. Oh, and then we're looping over. I see. So we're in an async loop here. So map.tilesets. And then this tileset has to
have a source that is a string, and it has to have-- So let's look at the source key
and see if we can figure out what tilesets is in the-- and we're still reading-- this
is all from the JSON file. So in the JSON file, tilesets. Here we go. So I'm expecting somewhere--
here is a source, and it has TSX not JSON. That's the error. But you made a video about this. How did you-- do you remember
bumping into this error at all? TREY HOPE: It looks like it's
referencing tiles though, so that should work
because you have that in the tiles directory, right? The TSX file? CRAIG LABENZ: So I
agree that this should-- this seems like it would work. But this is the code
that's complaining. TREY HOPE: Got you. CRAIG LABENZ: So it says,
in the given tileset, the source key,
if it's not null, it has to contain
these two extensions. And we just definitely don't. And yeah, folks in the chat
are noticing this as well. I don't know how they saw it
before I put it on the screen, but that was mighty
fine sleuthing. [LAUGHS] So we need a JSON map here. Interesting. Why is this being so-- TREY HOPE: I think, if I'm
not mistaken, I noticed-- OK, so the file I sent over-- I think this might
be a rookie mistake, but I've been working
on converting the Mario game from Flame to Bonfire. So it was originally
in Flame, so I might have sent over a file
that I was using for Flame, but the JSON file is
what I've been using most recently for Bonfire. But that should still work. I'm just thinking out
loud why it's not working. CRAIG LABENZ: Yeah. So you sent something
that runs on your machine? TREY HOPE: Yes. And I'm using Bonfire right
now, so that should work. But let me see. CRAIG LABENZ: Oh, you know
what we can do potentially? I just had an idea that I
think might work pretty well. Let's go to Bonfire,
Flutter, and download one of their examples
and steal the map. TREY HOPE: That works too. That's a good idea. That would save a lot of time. CRAIG LABENZ: So I guess
we can go to GitHub. And I'll make this bigger. And in Examples, Lib, let's
see what they've got here. Shared? Oh, no. Is it going to be in Assets? TREY HOPE: You want to take
their-- yeah, the assets. CRAIG LABENZ: All right. So Assets, Images. They've got a ton of stuff. TREY HOPE: It would
probably be in Maps. Is there a Map directory? CRAIG LABENZ: So
they do have Tile. So here's the Floors. And then I wonder if
we're going to have to just take all of this. TREY HOPE: Probably
would have to. CRAIG LABENZ: All right,
that's what I'm going to do. So I'm going to
go back to Bonfire and I'm going to
just download this. I'm going to download
the zip for now. TREY HOPE: I also have-- maybe this would work. I'm going to send over
another map for another game that I made for
the "Green Ninja." There's less files,
so maybe this will translate a little
bit easier coming over. CRAIG LABENZ: Yeah, that
was your YouTube video. TREY HOPE: Yeah. So we'll see. This one's a very basic
map, but hopefully this works better for what
we're trying to do. CRAIG LABENZ: Yeah,
basic will do. TREY HOPE: Let's see here. I'll send. CRAIG LABENZ: All
right, so I'm going to hold off on using the
downloaded Bonfire example, and I'm going to try to use
the "Green Ninja" map as well. TREY HOPE: All right, let's see. Then email it. CRAIG LABENZ: So while
we're doing this, heard a great joke
the other day. Actually, heard
it several times, but heard it again recently. Oh, I think I told this
joke on stream recently, actually, so some of you are
going to know the answer. What do you call a widget
that lives in Washington, DC? This is a very US-centric joke. What do you call a widget
that lives in Washington, DC? I'm waiting for an
answer in the chat. TREY HOPE: I think
I know the answer. If I do, that's funny. That's pretty funny. CRAIG LABENZ: Right? It's not bad. Randall recalls the joke. And you're being a good citizen
and letting other people guess. All right, I'm putting
the Images directory in-- I've moved Images over, so
this Images directory is now everything from your game TREY HOPE: Yes. CRAIG LABENZ: I'm going
to delete this, which was also from the old thing. TREY HOPE: The map name on this
one is going to be different. It's going to be-- CRAIG LABENZ: map_one.JSON? TREY HOPE: Yes. CRAIG LABENZ: OK. So close a bunch of stuff. map_one.JSON. Here we go. We've got our first
answer, a capital widget. You're thinking in the
right direction, but-- oh, Widgeton, DC. That was actually
the first answer. Both of them not quite
what we're looking for, but you are on the right track. Hey, look at us. We have a map. It's not the fanciest
thing in the world. TREY HOPE: Very basic
map, just empty space. CRAIG LABENZ: Yeah. It'll do. OK, it's time to start
doing some Bonfire things. So Trey, one of
the things that I was impressed by in
your video was just how easy Bonfire makes
it to control a character and swap to the
correct-facing sprite every time they move, every
time you change their direction and whatnot. Oh, we've got
another answer here. Is Center the answer? Washington, DC, does think it's
the center of the universe, so that's also a pretty
good answer, but not the one we're looking for. So Trey, I would love to begin
by adding the main character to this game and moving it
around and seeing the asset swap because I thought that
was really strong from Bonfire. TREY HOPE: Got you, OK. So is this-- we had to figure
out what type of player we're creating because
if we're doing it from a perspective of 45
degrees or top-down level, then if I'm not
mistaken, you would need to add specific animations
for going towards the left. I don't think that
you can just swap it as easily if you're doing it
from a certain type of player because there's three
different types of players you can have in the game-- simple player, rotation
player, and platform player. And they're all based on how
you're viewing the character. So some characters, you wouldn't
necessarily be flipping. You have to have a
specific direction for what you want to do. But we can figure that out
as we're going along with it, as we're creating the player. CRAIG LABENZ: OK. Are there any player assets in
the stuff that you just sent? TREY HOPE: Yes, there's an
asset for the Green Ninja, which is the main character. Is there-- I don't
see Green Ninja. [INAUDIBLE] CRAIG LABENZ: Oh, here we go. Yep. TREY HOPE: Yeah, OK. Yeah, there's that one. CRAIG LABENZ: OK. So yeah, let's add
this Green Ninja. Oh, there's lots of
different ninjas. Let's add the-- oh man,
these are kind of terrifying. TREY HOPE: Yeah, those
are some enemies. CRAIG LABENZ: What
do you say we just pick whatever perspective is
appropriate for this asset and add the Green
Ninja to the game? TREY HOPE: Yep,
that sounds good. CRAIG LABENZ: OK. We also did get it. It is a stateless widget because
Washington, DC, the District of Columbia, is not a state. TREY HOPE: Oh, wow. CRAIG LABENZ: All
right, how do I get started adding
the Green Ninja, Trey? TREY HOPE: So we can
create a new class called I guess Green Ninja or whatever
we want to name the player. And then we would extend
a simple player widget. CRAIG LABENZ: Simple player? TREY HOPE: Yeah. CRAIG LABENZ: OK. So that looks like
it's already imported. TREY HOPE: It should have a-- CRAIG LABENZ: Oh, we have
to call the constructor. Oh, geez. That was nice. So it needs a position. It needs a size. Now, one thing I am comfortable
with in Vanilla Flame is where-- I would probably extend
one of these widgets or I would extend the world. And in the onload
method of that new class is when I would call the add
method to bring in the Green Ninja here. Does it work the
same way in Bonfire or does it work differently? TREY HOPE: So in
Bonfire, you actually just assign an instance
of the Green Ninja to the player property
that's on the Bonfire widget. CRAIG LABENZ: OK. So there's a player
right here, so we'll say player equals Green Ninja. TREY HOPE: And then
we need to specify the position, which is going to
be where we want it on the map. Just starting out, we could
do something like that. And then the size, which
I think I typically had them at 32-by-32. Yeah, you can change that to 32. That should look good. CRAIG LABENZ: OK. Oh, I've still got all of those
print statements happening. And I don't really
want them anymore, so I'm going to very
quickly kill those. Actually, I'll restart the
app while this happens. It was in the builder, and
then it was in read map, so I got rid of these two. And then in read map, we'll
look for these other print statements. We're going to have
to click on Read. Read map. All right, I can't find it. Worry about it in a second. So we didn't assign the
asset to our Green Ninja yet, so I'm actually in
hindsight not surprised-- TREY HOPE: You're right. CRAIG LABENZ: --that
we don't have anything. TREY HOPE: You're right. And so for that, that's
where we would pass in on the Green Ninja. In the super constructor,
there should be a-- actually, what we can do
is add a super constructor to the simple player to
specify the animation. CRAIG LABENZ: Yeah, so I'm
seeing this simple direction animation thing, which I
think, if I remember right from your video, this is where
a lot of the heavy lifting starts to happen. TREY HOPE: It gets pretty
technical at that point. But the thing is, the
only thing-- there's only two methods required for
the simple direction animation, I believe, and that's facing
right and running right. So you don't have
to have animations for every direction
on the map as far as where the player can go. So if you hover under that-- so only idleRight and
runRight are required. CRAIG LABENZ: Interesting. So we'll say idleRight and-- oops. TREY HOPE: So in this
instance, if you were to have-- for this character, I believe
we could do the flip rotation thing if you wanted to. But since it's
already a field here where we can specify what
it looks like going left, that might help too. CRAIG LABENZ: No, I think
that would totally-- yeah, I think that
would be totally fine. So how do I define this-- oh, this is just a
flame sprite animation? TREY HOPE: Mm-hmm. CRAIG LABENZ: Oh, nice. I'm not going to
lie, I've actually never used a sprite
animation in Flame, so you can tell that I'm not
actually a professional game developer yet. TREY HOPE: I think
there's a method like spriteanimation.load
or sprite.load-- yeah, sprite.load. And then this is where
you can specify I believe the actual path to the gif. But now that I think about
it, I think that only loads-- it'll load an animation
for one specific-- Let's say that Green Ninja
didn't have 36 pieces and it was just one, then
sprite animation load would load that one. But loading all 36 of those
wouldn't give us what we need. So I think that there is
another method in there. It's called something different. I think it's sprites-- the name slips me, but it
should be in here somewhere. It's where you take a
list of different GIFs at a specific position. CRAIG LABENZ: So there was a-- there are a few
that involve lists. There's sprite lists and
variable sprite list. But I didn't see
in sprite list-- create an animation
from a list of sprites, all having the same-- TREY HOPE: I think
that might be it. CRAIG LABENZ: --transition. So where would we specify in
this which subset of the sprite we need? TREY HOPE: I believe that
on that-- so in the method, we're taking a list of sprites. I think that's where we would
call sprite.load and then-- there's a method-- let me
just do some quick research. I think there's a method
where you can actually specify the row or
column on an image when creating the animation. CRAIG LABENZ: Got you. So this was
spriteanimation.spritelist, we're thinking. And then sprite list
takes a list of sprites. And that was positional? That is positional. TREY HOPE: So we actually need
to create a sprite sheet that represents the image I sent
over with the Green Ninjas. CRAIG LABENZ: Got it. TREY HOPE: There's going
to be a method that we can call to load that sprite sheet. And then from there, we'll get
instance of that sprite sheet and say get sprite at
index or column 0, row 1. And that'll be how we
know which Green Ninja we're getting on the map. CRAIG LABENZ: Got it. Now, should I-- I feel like maybe I should turn
this into a stateful widget, and we can do that asset
loading in its init method. How does that sound? TREY HOPE: That should work. Yeah, that should work. CRAIG LABENZ: All right. So we're converting real quick. I'm going to add an initState. And this will be loadAssets. loadAssets. So in here, we should
be able to do that. So you've mentioned loading. We need to create
a sprite sheet. TREY HOPE: Which would
tend to be, I guess, like-- not a constant variable,
but like a global variable that we'll be using. CRAIG LABENZ: And this is
just pure Flame, right? TREY HOPE: Yeah. So we could do-- it should be flame.images.load. There it is right there. CRAIG LABENZ: Yep. TREY HOPE: And that'll give
us the image that we then pass into the sprite
sheet constructor to give us that sprite sheet. CRAIG LABENZ: Amazing. So this was Green
Ninja, and it's .png. And do we need to give
it the whole asset path of assets, images,
sprite sheets, Green Ninja? TREY HOPE: No, we
don't need to do that. CRAIG LABENZ: Really? TREY HOPE: If it's in-- let me see. CRAIG LABENZ: So all I
put in the pubspec was-- TREY HOPE: Actually,
since the file is-- the Green Ninja is
on the sprite sheet, so we just need
sprite_sheets/green_ninja-- CRAIG LABENZ: Sprite sheet. TREY HOPE: Yeah. CRAIG LABENZ: OK. All right. What are you whining about? Image is not defined. Whoops. What's it complaining about? Value of type image where
image is defined and-- can't be defined-- oh,
there is a collision of the types of images. So I think, first of all, we're
not using Flame tiled anymore. Oh, there's also
just a crazy amount of incomplete code down here. I'm going to comment
all this out. I think that's throwing
the thing for a loop. So this is unnecessary,
apparently. I do want to import
the correct-- yeah, so which one did it say? TREY HOPE: Maybe you could
just take the image out-- not the image itself,
but the constructor part. Just say var image to
ignore the red error. And then for what we need to
do, we can just keep going. But there should be a method-- what is it called? Spritesheet from-- well, we
can create the sprite sheet. So create an instance. CRAIG LABENZ: So the image
that it actually pulls in is from Flutter Painting. So I am going to just
grab that real quick. And then, now I think we
can say painting.image. Nope. [LAUGHS] Really? TREY HOPE: It was
a good attempt. CRAIG LABENZ: [LAUGHS] Why
is that not doing a thing? Dart UI. Where's the image in here? Oh, this is just a barrel file. Wait, let's go back. I want to get there. I want to-- because
we're going to-- I don't want to use dynamic. TREY HOPE: Yeah, you don't
like using dynamic either? CRAIG LABENZ: Yeah,
there be dragons. So the value of
image is defined-- yeah. And then it's also
defined in widgets image. That's fine. I'm actually not quite working
out what the issue is here. If I got rid of-- I don't need flame game either. That's not needed. So if I get rid of
Bonfire, of course, other things will error,
but then it knows-- oh, image extends
stateful widget. So that's the issue
that we're getting. It's that image from Material. And then if we get rid of
Material and show Bonfire, now image is going to
come from somewhere else. TREY HOPE: Dart UI. CRAIG LABENZ: And this says
now it's coming from painting. So this is Flutter. Lib. UI. Painting. Back to main.dart. I've never had
this issue before. This is also kind of weird. I wonder if it's-- TREY HOPE: I never had this
issue because I usually do my file initiation
in a separate file, so I never use anything
Material related. CRAIG LABENZ: Yeah. Well, let's do that. So let's add another thing,
which is green_ninja.dart. And the Green Ninja
will go into that file. And we'll add Bonfire in here. Now the Green Ninja is-- oh, I don't think we
want to actually put the Green Ninja in there. TREY HOPE: You just want
to have the sprite sheet initialization in there, right? CRAIG LABENZ: Right. So I'm going to delete this. We'll make a new file. And this will just be load
assets, I guess, dot dart. And in here, this will
say future void, I guess. loadAssets. async. And it's going to do this. And now we'll import the things
that we need, which is Bonfire. And that all works just fine. And let's attach this to
the global variable Image greenNinjaImage. OK, there we go. TREY HOPE: And that looks good. CRAIG LABENZ: So now we don't
need any of this anymore, but we do need to add-- we need to import load
assets, and we need to call will await loadAssets. Oh! I just looked at chat. People were trying
to help us out. I appreciate that. So we've loaded
the image, and now we still need to turn
it into a sprite, right? TREY HOPE: Yes. So I guess in load assets,
what we can do is we really-- I don't think we need to
make Green Ninja global because is the reason you
make the Green Ninja image global is so we can
represent other files? CRAIG LABENZ: Correct. TREY HOPE: So we really would be
using the sprite sheet version because we still have to do
some conversion on that image to make it a sprite sheet. CRAIG LABENZ: Got it. TREY HOPE: So there's a method
called SpriteSheet.load, I believe. If you just type in
SpriteSheet.load. Load from-- is it load from-- oh wait. SpriteSheet.fromColumnsAndRows. So that's where we're-- CRAIG LABENZ: Exciting. TREY HOPE: passing that image. CRAIG LABENZ: All right. And now these columns
and rows are-- TREY HOPE: Those are
going to be the columns and rows in that image. CRAIG LABENZ: --a single int. TREY HOPE: Yeah. CRAIG LABENZ: Interesting. TREY HOPE: I forgot what
the columns and rows was on that image, though. We had to look again. CRAIG LABENZ: So to
walk to the right is-- I actually don't quite
understand this constructor. We just give it one integer
for columns and rows. TREY HOPE: Right. CRAIG LABENZ: Do
you understand this? TREY HOPE: So we're
making the sprite sheet based off that
image, and then we're specifying how many rows
or columns are in it. So that way, when we need
to call in that simple-- the Green Ninja
class, we can say, give me the sprite at index 04. So that'll mean-- CRAIG LABENZ: I see. TREY HOPE: --we're
walking at 04. CRAIG LABENZ: I see. OK, that makes more sense. Sorry. I was thinking--
thank you, Trey. I was like-- I thought we were
going to be giving it a slice, a subset of
this or something, which in hindsight makes
absolutely no sense. Anyway, so it's four columns
and one, two, three, four, five, six, seven rows. Amazing. And now this is the
Green Ninja asset, right? TREY HOPE: Yeah. So I think that's
the sprite sheet. CRAIG LABENZ: Or sprite
sheet I meant, yeah. TREY HOPE: Perfect. And so then from here on the
simple direction animation, we should be able to now
create those sprites based off that sprite
sheet because we're going to be passing
in a list of sprites. So we can say
greenNinjaSprite.getSprite. CRAIG LABENZ: OK, love it. I do have a bunch of
stuff to close here. Let me go through
that real quick. Oh man, there's a lot of stuff. OK, I'm going to close
sprite sheet as well. So we're back in main.dart,
and this is where-- so the first thing
is a list of sprites. And like you were
just saying, it's going to be
greenNinjaSpriteSheet.getSprite. TREY HOPE: GetSprite. CRAIG LABENZ: Row, column. And this is idleRight. So which ones do you
think we should use here? TREY HOPE: So the thing
is with these games, idleRight could really
be almost any of them where he's not making
too much movement. But it's funny because you
can use an idle one within one where he's running because
he would eventually have a walk posture while
running at some point. So it's really up to you, but
I'd say maybe the second one down all the way to the right. No, it looks like he's moving. CRAIG LABENZ: If we
gave it a couple, would he just kind of bounce? TREY HOPE: Yeah, that's
exactly how it would be. CRAIG LABENZ: So what
do you say we try to give it these first four? TREY HOPE: OK. Let's see what it looks like. CRAIG LABENZ: So
this would be row, and these are probably
zero indexed, I'm guessing. So column 3 in rows 1 through 3. So rows 0 through
3 and column 3. And then we just do 1, 2, 3. And then this needs step
time, and that is a double-- I'm guessing that's
milliseconds? TREY HOPE: I don't think so. I think it's actual seconds. I think. CRAIG LABENZ: All right,
I'm going to give it-- TREY HOPE: I'm not exactly sure. CRAIG LABENZ: I'm
going to give it 1. It's either going
to be unbelievably fast or unbelievably slow. TREY HOPE: I think
it's actually seconds, so I think that will be slow,
but we'll check it out and see. CRAIG LABENZ: OK so now we need
to copy all this for runRight, but I think we want to give-- where is he actually running? You know what? I think I realized
what you were saying. I think I only want to
do this one at index 2, and then we're going to
do the same set of ones for the running. So I'm going to
leave running alone. That should be good. And then I'm going
to delete all of them except for 2 for the idleRight. TREY HOPE: Two sprites
or just one sprite? CRAIG LABENZ: One
sprite at row 2. I think it was the
one you were saying. It looks like he's just
totally standing there. It's actually-- yeah, it's very
similar to the one above it. Is the game-- do
we have any chance that this is going to work? TREY HOPE: Real
quick, getSprite, is that asking for the column
first or the row first? CRAIG LABENZ: Row. TREY HOPE: OK. I was just making sure we
had the coordinates right. CRAIG LABENZ: Yeah,
otherwise our character would look like they're
doing some nutty dancing. We've got an error, and
I have to call the thing. We got a good question here. So why is Bonfire
not just Flame? And Trey, I certainly want
you to take a crack at this as well. But just like-- I
heard someone describe Flame in a really useful way. They were fielding
a question why use Flame and not just Flutter. And they said, well, if
you make a game in Flutter, you will eventually arrive
at many of the solutions that are in Flame. And I just thought like, oh,
that's, A, correct, and B, a very succinct way to put it. And I think the same thing
is still true for Bonfire. Of course, there's
no magic in here. There's nothing
that you can't do. We're getting-- I seem to
have a bad column here. So it's assets,
images, sprite sheets, and it has assets/images/sprite
sheets/green ninja.png. green_ninja.png. Wow, that really does
look like it's there. [LAUGHS] Unable to load assets. assets/images/sprite
sheets/green ninja.png. Trey, are you seeing the error? TREY HOPE: Yeah. I'm not-- [INAUDIBLE]. Did you completely restart and-- that wouldn't matter. I'm not sure why
it's doing that. CRAIG LABENZ: Oh, yeah,
sometimes with assets maybe. But I thought I did
completely restart. I don't think it's
going to be an issue. Anyway-- TREY HOPE: Whenever I
have issues with assets, I always think
it's just something related to restarting again. CRAIG LABENZ: Right. And it rarely is. It's normally I actually
genuinely did something wrong. But boy, here I'm really
not seeing what it is. TREY HOPE: Sprite sheets. CRAIG LABENZ: So
unable to load asset. assets/images/sprite_sheets. So I'm going to
copy this string, and then I'm going to say ll. And there it is, 5.1kb image. That's from the
root of the file-- or root of the project. So is it because this
assets directory-- is there something else that
we have to do to populate this? TREY HOPE: No, not
to my knowledge. There shouldn't
be anything else. CRAIG LABENZ: So folks
in chat are asking, is there something we need
to do in pubspec.yaml? So honestly-- TREY HOPE: That could
be it, actually. CRAIG LABENZ: Getting assets
to load is always the-- TREY HOPE: That's
a very good point. You do have to specify the
sprite sheets the right-- you got to specify every directory
you use under Images, unfortunately. CRAIG LABENZ: Right, yeah. TREY HOPE: I forgot about that. CRAIG LABENZ: I picked
the wrong thing. So chat to the rescue. Thank you, the many people who-- Eren had it right. This name that I am
too illiterate to read had it right. Another one. Another one. [LAUGHS] Thanks, everybody. TREY HOPE: Yeah, they
were on top of it. CRAIG LABENZ: Yep,
not recursive. OK, so there we go. Let's run again. So to wrap up that other
thing Eric said, OK, got it. But for anyone else
who's wondering, this is just a bunch of
helpers on top of Flame that if you're making
an RPG game in Flame you're probably going to build. TREY HOPE: And
real quick, I want to piggyback off that answer. As far as using
Bonfire over Flame, I recently ran into
something where, when I was creating a
Mario game in Flame, I was trying to create
physics that allowed him to run into boundaries. That way, he can
stand on the ground. And I had to do a
lot of extra steps that Bonfire already
took care of because they come with different
gravitational pulls, blocking, callbacks, and things
like that to where I could allow Mario to stand
on the ground out the gate. So like Craig said,
it's just coming to solutions that have
already been built when using certain packages. CRAIG LABENZ: Yep, absolutely. TREY HOPE: And
that's good that we got the game in here, though. CRAIG LABENZ: Yeah. Especially with
physics-based stuff, collision detection, any of
those really tricky kinds of things, man, I
appreciate packages that deliver that for us. So now he's-- I think 0.1 seconds is too fast. Is there a time that you
remember feeling good? TREY HOPE: I don't know. 0.1 looks accurate to me. CRAIG LABENZ: Oh, you think? TREY HOPE: You feel like
he's moving too fast? Yeah, I think--
because he's small so I think his arms are moving. CRAIG LABENZ: OK. He's moving slowly. I feel like his feet--
he's really shuffling. TREY HOPE: Yeah. CRAIG LABENZ: But all right. Now, here's a cool thing. You mentioned that
they only said you had to do the
Right and idleRight, and that's because here's
another great trick from Bonfire. They're just flipping it
for us if we're going left. TREY HOPE: Mm-hmm,
looks like it. CRAIG LABENZ:
That's pretty good. That's pretty good. And this joystick widget is
nice if we were on mobile, but it's not my
favorite on desktop. Do they have a similarly
convenient WASD widget? TREY HOPE: No, not
to my knowledge. The joystick is I think
the only UI component that they have within Bonfire. CRAIG LABENZ: Got it. Maybe we can build
one in and contribute. So the dude is moving. And honestly, this wouldn't
take forever in Flame, and it kind of took
us forever because we didn't know what we were doing
and we had the wrong map type and lots of things that
aren't really Bonfire's fault. But you can I think
already see that if you've gotten past a little bit of
learning curve with Bonfire, you start whipping stuff
together pretty darn quick here. So I think that's pretty neat. What's another thing that
might make sense for us to do? TREY HOPE: So now
we could potentially add some enemies or objects. I guess that would make sense
to have an enemy in the game. CRAIG LABENZ: Yep. It's not too rich of a
game experience just yet. So I'm going to go back
to the spreadsheet. And you got this crazy thing
here of a demon cyclops thing. TREY HOPE: Demon cyclops. CRAIG LABENZ: Is that-- oh yeah, that is
what you named it. So let's add this-- TREY HOPE: There was
some random, I guess, sprite package that I
found for a ninja game, and it came with
all these assets. CRAIG LABENZ: Nice. I love it. I use Kenney.nl a lot,
so I'm no stranger to random sprite packets. I do love Kenney.nl's art style. So I'm going to add this. I'm guessing there's
probably-- just like we had tons of helpers,
simple player and whatnot, I'm guessing there's
similar helpers for enemies? TREY HOPE: Yeah,
there's a simple enemy. CRAIG LABENZ: I love it. So this is going to be
Demon extends SimpleEnemy. And let's see what this needs. Again, called a constructor. Looking very similar. Now, here's another thing. I'm guessing, on Bonfire
widget, there's a place for-- there's a thing for enemies. Oh, there's already--
there's actually not. Well, that's not
bad because I wanted to make enemies through some
kind of spawning mechanism. TREY HOPE: Oh, I see what
you're saying, like on a map and have multiple and
specify where they're at, actual locations and
things like that? CRAIG LABENZ: Yeah. And maybe when you
kill one, another one spawns, stuff like that. So how does Bonfire think
about adding enemies? TREY HOPE: So with enemies, I
think you just rolled past it, but there's a param
called objects builder. So objects builder
lets you put-- you can put enemies on the
map, decorations, other items, just any type of object. But this is within-- this callback in here
is where you specify, all right, I want five demons
at these positions on the map. CRAIG LABENZ: That sounds
like exactly what I want. I don't think it's
called objects builder. Let's look at the actual
super constructor again. Joystick. Player interface. Debug. Collision. Lighting. Components? overlayBuilderMap. I don't think that would be it. What is components? List of game components. So that could be
it, but I do want-- oh, I guess we could have a
game component that is just our enemy spawner or something. Does that make sense? TREY HOPE: Oh, you
know what it is? The object builder
is within the map. So if you go back to
the main.dart file and go within world
or by tile, it should be-- yeah,
objectsBuilder. CRAIG LABENZ: Ah-ha. And this is a map of
strings to object builders. And that is just a
function that takes this TiledObjectProperties
thing. That's unknown to me,
but we'll learn together. Well, you probably already know. And it just returns
a game component. This seems good, so
let's get into that. objectsBuilder. Now, the key that
you give it, is this how you access it later? So could this be like
demon spawner or something? TREY HOPE: So this would
be, within the JSON file that we made, on that map
is where we would specify where we want that demon at. And so it's pretty
much reading that JSON file for how many occurrences
of demon spawner did we see. Put them at these locations. So I should-- if you go to
the JSON file real quick, we can just look up what the
name is I have for the demon. It might be-- what is it called? Type in demon. dark_ninja. OK, it's just demon. And there should only
be one right now, but if we come back to
the main.dart file-- CRAIG LABENZ: OK. So now we just put "demon" here. TREY HOPE: Yeah,
change that to "demon." And then I think on
the objects builder there should be an extra param. CRAIG LABENZ: I think I see
how this is going maybe. So I'm going to have a-- actually, let's make it a static
Demon buildDemon or something. And this is going to take that
tiled object properties thing and it's going to
return a demon. And we're going to
give it the position from the tiled object
properties is what I think is going to happen. TREY HOPE: Yes. CRAIG LABENZ: So
Demon.buildDemon I'm guessing goes here. TREY HOPE: So what you can do
right there-- actually what you want to do is first
there's a param that comes with the objects builder. So take the Demon.buildDemon
out and put properties. CRAIG LABENZ: What's this? TREY HOPE: Oh OK,
that's how you went. OK, got you. That works. CRAIG LABENZ: And then what
I'm thinking we're going to do is just take-- TREY HOPE: That's exactly it. CRAIG LABENZ: --the x and
the y out of this, right? TREY HOPE: Exactly. CRAIG LABENZ: And that's
going to be its coordinates. TREY HOPE: So you can really
just props.proposition. CRAIG LABENZ: Oh, love that. Amazing. And then the size is
probably also specified. Oh, no. I guess that's just known-- oh, well, we've got width and
height, so we do have size. TREY HOPE: You can do
the same thing here. CRAIG LABENZ: Nice Now, the
demon needs some sprites. So we're going to get back into
the same game we were playing with the Green Ninja
and again close some of these other files. Load assets. So here we'll have
the demonSpriteSheet. And I'm going to
copy all of this. And you are the demonImage. sprite sheets. Oh, we actually have two
of them because there's Idle and Walking. So there's demonIdle
and Walking. So this was demon_cyclop_walk. And this will be
demon_walk_image. And then this will be the
demonWalkingSpriteSheet will come from the
demonWalkImage. And there's just one row,
but how many columns was it? For walking is six. So one row and six columns. Now we duplicate
this for idle demon-- get out of here, emoji popup-- idle image comes
from demon_cyclops-- cyclop-- singular-- idle. And then this is
the IdleSpriteSheet. And this comes from
demonWalkIdle-- that's not it. demonIdleImage, and it's
five columns this time. Now we're back to the
demon super constructor, and we're just going to steal
right out of the animation, grab all of this. How much did I copy? I don't know what I copied. I'm grabbing the animation
parameter in the pot. There we go. So right now, it's going to
look a lot like the ninja. That'd be confusing, but
maybe an advanced demon type. So this was idleRight,
so that is-- basically, just we
want all five because I think he's got a little
bounce to him here. So this is demonIdleSpriteSheet. And let's say the row for us
here is 0 and the column is 1. And then we're going
to add five of those. And that wasn't correct. That was really-- TREY HOPE: You got
them all in an array. CRAIG LABENZ: Yeah. [LAUGHS] Try it again. 2, 3, 4, 5. So that's our idle
dude, and he's really going to be popping here. I think we're going to
want to slow him down. Let's try a 1/3
of a second even. I don't know. We'll find out together. Now then for
walking, again we're just going to want
all six of them. So this is the
DemonWalkingSpriteSheet, and it's all row 1 and
the different columns. So we're going to
grab six of these. 2, 3, 4, 5, 6. What do you think? Are we going to have a dude? TREY HOPE: Looks good. CRAIG LABENZ: Oh! Oh, Randall says he thinks
we can do assets thingy thing thing. I did actually try to use
a single star for the blob earlier, and it wasn't working. But I of course should
have tried two stars, so maybe that would work. I do want to speed up
our Ninja because Ninjas are supposed to be fast. Oh, they're blinking in and out. That's interesting. Oh, why are they doing that? TREY HOPE: So they're probably
getting an incorrect GIF while they're stepping through. CRAIG LABENZ: Yeah. Also, so they're
moving way too slow. So demon idle, we got to
get them popping here. Oh, of course, it's 0 indexed. TREY HOPE: Oh, yeah. Good. Didn't even notice that myself. CRAIG LABENZ: How silly of me. And this time, instead
of renaming them all, I'm going to be smarter. OK, that should be better. So here we go. The game is building. Why does the column
start from 0? Because all counting in
computing starts from 0. TREY HOPE: That's
the best answer to give to that question. Everything starts from 0. CRAIG LABENZ: Yeah. Hey, now they don't
blink out of existence. Though if they were
interdimensional demons, the blinking in and
out wasn't all bad. I think-- oh man, that's
a really subtle bounce. I don't know how much it's
even going to be coming through on the stream here. They're pretty-- TREY HOPE: Yeah, that bounce
time is still kind of slow. CRAIG LABENZ: Yeah. So we got it at 0.2. I guess let's double the speed. I wish it was a little
more pronounced, but maybe just all the
characters need to be bigger. TREY HOPE: Could be. CRAIG LABENZ: Oh, Randall
verified, can't do it. TREY HOPE: I was about
to look at up for myself. CRAIG LABENZ: OK. Next thing I want to do is
make the Green Ninja run faster after we confirm. Now they just look like
they're hyperventilating, but we're going with it. They're hyperventilating demons. Why it started from 1, not 0. My bad. Oh, that's just because
I typed total nonsense. It wasn't ever going to work. OK. How do we make the
Ninja run faster? That's what I want to do next. Here we go, speed. Wonderful. So there's probably
some default speed which we're going to find
as we keep drilling around. We still don't know the speed. Speed, speed, speed. Still don't know the speed. Movement maybe. Default speed, 80. Great. Oh, this is very
precise something. Oh, diagonal reduction. Oh, nice. OK, that's how you have
moving diagonally not be a speed boost. So the speed was 80. I guess we'll go
after animation. Speed. I really want to be much faster. I want to get to those
demons on the map. TREY HOPE: Double the speed. CRAIG LABENZ: All
right, what should we do next after we test this out? Oh, that's a ninja. That's a ninja. Look at that. He's just scooting. It's like Percy Harvin out here. Watched the Florida
Gators documentary on Netflix recently. Anyway. The enemies need to
follow us, right? TREY HOPE: Yes. So that's where we would need
to modify the demon class. There is a mixin that we
add to the demon class. It's automatic movement
or something like that. But it'll give us
some callback methods for what we do when we see
the player close to us. AutomaticRandomMovement. CRAIG LABENZ: You have
AutomaticRandomMovement. TREY HOPE: And it provides just
a bunch of different callbacks. CRAIG LABENZ: We're adding
random movement like enemy walking through the scene. OK. So run and movement. Method that should
be used in update. So this has a lot
of parameters here. Is this going to add-- it looks like this is definitely
doing a lot on its own, so should we just
try it right now? TREY HOPE: Yeah. CRAIG LABENZ: Yeah,
this method looks like it has some serious
logic in it already. There's some target
distance stuff now. So we're going to have to do
something to activate this, it looks like. They did say to call
the method in update, so maybe that's
what we have to do. We actually have to call
the method in update. The doc said that. So void update. That takes the double dt. And then we'll call this method,
which itself takes the dt and override. Oh no, still mad. Must call super. I still have that print
statement coming somewhere. It's printing the whole
JSON map every time, and I'm super annoyed by it. But I couldn't find it. TREY HOPE: Did you type
in-- was it a print? What if you searched for the
print statement in the code? CRAIG LABENZ: Yeah,
I've closed the files. We just have to
find the right file. So they are moving. They're not following us yet. But they're moving, so that was
certainly part of the issue. And the movement is random,
so the mixin I think has lived up to the billing. Wonder if there's
any parameters-- checkPositionWithRaycast. onStartMove. So there's nothing--
there doesn't seem to be-- TREY HOPE: There should be
a method for seek and move to player. CRAIG LABENZ: Oh. I'm not seeing it in here. I'm guessing it's a different
thing, a different mixin. So I'm going to search
Bonfire, SimpleEnemy. Oh, I didn't really find it. Wait, what is this? SimpleEnemy. Oh, it is in Bonfire. Oh, the Google result here
hid that it was Bonfire. Can't see it, other than I
guess now I'm noticing it here. Interesting. Let's also make this bigger. So we're in Bonfire. Oh, I wanted to see
also documentation. Does this have random? So this has
AutomaticRandomMovement. So this should also list
whatever the mixin is. Will this mixin have a see also? No. See kids, always
write your see also. Really handy. Just skimming here. Do you remember what the
other mixin's called? TREY HOPE: The way I have it
it's just called that mixin AutomaticRandomMovement. But it came with a method
called See And Move To Player. CRAIG LABENZ: Interesting TREY HOPE: I don't think
they removed it that quick. CRAIG LABENZ: It seems
to be somewhere else. I feel like I want
to just open the-- I'm going to open all of
Bonfire in another window so we can search for stuff. Bonfire. Loading. There we are, into VS Code. TREY HOPE: I'm not sure
why that's not in there, in the AutomaticRandomMovement. But it doesn't look like
it's in there anymore. It's weird. CRAIG LABENZ: All right, I'm in. Got Bonfire pulled down. Oh, interesting. So Randall did say there's an
issue about this, the assets thing. It's not going to be a thing. And Bard was how he found that. Way to go, Bard. Way to go. All right, we're looking
for-- what was it, seek? TREY HOPE: See, S-E-E. CRAIG LABENZ: Oh, see. TREY HOPE: And Move To Player. CRAIG LABENZ: Interesting. So these are in the examples. I don't really want-- oh, you know what? I should have loaded this up. Oh no, it's fine. OK, back to the search. I don't want
rotation_enemy_extension. npc_extension. TREY HOPE: I think that's
the one that you want, npc_extension. CRAIG LABENZ: seeAndMoveToPlayer
returns a shape? Interesting. So we're back in
main.dart in our game, and on the demon class we're
going to add npc_movement. Nope. We're going to add
npc_extensions on NPC. I wonder if an enemy is an NPC. They're not. There's also rotate-- oh,
enemy_extensions, this is what we want. seeAndMoveToAttackRange. seeAndMoveToPlayer. Critter. I'll see how they do it
in this goblin class. Oh, so it's
seeAndObserveToAttackRange. Interesting. So seePlayer is what they call. So first of all, Goblin
is a simple enemy. And they've got
AutomaticRandomMovement, and then that's it. So that's fine. Now, in their update method
they got a kill switch here. That's good. Is the game actually running? If not-- and this is a
whole other kill switch. It's good. So they call seePlayer, and
this is going to basically look at its range and maybe like not
see through walls or something. That would be pretty cool. And then we've got observed
and notObserved options. So that's pretty cool. And I'm going to
copy all of this. So seePlayer is
going to go into-- we're not calling
random movement anymore. We're calling all of this. So we don't have an attack yet. radiusVision. Let's just give it
100 or something. tileSize. So 100 for us is going
to be several tiles. And the attack range here--
so minDistanceFromPlayer. This is the attack range. Sure, that's fine. So we'll say 32 or something. And then, once
we're in position, there's a ranged attack that
we're not going to have. The vision for them is-- wait a minute, I thought that's
what I was specifying up here. TREY HOPE: Yeah, there's a lot
of parameters on this method. CRAIG LABENZ: We'll just
give this 100 again. I'm not really
sure. notObserved-- TREY HOPE: I think the
difference between those two are one of them
is for the radius that it would take for them
to go into attack mode. The other one is for
them to just notice you because they can notice you
without doing an attack yet, I believe-- a certain attack. CRAIG LABENZ: I see. TREY HOPE: But yeah, there's
a lot of customization when it comes to these methods. I don't think you need that. CRAIG LABENZ: Oh, you're right. We don't need anything for that. Oh right, because I just
grabbed a bunch of actual code. We weren't leaning
on a mixin anymore. TREY HOPE: It's coming
together, though. CRAIG LABENZ: I'm moving around. Hey, hey, hey. So if we get close, are
they going to follow us? No. Boo. TREY HOPE: They still got the
random movement, it looks like. CRAIG LABENZ: So they
call random movement in this update method. They're seePlayer. So I'm guessing
we're not observed because they're-- well, move
towards target and then not observed. They do eventually run
some random movement. TREY HOPE: I think that
radius vision might be throwing something off. CRAIG LABENZ: Yeah, let's
look at the docs for that. NpcExtension. SeePlayer. So this method we notify
when we detect the player in radiusVision configuration. It doesn't really say much
more about what that is. radiusVision goes into
the seeComponent method. radiusVision defaults to 32, and
then it gets use shape vision. radiusVision. _canSee. RectangleShape. otherShape. So we draw a thing, and then
just check for a collision. So that all makes sense. It feels like those
should just be normal-- here's some more why
you'd use Bonfire. They're going to
check to make sure he can't see through walls,
so that's pretty good. Anyway. Yeah, it feels like we
haven't seen any evidence that this would be anything
other than game pixels. I'm going to-- TREY HOPE: What
happens if you take out the move to random call? CRAIG LABENZ: Oh,
that'll be a good test. Yeah, that'll be a good test
because then we'll know-- are they just
going to sit still? I think if we can get them
following the player, that could be a good breaking point. What do you think? TREY HOPE: I think that's good. CRAIG LABENZ: Great thinking. They're not moving. So we're pretty sure they
are sitting in notObserved and seeAndMoveToAttackRange. I wonder-- well,
we know they were getting down here
because we commented out and they stopped moving. So the question is just, why
are they not seeing the player and getting into observed? TREY HOPE: I wonder-- I don't think that
there's anything with collisions needed here. CRAIG LABENZ: (WHISPERING)
gameRef.player. Well, this is interesting. We did-- there is-- we set up
the player correctly, right? TREY HOPE: Yes. CRAIG LABENZ: OK. So return see component. Yeah, I'm just looking
at how this code works. See component has the
observed and not observed. And this is what we
were just looking at. So if we're moving-- that's not us. Can see observed. Not observed. Right. So we're definitely
getting into not observed, and I want to see why. So I have one thought here. We thought there was one demon. There are two, it seems. Demon. Demon. There we go. So I'm going to get
rid of one of them so I can put print
statements in here, and we'll know that each
print statement is just for that one demon. We won't have to worry
about what's what. So in vision, in canSee, there's
inShape on a first check. And I also want to see,
how big is this rectangle? And I guess the size
is what we really want. And then we've got inShape. So if inShape and
checkWithRaycast-- what was the default on that? And checkWithRaycast is--
where did that come from? Is set to true. So we are going to
make this check. Then we've got our
raycast results. I'm going to check that as well. Print raycast result. And
we're going to get all of this. See what we see. TREY HOPE: Raycast-- CRAIG LABENZ: Py Workshops
asks, "Will Flutter support 3D games in the future?" TREY HOPE: It's a good question. CRAIG LABENZ: Yeah,
I think eventually. So when we're really
far away, we're seeing that this
rectangle is pretty small. It's like one square. And then we're not in the
shape, so that's expected. So let's get down
by this demon here. So now inShape is true, but
we're getting false on raycast. So you mentioned hitboxes, and
is it that we need a hitbox? TREY HOPE: I think that's
exactly what it is. So we can just do that in
both of the constructors of the player, the Green
Ninja, and the demon. CRAIG LABENZ: Got it. TREY HOPE: So yeah, you got it. No, it'll be-- actually,
it should be add hitbox. Should be a native method
on the player class. So if you create a
constructor on this class-- CRAIG LABENZ: In on update
I think we would, right? I wasn't even doing
the right thing. TREY HOPE: We're
just assigning it, so we would need to
put it in the update. We can just, in
the constructor-- CRAIG LABENZ: Oh, sorry. TREY HOPE: --just
add a collision. CRAIG LABENZ: OnLoad is
what I meant, but I'm-- so we've got positions, size. Oh. No, there doesn't seem to be-- I think we are going to
have to do it in onLoad. But you're definitely
right, not on not update. TREY HOPE: Yeah. Actually, I think there's-- OK, so there's a mixin
called object collision that we can add. I think with that
mixin, then we'll have a method called
setup collision. CRAIG LABENZ: Oh,
is this base Flame? TREY HOPE: Yes. CRAIG LABENZ: Got it. So it's like-- yeah, Has-- oh, how'd the cursor get there? With HasCollisionDetection. I can never remember
whether it's-- there's one you put
on the root game, and then there's another
one you put on the actual-- TREY HOPE: I think
HasCollisionDetection is on the game. And then ObjectCollision
is on the character. CRAIG LABENZ: So that one
didn't come up for me, but I don't know
if I-- are you sure it's called ObjectCollision? TREY HOPE:
ObjectCollision, yeah. CRAIG LABENZ: Now, there
is a new version of Flame. TREY HOPE: OK. Let's have it just-- well
yeah, we can look here too, but I was going to just type in
collision and see what pops up. CRAIG LABENZ: Oh, in-- TREY HOPE: In
Flutter for a mixin just see what pops
up when we type in. CRAIG LABENZ: Am I in
the right one here? Up on-- oh, that's not
even the right one at all. So sorry, you were saying-- TREY HOPE: Yeah, just
type in CollisionCallback, HasCollisionDetection. Yeah, I feel like
it's one of those two, HasCollisionDetection
or CollisionCallbacks. CRAIG LABENZ: Can be used to
get callbacks from the collision detection system. No, I don't think that's--
this isn't the one we want. Collision callbacks
on component. Oh, maybe it's this. What's the error here? Private name
_activeCollisions conflicts. Conflicts with what? TREY HOPE: Maybe it's-- is it
already in the simple player? CRAIG LABENZ: Oh. Attackable. GameComponent. Is that from Bonfire as well? It is. So PositionComponent,
CollisionCallbacks. Ah-ha! But we still need to
add the hitbox, I think. TREY HOPE: I think
what we could do is call SetupCollision
inside the Green Ninja, and that would give
us what we need. CRAIG LABENZ: And where
does SetupCollision happen? TREY HOPE: In the on
load or the constructor. CRAIG LABENZ: Oh, sorry. Where is it defined? Sorry, my bad. TREY HOPE: Got you. CRAIG LABENZ: Because yeah, all
the way through GameComponent I'm not seeing SetupCollision. TREY HOPE: It looks like it's
in Collision on the Bonfire package. I see we have lib,
base, game_component. I think it's lib, collision. CRAIG LABENZ: So I
see collision_util. No, that's not it. Collision.dart. In lib you saw it? TREY HOPE: Yeah, but now
that I think about it, I'm using an older
version of Bonfire, 2.1.2. You're on 3 it looks like. CRAIG LABENZ: Yeah. So I'm going to--
oh wait, that was-- then the collision--
so callbacks, this does just come from Flame. CollisionCallbacks. So I think we just need to
follow base, flame, collision stuff, which I never remember. 100% always have to look it up. Look at the latest stuff. Collision Detection. Very good. So MyGame, looking good. MyCollidable. So CollisionCallbacks,
that's what it was using. So there's just the
onCollision, but we still have to add the hitbox. There we go. This is what we want. So now we'll return
to the player. No, to main.dart, see? And we're almost
all the way there. What's your problem? Isn't a valid override. Oh, future or. Fine. So now it might work. TREY HOPE: Do you have
it in the demon as well? CRAIG LABENZ: I
don't, but I don't know if it needs that because
in the raycasting code it was just looking for
a collision with the-- oh, we are now invisible. But he's following us now. He's got better
vision than we do. [LAUGHTER] What a crafty enemy. TREY HOPE: I'm not sure
what happened, though, why you disappeared. CRAIG LABENZ: Yeah. Oh, probably because we
have to call super.onLoad. I bet I just ruined everything. [LAUGHS] TREY HOPE: Everything
else is working, though. CRAIG LABENZ: Yeah. Well, this feels pretty good. Get back here, game. There we go. It helps when you don't delete
all the code or short circuit the code that the framework has. Yeah, look at that. TREY HOPE: Perfect. CRAIG LABENZ: And it looks
pretty good with this walking animation you found. Of course, this and so much more
is in your video on Bonfire. Oh, look, it got lost now. Oh, boy. There he goes. Come back! [LAUGHTER] And we have no
boundaries, so he's just going to leave the game world. Oh, interesting. He kind of got
stuck in this motion as we left his radius because
we're faster than him. Then he just got stuck
in that direction. TREY HOPE: Yeah, what is that? CRAIG LABENZ: It's interesting. TREY HOPE: That's interesting. CRAIG LABENZ: Yeah. Potentially some
refinement to be made on the simple enemy class. Maybe a little too simple. So I do want to just
remind everybody, you got to check out Trey's
channel here where there's-- you got a ton of videos. And the Green Ninja-- yeah, I think it's
this one right here. So this is a video
that we just did the simplest little version of. But you've got a ton
of stuff going here. Oh, you have a backend
one too with Nakama? Trey, you're killing it. TREY HOPE: Yeah, I
just got hip to Nakama. I really-- I want
to see what Nakama can do for me with a lot of
the games I'm working on, but I just got hip to it. It's pretty cool. CRAIG LABENZ: Nice. Yeah, I've heard
great things about it. What's your initial impression? TREY HOPE: I think
that it's a very-- just a simple way to set
up some online gaming features like leaderboards or
online matches, friend groups. It's pretty much just a simple
API to get all that integrated. I haven't seen any other
type of gaming package that integrates as well with
apps and stuff like that. So Nakama, from what
I can tell, it's a really good contestant
for adding online gaming. CRAIG LABENZ: Nice. Did they have leaderboard--
or you said leaderboards. Do they have matchmaking and
individual dedicated instances to actually run a
single real-time match? TREY HOPE: I know that
they have matchmaking. I don't know how
detailed they get into what they're
providing, but I know they do provide
matchmaking though. CRAIG LABENZ: Nice. Awesome. Yeah, Nakama's been
on my list of things to look at more as well,
but I just haven't yet. Well, man, Trey, thanks
so much for coming on. Thanks so much for
walking me through this and showing everybody
that you don't just have to stop with
Flame and there's a whole bunch of super helpful
utilities in Bonfire as well. Any last thoughts
from you, Trey? TREY HOPE: I want to
thank you personally for reaching out to me. I've definitely been watching
your videos for a long time, so I'm definitely honored once
I got the invite, so thank you for having me. And thank you to everyone out
there who watches my videos. Thank you for the entire
Flutter community itself. Just very grateful to be a
part of something that's still growing and learning every day. So thank you so much. CRAIG LABENZ: Well,
man, very well said. I just put my computer
to sleep on accident. I don't know if the stream
actually flickered or not. [LAUGHS] Very well said, Trey. The pleasure was all mine,
I assure you of that. Everybody, thanks for
tuning in, whether you're watching live or in the future. Hopefully Bonfire
looks pretty exciting. If you've been into
Flame, hopefully you feel like you're now
into Bonfire as well. And folks, we'll see you all
on the next episode next week. TREY HOPE: See you
later, everybody.