Check out a free preview of the full Rust for TypeScript Developers course
The "Custom Traits" Lesson is part of the full, Rust for TypeScript Developers course featured in this preview video. Here's what you'd learn in this lesson:
ThePrimeagen creates a collision module. The Collidable trait is created and implemented for all collision scenarios. For example, rectangles colliding with rectangles, circles with circles, and rectangles with circles.
Transcript from the "Custom Traits" Lesson
[00:00:00]
>> There's a lot of different ways you can work with this. Just keep those things in mind, it's fun. All right, but still, we're still going the wrong directional right now. That's been intentional. So let's try to do this a little bit better. This still doesn't feel good to me, right.
[00:00:15]
I don't really like what we've programmed. It feels a bit clunky and I have to do it for circle. It's a lot of stuff. So let's make it worse before we make it better, okay? The best way to go about it. So we're gonna create this really, really super flawed version of a collision.
[00:00:29]
Collision is only a collision if the shape contains a point. So in other words, our algorithm would miss this, right? It would miss that because the point is the center right here and the points are right here, right? No one contains each other's points. So this is a super flawed version of this, but that's because doing actual 2D circle to A a, b B access line bounding box requires a lot of math.
[00:01:00]
And I don't really wanna be thrown out hypo newsies at people and all that fun stuff and gets into a lot of fun angle stuff. So instead we're just gonna do super flawed collision detection. All right. This is our super flawed collision detection system for early warnings. All right.
[00:01:16]
So let's create a new file called collisions.rs inside of our shapes folder. And then inside of our mod rs, make sure you publicly exported collisions cuz it's very important to get that collisions out there. So I'm gonna jump back in here. I'm gonna create the collisions.rs and I'm gonna go to mod.rs and do collisions.
[00:01:37]
All right, fantastic. Okay, looks good. Everything's good. We have a collisions file, it's being exported. That means I can import anything in our main file. All right. So now what I'm gonna do is I'm gonna implement a trait called collidable. With t this is where we're gonna maximally write the suckiest code.
[00:01:55]
So just be ready. All right, collidable that's gonna have a function called collide, that takes a reference to self and a reference to t. And then a second function collides, which takes a reference to self and a reference to some list of t, right? So technically, it's just a view, a slice into either a vector or an array.
[00:02:16]
Are you guys ready for the greatest thing ever? I feel like this is the moment we gotta lean in. This is it. We're gonna do a bunch of typing. If you guys feel like you can't keep up, I get it. This is gonna get really annoying for a second, okay?
[00:02:32]
Because it has to get really annoying to really feel how good it can be. And this is the one thing about rust, is that if you do something wrong, it can really become frustrating. We're already kinda going down a route that are sending kinda some signals to me that I don't feel like we're doing the right thing right now.
[00:02:50]
Obviously, we're not doing anything for performance. I'm doing the vector I'm not doing a reference. There's things already wrong. But that's for the purpose of this but even more so it feels like we're kinda getting to this weird duplication heavy specification moments. So let's just quadruple that and then we'll back it off, okay.
[00:03:07]
So let's implement a trait called collideble. It's gonna be generic over t and it's gonna have two methods on it. Collide, which takes a reference to self and a reference to t. And then B, it's gonna have collides, which is gonna take a reference to self and a view into a list of t.
[00:03:25]
So, let's do that now. So I'm gonna like this PERB trait collidable t function collide takes a reference to self and a reference to t and returns a Boolean. All right? Hey, wait a second. What the heck's going on here? Yeah. This would be other, sorry. There we go now here comes first magic moment.
[00:03:53]
Are you ready we're not even using us this but this is super magic. Are you ready I'm even leaning forward okay. This is so cool that I want you to copy the line add a little s, make this into that whole view thing. But we're not gonna leave it here.
[00:04:11]
Okay, normally this is what you'd have for an interface, right? But forget that racket, let's just implement it because we already have collide. We already have a function so that means we can go like this for other. We can like this, yeah, for other and let's call this thing others.
[00:04:29]
In others, self.collide. There we go. Let's go like this if self.collide return true else return false. So first off, what the heck just happened here? How did we just do that? Well, here's the deal. In a trait, if you have methods defined on there, you can use those methods and other ones and you can provide a default implementation.
[00:05:01]
So this makes perfect sense, right? I can only use a singular item on self, which is dot collide. And since it's generic over t, those are the same types. So long as you implement collide, I can implement collides. It's pretty cool, right? Yeah, someone right now in chat is saying c sharp.
[00:05:25]
I don't wanna hear about it, okay? I don't wanna hear about c sharp. This is not c sharp, okay? This is rust, it's cooler. You can't tweet about c sharp, but you can tweet about rust. And that's what's important to developers. All right, so I've been told. All right, so we've done this we've now implemented collide or collidable.
[00:05:41]
Or sorry, we've implemented collides on collidable generic over t. So now let's actually go through our shapes and let's create the whole collides thing. All right, so here we go. So we need to do this right here. Where's my mouse? We the implement contains point for rectangle and circle, okay.
[00:06:00]
Contains points not too hard, right? So let's go over to rectangle and I'm gonna go up to the tip pity top because I like to put my. For whatever reason me personally, I like to put my implementation of rectangle, near rectangle. It just makes me feel, I don't know, warm and fuzzy.
[00:06:15]
I have these unreasonable ways I like to organize things. One of them is I like to put all properties at the top of the class then a constructor, then the public methods, then our private methods, then our static method, then our static private methods. I just have this way that I like doing things for whatever reasons that's why am doing it.
[00:06:33]
So here we go. Here is one of my unreasonable things I do. So let's go like this. Let's implement a public function contains point. This takes a reference to self and it takes a point which is gonna be a float 64. My goodness float 64. And it's gonna return a Boolean.
[00:06:56]
Now remember, we're super good at rust at this point. So you should have used pattern matching. Why didn't you do that? So I wanna go x y. Look at that, look how cool that is, come on, that's a little bit cool, isn't it? Now we have x and y.
[00:07:08]
So how do we know if a point is within an AABB and axis aligned bounding box? Well, it's really not all that hard. You got to like this. If self.x is less than or equal to x and self.x plus self.width is greater than or equal to x. We're within the x-axis, right.
[00:07:30]
So we project ourselves in the x. So now we do a little and over here I'm gonna take that whole line and I'm gonna do the same thing but I'm gonna do it for y. I'm just gonna replace x with y. So if self.y is less than y and self.y plus self.
[00:07:49]
Height, wow that would only work out for squares not rectangles. Is greater than or equal to y. Then our y is also in line so long as that's all true. Our rectangle our AABB contains the point right, fantastic. So let's do the same thing for circle this time.
[00:08:05]
Jump over circle. Let's implement for circle. Let's have the function contains point it's gonna take in a reference to self. It's gonna have a x. Good job destructuring by the way. Proud of you guys. I can tell you guys are learning, feeling smoother and faster at this point.
[00:08:24]
And we're gonna return a Boolean. Exact same thing. How do we do that with this? Well, we're gonna have to pull out the old-fashioned Pythagorean theorem and do theoretically prove it return x times x plus y times y. This has to be within our radius. But we can't just do that, right?
[00:08:42]
Because that's just anywhere within the plane. We have to do the difference between rx and the difference between ry. So let's go like this, let x equals self.x- x and then I'll just do this whole y thing. My goodness, x y, there we go. And now we have the difference is really can be called dx at this point.
[00:09:04]
So I can just rename this thing, dx and rename this, my goodness, I called it dx, dy. There you go. That's probably a little bit nicer. So we get the differences and now, this has to be less than or equal to self.radius times self.radius. All right, does that feel good?
[00:09:26]
Feels good enough, pretty much accurate and then don't forget to make this function public. There we go. We have implemented as much math as we're gonna have to do. Remember, we've made the world's crappiest collision detection software for early warnings. There's a joke in there, it's not early warnings.
[00:09:45]
For a point to be contained, you're already colliding. Significantly in some cases. All right. Anyways, early warnings, that's what's important. All right, so now that we've done that, we need to now implement collidable of generic rectangle for rectangle. Right cuz we need to define what our t is so this is where it kinda gets annoying, right?
[00:10:09]
This is where I think all of our decisions really got terrible. Because now I'm gonna have to go like this, implement collidable rectangle for it's not called a rectangle. We're gonna have to do that and we're gonna have to implement this one, so we're gonna have to go through.
[00:10:27]
And does rectangle collide with another rectangle? Well, we know that it's colliding if one of its points are contained within us, right? So we'll have to do this. For a point in a rectangle let's see, other. Cuz remember, we have this iterator now so we can iterate over the points of the rectangle.
[00:10:50]
And we go like this. If point, or if self.contains point, contains this point, then return true, else, return false. Now, there's plenty of things we're doing wrong as far as math goes. Again, it's not the point. We're trying to drive a different point here. So we've now defined how we can collide from a rectangle to a rectangle.
[00:11:15]
[SOUND] Let's do a circle now. I'm gonna do that. I'm gonna go over here and change the collidable from rectangle to circle. Now I'm gonna have to import circle. And I'm gonna have to change that to circle. And then we're probably we don't have an iterator made for circle.
[00:11:35]
So we're gonna have to probably do something like return self.contains point, other.x, other.y and make sure that that's like a point, right? Does this make sense, right? We're just like, do you contain the circle's middle point? Man, this is a lot of specifying? Well, don't worry, we're only halfway through specifying isn't that exciting?
[00:12:02]
We have so much more specification we can do. I'll give you guys a moment to catch up.
>> It should be collidable for circle pardon?
>> Yeah, collidable of generic. So the generic is circle for rectangle.
>> For rectangle, okay.
>> So that means rectangle can now tell if another rectangle is hitting it or if another circle's hitting it.
[00:12:25]
But the other way isn't true, a circle cannot tell if anything's hitting it yet cuz we haven't implemented it yet. I'm gonna highlight this. I'm gonna highlight that. And we're gonna take a little yanking right here. And we're gonna need to go create this for our circle. So you may wanna copy and paste that because it's gonna be probably semi similar code here.
[00:12:48]
Let's go to circle and let's do the same thing. So I'm gonna paste in this. So collidable rectangle for circle because remember, we're the circle. So we're gonna implement this, all right. We have to import rectangle. Well, don't you feel gross having a circular import rectangle relies on circles circle relies on rectangle.
[00:13:13]
Yeah, you should be having all of your best programming skills telling you that what we're creating is terrible. This is good. You're doing the right thing here. So there we go. We've done that. Disgusting, right? All right, well let's do it again. Circle for circle. And there we go.
[00:13:28]
We now have it written fantastic. Our circles can intersect other circles. Our rectangles can intersect circles. Circles can intersect rectangles. We have our cross section all defined, right? Yeah, we've done this. Let's go to our main file, now that we've created something super cool. And, let's create, a circle and a rectangle.
[00:13:51]
Actually, let's create two rectangles. I'm gonna call it rectangle1, rectangle2. And I'm gonna create two circles. So we haven't implemented default for circle yet, which is just too bad. This one will be at 0. Zero. And let's see what's what is our radius. What's a good radius one?
[00:14:11]
I like that radius. And circle two, it will be at 1.5. Let's just do that because that seems, you can put whatever you want in here. Honestly, don't just. Don't do what I'm doing here. I'm gonna go like that or do what I'm doing. I don't care what you do.
[00:14:27]
Live free, okay? Don't define your circles based on me. All right? Right, cool. So I have two rectangles, two circles. So if I've programmed everything correctly, I should be able to go rectangle collide with, rectangle two. We gotta do a reference, because we defined it as a reference.
[00:14:49]
Look at that. We did it. It's successful. It's saying, everything's good. So, long as you've programmed this correctly, you should be able to do that. Which means I should also be able to do to circle two. My goodness. What did I call it? I call them by their full name.
[00:15:07]
Look at that, we did it. Does this look good? All right, ultimate final challenge. Can the circle collide with the rectangle? We've done it. Man how's that feel everybody? Pretty horrible? All right. Well, don't worry, we're gonna make it better, all right? We're about to show what I consider like one of my just.
[00:15:29]
Sometimes, have you ever been a hammer in search of a nail? When I discover something new it just makes me feel like a hammer in search of a nail. I get so excited on the inside and I just wanna make everything do it. So, now that we've done all that, we now have all this code.
[00:15:47]
Holy cow, way too much code. So, the code is a bit similar. It's a bit ugly. We have circular references. It's repetitive. You can just tell we're doing the wrong thing, but you may not know how to do the right thing. And that's always a problem. So, the good news is, it's not that we have too many traits, we just don't have the right and enough traits.
[00:16:08]
We don't have the right ones or enough of them. So let's. Make some more traits, are you ready for this I'm ready, I'm excited.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops