So you want to make a game engine? Part 7 – Rotation

Hey everyone!  I really hope you guys are enjoying this series, and as always please feel free to leave feedback and ask questions!

For those following along with development, let’s take a quick look at what we have so far:

  • We’ve discussed the purposes of a game engine and described the things it must be able to do.
  • We’ve implemented a double-buffered drawing system and implemented a Vector class.
  • We’ve written the skeleton framework of a game project created with our engine and looked at how it all works together.
  • We have a way to encapsulate the data of an in-game object and have a method of drawing it.
  • We can check for collisions between objects and simulate bouncing objects.
  • We can detect and handle input from the mouse and keyboard.

We basically have the core of a game engine, and it can be used in its current state to make simple games.  The next 3 posts, including this one, are just adding some additional things that are nice to have.  One of them is the ability to rotate objects.  While the .NET Framework makes easier some things that can get mathy in other engines and languages, this post may still get pretty involved, so hang in there!

Where to start

We’re adding a pretty big change to how our engine is able to draw things, and it’s not quite as trivial as rotate the image before drawing it.  Not only do we have to account for rotating images, we have to make sure that implement it in a way that will also be able to rotate shapes, such as boxes and circles which are drawn differently than images.  Additionally (and we hinted at this before), we need to modify the collision checking functions to account for the bounding box of a rotated object.

It’s a lot of work, so we’ll start with the harder part: drawing a rotated object.  Once that’s implemented, we’ll tackle the collision side of it.

Drawing a rotated image

If you’ve ever taken a linear algebra or matrices class, you’ll see some concepts from that appear in this post.  I promise there is nothing you have to do by hand, although understanding the concepts of some matrix operations, including transformation, can be very helpful in understanding why this works.

The process of drawing a rotated object, generally, is as follows:

  • Create a new blank image in memory that is the size of the drawing buffer (which is the size of the display area of the window)
  • Get a graphics context that allows us to draw onto this new image
  • Perform a translation transformation on the image so that the ‘origin’ is at the rotation anchor, which is usually the centerpoint of the object to be drawn. The origin is a point relative to the upper-left corner of the object to be drawn, so we need to translate by an amount equal to the location of the object, plus its rotation offset.
  • Rotate the image around this point by the rotation angle, given in degrees
  • ‘Undo’ the translation transformation
  • Draw the sprite to the now-rotated image in memory
  • Draw the image to the drawing buffer
  • Dispose of the memory resources used by the image and the graphics context to prevent memory leaks

That sounds like quite a lot!  It’s certainly more involved than ‘rotate the image before drawing it’ because it turns out there’s no way to do that without resorting to this kind of procedure.

Given the procedure listed above, we can identify four things we’re going to need as input for our rotated drawing method:

  • The buffer to draw the object onto
  • The bounding box of the image to draw
  • The rotation angle in degrees
  • The anchor point around which the object will rotate

If you’re wondering what an anchor point is, it’s simply a point within the image around which the image is rotated.  By default, we’ll rotate an image around its centerpoint, but we also allow for changing it to some other point, as in this example.

rotatedbox

Open up Sprite.vb and let’s get started.  Add this right below the normal Draw() method:

    Public Sub DrawRotated(ByRef b As Buffer, bounds As RectangleF, rotation As Single, anchor As PointF)
        If rotation = 0 Then
            Draw(b, bounds)
            Exit Sub
        End If

The first thing we do is make sure there actually is a rotation angle; if not, call the normal Draw() method and be done with it.

Note that we’re passing in the rotation as a parameter, and not making it a member of the Sprite class.  The rotation will be passed in via the GameObject’s Draw() method.

        ...
        
        Dim rotatedBitmap As New Bitmap(b.BufferSize.Width, b.BufferSize.Height)
        Dim g As Graphics = Graphics.FromImage(rotatedBitmap)

We initialize a blank image and make it the size of our buffer, and we get a graphics context for the image.  The graphics context allows us to manipulate the image we created by applying matrix transformations and drawing to it.

        ...
        
        Dim offset As New PointF
        If anchor = Nothing
            offset = New PointF(bounds.Width / 2, bounds.Height / 2)
        Else
            offset = anchor
        End If

Next, we get the rotation anchor and store it in a point called offset.  If the anchor is null (Nothing in Visual Basic), we do a quick calculation to get the centerpoint. Notice that we’re not calculatong the centerpoint that describes the location of the object within the window; we’re calculating the point that lies at the center of the image, so for a sprite of size 32×32, the center would be 16×16.

        ...
        
        g.TranslateTransform(bounds.X + offset.X, bounds.Y + offset.Y)
        g.RotateTransform(rotation)
        g.TranslateTransform(-(bounds.X + offset.X), -(bounds.Y + offset.Y))

We apply the transformations described previously: first we translate the image to the actual offset location, which is the location of the bounds rectangle plus the offset.  This is the point that we then rotate the image around before translating it back.

        ...
        
        g.DrawImage(_imgs(_curFrame), bounds.X, bounds.Y, bounds.Width, bounds.Height)
        
        b.DrawImage(rotatedBitmap, New Point(0, 0), b.BufferSize)

The hard part is done!  We can now draw our sprite image to the now-rotated bitmap in memory, and then draw the whole thing to our buffer.

        ...
        
        rotatedBitmap.Dispose()
        g.Dispose()
    End Sub

Finally, we release the memory used by the bitmap and the graphics context.  I learned why this was necessary the hard way: it was in a game I wrote that repeatedly had to draw objects with rotation.  Because the bitmap and graphics context weren’t disposed when the function was done with them, they remained in memory.  I only found this out when the program eventually raised an OutOfMemoryException and I checked Task Manager to see it was sucking up at least a full GB of memory.  So… yeah, pro tip: don’t do that, and always clean up after yourself.  We’ll eventually need to add a Dispose() method for garbage collecting to our GameObject and Sprite, but not until next time.

We now have a nifty new method to draw things with rotation!  Let’s modify GameObject now so we can use this.

Add two new properties to GameObject.vb.  I’m putting them right under our Sprite property to keep them all grouped together:

    ...
    
    Public Property Sprite As Sprite
    Public Property Rotation As Angle
    Public Property RotationAnchor As PointF
    
    ...

Wait a second, there’s no Angle class!  In my engine, I used a class to represent the value of an angle, which was nice because then I didn’t have to remember the conversion between radians and degrees if I needed to switch between the two.  The idea, similar to our Vector class, is to store enough data to calculate any other data we could want.  We’ll design the class to store the value in Degrees (I like degrees more), and we’ll provide a property that converts it to radians and sets it based on radian input.  It’s really short, so I’ll just list it here:

    Namespace Engine
        Public Class Angle
            Public Property Degrees As Single
            
            Public Property Radians As Single
                Get
                    Return Degrees * (System.Math.PI / 180)
                End Get
                Set(value As Single)
                    Degrees = value * (180 / System.Math.PI)
                End Set
            End Property
            
            Public Sub New(value As Single, Optional radians As Boolean = False)
                If radians Then
                    Degrees = value * (180 / System.Math.PI)
                Else
                    Degrees = value
                End If
            End Sub
        End Class
    End Namespace

Our constructor allows us to choose whether we want to initialize an Angle object using a value in either degrees or radians.  If this argument is left off, degrees is assumed.  This is a lot nicer than writing the conversion every time you need to, but be careful about the value you’re using to initialize it!

Back to the GameObject: we need to set the initial values for these new properties in the constructors. In each of the constructors, we need to set the initial rotation angle to zero, and the rotation anchor point to the center of the sprite’s image.

    ...
    Rotation = New Rotation(0)
    RotationAnchor = New PointF((w * scaleW) / 2, (h * scaleH) / 2)

Let’s also add a method that resets the rotation anchor to this point at the center of the image in case we change it during a game and want to change it back to the default.

    Public Sub ResetRotationAnchor()
        RotationAnchor = New PointF((w * scaleW) / 2, (h * scaleH) / 2)
    End Sub

At this point, we can draw rotated images! There’s one more very crucial thing we need to fix before we wrap up today.

Collision checking for rotated objects

One more main thing to cover: if we draw a rotated object and then try to check collisions with it, the collision checking will be thrown off.  The collision checking code is still using the bounding box of the object without taking into account the rotation, so we’ll need to modify it so that we can calculate a new bounding box.

rotatedbox

When I first added this to my engine, I was expecting to run into a lot of trigonometry for this, but it turns out that once again, the .NET Framework saves the day and provides an easier way.  Are there better ways?  Probably, but this has always worked nicely.

We’ll create a new file, this time a Module, and we’ll name it Functions.vb.  For those unfamiliar with Visual Basic, you can compare it to a static class in Java with static methods that can be called by any part of the program at any time.  Add it to our Engine namespace as always, and we’ll add a function called GetRotatedBoundingBox() that returns a RectangleF object.

    Public Function GetRotatedBoundingBox(bounds As RectangleF, rotation As Single, anchor As PointF, Optional objectIsEllipse As Boolean) As RectangleF

All of the parameters here should be pretty straightforward: we need the existing bounding box, the rotation in degrees (not radians), and the anchor point to rotate around.  The last one, objectIsEllipse, is one that should make sense after the next chunk of code.

        ...
        
        Dim g As New Drawing2D.GraphicsPath
        Dim m As New Drawing2D.Matrix
        
        If objectIsEllipse Then
            g.AddEllipse(bounds)
        Else
            g.AddRectangle(bounds)
        End If

What we’re doing here is creating a 2D graphics path object that allows us to draw 2D shapes.  This is different than drawing onto an image, because we can do a little bit more with what we draw.  A graphics path allows us to get a bounding box that perfectly fits around whatever we draw to it, which is exactly what we need.  The bounding box around a round shape when it is rotated can be slightly different than the bounding box around a rectangle, so we account for that here by drawing an ellipse if needed (see the following image for an example of this).  This will be especially useful when we add support for using circles and ellipses as sprites instead of just images.

box2

        ...
        
        m.RotateAt(rotation, New PointF(bounds.X + anchor.X, bounds.Y + anchor.Y)
        g.Transform(m)
        
        Dim r As RectangleF = g.GetBounds()

When m is instantiated, it is set to the identity matrix, which represents no transformation.  We need to apply our rotation to the graphics path, so we apply a rotation transformation on the matrix, and then transform our graphics path with it.  Like I said, .NET takes care of all the nasty math involved here!  We then get the bounding box around our rotated shape.

        ...
        
        g.Dispose()
        m.Dispose()
        
        Return r
    End Function

Finally, we dispose of the graphics path and the matrix to free up the memory used by them and return the box.  The box itself isn’t rotated, but it fits perfectly around our rotated input rectangle.  This is what we need to use in collision checking if we’re dealing with rotated objects.

Recall when we first added collision checking to our GameObject, we had two Bounds properties, one of which was specifically for collision checking purposes.  All we need to do is modify its return value to return the results of this function.  Open up GameObject.vb and go to our CollisionBounds property.

    Public ReadOnly Property CollisionBounds As RectangleF
        Return Functions.GetRotatedBoundingBox(Bounds, Rotation.Degrees, RotationAnchor, False)
    End Property

Collision checking is now fixed for rotated objects!

Now, we need to modify our GameObject’s Draw() method so that it handles drawing things with rotation.  We already implemented the DrawRotated() method in Sprite, so all we need to do is make a call to it from the GameObject().

    Public Sub Draw(ByRef b As Buffer)
        If Rotation.Degrees = 0.0 Then
            Sprite.Draw(b, New RectangleF(Location, New SizeF(w * scaleW, h * scaleH)))
        Else
            Sprite.DrawRotated(b, New RectangleF(Location, New SizeF(w * scaleW, h * scaleH)), Rotation.Degrees, RotationAnchor)
        End If
    End Sub

We first check to make sure there is a rotation; if there isn’t we call the normal Sprite.Draw() metod as before.  If there is a rotation, we call Sprite.DrawRotated() with two additional parameters: the rotation in degrees, and the rotation anchor point.

Wrap up

Our game engine is now able to handle objects with rotation in both the ability to draw them and the ability to check for collisions between them.  Next time, we’ll take a look at the other graphics technique we’ve been hinting at a lot: animation!

There’s not going to be an example project today, since today’s challenge question builds off of the previous one.  However, feel free to experiment on your own! The full solution to the challenge problem at the end of this post will appear in the sample code in the next post.

You can download the code from today’s work here: Dropbox

Question: No question today!

Previous Question Answer:
When moving up/down and left/right (in other words, moving diagonally), we see the sprite as either facing left or right, not up or down. Can you figure out why? Is there a solution to this?

Take a look at the ProcessInput() method.  We first check for Up/Down movement, and set the sprite accordingly.  We then check Left/Right movement, and change the sprite again if needed.  Thus, when moving diagonally the player will only see a left- or right-facing sprite because it is changed after the up/down sprite is set.

There are two ways to fix this: one way is to create an additional four player images for the diagonal directions, then after all input is handled, somewhere in GameLogic() we check the IsMoving[Up/Down/Left/Right] properties in pairs to determine if a diagonal sprite is needed.

However, this is time-consuming and completely unnecessary.  The other way of solving this is your challenge for today! 🙂

Challenge: Take a look at the example images provided in the example project for last time.  We used a total of 4 player images, one for each direction.  We also noticed that we don’t have diagonal-facing sprites, and we see only a left- or right-facing sprite if the player moves diagonally.  We can fix this by using only one player sprite and rotating it according to the direction of movement.  Start with the provided code for Example Project 6 and implement this solution using the rotation we added today.

This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply