|
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
|