Free Web Hosting Provider - Web Hosting - E-commerce - High Speed Internet - Free Web Page
Search the Web

vbProgramming | Tutorials | Games | RPG Programming V : Optimizations
    vbProgramming 
Tutorials -
RPG Programming Series:
Stage V. Optimizations
 
     

vbProgramming Home :: vbProgramming Forums :: Tutorials :: Contact :: Links 

 

 

This is the fifth tutorial in the RPG Programming Series. This will teach you how to make your application more event-driven (or self-driven).

Intro
This tutorial is all about optimizations. Nothing else to say about it....

It's actually a pretty short tutorial because the optimizations we'll be doing here are pretty basic. Check it out:

Indicing
Our game only has 3 types tiles (for now), but why do we have a 22x16 array of bitmaps? It's better to have 3 bitmaps, and have another 22x16 array of integers refer to the the 3 bitmaps using indices.

Less walking around by using the Multiplier
Not everyone has a fast PC. Unfortunately I don't know how to detect system performance, therefore I'll let the user specify his performance manually. If you remember back in the first RPG Programming Series tutorial, we added a Multiplier value. This value basically tells you how many steps the character walks per tile. For example if your multiplier was 1 ( = 30/30) then the character would walk 30 steps per tile. If it was .5 (= 15/30) then your character would walk 15 steps per tile. The lower the multipler the faster your character would go. Unfortunately while making the source code I included a multiplier of 1 (because it ran fine on my system with that speed). Here we'll adjust the multiplier and add some hotkeys to change it using + or - to increase or decrease the performance.

Clipping
Really simple concept: Anything that's off the screen won't be rendered.

These are the 3 things that we'll be doing in this tutorial. It's quite possible that in a later tutorial we'll be looking at Dynamic Refreshing (refreshing only the tiles that need to be redrawn), Manaul Backbuffering (for performance), and GDI+ Transforms. But for now, we'll focus on these 3 things.

Indicing
This is rather simple. Just open up your clsMap class and we'll fix some stuff :).


Replace your existing
 Public Tiles(,) As Bitmap

with
 Public Tiles() As Bitmap
 Public TileIndex(,) As Integer

Why? Because we only want 3 bitmaps, and a bunch of integers in a 2D array.

Now let's go to the ReadMap sub. Right after
'Store the next line (which contains the number of types of tiles) into an integer
TypesOfTiles = SR.ReadLine()

Type in
ReDim Tiles(TypesOfTiles)
Dim ind As Integer

For ind = 1 To TypesOfTiles
    Tiles(ind) = New Bitmap("tiles\" & tileset & "\" & ind & ".jpg")
Next

All this is fairly self explanatory code. What this is doing is storing the 3 types of tiles in a bitmap array.

Now make your way down to the bottom of the ReadMap sub. You should see a loop:

        For CurrentRow = 0 To Height
            'Read each Line 
            ln = SR.ReadLine()
            Line = ln.Split(",")
            For CurrentColumn = 0 To Width
                'Read each character (Go across the file)
                Tiles(CurrentColumn, CurrentRow) = New Bitmap("tiles\" & tileset & "\" & Line(CurrentColumn) & ".jpg")
                Passable(CurrentColumn, CurrentRow) = Walkable(Line(CurrentColumn))
            Next
        Next
Take a look at the red line (it's the one that's giving you errors). Replace the red line with:
TileIndex(CurrentColumn, CurrentRow) = Line(CurrentColumn)

Line(currentcolumn) represents the tile you're looking at. So if the tile is "1" then TileIndex would be 1, instead of being a bitmap: tiles\basis\1.jpg.

Now let's go back to GameClass and fix the tile drawing loop.
In the loop where you draw the tiles (the map), you should see this line:

e.Graphics.DrawImage(Me.map.Tiles(x, y), New Point(x * Me.TileWidth - Me.alex.Position.X + 300, y * Me.TileHeight - Me.alex.Position.Y + 240))

The red text should be giving you an error. Tiles is a 1 dimensional bitmap array. TileIndex is a 2-D bitmap array. 

Replace that line with this (I split the line into 2 for the sake of not making the page so wide)

e.Graphics.DrawImage(Me.map.Tiles(Me.map.TileIndex(x, y)), New Point(x * Me.TileWidth - Me.alex.Position.X + 300,  _
y * Me.TileHeight - Me.alex.Position.Y + 240))

Take a look at the underlined portion:
Me.map.Tiles(Me.map.TileIndex(x, y))

TileIndex(x,y) returns an integer. Ex; for (0,0), it would return 1. So the code would be simplified to Tiles(1). Tiles(1) is a bitmap: tiles\basis\1.jpg. 

That was really easy.
Less Walking Around: Using the Multiplier
This is another easy section. We'll just program in the + and the - keys.
In the Gameclass.KeyDown event add in the following cases (in the same area where you added in the Keys.Up, Down, Left, and Right cases)
Case Keys.Add
   If Not alex.InMotion Then IncreaseSpeed()
Case Keys.Subtract
   If Not alex.InMotion Then DecreaseSpeed()

I took a little shortcut here to save me some time explaining: Go to clsSprite and make the InMotion variable public instead of private.
The reason behind this is because if you increase your speed while you're walking, things could get rather messed up.

We'll get to the IncreaseSpeed/DecreaseSpeed subs in a minute. For now, set your default multiplier to 30/30 and declare a new variable: 

'This multiplier will be adjusted according to the speed of the character.
Public Multiplier As Single = 30 / 30
'Just a dirty little trick: Adjust the MultiplierNumerator according to Multiplier
Public MultiplierNumerator As Integer = 30

If you want the default multiplier to be lower then make it lower, but adjust the MultiplierNumerator as well. Ex: If you set your default multiplier to 15/30, 
make the multiplier numerator 15.

We're going to increase/decrease the multiplier numerator in increments of 3 (1 is too little)
    Private Sub IncreaseSpeed()
        alex.MultiplierNumerator -= 3
        'We can't make him go too slow.
        If alex.MultiplierNumerator < 6 Then alex.MultiplierNumerator = 6
        alex.Multiplier = alex.MultiplierNumerator / 30
        Console.WriteLine("New Multiplier Numerator: " & alex.MultiplierNumerator)
    End Sub
    Private Sub DecreaseSpeed()
        alex.MultiplierNumerator += 3
        If alex.MultiplierNumerator > 30 Then alex.MultiplierNumerator = 30
        alex.Multiplier = alex.MultiplierNumerator / 30
        Console.WriteLine("New Multiplier Numerator: " & alex.MultiplierNumerator)
    End Sub
Pretty easy right? It's a lot easier adjusting the numerator of the multipler as opposed to adjusting a value like .5 (30/30) or .666 (20/30), and then dividing it by 30, the
denominator, to get the actual multiplier.

Run the app. Finetune the multiplier to your settings until the program runs smoothly.
Rendering Only What You See On The Screen 
Pretty easy.
The character is on the center of the screen at all times. At all times, there are:
-10 tiles to the left of him
-8 tiles above him
-11.5 tiles to the right of him --> Round this to 12, because half a tile is sticking out.
-8 tiles below him

How do I know all this? By 2 minutes of testing around I figured this out. 

Why 10 to the left, and 11.5 to the right? 30x30 tiles don't fit the screen at 640x480, because 30 doesn't divide evenly into 640. It does divide evenly into 480 though, 
hence 8 above him and 8 below him. 

We'll just lop the 11.5 to a 12 becuase there's a half a tile sticking out.

Now that we know the borders, it's pretty easy to calculate what's on the screen and what's not.

In GameClass globals:
Private topleft As Point
Private bottomright As Point

Now create a sub called CalculateBorders() <-- in GameClass.

    Private Sub CalculateBorders()
        topleft.X = alex.TilePos.X - 10
        topleft.Y = alex.TilePos.Y - 8
        If topleft.X < 0 Then topleft.X = 0
        If topleft.Y < 0 Then topleft.Y = 0
        bottomright.X = alex.TilePos.X + 12
        bottomright.Y = alex.TilePos.Y + 8
        If bottomright.X > map.Width Then bottomright.X = map.Width
        If bottomright.Y > map.Height Then bottomright.Y = map.Height
        'Console.WriteLine(topleft.ToString & ControlChars.NewLine & bottomright.ToString)
    End Sub
Just note the 4 If Statements.  I'll get to that in a minute.

Alright now let's go to the Paint sub (in Gameclass)

The map rendering loop normally looks like this:
For x = 0 To map.Width
For y = 0 To map.Height 

However let's change it!
For x = topleft.X To bottomright.X
For y = topleft.Y To bottomright.Y

That way it only renders what's on the screen.

Now about those 4 if statements. What if the character was on (1,1)? - According to that sub his topleft position would be tile (-9, -7) which doesn't exist. So we just lop it
off to (0,0). Same goes with the BottomRight.
 
Now let's test it out with a new map file. Adjust Lightworld.map like so:

52,38
3
#LEGEND
1=TRUE
2=TRUE
3=FALSE
#END LEGEND
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
1,1,1,1,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
1,1,1,1,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,1,1,1,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
1,1,1,1,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
1,1,1,1,2,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,2,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
3,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
3,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,2,1,1,3,1,1,2,1,1,1,1,1,1,2
3,1,1,1,1,1,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,2,1,2,1,2,1,1,1,1,1,1,1,2
3,1,1,1,1,1,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,2
3,1,1,1,1,1,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,2
3,1,1,1,1,1,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
3,1,1,1,1,1,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,2
3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,2,2,1,1,1,1,1,1,1,1,1,1,1,1,2,1,2,1,2,1,1,1,1,1,1,1,2
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,2,2,1,1,1,1,1,1,1,1,1,1,1,2,1,1,3,1,1,2,1,1,1,1,1,1,2
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
1,1,1,1,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
1,1,1,1,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,1,1,1,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
1,1,1,1,2,2,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
1,1,1,1,2,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,2,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
3,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
3,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
3,1,1,1,1,1,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
3,1,1,1,1,1,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
3,1,1,1,1,1,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
3,1,1,1,1,1,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
3,1,1,1,1,1,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,2

Pretty big map. 

In the beginning of the Paint sub just call CalculateBorders(). 
Now run your application.  You should notice that even with the big map you get a decent speed. If not, adjust your multiplier. Personally I'm comfortable with 18.

Fine Tuning
Just a few quirky things that's all. 

Notice that when you hold down a key it goes a bit slower than when you just tap the key? That's because you're forcing it to animate more while he's in motion. How 
do you fix this?

In Gameclass.Keydown, adjust your keys like so:
If Not alex.InMotion Then alex.MoveUp()

Just add the "If Not alex.InMotion Then" part in front of the whole thing.

One last thing: I personally hate Up Down Left Right. I'm more comfortable with W S A D. So I've included a small change to allow the engine to check for both WSAD
as well as Up Down Left Right. The first Select Case block (there are 2 of them) should look like this:
        If Not GameClass.alex.InConversation Then
            Select Case e.KeyCode
                Case Keys.W, Keys.Up
                    If Not alex.InMotion Then alex.MoveUp()
                Case Keys.S, Keys.Down
                    If Not alex.InMotion Then alex.MoveDown()
                Case Keys.A, Keys.Left
                    If Not alex.InMotion Then alex.MoveLeft()
                Case Keys.D, Keys.Right
                    If Not alex.InMotion Then alex.MoveRight()
                Case Keys.Add
                    If Not alex.InMotion Then IncreaseSpeed()
                Case Keys.Subtract
                    If Not alex.InMotion Then DecreaseSpeed()
            End Select
        End If

Now. Run the program - you should notice a difference when holding down a key.
Alright I have one more change to make before we finish this tutorial. If you reduce your multiplier numerator all the way down to 6 (the minimum) you'll notice him doing
1 step per tile --> This is not good!

Why? The SFrames (see first tutorial if you forgot what the point of these are) are doing this.
If you remember, in the first tutorial we had a multiplier of 30/30. This meant that the character stepped (as in moved his frame) 30 times per frame. This was too much 
because he looked like he was a baby on a treadmill: running around a lot but not getting anywhere.

Well we made SFrames (secondary frames), and set the default value to 9  So what happened was, for each 
iteration of the loop (there are 30 of them).... the frame wouldn't change until SFrame reached 9, which was every 10 iterations (each Sframe went from 0 to 9, 
meaning the value was 10). So the character only walked 3 steps per tile, which was decent.

Now let's fix this and make it so that he walks normally when you decrease the multiplier.

In clsSprite we have an Animate sub, and in there we have the line:
If SFrame = 9 Then
'increment the next frame

Well, change 9 to sFrameLimit (declare this at the top as an Integer)
If Sframe = SFrameLimit then

Now we'll adjust the SFrameLimit according to the multiplier numerator.

Adjust the Animate() sub like so:
    Public Sub Animate()
        SFrame += 1
        Select Case MultiplierNumerator
            Case Is >= 30
                SFrameLimit = 9
            Case Is >= 15
                SFrameLimit = 6
            Case Is >= 6
                SFrameLimit = 3
        End Select
        If SFrame > SFrameLimit Then SFrame = SFrameLimit
        If SFrame = SFrameLimit Then
            SFrame = 0
            ' Make him move
            Frame += 1
            ' Don't let it go too high!
            If Frame = 3 Then Frame = 0
        End If
    End Sub

There you go. Now he won't be doing 1 step per tile....jeez ;).

If you want to do a performance difference test, put the new supersized map into RPG Engine IV and give it a shot. It's considerably slower! This Optimization tutorial
should have made a difference for you.

Hope you enjoyed this tutorial - and I hope it didn't seem too rushed - I do realize that I was moving at a fast pace here.

The Source Code for this tutorial is located here:

You can also locate this by logging in to vbProgramming Forums and going to:
Tutorials > Tutorial Source Code > Source Code