Placeholder Image

字幕列表 影片播放

  • Hello.

  • In this video, I thought we'd take a look at a fundamental component of all to D engines, the ability to transform your sprites.

  • But before we get started, I'm just going to remind you about the warm loan code.

  • A code jam.

  • 2018.

  • You haven't signed up.

  • Check the link below.

  • It starts next week, and that's when I'll be releasing the theme on The fear of It is going to pay.

  • Well, of course, you're gonna have to wait for that until next week.

  • Anyway, we're going to be looking at rotating sprites while not just rotating them, but providing all sorts of different types of transformations to sprites.

  • So here I've got a spite of a car and I can rotate it.

  • I can also change the scale of it in both axes.

  • Make it a bit bigger again so we don't lose this.

  • Let me go and I can do a sheer transformers.

  • Well, she is a bit weird.

  • I don't know how useful they are, but they can be used for sort of wobbly effects.

  • But certainly the ability to rotate and position and scale sprites on a screen is really important.

  • And So in this video, I want to introduce the code to implement and I find transform.

  • Let's get stuff for this video.

  • I'm going to use the one lone Koda pixel game engine, and I've already created the bird bones of what's required here.

  • I've overridden the pixel game engine class with a base class called Sprite transforms on that will provide two functions on user create on our news update on user create is called Once andan user update is called.

  • Every single frame are creates.

  • An instance of this Dr Class in my main function on the size of the pixel game engine window I'm creating is going to be 256 pixels by 2 40 were each pixel in game.

  • Engine space is four by four pixels on my screen space.

  • Since I started with the council game engine and now the pixel game engine, the concept of a sprite has been quite fundamental.

  • So I've added a variable called SP R card pointer to a type of sprite and in our music, great, I'm going to load a PNG file into that sprite object.

  • Drawing the Sprite to the screen is quite a routine operation Firstly, I'm going to clear the screen entirely.

  • Too dark Scion.

  • In this case, it makes a change from Black makes the thumbnail a bit more interesting.

  • I'm going to draw the Sprite to Location 00 which is the top left with a single one line command draw.

  • Sprite.

  • So that's it.

  • That's our program.

  • We're going to load the Sprite on, draw it to the screen, something we've done many, many times on this channel.

  • And there we go.

  • We can see the spite of a yellow car with a white background on the dark side and background off the pixel game engine window.

  • The car has a white background because we've not told the pixel game engine to care about Alfa blending the transparency off the PNG file.

  • Now P and G files.

  • They contain their own transparency information, but we choose not to use is in most cases because actually blending things together is quite computational intensive.

  • She only wants to do it when and where you need it.

  • So just before I draw despite, I'm going to set the pixel mode to Alfa Nuttall, instruct the pixel game engine to actually acknowledge that the pixels in the PNG file of Sprite have transparency, but it's important.

  • Once we've enabled it, we should also disable it as well.

  • Because here, if we didn't set these back to normal the next time we call the clear screen function, it will try to take into account all of the Alfa values that is there, too, which would slow things down on just to eternity.

  • Check that that works.

  • There we go.

  • We've now got the car without the white border.

  • So far, so good.

  • We can easily draw the spikes in any X Y location on screen.

  • We can offset it or translate it anywhere we want.

  • And in fact, in the pixel gay measure, we can go on further.

  • We can introduce scale the Strike two, and this is very simply done on by introduced scaling.

  • What I mean is we've provided interview value as the fourth argument of the drawer Sprite function on it will modify, as we can see now the car is twice as large, so it is skilled in both axes by a factor of to change it to three and it's three times bigger and I can do interview scaling quite simply, because the striped shares its X and Y axis with the X and Y axis of the screen in the example to show instead of drawing, one pixel will draw three pixels instead.

  • Easy stuff, but this is not very applicable when you want to start rotating the sprites on.

  • We've done the rotation of a point many, many times on this channel, and so we should know these formula off by heart now.

  • But if he wanted to transform Point X, we would take cars theater, which is how much we want to rotate it by Times X plus sign theater times Why and for the white component.

  • It's very similar.

  • Minus sign Theater X Plus costs Theater Why and so this will take our original X and Y point rotated by a theater and produced a new X and y point.

  • If you're interested in how this is derived, I suggest you go and check out the code it yourself asteroids video.

  • I do a full derivation there alongside rotation.

  • We may also want translation the ability to move the Sprite somewhere along the plane.

  • Well, we can see here that we once were rotated the point we can offset it quite easily by adding another component.

  • So let's say a translation in the X on a translation in the why on depending on how you want to look at things, you could also take the whole lot and multiply it by a scaling factor scale, X or scale.

  • Why?

  • The order in which you do these things is, of course, important.

  • One of the things I've done a lot off this year is transformation matrices.

  • So here we've got the rotation matrix equivalent of the equations have just seen on the scaling Matrix.

  • We don't have one for translation, and that's because we've not got enough dimensions here.

  • If you remember, how we calculate matrices is quite a simple algorithm.

  • We go along each row of the elements on the left hand side of the multiplication and multiply it by the column on the right hand side.

  • So this cause gets multiplied by this X X costs seater, and we add to that this sign multiplied by this why, plus why, Science eater.

  • Then we do the same for the bottom row minus X Sign Theater plus y costs theater.

  • I'll just close off my matrix.

  • These two equations, of course, what we've just seen on the previous page, but you'll notice we need 1/3 additional term to implement the translation.

  • So let's just rewrite things a little bit.

  • I'm going to extend the rotation matrix to accommodate the additional term so it now becomes a three by three matrix.

  • And when we do our multiplication multiplying by X on by why on by one in this case, the third term would be one multiplied by zero or one multiplied by one in Either way it's irrelevant.

  • In fact, we'll do exactly the same from our scaling matrix, you know.

  • 0100 So now are scaling.

  • Matrix is also a three by three matrix, but now we have enough dimensions to create a translation matrix is 10 t x zero won t.

  • Why?

  • Because these are the terms that get out.

  • I don't remember on 001 just to make it a three by three and no, that's our three fundamental transforms all three by three matrices.

  • Of course we can combine them so we could scale something.

  • Then rotate it, then translate it, then rotate it, then scale it, then translate it again, Then scale something else on.

  • We'll get a complete bind.

  • Transform.

  • The result of this combined transform is itself a three by three matrix, which means we can have a very complicated transformation, represented with a little amount of data.

  • And these sorts of combine transforms are called a fine transforms.

  • Fine transforms have lots of interesting mathematical properties in their own right.

  • But I'm only interested in rotating and drawing sprites in different places.

  • And as with a lot of programming algorithms, Wikipedia isn't a bad place to go.

  • Yes, it starts to talk about the A fine transform from quite a mathematical perspective.

  • What I like about the article is about 2/3 of the way down we get to see it.

  • Lists are fine matrices as three by three matrices with an example of what they do.

  • So here we've got the identity matrix, which does nothing.

  • It keeps the image on transformed.

  • We've got the reflection matrix.

  • Well, if we look at the reflection matrix, we can actually see that that's the same as the scale matrix.

  • We're only affecting things on the leading Dagnall, in this case, were multiplying our X value by minus one.

  • So we get a flip in the X axis.

  • That's no different to scaling, except we're using a minus sign in the front.

  • We've also got Here we go.

  • The rotation three by three matrix.

  • Very nice.

  • We've just seen that on.

  • We have an odd one here, which is called Sheer, which allows us to sort of stretch along.

  • One access only on the thing to know about.

  • A fine transforms is that parallel lines always remain parallel.

  • So let's get started with implementing our own matrix framework.

  • I'm going to remove what we've already got so far, I'm going to create a basic structure called Matrix three by three.

  • It's just a three by 32 dimensional array of floating point numbers, and I'm going to adopt the convention that I'll do Things column and then row.

  • This is the same as X and Y Justus.

  • We did with the code of yourself three D graphics.

  • Siri's.

  • I'm going to add some utility functions to help me work with these matrices, so the first functional ad is a function that just simply sets the matrix that's passed into it to be an identity.

  • The leading diagonals are set toe what?

  • And we know that we're going to be multiplying matrices a lot, so I'm going to add a quick function.

  • Just to do that.

  • It simply loops through the columns and the rose on multiplies them accordingly.

  • We also need a function to pass a point through our transformation matrix.

  • So this is going to take in an X and A Y value in The Matrix and produce a new X and A Y value after that point has been transformed.

  • So you'll see here.

  • We've got the X coordinate multiplied by a matrix y coordinate multiplied by a matrix.

  • But we don't have the Zed corner.

  • I'm going to assume it's one, so we're just going to accumulate the Matrix.

  • The humble draw spite routine is no longer any good to us.

  • Now.

  • We'll have to create our own Thio.

  • Use the transformation matrix, so I'm going to make an assumption that will have a final transformation.

  • I'll call that Matt Final, and it just to be on the safe side right now.

  • I'll just make that an identity matrix to perform the transformation.

  • I'm going to take a really naive approach on.

  • It's not going to be the approach will finish with, but I think it shows an interesting concept on the journey.

  • I'm going to create 24 loops that will allow me to iterated across each pixel contained within the Sprite, and I'm going to sample that pixel and store it in this value p.

  • Here.

  • Two additional floating point variables are going to represent my new X and new Y cornet after I have transformed the X and Y coordinate from Sprite space.

  • So this will put it into screen space.

  • And this is just a matter of calling the forward function we've just defined.

  • Once we've got the New X and the new wife function with, then draw the pixel using the standard pixel game engine drawer function in the new location.

  • So we've taken the point within the Sprite, transformed it on.

  • We're drawing it to the transformed location on the screen now because this is an identity matrix.

  • What we should see is no difference at all.

  • Let's take a look perfect.

  • What we see is the car aligned with the top left, so let's add a translation matrix.

  • We want to move the sprite around on the screen.

  • Well, that's just a case of setting this value on this value in the right most column.

  • And instead of calling the identity function, let's call the translate function.

  • I will pass into that value 100 by 100 so they should offset the Sprite from the top left somewhere towards the middle of the screen, take a look and very nice.

  • It has done so.

  • Our translation effect has worked quite nicely on we've used major seats.

  • To do this.

  • I'll go ahead and simply add in the three other types of our fine transform matrix rotation scaling and sheer.

  • Let's try them out.

  • So let's say I wanted to draw despite half the size it should be.

  • Well, I'll apply scaling factors over no 0.5 to both axes.

  • Let's take a look at that.

  • Well, the car seems smaller and quite nicely.

  • It's not got any awkward artifacts there to complain about good.

  • Let's make it larger.

  • We'll make it twice as large, so we'll have a scaling factor of two in both axes.

  • Let's take a look now.

  • Uh huh.

  • Yes.

  • We seem to have hit a bit of a snack here.

  • This caps throughout the image.

  • Why is this?

  • Well, simply we've not got enough pixels as we've iterated full of the pixels in the Straits.

  • We've created new locations for those pixels, but the gaps in between them have a look at filling these gaps in in a bit.

  • But let's try the other transforms first, while scale was a bit unsuccessful.

  • So let's try rotate.

  • Instead.

  • We'll rotate by some arbitrary amount.

  • No point to this, of course, in radiance.

  • Well, it certainly rotated the image, but there are those pesky blank pixels again.

  • Nonetheless, let's try some combined transformations as well.

  • I'm going to need some more matrices for these, just some temporary ones.

  • At the moment, we've just rotated the Sprite around.

  • Its top left coordinate was rotated around the middle, so this means we'll need to translate the sprite before we do any rotation.

  • So let's create a translation matrix to do that.

  • Well, still, that in Matt A.

  • Now I happen to know that the sprite is 200 pixels wide by 100 pixels high, So if I translated by minus 100 on minus 50 I'll move the zero point to the middle of the Sprite I still want to do the rotation, but I'm going to store that in Matrix B.

  • And so I want to combine these two matrices into my matrix final.

  • I'll call the Matrix multiply function that we created earlier.

  • The order of operations here is quite important.

  • Let's see if we've got it right.

  • Well, I'm looking at that and thinking, No, we didn't get it quite right.

  • Let's reverse those around.

  • Okay, that's looking a little bit more sensible.

  • Certainly the middle of the roof of the car is now top left, and it's angled, so we probably want to offset the car now to the middle of the screen.

  • This means we need another translation operation Overuse matrix.

  • Say, Now we're done with it, and I will set the translation to be screen with Divided by two and screen height divided by two.

  • Make sure I put this matrix multiply in the right place first, so we've done the translation and rotation, and that's given us Matt Final.

  • Then we're creating another matrix.

  • A.

  • I'm going to need some more matrices here on site.

  • Let's add in a mat, see?

  • So instead of translating to the final will go to Matt.

  • See first and now our final multiply will goes the final matrix.

  • We've got our new translation going in, and we want to multiply that with what we had previously.

  • It complicated this, and there we go.

  • We can see the car now offset the middle nicely.

  • Bang on in the middle on rotated by certain amount.

  • Let's just test this rotation.

  • I'll add in some key strokes toe.

  • Handle some user presses to rotate the car.

  • We'll need a quick variable.

  • I'll call this one f rotate, and I'll just initialize it to zero.

  • And I'm going to be sensitive to the Zed and Ex Keys to increase or decrease that value is necessary.

  • I'll use every lapse time, so it's nice and smooth.

  • Instead of this hard coded value, let's put in our rotate value.

  • We'll come back to this sort of matrix mess in a minute.

  • Let's just see if this works so that looks like the car.

  • Normally, I'm going to start rotating it on dhe.

  • It does rotate still got these pesky Sion dots all over the place, but it seems to be doing quite a nice and smooth rotation and certainly the performance is quite good, too.

  • That's a pretty funky pattern.

  • The reason The Matrix stuff gets a little bit complicated.

  • His matrix multiplication is not commuted.

  • A tive i the order matters.

  • That's why we saw at the start I had to switch around the A and B values.

  • So I created to exclusive Major sees.

  • Want to translate on, want to rotate on dive, multiplied them in the correct order to give me a new matrix matrix C, and they want to take matrix ears my already existing matrix and multiply it by a new matrix, which represents a translation to the middle of the screen.

  • What is it we've just done here?

  • Well, without any transformation, we saw the Sprite was drawn on the screen in the top left, and that's because this Sprite top left was aligned with the screens.

  • Top left.

  • The first translation operation translated the cast right so that the middle of the sprite was aligned with the top left of the screen when we moved to the origin of the sprite to its middle.

  • The origin being in the middle of the sprites is quite useful for scaling and rotation, because we'll always rotate around the origin.

  • And so in this case, it's exactly what we did.

  • Spike doesn't quite fit on my screen here, and once we've rotated despite, we then translated it back to the middle of the screen.

  • So what can we do about these annoying gaps in our stride In the source?

  • Spite marked here in blue were iterating across our pixels one by one.

  • This means our transformed image will only ever consist of the same number of pixels.

  • But the locations of these pixels may not align very nicely with the pixels on the screen, and so we start to see the gaps in between.

  • We saw this, especially when we were scaling.

  • I think that's a very visual example of this effect.

  • And so the problem has come around because of the forward way.

  • We're doing things.

  • I'm going to suggest doing things backwards.

  • So firstly will work out the area that are Sprite is going to occupy once it's transformed, have some sort of bounding box we know that will cover every single pixel of the Sprite within this region on, Instead of projecting the source sprite to the destination spite.

  • I'm now going to iterated through the bounding box pixels, which I know and now aligned with screen and sample from the correct location in the image.

  • In effect, what we want is the chance form that takes our final Sprite location, rotation scale and sheer and gives us the original Sprite location rotation scale.

  • In sheer.

  • We want the inverse of the transform we actually created to begin with.

  • Now we know that this approach will leave no gaps because we're going to go across all of the pixels in that region.

  • But this does require a matrix inversion on.

  • As I said in the past, Metric conversion is a very complicated field, and it's well beyond my skill to be able to explain nicely.

  • So I'm just going to take it off the shelf.

  • Three by three matrix inversion function Manipulated to our needs and so glam There we go on.

  • It looks terrific, and indeed it is horrific.

  • But what I should point out is it's nothing more than multiplies on.

  • Additions, subtraction, Addition are pretty much the same thing.

  • So even though there's a lot to do, it's actually quite simple for a C P, which is optimized for doing things like multiplication and additions.

  • It's also important to know that we're not going to need to do this for every pixel.

  • Just once the transform is established.

  • Once we've got the correct transformation matrix or, in this case, the inverted transformation matrix, then we just pass points through it.

  • Using our forward function is normal.

  • So in many respects I tend to treat things like this as a non recurrent expenditure.

  • I also have made no attempt to optimize any of this at all.

  • I want to just keep it clear, as I did with the three D graphics engine.

  • So knowing that we're going to need an additional matrix, let's quickly add it.

  • Matrix Final in on will calculate the inverse matrix right here.

  • Invert Matt Final.

  • That's final in now.

  • Not change the drawing function just yet because we've got one last problem to solve.

  • I'll just quickly run this again, and what it is is how do we work out where the bounding box of our spite is?

  • Well, one assumption we can make is that the corners of our spite are accurately projected in space.

  • And so if we pass through the coordinates off the corners of our spite through our forward traveling matrix A.

  • The non inverted one will get some screen coordinates, which we can use to determine the bounding box.

  • The way that we were originally drawing the Sprite is now inaccurate, so I'm going to comment that out.

  • We don't need it anymore.

  • Instead, I want to work out where the bounding box of the transformed spite is going to be.

  • And so to do this, I'm going to pass through each corner of the existing Sprite forward through the transformation toe workout, where the bounding boxes and so I've created four variables.

  • Start axe and start y an end x an end.

  • Why don't give us the four sides of our bounding rectangle?

  • I want to create two additional valuables, PX and P Y.

  • You'll see why in a minute.

  • So taking the top left of the sprite, which would be 00 I can pass that through our forward function, using our standard transformation matrix.

  • There's the 00 on.

  • The result is going to be placed into P X and Pete.

  • Why?

  • Well, to start with.

  • That's all the information I have.

  • So I'm going to set my starting X and Y unending X and Y positions to be P X and P y, and then going to send through in the forward direction the opposite corner of our sprite.

  • So this is like the full bottom right location of the sprite, the width and the height.

  • Again, this goes through the normal matrix.

  • Now, ultimately, I want to traverse through the bounding box from the top left of the bounding box to the bottom.

  • Right.

  • So I want to sort my starting X and Y positions, which I do here.

  • So for my starting X is always going to be the minimum off the point that we've just calculated or the existing starting X location and likewise, the ending explication is always going to be the maximum of my most recently calculated point or what I've already got.

  • The same happens for Why so now I've got the top left on bottom, right of my bounding box.

  • You might think that would be enough, but not when you've considered You've got rotations.

  • We actually need to do all four locations of the Sprite.

  • So here is the bottom left.

  • And here is the top, right, so this algorithm will give me the bounding box of my transform Sprite on.

  • I've needed the forward transformation matrix in order to do that.

  • Now that I have the bounding box, I cannot traverse through each pixel of the bounding box and use the inverse transformation to sample the spite of the correct location So all of the pixels in the bounding box is going to get selected.

  • I'm not going to have any gaps on the code.

  • Looks very similar to what we had before.

  • I'll use my forward command, but I'll use the Inverted Matrix to give me to new locations, and I'll use these locations to sample the Sprite.

  • At a particular point.

  • I've offset an X and end.

  • Why?

  • Because these are no longer interview.

  • By adding no 0.5 to them, I make sure that I'm rounding to the correct pixel.

  • And so I guess the final thing to do is to draw the new pixel in the X and Y location on the screen.

  • Let's take a look.

  • Well, the spite looks good, but we've got no gaps, but we're not rotated anything yet, so let's start to rotate on.

  • We can see nicely now that the bounding box is expanding and contracting as required, that might be useful for other things.

  • But what we see is no gaps at all.

  • In fact, it looks very nice.

  • I like the fact that it it bounces.

  • It's very jolly.

  • I think the last thing I'm going to do is enable out for blending again for the Sprite, as were drawing it.

  • But I'll just take this bit of code and move it down here.

  • So we disable when we're clearing the screen.

  • There we go.

  • And so there you have it.

  • We've got a sprite that has been transformed on the screen.

  • It's been translated, it's been scaled, it's been rotated.

  • We've looked at shears, but I don't think we'll ever use them for anything.

  • That's quite a nice, flexible routine for handling sprites.

  • In fact, I like it so much that I wrapped it up in what has become the first pixel game engine extension.

  • I'll now quickly prepare the program we've just written to use the extension on.

  • This is a bit brutal.

  • We're going to delete pretty much everything we've created.

  • All I have left in is loading of a sprite handling of the rotation on DTH E Alfa mode settings to use an extension for the pixel game engine.

  • After you've included a well see pixel game engine dot h, you can just include the extension.

  • That's all you'll need to do.

  • In this case, it's graphics to D.

  • The extension provides several interfaces, one of which is that we'll see GI effects to d Transform to Day.

  • It's all creates an instance of that, and just as we've done manually will first to this transform, we will translate it well, then rotate it, toe are rotate variable on will translate it again to the middle of the screen.

  • You'll notice I'm not needing to deal with matrices or matrix multiplication.

  • That's all hidden behind the scenes on.

  • It's written in a way that optimizes the creation and manipulation of Matrix data.

  • The final thing to do is to call another drawer sprite routine.

  • Herbert.

  • This one exists within the name space of graphics to D.

  • It's a special one because instead of taking coordinates, it takes the transform object we've just created.

  • Let's take a look.

  • As you can see, it has exactly the same performance and behavior as the manual code, but it's considerably more concise.

  • In fact, that's the entire program.

  • I quite like the idea of having extensions for the pixel game engine.

  • So I think I'll probably develop some more on the interface is written in such a way that you guys can also create them quite easily too.

  • I know some people in the disco have already been working on controller interfaces where things quite cool.

  • Anyway, I've got a plan for being able to rotate spice.

  • Give them we're approaching.

  • The end of the year is usually about this time I start having to think about a big project to do in the run up to Christmas.

  • So watch this space Also, don't forget Thio.

  • Take part in the jam If you're interested in doing so.

  • The theme will be announced next Friday US the 16th If you like this video as usual, big thumbs up please have a think about subscribing and I'll see you next time.

Hello.

字幕與單字

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

B1 中級

2D Sprite Affine Transformations (2D Sprite Affine Transformations)

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