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.