Observable Flutter #29: Game development

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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.
Info
Channel: Flutter
Views: 16,560
Rating: undefined out of 5
Keywords:
Id: BfREhtybQI8
Channel Id: undefined
Length: 96min 10sec (5770 seconds)
Published: Thu Nov 30 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.