Observable Flutter: Speed-coding Pong

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
CRAIG LABENZ: Hello, everyone, and welcome to another episode of "Observable Flutter." Today, one man, one video game, roughly one hour. Oh, yeah, and he's going to try to write that video game. [LAUGHS] Thanks for joining, everyone, today. I'm your host, as always, Craig Labenz. And I'm realizing my camera is a little crooked. Let's fix that. And today, I'm going to try to write the video game "Pong" using Flutter and Flame as fast as I can, because I'm probably not going to finish in about 90 minutes. So there will be very little time to waste. So before we get too far into it, I just want to remind everybody that we're here to all be nice to each other and learn Flutter, and today Flame. And so no meanie heads. And I don't think there's any more time to waste at all. So let's do this. (SINGING) Let's do this. All right. I need a terminal window, which needs to be on the correct screen. And I'll switch into my dev directory and run flutter create flame_pong. Here we go. OK, code flame_pong. For those who don't know "Pong," by the way-- because that could be the case-- "Pong" is like one of the first video games ever made. And this might be a waste of time, of which I have very little. But I don't know. Pong video game. This is what we're making. There's a ball that goes back and forth from side to side and two paddles on either side. Here we go. And so this ball just kind of bounces around, and it's 1 v 1 game. You move your paddle up and down, that's it. No left-right shenanigans. That's some that's some silliness. But if you don't block the ball, if it hits your boundary, then the other team scores a point. So in this situation, the ball is hit the left boundary twice, awarding the right player two points. And that's we're going to build. I don't use my Google Buds as a microphone, no. Oh, I clicked the wrong one. I use a microphone as a microphone. But I use them as a speaker, into which no one is talking right now. So it doesn't even need to be in my ear. OK. Let's go. All right. So the first thing we know we need to do is add Flame as a dependency. So I'm going to OK. The Dependency window is open, so I'll add dependency-- it's on a window to block the YAML. And we're going to add Flame. I think that might be the only dependency. OK. Looks good. We don't need Cupertino icons. OK. I think we're just going to code everything in main.dart. Let's make the code bigger, full screen. And all right. So I'm going to import Flame-- flame, flamety, flame, flame, flame, flame, flame. OK. And whoo, it's been a minute since I've done this. So the Material app, that's fine. Oh, let's get rid of all the comments. // rest of the line, regular expression. We'll grab the newline and we'll replace it all with nothing. OK. So I'm definitely going to need the Flame documentation. So I don't know why I didn't just put that up. OK. Here we go. Flame Flutter. You are my friend. And-- where is the documentation? There's a website for it. This is it-- docs. Great. So the first thing we need is the GameWidget. So it's funny, they actually-- oh, it does extend StatefulWidget. OK. That's not what I remember from the last time I used this, but this is the structure we're going to go for, a GameWidget. Oh, and then we pass it MyGame. Right. That's right, that's right, that's right. OK. So this is great. We'll just steal this as is, not going to use any of the old home page stuff. So we're going to have this class. And then we are going to ultimately have to pass it. Oh, I should have kept the Scaffold. So we'll put a Scaffold back in. I don't know if we even need a Scaffold. Probably do, maybe. And then our body is going to be this GameWidget. OK. Now, the GameWidget requires this actual game, as we saw here. So let's look at what MyGame should extend. Where you at, MyGame? MyGame. Do we see its definition here? No. Game Loop? It might appear here. Class extends FlameGame. That's right. OK. So I'll grab FlameGame. I'm going to read chat right now, but I'll probably ultimately read chat a little less today since this is something of a speed challenge. "Please make this like an Apple dynamic island pong game." I don't know what that is. Did I start just now? You're seeing the very beginning. All I've done is add the Flame dependency and remind myself how to do this on the documentation. And OK. So the FlameGame is where most of the logic happens. And I do remember that the main methods-- wait, what are you upset about? What's the error here? The main methods-- oh, right, OK. So well, this would be MyGame. But what is pong extend unhappy about? Do we have to import something else for this? Oh, that's interesting. So this flame/flame import, we haven't needed yet. I thought that would get us that. So GameWidget-- of course, this is going to be upset because it is missing a lot of things. I'll actually just make it a StatelessWidget real quick, which is probably wrong. But this will allow me to, say, create the missing override, and then we'll convert it to a StatefulWidget. OK. I don't think we need this. That's a bit of an error transcription-- State of GameWidget. Well, we don't need this either. OK. Good stuff. State GameWidget. I don't think we need this. I don't know if it hurt-- it was underlined. Well, that was interesting. Yeah, that transcription didn't work-- really didn't work. All right. So we have our build method here. And we'll worry about that in just a second. What does that build, actually? What does our GameWidget build? Does GameWidget come from-- do I not define GameWidget? Is that the problem? What if I didn't define GameWidget? Does that just work? No, it doesn't know what GameWidget is. I do have to define GameWidget. Huh. OK. What's the problem here? GameWidget-- key parameter. That's fine. Oh, the problem is that I don't know what to put in the build method. That's right. OK. So I'm going to keep looking for GameWidget here. I think I might just have to pop into one of the samples. Where are those samples? Tutorials, Bare Flame game. Sure. This will probably show what we need. Git-- oh, no, that's just going to tell us to literally download Git. OK, open the stuff. This is what we've been doing. Add Flame as a dependency. That's what we did. Make a GameWidget, pass it a game. OK. What do we put in the render method? OK, they told-- we didn't put anything in the render method. They also-- oh, flutter/widgets. That's probably going to-- no, flame/game. No, flutter/widgets, what am I talking about? FlameGame. What do they put in the-- where is the actual repository? Maybe I'll look at a bigger one. Where is this on GitHub? Off to a really hot start. Are you going to take us to the actual game? That's not what I want. Let's go to Flame on GitHub and look at examples. Games. Any of these-- T-rex. That seems good. Love it. Lib, main.dart. OK, TRexGame. They're just using GameWidget again. Is this from the package? What am I doing? I'm a little confused here. I don't know if GameWidget is actually even needed. I'm just pretty confused about why it doesn't seem to exist. But I don't have any memory of putting this game in a build method. Maybe-- are you going to tell me what to import? No, you're not. This is good stuff. So we do know the game-- yeah, we'd have to give it a constructor and everything. I'm pretty confused here. Flame/game, flame-- where are some other imports that we see here? They're just importing flame/game. This is the most unceremonial-- unceremonious start ever. Game TRexGame. I'm going to literally download this-- all of this code. Back to-- I guess I'll go here. Download a ZIP, I guess. Well, you can see that I didn't do any prep for this. But I did think I was going to remember the very opening more elegantly than this. So Flame game-- or flame-main. That's good. I think we're still going to get pretty far, as soon as I do anything at all. So games, trex. This is what I'm going to move into code. So we can just see where does this get defined. Nope, nope, nope, not that. Nope. That was it. I was close. Please, will you go into the thing? Oh, my god. Finder, work with me, not against me. Downloads, flame, examples, games, trex. OK. So games, code trex. That was the most petulant I've ever seen Finder be in my entire life. I didn't like it. All right. So terminal here. We will now, yes, run pub get, open up main. GameWidget, who are you? You come from Flame itself. So we don't have to define it. Why are we not getting this? What am I doing wrong? What am I doing? Oh, I wonder if I'm not doing anything wrong and I just need to pass the game. Can you-- so folks have been saying this. John, you only missed me not know what I'm doing, which is what you're going to see for the rest of the episode. So you missed nothing. OK. We're-- stuff's happening now. So GameWidget comes from the package. There we go. And the error that I got was that I wasn't satisfying its constructor of giving it a child of FlameGame. And you do not define your own GameWidget. That's silliness. OK. So for the game itself, this is where we can now just go back to the normal documentation and-- actually, let's look-- I'm just going to stick with this other implementation. That may have some efficient helpers for us. So let's look at the T-rex game. And we'll grab the signatures of the two biggest methods that we need, which is onLoad and-- actually, you can often get away with only having onLoad. There's two big methods. They're onLoad and render. But if everything in your game is a Flame component, then they can call their own onLoad method and render the framework Flame itself will call. So you only have to call-- you only have to define onRender-- or just render yourself. I think it might not be onRender-- if you're going to do something different than what it would do on its own, which, if you stick with Flame components, you'll rarely need. This code theme is SynthWave '84. And it's really fun. OK. So onLoad-- this is where we'll add our various flame components. So the first thing that we're going to need is the ball and the two paddles. So I'm going to add those now. Actually, we'll define the classes first. So let's first make our paddle component. So class, we'll call it maybe just Paddle, extends, is it flame component? How do we do a component here? Component-- TextComponent. Is that a different import? Flame/components. It is a different import. OK. So I'm grabbing flame/components. And now maybe if I say-- let's look at what they all-- what are the different. Components ShapeComponent, ClipComponent. Is there just like a-- RectangleComponent? That one seems like a pretty good one for the paddle, RectangleComponent. And what do you need, Mr. RectangleComponent? Apparently nothing. Let's flip into it. So we can give it a position, size. These seem good. And the anchor, I know we want to put in the center. I've wasted time with that before. So first, let's make our constructor. And it's going to accept super.size, super.-- or those? Oh, position. That'll be key. And anchor, we already know. So I'm going to just wire through for us to the super constructor, the anchor equals Anchor.center. And I'm writing Python, not Dart. All right. So here are two paddles. And I'm just going to try to position the paddles and we'll get our first render. So we're going to have final Paddle leftPaddle and final Paddle rightPaddle. And I guess there-- now we can not instantiate them here probably. Let's have them be late final. All right. So now in onLoad, we'll say leftPaddle equals new Paddle. And the size should be-- everything in Flame is a Vector2, which is great. So our-- do you just give-- do you take positional arguments? Positional arguments. So our-- they're really narrow and they're tall. So let's start maybe giving this, I don't know 5, and then a height of 30, something like that. And then it also needs a position. And the position is going to be a little tricky to calculate. So for now, let's just get it rendering. This is going to be another Vector2, the offset from the upper left. And let's just give it 10, 10. This location really won't make a lot of sense, but we'll see it render there, and we'll know that we're kind of on the right path. So now we need to load the leftPaddle, which I'm going to go back to-- oh, yeah, the T-rex game-- and see on its onLoad-- what was that syntax like again? Oh, we just call add. And add is a method that sits on the FlameGame component. Yep. So we will add the leftPaddle. OK. Let's render this and see what we get. Build. OK, we're getting some more tips here. You don't even need the Home class or Scaffold. I don't know why you did a GameWidget. I know why I did a GameWidget-- because I was clueless. I was clueless. But we don't need the Home class or Scaffold? I think you mean the home parameter? So this means we don't need the Scaffold at all. That's what you're saying. But I do have good news. We are seeing-- oh, this just blew up to very large sizes. Please don't be full screen. But we have this small, white rectangle in the upper left. This is our paddle. This looks really good. But we can already see that we have some issues with sizing, right? This is going to dramatically change the structure of the game based on where-- how big the window is. So that's kind of our first big problem. And let's address that. So now, you said-- Abenezer says we don't need the home or the Scaffold at all. So I'd like to try that. I think what you mean is that we can do this. But let's see. Yep. Sure does still work. But if we do that, can we get the size? Let's try to-- is there a size attribute here? Vector2-- OK. Let's print this. I wonder if we know how big we are. print(size). And now I'll bring up my output. And ha, Reload. It's not printing the size. Hmm. It's not printing anything. That's not what I expected. Oh, because I'm in the wrong thing. Oh, it doesn't know its size. OK. And this is probably going to reprint-- no, that was only on onLoad, of course. Hmm. I'm going to skip the-- I'm going to skip the scenario of the window being resized. So we can now actually position these things and size them correctly. So let's maybe say we want each paddle to be like 5% in from its boundary. So the leftPaddle should have an x-value that will be 5% of the size. So that will be size.x times 0.05. And then the y-value-- and we're positioning the center of the paddle because we set that as the anchor in the class. So the y-value will be size.y times 0.5. Let's see how this looks. OK, I think this is reasonable. It could maybe be a little closer to the wall, but that is-- I think this is good. Randal, you're saying use a LayoutBuilder. That certainly would work. And that's what I was going to do if there wasn't a sizing thing here. Do you think we still need a LayoutBuilder? All right. So we have a leftPaddle. And now we need a rightPaddle. And the rightPaddle-- we're going to add them both as well ultimately. So this rightPaddle is going to have the same size-- actually, the size seems kind of small. Let's make it taller. Let's give it 50 pixels on both of them. It seems really small. And this size is going to be the x times 95%. And that should position that. So if we return to our UI, we've got two paddles. All right. Good stuff. We're moving fast with Flame here. Let's now get these paddles moving in response to keyboard events. And Flame has really nice handling for that, which I think I maybe saw some of in this sample. Yeah, onKeyEvent. But we have to add a mixin to the class for this to work. So if we scroll up to the top of the definition, we'll see with KeyboardEvents. And you can add this to the game itself, or you can add it to the component. I'm not really sure what I would be happiest with in the long run if I was making a really big game. But I'm just going to add it to the component for now. And then, hmm, I kind of thought it would tell me-- oh, I see. Events or input. Well, I guess we'll import events. And there we go. Now it says you need 33 missing overrides. Let's try input. I don't want 33 overrides. 33 overrides? What did you go with? They went with input, not events. OK, so that doesn't make a difference. So I was wondering if there was like a collision. Anyway, whatever. onKeyEvent-- hopefully this is the only one we really need because it sure is the only one I want to add. So back to my code. I'm adding onKeyEvent. Oh, boy, 33 overrides? I really don't want to-- I don't want to add 33 overrides. Something ain't right. Something ain't right. Flame/game components, flame-- they did import flame/flame/ I wonder when that's helpful. Because when I tried to import flame/flame, yeah, it just said you're not even using that, you fool. Hmm. Not going to lie. Pretty confused. onKeyEvent-- what do you say? What's your problem? All these things are unhappy. Hmm. You know what? I guess I'm just going to put it on the game. Maybe it's different-- I don't know if I've-- let's put it on the game. I really wanted-- oh, good. I thought-- in my brain, I cut the-- in my brain. I cut the method signature. Do you tell me we need 33? I'm doing something wrong. Does anyone know what I'm doing wrong? With KeyboardEvents. Isn't that exactly what they're doing? Class extends FlameGame with KeyboardEvents. And they don't have 33 overrides here. And they've imported the same things-- components, game, and input. Like, if I get rid of input, yeah, it's KeyboardEvents that becomes upset. Can you use KeyboardListener? Yes, we could. We definitely could. But I want to stick with the Flame concepts here. Gabriel, I think it doesn't-- the question is, "What low level Flutter API does Flame use to paint stuff? Does it use render objects somehow? Or is it completely independent?" I do not think it uses render objects. I think it uses custom paint, low-level stuff. But I am-- this is an old thought in my head. My confidence there is like 80%. I just think you-- to have render objects, you'd have to have widgets, or you'd have to reconstruct your own layer on top to juggle how the render object should change from one to the next. And the components in theory could be that. The components could take the place of widgets and then ultimately turn themselves into render objects. But I just don't think that's what they do. That's a really good question, though. OK. So you've still added to the component. Yeah, I haven't finished the transition. I'm looking through the sample code. I haven't finished the transition, because I saw that we still had-- LogicalKeyboardKey. So you need to come from flutter/services. And what's your problem? Remove the override annotation? Right, see? This is something is really-- add the with to the game. Do I not have a with up here? It's inside game, but where did I put that? Did I put it nowhere? Did I paste it and then think, oh, it's not working, and I didn't keep it? With KeyboardEvents. Well, now it's just fine. I've literally no idea what I was doing wrong. I don't have a clue what was going on. I'm not going to lie. I really thought I put it there and it said add 33. I must've put it in the wrong spot. I'm sure when I watch this later I'll be like, Craig, you fool. OK. But this is good. What are you upset about? It's that we're not returning the result. Yeah. So the key result-- onKeyEvent-- the key result that we're going to return is whether or not it's handled or probably it's like propagating or something. Let's switch to this. Yeah, handled, ignored, skipRemainingHandlers. So ignored would be it continues to propagate. Yeah. And then if we think we've fully handled this, then we say that we handled it. So I'm actually just going to copy their code. So if the key pressed contains-- so I'm going to use W, S for the left paddle and the Up and Down arrows for the right paddle. So let's do that. I'll copy this. So keysPressed contains-- oh, this is going to be W, I guess-- oh, keyW, yeah, keyW. Then we're going to start moving the left paddle up. So let's add an enum for the nature-- the different ways that-- I'll put it at the bottom, I guess. This will be enum. We'll call it PaddleMovement. And it can either be going up, down, or it can be idle. So now we'll have-- should our Paddle class contain their own movement? Ah, we might as well. How about that? So let's say PaddleMovement-- and it'll just be a movement variable. And it's definitely not final, but it will initialize to-- well, actually, we can just put it right here. This will start at idle of course-- PaddleMovement.idle. So if we press W-- oh, we need to know is it a key press down or a key release? So how does that work? keyPressed-- or keysPressed? Oh, no the event. That's what I want, I think. So if event.isDown? How does that work? IsKeyPressed-- how do I see if it's a keyDown or a keyUp? keysPressed-- how does this work? Iterator, add, contains. Let's see here. How does this work? Oh, it's the-- I think it's the type of the event. So we could say final keyDown equals event is RawKeyDownEvent. OK. So now we know if the key went up or down. And so if they press W, then we say the leftPaddle equals-- well, if it was a keyDown, then it equals PaddleMovement.down. And if it was a keyUp, then it equals PaddleMovement.idle. LeftPaddle.movement. And we need to do this for all the permutations. So that was W. And now we need the same thing for S, keyS. And so if keyS comes up, it's still idle. But if keyS-- oh, this was up. Of course, W is up. What am I talking about? And then S is down. So that's good for those. And I'll also say return key-- what was it called? KeyEventResult.handled in this case. And the same thing up here. And now we're going to copy all of this and set it for a key Up arrow, upArrow, arrowUp? There we go. So this would be the rightPaddle now for these two. And yeah, so if the Up arrow was pressed down, then the rightPaddle is moving up. Otherwise, it's idle. And if the-- I'm guessing this is going to be arrowDown. If that was pressed down, then the rightPaddle is moving down. Otherwise, it's idle. Whoo OK. [LAUGHS] "If in my team someone was coding like you, they'd be fired immediately." That's fair. That's fair. OK. So onKeyEvent-- oh, right. And then at the end, we need to return KeyEventResult.ignored. So if none of those things happened, then the event continues to propagate. And that's what the ignored thing told us. OK. So now we get to another method here. I think it's called update. Future, probably also void, update. Async-- I don't know if it's async-- override. And this probably gets the delta time, I believe. Isn't a valid-- oh, yeah, double the delta time. How do they define it up top? Do I have to go super.update to get to it? So not much in the way of a docstring here. Oh, but we must call super. So we were going to have to do that. OK. So update here-- this is where every frame of the game, if the paddles are moving, we need to update their position. So I'll start by saying, if leftPaddle.movement does not equal PaddleMovement.idle, then it's either moving up or down. So let's grab a delta x here. First of all, we're going to need like a constant paddle speed. And I'll put that on the paddle itself. So this will be, what is it, const final, final const? I can't remember. Final const speed. And it's a double, which is definitely not capitalized. And I'm just going to pick a number. I really don't know what's going to make sense here. Const, you go there. Whatever. Figure out how to make it const later. So its speed is going to be 10 somethings per some amount of milliseconds. And this is what we're going to play with. But we just need a number here to compare against the amount of milliseconds that have been passed in since the last update. One of the keys, if you've never made a game before, the difference between a game and an app is that a game is always running. There's always what's called the game loop. It's kind of if you had an app that was always animating lots of things, and you'd always have an animation controller ticking, and so you'd always be spitting out new frames. But it's really important to have constant movement, right? If a frame is dropped in a game, you don't really want the logic that updates that character to skip some movement. That would create really stuttery movement of actors in your game. So the convention for this update method in games is that the amount of time since it was called last is passed in. And that allows you to normalize how far the actor should move in this particular frame. Generally, you'll probably get just about the same value passed in every time. But maybe if someone's computer gets busy, then you'll see a much larger amount of time because maybe the last frame was skipped. Anyway. So we're going to compare how much time has passed since we last moved the left paddle with its movement. And this is going to ultimately be our delta y. The paddles never move left and right. They only go up and down. So we just need a delta y for this. Let's say final delta y equals the amount of time that's passed to delta time times Paddle.speed. And because-- oh, I didn't make it static. I think that's what I was missing. It's static const or const static. Hey, there we go. There we go. OK. So now we have our delta y. Then the question is, should it be positive or negative? So I'll say times. And if-- there's probably a better way to do this. So this is just going to be if the leftPaddle.movement equals PaddleMovement.up, then it is simply times 1 because we're going to-- oh, no, it's now minus 1 because of how game coordinates work. So we'll multiply by minus 1 because 0, 0 is actually the upper left. So to decrease-- to move up visually on the screen, we need to decrease our y-value. So it'll be minus speed here. Otherwise, we'll just multiply by 1, which is a no op. So now we have our delta y. And we can say leftPaddle.position equals a new Vector2, which will be the old x and the new y. So this will be leftPaddle.-- I should make a method on the paddle. I'm going to do that. I'm going to make a method that is going to return a Vector2-- no, it can be void. And this will just be like, slide up or down. And it's going to take a double that is the delta to-- yeah, delta y, essentially. And so what we're going to do is reassign our position to equal our old positions. Oh, no, it's a new Vector2. And the x-value is always the old x-value. So this will always just be position.x. And then the y-value is position.y plus delta y. Yeah. So slideUpOrDown is now how we're going to call this in our update method of the game. So we'll say leftPaddle.slideUpOrDown with this delta y. And we need this again for the rightPaddle. And I think we can just say rightPaddle. Should all be the same? What do we think? Is this going to work? Oh, Damien. Thanks for saying this. "Are you planning on continuing the Dart Frog and Postgres series anytime soon?" I've actually been doing a lot of Dart Frog and Postgres on the weekends. So I'd love to share what I've been working on there. Yeah. Great. Thank you for just letting me know what you'd like to see. So I think we can see if this is going to work. I'll hot restart because we've changed some initialization code and switch to the app. And nothing renders. I broke something. We had paddles. We had paddles, and we don't anymore. OK, we're still adding them. I don't really have any errors. Wait, there's @mustCallSuper somewhere. Where's that update? OK. super.update. And we'll pass in delta t. What's going on? Now they're here. Don't know. All right. So when I press W, it moves really slowly. 10 is not enough of a number. So let's make it 100. I think in "Pong," the paddles move so fast. So switching back here, pressing W. OK, this moves pretty fast. And-- oh, wait. It isn't-- my keyUp having it stop event, that code isn't working, because I'm hands off the keyboard now, and it's still just sliding off. So we have our first bug. Why is this not making it idle? It might be making it idle. Let's print its movement in update. So I'm going to print its movement in update. And we'll need a debug console up. And it's idle here. So I'm going to press W. And up it goes. And when I press my-- when I lifted my hand, it keeps thinking it should move up. So the update method is fine. It's the key handler. So keyDown-- oh. keyDown. Yeah, man, that really should have worked. Print keyDown $keyDown. And then let's see what we set this to print-- OK. Rebuild. I'm going to get it out of idle now or out of onLoad, update, whatever the thing's called. It's really spamming us. OK. W. KeyDown was false. But it didn't set the-- false just didn't do anything. So we saw keyDown print to false. So we got back in the method. Oh, key pressed might not contain W anymore, because it's not pressed. OK, cheeky. So if event.logicalKey-- and isn't contains. So this would just be like equals? I don't know. There we go. Yeah. Well, I guess the key isn't pressed anymore on a keyUp event, now is it? Event.logicalKey equals-- and we'll get rid of these things. Cursor, will you move? OK. Reload. Pressing W, releasing W. Much better. Yeah, indeed. So-- hmm. Hmm, hmm, hmm. We at least got that. So this is good. We have the paddles moving. It's now time to add the ball. And another thing we can do-- so this paddle component that I added-- it's a RectangleComponent, basically with an opinionated constructor and then some tiny-- a tiny bit of logic here. So this logic could have lived on the Game class. And slideUpOrDown could have been on the Game class. And then I could have just used the raw RectangleComponent in onLoad. Like, leftPaddle could be a RectangleComponent. And this same invocation would work, although I'd have to add the center or the anchor. Anyway, I don't think that's actually better. I just want to point out, you don't have to subclass these components if they're very, very simple. You can just use them directly. But the bigger your game is, then you're just going to get more and more and more code in your actual game class, which for us right now is called MyGame. And of course, in a real situation you wouldn't have this be all in one file. So it probably is better ultimately to subclass them, which we'll do again for the ball, which will extend probably-- I bet there's a CircleComponent. Right on, bro. And extends. OK, let's make its constructor. Actually, I don't even think we need to type that. But Ball. And let's look at your-- OK. We're just taking a radius. That of course makes sense. A position-- great. And we do want to duplicate our ankle-- [LAUGHS] anchor situation. So this ball is going to take the same-- well, its super.radius and position. So radius here. And then we're going to-- I'm going to copy this same super thing for the anchor because we want the measurement-- so what the anchor is, by the way, it means when you define the position, where within the component are you even talking about? You could position things in the upperLeft, or whatever they're called-- topLeft. So I'm going to minimize the other game. And I'm going to move you to the side. And I'm going to move my game here and close the terminal. OK. So when I save topLeft, we see the paddles move because this spot right here, where my cursor is, that's still 5% off the left, and in the middle top down. But what goes in that spot? Well, we've decided now it's the top left of the component. That makes the math harder, in my opinion. So I like center for sure. And this means that same coordinate is where we're talking about. But like, what part of the component do we pin to that position? Well, it's the center of the component. So that's what we're doing there. Now back in the Ball, it is also going to need to move a lot. So we'll-- oh, I think movement for the Ball, it'll be way more fun to have a vector. And speed, I think, can also be variable. If you watch the old "Pong" game, the ball moves pretty erratically. And every bounce, its speed changes. So let's play with this. Let's have a Vector2 here of movement. The ball will always be moving. But it'll also have a double that's speed. And let's start it at 100, but it's going to change. What are you upset about? Movement must be initialized. Indeed. So you'll start with a-- you'll just be late because we're going to initialize that somewhere else. OK. So the Ball is now ready to go. And we have to add the Ball. So in onLoad, let's make a new ball equals Ball. And we'll have to set-- we'll need this here. So now our game has a ball, and we've added it. And it needs a radius of, I don't know, how about 20 pixels? And-- what was our other thing?-- a position of-- I'm going to copy this because it's going to look real similar. We're going right in the middle of the entire screen. So now when we return to the game-- initialization code doesn't hot reload, so I'll run a hot restart. And I didn't call add. It might have not needed the hot restart. There's our ball. That's way too big. That's really cartoonish, isn't it? OK. 10-- I think this is about right. This is starting to look like "Pong," especially when we see this. I'm pressing the arrow keys here. The ball just needs to actually move. Oh, I had a-- Oh, no. I have some problem with the right paddle. Hands off the keyboard, and it's still moving. So I'm going to fix that real quick. I thought I made that change everywhere. No, I totally didn't. I only made it for the other key for the left paddle. So I'm going to copy that change. OK, now the right paddle should work. OK. I'm also going to get rid of these print statements. And the ball needs to move. So we have to initialize it with a first speed. So let's-- no, we gave it a first speed. It was 100. We need to give it a random vector. So let's say for our movement-- I think we're going to go between values of 0 and 1 for each x and y. So ball.movement is going to be a new randomVec2. And let's add this method real quick-- Vector2 randomVec2. And you are going to return-- we'll need a random object that we can use here. So final Random rnd equals Random. And we have to import the math library for that. That's true in every language. I don't know why. We need this all the time. Can it just be imported for us? So this is a Vector2 with an x-value of rnd.-- you know what, if we write nextDouble, I think that gives us a value between 0 and 1. 0 on 1 exclusive. Perfect-- rnd.nextDouble. So we can call this every time-- no, actually, we're really going to only call it the first time. Oh, I have an idea. So sometimes when the ball bounces, we know-- we know some of its changed. If it bounces off the left, then it had a positive-- sorry, I'm emoting everything wrong here. But just picture for yourself the ball is moving left to right. So it has a positive x movement value. We know when it hits the right side, it's going to-- it must now have a negative x-value. And we also know if it's kind of heading up and to the right, we want it to keep going up. Like, that certainly makes sense. So maybe only the speed should be random. But I think in "Pong," actually the new direction of the ball may depend on where it hits the paddle. So we'll get to that when we get to some collisions. But for now, this is going to give us some random movement to start. So if I restart and go back to the game, the ball should just do nothing because I have to honor this in the update method. So let's do this now. We know the ball is always moving. There is no check for whether or not it's idle. So we can say ball.position plus-equals a Vector2. And we have to remember a count for the delta time. So we can say ball.movement-- that's not how you spell "movement"-- .x times the delta time. And ball.movement.y times the delta time. I have no idea if this will be too fast or too slow. It's still not moving. It has a speed. Oh, we didn't count-- we didn't include the speed at all. So also times speed. That's ball times speed. Oh, no, this was an instance variable because it's going to change its speed. ball times speed. This might be so cartoonishly fast, it might be just completely absurd. No, it's cartoonishly slow. All right. So the speed-- but it got its random vector, and it started moving. And so now we can play with this speed. And it definitely seems to slow, so I'll just set it to 500. And I'm going to return to the UI if I can ever do it. It's too late. It already left the screen. So I'll save again, return to the UI, and it's quickly off the screen. Nice. All right. "Paddles in 'Pong' just barely showing on the screen." Should this all be bigger? Should I do this? So we said in the beginning I was not going to account for the window resizing. But I will make things bigger and then just rerun everything. So let's say you'll be 20 pixels wide and 100 pixels tall. And same same over here. And the ball, you can go back to being 20 now. So if we refresh, get a full one. All right. So this is a lot clearer, and then I'll move it to I'm not in the way and also chat is not in the way. All right, hopefully this helps. All right. The ball needs to bounce off the top and bottom walls. That's important. So in the ball movement, we can say-- let's check here. If ball.position-- no, I need to be in the update method. So if ball.-- oh, maybe we-- what if we clamp the x and the y at our maximum possible values and then also change its vector, its movement vector if it's at a boundary? Yeah, I think this is what we want. So I'm going to wrap all of this. So now coming out of the parentheses, we should have a double here. So we can call clamp. And the boundary we're going to give it is 0 is the minimum. And size.-- this is the x thing, so it's size.x. So this will prevent the ball from leaving the screen. And if I call the same thing here and give this 0 and size.y, we'll clamp it in the y directory as well. So now if we hot restart, the ball didn't-- wow, that didn't work at all. Oh, because I said plus-equals, plus-equals. So clamping, what we-- plus-equals is just very silly. So it'll be ball-- now we say ball.position equals a new Vector2 of ball.position.x.clamp 0, and size.x. And then ball.position.y.clamp zero and size.y. Now maybe it'll work. I thought it would work last time, so I'm not going to be cocky. OK. That's pretty good. But it also-- we see the center is what is clamped, and we want the end of the ball clamped. So I have to subtract from this the-- oh, I think it's the clamp. So I'm going to add to the bottom the ball.radius. Yeah, it's just the radius. So this means when it's going all the way to the left or the top, the center of the ball cannot come closer than the radius to the end. And then for the maximum, we subtract the radius, so ball.radius. And we'll do the same thing here-- plus ball.radius and minus ball.radius. This could be better. Hey, look at that. Cool. It needs to bounce very badly. So we can now say-- let's calculate what these boundaries are. So this is going to be the final min-- this isn't the minX equals this and this is the maxX equals that. So clamp between minX and maxX, which we need to do so that we can detect if the value is exactly that and reflect the movement of the ball if it is. So final maxY-- and this is minY and maxY. Great. Now we can say, if the ball.position.x equals minX or ball.position.y equals minY-- that's not what I wanted-- x equals maxX. So here we see that on the x-coordinate, the x-axis, the ball has just hit a boundary. This will actually indicate scoring in the end. I don't know if we're going have time to wire up a scoreboard and whatnot. So for now, we'll just bounce the ball. So this will mean that the ball's movement variable is going to become a new Vector2 starting from the old, we'll say, ball.x. And it's just minus now, right? Minus ball.x and still ball.y. No, that's wrong. Is that wrong? If the ball is going up, look at this, and hits the right wall, then it keeps its y movement. It keeps going up-- yeah. No, that's right. It keeps its y movement and it reflects its x movement. Yeah. Of course this is right. And I'll do the same for y. Why, you ask. Why do we have to do the same for y? Merely so I can make this terrible button. So when we hit y, we keep our x, and we get minus y. All right. There's a chance this will work. I see some ideas in the code-- or in chat to shorten this up. Whoa, super duper broken. Movement-- OK. So what happened was it bounced down to this corner, and then it teleported to the top left, and now it's staying there. Not what I intended to create. So yeah, we didn't-- wait, what is ball x? This is-- of course. This needs to be .movement for all of these. So I'll select both periods and add a movement. All right. That should help. OK. We're bouncing. This is kind of recognizable as "Pong." What if we had some slight randomness around the way it bounces? So the direction that it-- no, this should only happen when hits the paddle. This part should be predictable. And then when it hits the paddle, that's when it'll bounce a little randomly. So first let's get collision detection. Collision detection is one of Flame's best attributes, one of its best utilities. But I don't remember how to do it. I think I minimized the code. Do you have any collision detection, good sir? You has collision detection. It answers the question. Like, do you have collision detection? And it says, this has collision detection. So that's the name of the mixin that we're going to add. And I am going to put this on individual components. So this would be with HasCollisionDetection. And HasCollisionDetection allows us to add a method. Whoa, do they not implement the method? OK. I wasn't expecting that. Click through to HasCollisionDetection and-- I guess the method is collisionDetection. But it has-- no, it's not collisionDetection. I don't know. I'm just going to google this. Flutter flame collision detection. Someone just tell me what to do. Where is a Stack Overflow question that tells me exactly what to do? onCollisionStart? Ho, ho. Wait, no. This is the question. onCollisionEnd-- No one answered this question. What are they asking? Blah, blah, blah. Oh, hey, look. This is really similar to what we have. How do I implement-- each approach that I tried in the end-- but this approach is incorrect and won't work in many cases. All right, whatever. Probably they have the correct signature. So we were adding stuff in Paddle. So let's add this and add our closing stuff. This method doesn't override an inherited method. OK, that's not good. onCollision? Are you a thing? Are you my mommy? What did that return? It was a void. onCollision-- hmm. Oh, wait a minute. Wait a minute. I don't think it HasCollisionDetection. I think it's CollisionCallbacks. It's CollisionCallbacks, which we can get-- OK. So added the import, CollisionCallbacks. This one is going to work. Void-- oh, now do you-- no. Void onCollisionStart. I think this will go back to working. So I'm going to return to the Stack Overflow question and copy it again. And we need a closing curly. There we go. So unnecessary override? What? Oh, it's unnecessary because I'm not doing anything other than calling super. Of course. OK. So when we get a collision, we also, by the way, have to add with-- or maybe we say HasCollisionDetection and we can just put on the ball. Let's try that-- with HasCollisionDetection. So CollisionCallbacks-- now I think they should bounce into each other. So let's just print other. And we'll see what we get. They may start noticing each other. Oh, we'll need our debug console and our UI. No, they're not printing anything. Hmm. Hmm. Oh, I think we need-- we might need HasCollisionDetection on the game. And then this maybe-- well, I think we can get rid of HasCollisionDetection on the ball. The whole speed coding this and trying to read documentation as fast as I can is-- well, that's an interesting vector that we got. We probably should clamp the-- This won't be a fun game. All right. Our random vector idea was a poor one, I think. Let's see. Do we-- no, they're still not noticing each other. Hmm. Collision detection. OK. So hitboxes-- oh, we have to add hitboxes to the components. That's right. Oh, my gosh. I totally forgot about this. So what's the simplest way to add a hitbox? That's right. That's right. That's right. So in the constructor-- no, in onLoad of each component-- so onLoad. And I think this returns void, and it's an override situation, and we'll call super.onLoad. And in onLoad, we have to add a hitbox. So we can set the variable to be-- what do we-- hitbox? oh we addHitbox. We can set it to whatever. OK. So we can just call add. And let's pass in a hitbox-- CircleHitbox, Polygon-- is there a RectangleHitbox? Relative? What do you do? With this constructor, you define in relation to the parentSize. For example, having a relation of 0.8, 0.5 would create a rectangle that fills 80% of the width. I love that. That's wonderful. And we may have to give it an anchor? I don't know. So the relation is just going to be 1. Wait, no, 1? Relative-- oh, it's a Vector2 of the relation. I love how they just use Vector2's for everything. 1, 1. And the parentSize is size. OK. That could be it. Now, in the Ball, you need an onLoad method. You get an onLoad method, and you get an onLoad method. super.onLoad. What's the issue here? Why does ball not think it? Future void? I don't think I-- did I really type that before? Did I type future void? No, I didn't do that here. What's the problem? Why would the CircleComponent's onLoad be different? Wow, it really is different. I stand corrected. All right. So this is going to probably be a CircleHitbox. And you need a radius. So the radius will be-- I wonder if you have a relative constructor as well. Relative-- is this real? Can you click through? Is "CircleHitBox" not what it's called? CircleHitbox. What's going on here? Did I type something wrong? Got relative again. Great. Relation. What is the relation parameter? It might not be a vector. Yeah, it's a double because all we have is the radius. So we want 1.0, again, here, but not relative. And what's the position? OK. What position would be our position? There's still a problem. The parameter parentSize is required. parentSize is size. All right. This might work. That was a good vector. Still didn't collide, though, potentially because Ball also needs with HasCollisionDetection-- wait, no, with CollisionCallbacks. And then let's just try to print this on both, see what we get. So this is going to be in the ball. We see the other parameter. And in the paddle, we'll also see the other parameter. Oh, almost. Instance of ball-- that wasn't when I thought it was going to collide. Oh, gosh, moving too small. And yeah, it's not printing again. No, it's still not doing collision detection. What am I doing wrong? So I think, yeah, we need HasCollisionDetection on the game, which we have. Is there any construction that we need for HasCollisionDetection on the game? I don't think so. Collidable-- PositionComponent, HasHitboxes, Collidable. No, there's easier ways than this. We can just use like HasCollidables. If you want to use collision in your game, and you have to add HasCollidables mixin to your game. What about HasCollisionDetection? Oh, boy, I hope I don't have an old version of Flame in my memory. I'm not seeing this at all. And yet HasCollisionDetection is real. Keeps track of all the ShapeHitboxes in the component's tree and initiates collision detection every tick. That sounds like exactly what I want and what I'm doing. I have the RectangleHitbox and the other one-- circle. Hitboxes are only part of the collision detection performed-- are only part of the collision detection performed by its closest parent with the HasCollisionDetection mixin. So that would be the game. If there are multiple nested classes-- oh, I see. OK. So I don't have multiple nested. So that's fine. So that seems fine. Yet, they're seeing each other very randomly but not really. What might I do? There's a different invocation that we need, I think. And I don't know what it is. Ball onLoad-- wait. Ball class. I think this is just wrong. This is not what I want. And CircleHitbox-- let me look at chat and see-- oh, I'm sorry. This is a speed challenge. I'm speed coding pong, but I'm doing it poorly. Yes, as Randal said, speed coding. I use OBS. And the middle thing is StreamYard, the website. StreamYard is what allows me to do this. But OBS is I'm composing everything on my end. All right. I just want a simple tutorial here. Oh, you know what I'm going to do? I'm going to watch the Widget of the Week on Flame, because I think it mentions collision detection. Widget of the week flame. Here we go. Who wants to learn together? I think Khan made this video, which is how you know it'll be good. I almost did. I said almost. Still check it out. I want those. That's the one. That's it. OK. How do I pick collision detection? Yep, did that. OK. Yeah. I know those. Pretty familiar. Yeah. Kendi. Hi, Kendi. Those of you who don't know, Kendi is the founder of Flutter Kenya, the Google Developer group, and had a great tip on the new Flutter tips series in YouTube Shorts. How does this work? Let's get to the collision detection. OK, there's-- is it in here? I thought it was in here. OK. This is good. Oh, I forgot about HasGameRef. Forgot about that. Oh, here we go. Whoa. And you're good to go? That's all you have to do? OK, this is our guide. So there-- [CLEARS THROAT] excuse me. I apologize. They've got a hitbox variable here. Whoops, that's not what I wanted. No, go back. Stop it. Stop it. So there's a hitbox variable. I tried to double click it like it was going to highlight the line of code. That's how smart I am. So hitbox variable. I'd be surprised if this was required, but it could be. I would think just add is enough. And then onCollisionStart-- is that where we're overriding? onCollisionStart. And we got the intersection points and the other position component. And then we're not getting-- we're not even printing in here. So we certainly can't be discriminating yet. We'll just print the ball. I don't know. Maybe we do have to save ShapeHitbox. I really don't know. But the speed of this coding challenge has slowed to a halt. So ShapeHitbox will be called hitbox. I'll be pretty surprised. If ShapeHitbox-- oh, guess what they're not doing here-- telling us how to instantiate the hitbox. They're just adding it. So we're going to add the hitbox. It's also going to be late so the constructor is happy with us. Hitbox has not been initialized. I know. I didn't mean to save the file. Are we in the ball? Is that where I'm doing this? No-- yes, OK. So we need to initialize the hitbox. Hitbox equals Hitbox-- no, CircleHitbox. What are the parameters on this? Relative, relative, let us down-- shouldFillParent equals true. It feels like a yes. Let's try setting shouldFillParent equal true. So back in-- where's my file? OK, main. Will you leave me alone? Ball. Here we go. So shouldFillParent, activeCollisions, hitboxParent, isColliding, renderShape, isSolid. Bunch of random stuff here-- scale, topLeft, children, isLoading, isMounting, isRemoved, parent, more methods. All right. I'm just saying shouldFillParent equals true. And what's your warning? Was that the default? Oh, no, it is at least false. Great. I swear. I really want you to stop barking at me about that. I'm making changes. So shouldFillParent. What's here? Can only be used within instance methods, which is literally what I'm doing. I guess we'll do this, and then hitbox. What are you talking about? Should only be used within a member of subclass of shape_hitbox. Circle it is a shape hitbox, shouldFillParent. Oh, equals radius equals null and position equals null, which I'm not passing those in. So I guess we just don't have to set it. I wonder if it's just going to work if we do, and I quote, "literally nothing." So let's add another ShapeHitbox here called hitbox. Oh, you know what? I'm going to test-- no, it should be the same. I was going to do one one way and one the other way in terms of setting the attribute or just wiring it immediately into add. But I'm going to do them the same way in case they have to be the same. Or if one was broken, I wouldn't know which one it was. So hitbox equals a RectangleHitbox. And I'm going to pass nothing because it seems like it's willing to kind of figure stuff out on its own. Maybe this is it. So non-nullable field. Yeah, yeah, OK. So you'll be late, much like me reaching the completion of this game. Oh. Hey, here we go. Wolfenrain, a veritable Flame super expert, says, "you have to add a hitbox to the component and then make sure it's relative to parent sizing." OK. Is that what it did by default here-- shouldFillParent equals size equals null and position equals null. So I'm going to assume that this means if I do nothing, it will be the-- it will be relative to the parent size. And I'm going to close a bunch of things and open main again-- had too much open-- rebuild. OK. Oh, not that vector. OK. Did it print? We got something. That seems like a good start. I moved this partially behind myself again. OK. So we can get this down while they're really slow now. They intersect. Let's go. All right. I could kiss you. You can add them, but they have to be relative. So I was using the relative constructor and screwing it up somehow, probably. OK. All right. So I think what you said-- I think what you meant when you said you can-- oh, just add them. You did say it. OK, yeah. So we didn't have to do this. We didn't have to save it in a variable. I basically could have just done this, which I will. So we're going to add a RectangleHitbox. And then in down the Ball class, I'm going to get rid of you. And I'm going to simply add a CircleHitbox. Wonderful. OK. Now in the collision, we need to know-- well, to start, we're just going to-- we're going to turn the-- (SINGING) turn around every time the ball hits the paddle. It turns around. So the other-- so we'll say, if other is a ball, then we can call on it-- ball.-- let's add a method to do this, which will just be like reverseX. And didn't we have-- oh, no it was on the paddle we had slideUpOrDown. So we don't have a method here yet. So reverseX will be void reverseX. And the parameter that we need to move here is-- or to adjust is movement. So you know what, that method name doesn't look great. We'll call it flipX. So this is now going to be movement equals the new Vector2 with the x-value being the opposite, so movement.x but minus and the same y-- movement.y. And now we call flipX if the ball-- oh, it's other, of course-- other flipX. Did I do something? OK. I've really got to speed all these things up after we made the window bigger. Oh, we just couldn't catch up to it. OK. Where are you going to hit? You're going to hit kind of high-- really high. Made it look hard, folks, but we're playing "Pong." So now we need to move the speeds up. The ball speed has to at least double. Let's triple it. And the paddle speed, I think we also need to triple. Paddle speed, where are you? Constant speed, here we go, 300. Rebuild. OK, That's way too-- [LAUGHS] This is a little chaotic. So the ball needs to slow down, but the paddles could honestly still go faster. Yeah. This is just cartoonishly silly. So paddles are going to go to 500. And I tripled the ball speed. Let's change that to just like a doubling. So we'll make this 1,000. OK, this is good. This is pretty good. I'm playing "Pong" with myself like a genius. Next, I think we want to-- let's make the ball go a little faster every bounce. That'll kind of ratchet up the tension. And we can also eliminate those really silly starting vectors, which are basically the straight up-down. So I think we want a minimum value on our starting x. Yep. That's what we want. So the ball gets a random vector. It's only going to be so random now because this x-value, we are going to clamp between 0.2, maybe-- I don't know-- and 1. OK. I think we already fixed the ball starting and just going straight up and down. So then the other thing was speeding up the ball every bounce. And I didn't make the speed static const, because in "Pong," the ball speed changes. So on flipX, we'll have another-- well, it'll just be another method. It'll be increaseSpeed. And here, we'll say speed equals speed times 1.05, something like that. And then we have to call that. So other.increaseSpeed. OK. Rebuild. Let's see how it goes. Oh, wow. It's straight left-right. So I do want some variability in the y. I think just reversing the y-- Oh, yeah. No, I kept saying it was going to be based on where it hits the paddle. And then I keep forgetting and I say it again. And then I forget it and I say it again. So y speed-- well, speed is just flat. We don't have-- so that's the vector, Randal, you're thinking of there, I think. [LAUGHS] We ain't got no time for comments in this here speed coding show. All right. I want to have the ball bounce more interestingly based on where it hits the paddle. But Randal, you were talking about like an x-speed and a y-speed. So we could have-- well, the x-speed and the y-speed, that concept comes from the vector having different values for x and y, I believe. But now let's flipX. And let's have an-- these methods like aren't really making any sense anymore. This is not clean or good or anything at all. But we're going to now set a new y-- newYMovement or something. I don't know. And that method is going to say newYMovement. And it's going to get a double y. And the new y movement is just going to-- so we'll keep our x, and we get a new y. All right. Now let's think about what this y should be. What are we going to pass into this method? So newYMovement should be, I think, dependent on where it hits the paddle. So what if hitting really high and low in the paddle would imply a more radical y-direction of the bounce? So what if you hit like the very bottom of the paddle, you get the maximum change to your velocity? "Real 'Pong' can english as much as 80 degrees." Yeah, english is going to be a challenge to put on this. Does that depend on the movement of the paddle as well? That'd be kind of cool. I guess it does if it's actually english, which, for anyone who doesn't play much ping-pong, if you hit-- or tennis has the same thing. If you hit the ball-- you don't just let the ball bounce off your paddle, but your paddle is moving down. So you put spin on the ball, then it starts to bounce in harder-to-predict ways. And you can really make life complicated for your opponent. OK. So it's not actually that, just return angle. Fair, totally fair. We've got another idea here. "Maybe like negative 45 to 45 degree based on where--" yeah. No, I don't think 45 would be too steep. I think that's a fine idea. All right. This will be the last thing that we try to do, and then we'll call "Pong" coded, but for scoring and resetting and all those shenanigans. So the new y-- first of all, we'll kind of need to figure out where did we hit-- where did we even collide. So let's figure out what this intersection point's variable looks like. So print the intersection points, and comment you out, and let's look at what we get. So intersection points is 2-- oh, it's interesting. It looks like it's two Vector2's. Every time, it's two Vector2's. That's probably the-- they're going to overlap slightly. They're not perfectly overlapping, right? If here's our paddle and here comes the ball, it's not going to always detect the second the first pixels touch, because it's moved something based on delta t, so they overlap some. So I think we're getting two vectors to represent that reality, that there's a little bit of overlap. So the numbers look generally pretty close to each other. I mean, sometimes they're up to 15 pixels apart. But sometimes they're very little apart. So this will actually be random. We won't really be able to predict. Sometimes, it will be a very, very, very close clip. And sometimes, they'll be kind of the maximum distance into each other if they just missed colliding in the previous frame, and then they kind of move their full amount, and now we've got a pretty hefty overlap. So I'm not going to worry about that too much. I'll take the first value off of the data set. And what is this data set? Is it a set? It is a set of Vector2's. So we're going to take the first one-- can you even take the first off a set? Is order a thing on sets? intersectionPoints.first. All right. It's going to give me one of them. So this is what we're going to count as the intersection. Now this has x and y. So we need to figure out the x-- or sorry, the y. Everything we care about right now is the y. So we need to figure out the y relative to us. So first of all, our center point, I'll just put a getter on here to return the center point. So this is double centerY-- And this will return-- no, this isn't important. We don't need to do that. So our centerY will just be a variable that we store ourselves. centerY equals-- oh, no, there was actually something worth putting in a getter here. But whatever. We'll have the raw code. So this is our centerY is size.y minus-- size, remember, goes back to the game. No, it doesn't. We have to add add game ref to this. WithGameRef? What was it? I just saw it earlier. With HasGameRef. HasGameRef. And that's MyGame, I think we still called it. OK. Now how does it work? gameRef is the variable that comes available to us. OK. So this was gameRef.y-- so that's the entire height of the screen-- minus our position. Minus position.y So that's the center in the entire universe of the game of us, OK? Now I think we can find out our distance from center. Final-- we'll call it the collisionDistanceFromCenter equals intersection.y minus the centerY value. So this is how far above or below the center of the paddle the ball collided. And with this value, we can make up an algorithm for how that will impact the y-- the new y movement of the ball. We had an idea. Oh, wait. There's a thing. "HasGameReference, Ref is deprecated." Reference, which needs a new-- oh, experimental. Whoa, cutting edge. Cutting edge. Ken. If this-- that's a good idea. I'm not going to be able to do it, because we're running out of time. But you suggested we write a test. And this would be some excellent code to test. You are absolutely right. Oh, no. It's not gameRef anymore? Hmm. Hmm, hmm. Game-- it's just called game now. OK. Game. So how do we think this should go? collisionDistanceFromCenter is going to range between half of our-- I think we should normalize this between 1 and negative 1. That'll kind of help the math. So the maximum value it could be is 1/2 of our height. The minimum value it could be is negative 1/2 of our height. So what if we take, yeah, the percentage of that. So let's say final normalizedDistance. I'm just going to keep this variable name a little shorter. So I want to map this on to negative 1 do 1 from min value is negative 50% of height, max value is 50% of height. So we're somewhere in there. You know what? The code to normalize this from negative 1 to 1 feels kind of confusing right now. And I think I'm not going to try to do that. Let's instead first just think, how should this work? Someone proposed a 45-degree angle max. And you know what, we've got 50% here. So we could do a 50-degree angle min and max. That seems close, and it's pretty reasonable. So-- oh, you know what? The angle, though, we have to know our x-movement to know-- to actually construct an angle, don't we? We do really need to normalize this. I'm sure there's a tighter way to do this, but I'm going to do it the old-fashioned way. So this will be our normalized y-- double normalizedY. So if our collisionDistanceFromCenter is greater than 0, then we know it's somewhere between 0% and 50% of the height of our paddle. So I'm going to say if it's maximized in that, our normalizedY is going to be at 80%. So the percentage-- final percentY equals the collision detection from center over size.y. So now we know the percent above-- and this would actually be the bottom half of the paddle because, again, higher values are lower. So the percent below kind of down into the bottom half of the paddle we hit. So let's say we're at 25-- or 50% is what we got here, and we have to divide this by 2. Even though those parentheses are technically unnecessary, I think they're nice. So now, if we're like 75% of the way down the paddle, we'll be sitting on a value of 50%, because that'll be 50% through the bottom half. And then we have to just decide what do we want to do with 50%? Well, I think we should clamp our values, maybe-- remember, our vector can go anywhere from 0 to 1. But I think we really kind of want to go from 2 to 8-- like, 0.2 to 0.8-- because if we're all the way at 1, then we're just going to go down really, really fast. And if it's 0-- Or maybe we go 0 to 0.8 because it'd be kind of nice if you hit just the dead center then you bounce straight across. I think that'd be nice. So maybe we go-- we'll flatten it into a range of 0 to 0.8. I think this might be reasonable. So to do that, we'll-- what is the right calculation for this? How do we normalize 50% into 80%? Would I multiply the value by 8 over 5? Yes. Right? 8 over 5 times 5-- is that right? I don't know. I really can't figure this out. percentY equals-- what did we just say this was going to be? 80% is our max. So I'm going to multiply this by 8 over 5. And I said this was fine also. That's not going to work anymore. Oh, no. That's fine. Final percentY times 8 over 5 equals our new movement y. Did I make a variable for this? I didn't. I guess normalizedY was what that was going to be. I'm really not sure if this is sensible. If it is 0, by the way, if it equals 0, then we'll just set normalizedY to be 0. And then normalizedY-- oh, equals 0, not comparison. And then if it is less than y, I have no idea if this code is going to work. I really don't. Oh, this does the same thing, doesn't it? So we can kind of just take the absolute value-- no, no, we can't. Because then it would never bounce up. It would always bounce down because we always have a positive value. So let's see here. If it's less than 0, how would this work? Let's say this was negative 50%. Yeah. Well, that's fine. We just say it's either 0 or it's not. So I think we can get rid of you. This will be positive or negative in the new scenario, and that means percentY will be positive or negative, which means normalizedY will be positive or negative. So now we'll say other.-- what was that method that we made? Where are you? On the Ball-- what's the method called? newYMovement. NewYMovement is going to equal normalizedY. What do we think? Is it going to work? Probably not. What are some ideas that people have? Normalized-- good idea. If you normalize, it's very easy to adjust the max degrees later. Yeah. This code would need to be commented like crazy because someone who came through later and looked at that would say, what on Earth are you talking about? How did you do this? First calculation doesn't have to be precise. And I agree. Let's do it. That certainly wasn't precise. OK. Well, that's still too fast. Too fast. So the ball's starting speed-- let's bring this down to 750. That thing's just so fast. Oh, yeah, OK. So-- whoa. [LAUGHS] Oh, wow. OK. Well, we're getting some erratic bouncing. It's not boring anymore, but it's also not predictable at all. Ken, did I satisfy your desire for imprecise calculations? [LAUGHS] All right. [LAUGHS] Just how you draw it up. So I think the problem is that our y is like way too high. It's way too high. So I think if we see what the normalizedY value is, that'll at least help me understand what I did wrong. So refresh. OK. Can you hit the paddle, please? Oh, goodness. Well, I'm not good at "Pong." OK. There we go. So we had a normalizedY of minus 6. Yeah, that's not even close to what I meant. So how did this work? The collisionDistanceFromCenter was-- oh, I never turned this into a percentage. This is of really high value. So I need to divide this by the height, right? Let's say we hit all the way at the very bottom. Let's say it's 100 pixels tall. We hit at the very bottom. We're going to get a value here of 50 because the intersection value might be, let's say, the center of the thing is at 100 pixels so it hits the very bottom. That's 150. So we have 150 is the intersection y. The center is at 100. And so collisionDistanceFromCenter is literally 50. But we need to make that 50%. So we have to divide that by the height of the paddle. Paddle-- we're in the Paddle. So size.y. Now it's 50%. So rebuild. OK. We're getting incredibly small normalizedY's-- also not what we wanted. Yeah, always, no matter really where we're hitting, we're getting really small values for y-- way, way, way too small. OK. So collision distance, this gave us between-- actually, let me count. I'm going to print this and make sure. But I think we're going to be sitting between negative 0.5 and 0.5. Refresh. Let's do this. That small speed is really easy to deal with, isn't it? No, wrong. It's negative 5. intersectionY minus centerY. Don't let anyone know I live like this. Hit the thing. I think I might have hit it. No, I didn't. Here we go. OK. So centerY is really high. intersectionY-- OK. What was our height again? The height is like 300. So those numbers look reasonable. Paddle.height-- no, that's not how that works. Paddle-- what's the height? Here at the bottom. Oh, it's in the add method. Well, the height is 100. Oh, that means I have something wrong, because I was not expecting these values to be that far apart. So I definitely just have something wrong here. centerY and intersectionY, I have one of these things incorrect. "Now it's a double. You should never compare doubles with zero directly without an epsilon." Yeah. So you are correct. So that will basically just not hit, is what you're saying, because the double is almost never going to be actually zero-- like, potentially, literally never be actually zero. Yeah, that's right. The worst case scenario is I would just always go into this. And if my logic is correct here, which it obviously isn't, then let's say this wasn't zero, but it was like 0.0000001. Then once this logic is correct, I hope that I would get a value, a normalizedY, that was super, duper, duper close to zero. So that all suggests that this whole if statement actually wouldn't even matter. That's a good point. Thank you for pointing that out. So what's going on here? centerY-- the last collision was-- centerY keeps-- centerY of what? I wonder if it's the-- no, it wouldn't be like the centerY. It wouldn't be like collision start is the center of one of the things and-- or sorry, the intersection points is like the center of one and the center of the other. I don't think that would make sense. Because they wouldn't be this far apart either. They can't be this far apart and collide. So what's going on in our UI? Do we keep getting these high values? Which one are you going to hit next? You're going to hit there. So you have a centerY. Where did I get that from? centerY was the-- so this is the y-position of this paddle, is how I intended this to work. So this is the height of the game minus my position in the y-axis. And that was 492. That left paddle is not 4-- maybe. Huh, maybe it's 492 pixels from the top. It could be. OK. And then the intersectionY, that was the first intersection point. And it thought the intersection was 163. "centerY seems to be the center of the paddle relative to the screen." centerY-- yes. Yeah, that's what I'm trying to calculate. Yes, right here. Because we're in the paddle. So position is the paddle's position, and the size here is the entire screen. So definitely correct. And then intersectionY-- I guess I really just don't know what this variable means. Are there docs that will tell us? That's not what I wanted. Jump to the definition. Why are you doing this? This isn't what I want. Oh, I have to click on the super, then it'll go in. And we don't have any. We don't have a docstring here. So the intersection points are-- intersection point is not here. So Wolfenrain, if you are still here, one question that I would have for you is, am I doing hit detection even the way that Flame intends for me to do it? Or is this-- older than 1.0. Wait, where's the latest? I've been looking at the wrong documentation the whole time. OK, where's collision detection? Other modules? No. How did this work? Where was I? What are we in here? What were we in? Collision detection was in Basic Concepts. In the new one, we've got-- oh, here we go. Collision detection. This looks more familiar. CollisionCallbacks. Well, that was confusing. I got an old version of the docs. Thanks, Google Search. All right. So let's look in here for intersection points. And something is going to tell me what they are in my dreams. So yeah, Wolfenrain, if you're still here, I would love to find out, am I doing this right or not? No, sorry, that was my last question. What are intersection points? I now know I'm doing it right because I'm not looking at old documentation. But I think maybe I'll just search "intersection." That appears more times. Ray2-- That's interesting. That's super interesting. Cast ray-- that's kind of cool-- collisionDetection. But I'm not doing ray casting. intersectionPoint distanceTo-- these docs are cool, especially with the visualizations here. Nothing's actually happening in this one. Oh. Well, I clicked it and now stuff is happening. All right. Well, I don't really know what's going on here other than it looks awesome. OK, more intersection. Forge2D migration from the collision detection in v1.0. Well, OK. Hitbox. Huh, huh. All right. So I just don't know what an intersection is. But we're coming up on 2 hours here, so I think this will be the time to call it. I got decently far. Don't know what the intersection points are, and that is making my code to add a little bit of flair to how the ball bounces really radically misbehave. Here, got a couple more. Aw, Ken. That's just so kind. "One of the most enjoyable things to watch on YouTube." Well, thank you. That's very kind of you to say. Luis, you say "We can debug this better if you place the ball in the middle of the screen on the paddle side and the paddle at the top. Move the paddle until it collides--" Oh, that's really a good idea. Oh, man, I'm out of time. That's such a good idea, though. Why didn't I do that 20 minutes ago? So the ball is going to stop moving. How fast can I do this? So in update, the ball is not going to move. So I'm going to comment out all this code. The ball position should have been its own method called moveBall. So the ball now no longer moves. And in onLoad, the ball's new position is going to be real similar to this position. But we're going to add a couple values. So the x-value-- the y-value should be fine. It's the x-value where we're going to need to add half of the paddle. So that's just 10. And then also the radius of us, and that's another 10. So we'll just add 20. And if we refresh, we see they are clipping not as perfectly as I thought they were going to. Why is that happening? I seem to need to add another 10, because the ball looks like it's right in the middle of that. So I'm not really sure why I wasn't expecting that, but there we go. OK, OK, OK. So now, debug console, what are you printing? I'm going to move you over here and move you over here. And so they are colliding. OK. I'm just going to tap the button. All right. So our centerY-- this was the center of the thing. The center of the paddles is almost 700 pixels. That seems fine. intersectionY, 350. I just don't know what this means. I just do not know what this means. Another coding challenge would be to make a reusable game first, breakout game of the same component-- reusable code. "First make a Pong game then make Breakout with the same components then do a mash-up." Holy smokes. Middle bricks? That actually would be kind of cool. Would take a while, but we could do it. All right. I am going to have to call it. But oh, we've got an open question for the Flame community. What are these intersection points? I'm not finding anything. I'm also covering up what I'm talking about. So that's good of me. What are these intersection points? I can't find anything that explains them, but I want to so that I can decide how to bounce the ball based on where everything is bumping in to each other. But that will have to wrap everything for today, folks. We made a decent "Pong" client that does not resize itself elegantly. But the "Pong" client is fairly decent. I just commented out the movement so the ball is not moving. But you might recognize this as vaguely "Pong" like. And yeah. Well, I'll be back next week. I don't know what I'll be doing yet. Who knows? Maybe if we learn more about Flame, we'll polish up this. Or if there are other things arise or emerge, then we might shift subjects. But for now, that'll be it for today. Thanks for watching, everybody.
Info
Channel: Flutter
Views: 14,096
Rating: undefined out of 5
Keywords:
Id: 3KpOyfxyPes
Channel Id: undefined
Length: 119min 13sec (7153 seconds)
Published: Thu Apr 06 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.