Placeholder Image

字幕列表 影片播放

  • Hello on.

  • Welcome to programming balls, Part two.

  • And in this video, we're going to be looking at extending the circle versus circle engine we developed in part one to deal with circle versus edges on.

  • What we'll see in this video is that's actually not a big leap.

  • In fact, we're going to come up with a way of treating edges as if there were circles, and I'll also be looking at changing the timing method of the simulation to make it more accurate.

  • So just in case you haven't seen part one, and I really recommend that you do, because I'm not going to cover all of this code again, we ended up with a program that looked like this on.

  • We could pick up a ball and we can see it deals with what are called static collisions.

  • Static resolution is where the ball's push each other out the way so they don't interfere with each other on.

  • We also implemented a Q, where we could give the ball some velocity on, watch them all interact with each other, and this was the dynamic resolution.

  • And so this video is just going to modify the code that we already have developed.

  • And just before we jump straight into the maths, I thought would be interesting to show what we're going to be developing today.

  • And as we can see, we've got balls again interacting on.

  • We've also got the ability to drag and drop these edges, and we can start to see interesting behaviors off the entities as they flow around the screen.

  • I'll just let these wants through from the top.

  • There we go.

  • So we've got balls of different sizes.

  • We've got edges at arbitrary angles on.

  • We've got interactions between all of the above on.

  • We've still got the queue action as well, so I can give the ball some velocity and we can see it bounce across the edges, as it needs to do is try and aim one at here.

  • There we go in.

  • It deflects around the scene in a realistic manner.

  • And of course this has applications in lots of games, mainly public sports games.

  • But I'm sure there's some creative types out there who can come up with all kinds of simulations where this might be useful.

  • So I wanted to start by talking about making the timing of the simulation a little more accurate on in the previous video, we had a ball which had an origin point on its velocity, moved it to a new position on this was over a fixed time step T That's all very well and good, because we know that speed equals distance over time and therefore distance equals speed times time.

  • But there was one problem with this approach.

  • Let's say two circles collide with each other.

  • The way we resolved this was to find the midpoint of the overlap between the collision and then displaced both balls in the opposite direction by the same amount as half of the overlap on.

  • On the surface that worked very well, but it's quite inaccurate simply because this collision must have occurred before the end of this iPAQ of time.

  • So if we momentarily treats our velocity vector as a timeline, we know that actually the collision happened about here, which is not a complete iPAQ of time, and this means we can split the park into two different sections.

  • We've got the before collision and we've got the after collision.

  • And of course, during this after collision we have some sort of dynamic response where the Balkan move a bit more.

  • And so by sticking to a rigid timing framework, as we have done in the first video, the bulls are individually breaking the laws of physics.

  • They're not traveling as far as they need to in the time that we told them to be simulated.

  • Now the second problem involves the length of the iPAQ itself.

  • Now, in the first video, one epoch was governed by F elapsed time.

  • AII the time between successive frames.

  • And so if the frame rate was high, the distance the ball could travel was much shorter.

  • This would lead to, well, more accurate simulation.

  • However, if the frame rate was low, then potentially, the park is considerably larger, and we risk a situation.

  • We're here.

  • We've got one ball after its position has actually gone through the object that is trying to collide with and so doesn't get registered.

  • And it's not.

  • Ideals have a physics engine where objects can pass through each other.

  • Now there's an easy way on a hard way to deal with this on the easy way is still an approximation, and it simply says that we break up the park into smaller simulation steps, and we choose a simulation step size which we know is suitable to the rest of the game engine that we're implementing.

  • The harder on yet more accurate way is to cast a ray along the velocity vector of the object.

  • But we're checking on DWI perform into section tests with all of the objects into the in the scene to see if they collide with that ray.

  • And of course, there may be several objects for one epoch here.

  • We would solve the objects to find the object which is struck first and then deal with the collision accordingly.

  • There is no doubt that this approach is more complicated and potentially more computational, expensive to aunt.

  • To keep this video in line with the video one on most of this channel, I'm just going to keep it simple.

  • I'm going to break up the park into smaller steps, so we're going to do two things.

  • Firstly, we're going to break up the park on then we're also going to deal with making sure that the object does all of the physics it needs to do within that iPod.

  • Now you we don't cut it short so continuing directly from the code last time, Let's get started could create a variable and interview called M simulation updates, which is how many times you want to subdivide the park.

  • And I'm going to start with the value for So this effectively means that for each frame update, we're going to run the physics simulation four times now.

  • Of course, we don't want to use F elapsed time anymore as the time step for our simulation.

  • We want to use some smaller version off it, so I'm going to create F Sim elapsed time, which is going to be equal to F elapsed time divided by the number of simulation updates.

  • And so each of the physics updates in this case is going to take 1/4 off the F elapsed time, and it really is a case of just taking all of the physics code and sticking it inside a loop.

  • We got static collisions, dynamic collisions and before we get to the drawing code, that's where we end our loop.

  • So we're going to do all of the physics four times over now.

  • The only bit that we need to change inside is we're no longer using F elapsed time.

  • We're going to be using F simulation elapsed time, so we need to change these variables and we'll just have a quick run to see what happens.

  • And hopefully absolutely nothing has changed.

  • Everything is as it was before.

  • Our epoch is no defined as F sim elapsed time, but we need to break this up even further into the time before the collision on after the collision and in fact, in one e pockets possible toe have multiple collisions, all of which take their own duration.

  • Now, given how many interactions each ball may face, it's going to need to keep track of its own time.

  • So I'm going to add to the ball structure a floating point variable f sim time remaining on Before we do any physics updates, I'm going to set the F same time remaining for every ball.

  • So for this frame, we're going to simulate four e pox on beach ball is going to keep track of how much time it's got left in that epoch as it's bouncing around now.

  • Currently, in any park, we update the ball's position.

  • We check for static collisions on we resolve the dynamic collisions, and that's it.

  • So that really means we can only deal with one collision per epoch.

  • Now that we're handling physics on fractions of any park, we need to check for more collisions on resolve them per bowl per epoch.

  • So I'm going to add another interview variable, which is Max Simulation steps on.

  • This really sort of suggests how many times in one epoch can we update the position, resolve the static and resolve the dynamic collisions and so internally to the simulation updates on.

  • After we've sent the E Park time for the ball, I'm going to again wrapped the code up in a four loop that's executes the simulation the correct number of times.

  • I appreciate that right now.

  • This actually might be a little difficult to comprehend, but as we start filling in a bit more code, it should reveal itself.

  • So let's firstly update the ball position routine.

  • Here we go through all of the balls on we update the velocity vector, the acceleration director, and we wrap them around the screen on.

  • We clamp them if they're near zero, but we only want to do any change to the ball's position.

  • If there is any simulation time remaining for this ball on this epoch?

  • So we'll capture all that in an if statement and instead, off Sim elapsed time.

  • Now we only want to update the cinematics on a time step equal to how much time the ball has remaining.

  • So how do we go about working out?

  • How much to reduce the same time remaining variable for the ball?

  • If nothing has happened to the ball on its journey, then same time remaining can be reduced by the time for this epoch, the ball is simply just relocated.

  • Everything's fine.

  • But if there is a static collision of any sort, then the park has been shortened.

  • So we'll look at how much to reduce same time remaining as part of our check for static collisions.

  • Now, as with all of these things, this again is only approximation.

  • But it is a theoretically accurate approximation and so helps his own on the whole with the overall accuracy.

  • Firstly, we need to work out what the intended speed of the object Waas.

  • And this, of course, is just the magnitude off its velocity vector.

  • No, forget speed is a scaler quantity.

  • And if we know the scaler speed of the ball on.

  • We know how much time the ball had to move in.

  • We can work out what the intended distance waas for the ball.

  • It's simply the speed.

  • Times the time.

  • Remaining distance, equal speed times time.

  • Now we know if there's been a collision, it will never have reached the intended distance.

  • And so we want to calculate the actual distance.

  • On the actual distance is where the ball currently is.

  • Now we've just displaced it.

  • We can see appear.

  • We've got some balled up PX and balled up P Y calculations.

  • But what we don't have is a record of where the ball started, so I'm going to add to the ball structure to more variables.

  • Old ex on old wife On in the Kinnah Matic section, where we update the ball's position, I'm just going to record the Were The ball is at the start.

  • I'm going to store those in the old X and Y values before we go and change them, and this makes calculating the actual distance very trivial indeed.

  • Now it is just the magnitude off the vector between the original point on the current point for the ball.

  • So even though it's a long line of code, it's just Pythagoras theorem.

  • Now, if we rearrange, speed equals distance over time to make time the subject.

  • We can say that the actual time taken to cover the actual distance is time equals distance divided by the intended speed, and so that we know that the actual time taken before the collision before the static collision has happened is where we're going to divide our epoch.

  • So the Bulls personal track of how much simulation time remaining it has is just reduced by the actual time before the collision.

  • So let's just double check.

  • Make sure all that works.

  • It should look and feel exactly as it did before.

  • But we can now operate safe in the knowledge that our physics simulation is considerably more accurate than it was before were effectively subdividing the time between frames as necessary.

  • So fur ball has lots of collisions.

  • It gets a lot more attention, as opposed to a ball that has no collisions.

  • It just simply updates his position and gets on with it.

  • So let's have a think about adding edges into the mix.

  • Here.

  • I have an edge, and here I have a bowl moving towards the edge.

  • And, of course, what I want to happen.

  • Is it The bull deflects off the edge of the appropriate angle on, Of course, no surprises here.

  • It's a reflection.

  • But before we get stuck into the physics, let's handsome coat out edges to our program.

  • And in fact, I'm not going to use edges.

  • I'm going to use capsules, and a capsule is like a line segment that has a radius attached to it.

  • So I'll draw in red here on the outer shape off a capsule with some radius about it.

  • And what we'll see is it gives the line some thickness.

  • Obviously, I'm not very good at drawing.

  • That should be central.

  • But when you get to the edges, they become circular.

  • And this is great enough physics engine because we know we can do circle versus circle collisions already, so this radius is consistent around all points.

  • Now I'm going to define my line segment as simply being to coordinates start X and why andan end X and Y on a radius, in which the same way that we manipulate the bull structure I'm going to create a line segment structure.

  • We're going to consist of coordinates for the starting point SX and S y coordinates for the end e x n e y Onda radius.

  • In fact, I'm going to do things very similar.

  • Well, we've got a vector to store the ball objects.

  • I'm going to create a vector to stall the line objects.

  • And I know I'm also going to want to select and manipulate the lines on the screen at some point.

  • So I'm going to steel list from here, change this to a line segment and instead of selected ball, will have selected light in our own user.

  • Create function We were defining the wife frame model of a circle on adding some balls to the simulation will add a line to the simulation, too.

  • So I'm just going to create a variable, which is the line Radius Could will probably want to add several lines at some point.

  • I'm just going to set that to four for now, Aunt, to add lines to the vector.

  • I'm just going to push the initialize a list to the back of the vector because it's a simple structure.

  • We know the order of these already.

  • It's the starting X and starting.

  • Why positions?

  • So I'm just going to pull some numbers out of thin air walls.

  • Put this one at 30 in the X, and we'll put it at 30 down the screen.

  • We wanted to be quite long, so I'm going to set this 1 200 We'll keep it horizontal to start with.

  • Sore set that y 2 30 and then we have the radius.

  • Now let's have the code to draw the lines on the screen.

  • Man, she's a little four loop to iterated through the vector on.

  • I'm going to use some newly added functions to the council game engine.

  • They've been there for all of the videos this year.

  • But Phil circle on.

  • We'll eventually will replace the drawing of the balls from being wife remodels to fill circle, too.

  • I'm going to draw a circle at the starting location with the lines radius.

  • I'm asking it.

  • Instead of shading it in solid colors, it's going to shade it in half, so it'll be a slightly dark gray color in a similar way.

  • I'm going to draw the end of the line using the E X and a y components.

  • Now I want to draw two lines that represent thesis IEDs off the capsule.

  • So let's just consider my line segment for a moment.

  • I know I've just drawn to circles, but the start and end, What I want to do now is draw another line from here to here on a second line from here to here, that's a draw.

  • Lines.

  • I need the coordinates of these points.

  • And to get these points, what I'm going to do is calculate the normal off my line and make sure that I scale the normal to be a unit vector and wanted to Unit Vector.

  • I can simply multiply it by the Radius to give me the offsets from the start and end locations.

  • So first I'm just going to create a normal vector, and then I'm going to calculate the length of it on.

  • Once I've got the length of it, I can normalize the vector components.

  • So it's now a unit vector and then in one line.

  • And apologies is gonna be a nice, long line of code.

  • I'm going to call the drill line function, but I'm specifying an X coordinate, which is this starting point plus the X component of the normal multiplied by the radius on again, the starting point in the white component of the normal times by the radius to the end of the line, too.

  • By default, it draws in white side pixels.

  • That's one line, and that's the other.

  • Except this time you'll see I've used minus the normal components.

  • Just have a look.

  • Make sure that works on.

  • We can see a pin the top left.

  • We've got to slightly grayish circles, joined on the outer edges by two lines, so we've defined a capsule.

  • Now, in the previous video, we have the ability to pick up the ball and move it around the screen.

  • And we do that by checking.

  • Is the mouse coordinate inside the circle that represents the ball?

  • We're going to do the same thing for the lines.

  • Except this time.

  • A line, of course, has two circles that can be clicked in order to manipulate it, its staff and its end.

  • And so I'm going to have to additional checks across all of the lines in our vector of lines.

  • So we go through each of the vector on we check is the sounding circle.

  • Does that contain the mouse coordinates?

  • If it does, then we say are selected line Is that currently selected line?

  • Or if the mouse accordance air within the end circle of the line, then we do the same thing.

  • But what I want to record is whether we've got the starting A circle or the ending circle of the line selected, and I'm going to create a bullion value to do this.

  • So if it's true, we've selected the start of the line.

  • If it's false, we've selected the end of the line.

  • So I'm going to add in a bullion value off here.

  • Well, just defaulted to false.

  • And the reason I'm doing this is because when we check if the mouse is held, that's usually when we want to move things around.

  • Firstly, I want to check if the line is indeed selected.

  • We've got a line, but then I need to know which part of the line to move.

  • Is it starting circle or the ending circle on?

  • I'm going to use the flag that we've just created to determine that, So if it's the start, I'm going to update the start, axe and start.

  • Why components off the line segment on, you'll notice amusingly get Mouse X and get Mouse y functions instead off, eh?

  • Most pas X and Emmaus pas y.

  • I'm trying to tidy up the council game engine a little bit, too.

  • Be a little more friendly to people that might not be familiar with Hungarian notation.

  • Regardless, if we've not selected this start, then presumably we've selected the end and we mustn't forget.

  • Also, if we release the mouse button, what do we do?

  • Well, we're not selecting a line it'll so we need to sets our lines.

  • Selected value to know.

  • Let's take a look.

  • So when are we could see our line segments?

  • I can select one end of it and move it around and you see it adapts That's necessary.

  • There is no collision, of course, yet very good.

  • In our main physics loop, we update the Kinnah Matics of the ball and then we check for static collisions.

  • It's at this point, I'm also going to check for line segment collisions.

  • So again we use a four loop to it straight through all of the lines in our line vector.

  • So how do we work out whether our ball object has collided with a capsule object.

  • Well, let's start by ignoring the fact that both the capsule in the ball have radio.

  • Let's just treat them as points.

  • We need to determine which point along the length off the capsule is closest to the origin of the ball, the center of the ball on visually.

  • Right now we can see it's about here.

  • And indeed if I just draw an imaginary line from the origin of bull to this point, we can see there at right angles to each other.

  • Now, when working with line segments, it's unusual to use the absolute world coordinates.

  • Instead, we use normalized coordinates so we can suggest that the starting point is at zero onto the ending point is that one?

  • So we've effectively parameter rised the line.

  • So how do we work out what this location is?

  • Well, if we create a second vector from the midpoint of the ball to the starting point of our line segment in this area here is determined as being the dot product between vectors P and S s two E.

  • If we divide that by the length of S A, that gives us our T value and there's a final important step.

  • Currently, this assumes that the Vector SC can go on Thio infinity in both directions.

  • But this is no good to us.

  • We need to clamp this to between zero and one and this is quite important because let's say ball is over here then Our closest point is, of course, the starting point.

  • Likewise, if the ball is here, the closest point again is the starting point.

  • If we appear, the closest point is the ending point because we have clamped it to between zero and one.

  • If we didn't do that, then this ball's closest point would be somewhat here.

  • This ball's close.

  • Its point will be somewhere here on this one while you get the idea now somewhere up there.

  • So by clamping it were effectively tethering the ball to the ends of the lines.

  • Once we know the closest point on the line segment to the ball, let's just for a moment pretend that there is a baller the length of the radius we've already solved knowing whether two circles have overlapped in the previous video, so to check for static collisions, we need to create two vectors, the vector that represents the line segment on a vector.

  • The represents the start of the line segment to the ball.

  • What was my apologies?

  • It helps if we actually put this code in the static Collision leaves.

  • We'll need to know the length of our line segment so we can normalize things later.

  • And our T value was the dot product between the line segment on our line, from the ball to the start of the line segment normalized and clamped to between zero and one.

  • Now this line is actually quite nifty.

  • Let's start by unrolling it from the middle.

  • Firstly, we take our dot product, which is the highlighted code here, and we're trying to work out the minimum of our dot product on the length off the line segments.

  • On this, of course, clamps is to our one value because any dot product that yields a value greater than EJ length doesn't matter.

  • It's excellent that's returned by the main function.

  • We then look for the maximum where between zero on whatever is returned here on this clamps is at zero, cause anything greater than zero will be returned and then we normalize the whole thing by dividing it by the edge length that gives our T value between zero and one, which represents the closest point on the line segment on actually calculating the closest point now is quite trivial.

  • We just take our starting point for the line segment on.

  • We work our way along it by t times the Grady in't.

  • For that access, I'm going to calculate the distance now between the closest point on the midpoint off the ball again, this is Pythagoras theorem on.

  • We know if a collision has occurred because this distance will be less than the radius of the ball, plus the radius off the line segment.

  • They must be overlapping if this is true.

  • So this is a static collision.

  • And now that we know static collision has occurred, actually resolving it is very similar to how we resolved it when the circle collided with a circle on.

  • In fact, I'm going to use exactly the same code.

  • I'm going to create a fake circle at the closest point to call it fake ball.

  • We want to fill in some properties off the fake ball first will set its radius to be the edge radius I'm then going to set the fake balls mast to be the same as the mass of the ball that it's colliding with our set the position of the fake ball to be the closest point on our line on.

  • I'm going to set the velocity of the fake ball to be in the exact opposite direction off the ball.

  • It's hitting it on.

  • The point here is this will emulate the ball, hitting a solid surface.

  • The conservation of momentum will play out in exactly the same way.

  • Now, when we have a circle on circle collisions, we would store the collision into a vector so it could be dynamically resolved later on.

  • I'm going to do exactly the same here.

  • Except this time, instead of storing the pointers to the two balls that have collided, I'm going to store appointed to the ball on the fake ball and it's going to be in exactly the same vector that we're using for all of the other purse.

  • So the dynamic resolving code we don't need to touch, but this leaves us with the potential for a memory leak and I know they'll be view is screaming at me.

  • Why if he used the new key word.

  • Get some smart pointers involved.

  • But I'm not going to do that.

  • I'm going to do it the old fashioned way today on.

  • We have got the vector of my colliding purse.

  • I'm also going to create a vector off fake balls.

  • And so this means I don't lose my pointer to the fake ball now to resolve the collision.

  • Statically, we've already calculated the distance, so I don't need to do it again.

  • But I can work out the overlap from this because it's simply the distance minus the ball radius minus the fake ball radius.

  • But when it was a circle versus circle collision, we wanted to displace both the object ball on the target ball by half of this overlap against an edge.

  • We don't want to do that.

  • So where is it previously says?

  • Half here in the bull code.

  • There we go.

  • I'm going to keep it.

  • Is one here?

  • We only want our object ball to move.

  • The edge is not allowed to move as a result of this collision.

  • And so, in exactly the same way that we resolve the static collision for the balls, I'm going to use the same code for the lines.

  • So we displace the ball backwards along its sir velocity vector by the amount necessary to remove it from being in static.

  • Collision on this is code recovered in great depth in video one.

  • Now, before we move on, we must deal with this.

  • We've allocated memory here for a certain set of collisions.

  • We've got to clean this up because this is happening on a per frame basis.

  • Once we've resolved the dynamic collisions between all of the purse, we've no longer any need to store any of this information for this iPAQ.

  • So I'm going to iterated through my vector of fake balls that I've created just for this on delete them on, then clear the vector.

  • I'm also going to add one important change, which I should have mentioned earlier.

  • My apologies is that we're going to remove all of the colliding purse that have happened to because now we're doing multiple pox per frame.

  • Previously, we relied on this vector simply going out of scope to make it expire.

  • We can't do that anymore.

  • Now who else will have collision accumulation between the pox when we're checking against edges.

  • We tried to find the closest point of the edge to the object ball on.

  • We work out if the two objects, including the Radius, is are overlapping.

  • If they do, we create a fake ball at the precise point of collision so that the object ball has something to react against when we're resolving it dynamically later on.

  • But during the static collision, we displace it in exactly the same way as we do for a regular ball on bowl collision.

  • Let's take a look.

  • So here we've got our regular scene.

  • It's got some balls on Now.

  • I can pick up these and we could see the static collision is working very nicely.

  • It acts like a wiper, pushing things around the screen and pick up the other end.

  • Very nice.

  • We can check the responses when you see it works it arbitrary angles.

  • Very nice, too, and we can bounce off the edges at the end, points off the line segment and it responds as you might expect it to its At this point, we can start, have some fun, so let's increase the radius off the line segment.

  • It behaves as we expected to And if we wanted to actually look like a line, as long as we don't specify radiance of zero, we can do that, too.

  • Now, of course, I'm using my capsule drawing code to draw this.

  • You wouldn't necessarily want to do that when you're drawing the line for your engine so you could make it actually look like a single line of pixels.

  • But this will behave just like a normal light.

  • And once we got edges like this, we can join them together to give us arbitrary shapes for things to react against.

  • I'm going to make some more changes.

  • First thing I'm going to do is get rid off the circle model wire frame.

  • We don't really care about that anymore now, and it's not as pleasing to look out.

  • So I'm going to replace the drawer wire frame code with simply a Phil Circle code.

  • Since we know that all of the objects in this particular code up, Paul's going to add a bunch more lines for us to play with, I'll just offset them vertically down the screen to begin with 50 70 90.

  • Do that again for the Y coordinates because having more lines to play with allows us to try some things out and see how the dynamics behave To hear of my fault lines on, I'm going to create some sort of book.

  • It's try and capture things in.

  • I want to see what happens when we squeeze objects together.

  • I was going to try and ignore the big ball for now.

  • So it's trying.

  • Capture these in so you can see there are some problems now because there's no solution for the physics.

  • So we start to get some artifacts.

  • Let's just put these two back in.

  • But if we leave a hole for the objects to escape, you can see they behave accordingly.

  • They try and blast out of that hole.

  • Let's get things moving around.

  • Well, we could see the handling the con cave edges quite happily indeed.

  • Now, in the demo with Start, I added a lot more balls.

  • So instead of just 40 I'm going to add 200 I'm going to set the radius of these to be considerably smaller and to make the balls rain or we need to do is apply a gravity vector to them, which we can do in the kingdom attics where we're setting the acceleration forces.

  • We just want to offset the V Y by Constant.

  • There we go.

  • Now we've put in code to make them wrap around screen, so that should happen.

  • What it magically?

  • Let's try and create a book it to capture them all.

  • Here.

  • Put another rampant the top.

  • Don't let those drip feed down.

  • Well, it's chaos, but it's actually quite fun to play with adding more lines on more balls at any point, we could still grab them and add energy to them.

  • I think there's a lot of scope for interesting and fun games to do this year.

  • As always, the code is available on Get Hope Future Download and hack for yourself.

  • If you're curious, I've just run this in release mode, and it does run with a significantly high performance.

  • If you've enjoyed this video, it probably would be the last part on physics engines.

  • For a while, I seem to have done quite a few of those recently, but if you enjoy this video, please give me a big thumbs up and have a think about subscribing.

  • I should also mention if you want to talk about things like this with me or the fantastic programmers.

  • Please join the discord server.

  • It's it's really quite active now.

  • There's lots of interesting demos and people discussing code.

  • I'm also live streaming a little bit more frequently on twitch as well.

  • Those are not like my regular YouTube videos.

  • It's usually may just getting everything wrong, so they can be quite humorous and embarrassing.

  • Anyway, take care on.

  • I'll see you next time.

Hello on.

字幕與單字

單字即點即查 點擊單字可以查詢單字解釋

B1 中級

編程球#2 圓圈 V 邊緣 碰撞 C++ (Programming Balls #2 Circles V Edges Collisions C++)

  • 15 0
    林宜悉 發佈於 2021 年 01 月 14 日
影片單字