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

vbProgramming | Tutorials | Miscellaneous | FPS
    vbProgramming 
Tutorials | Miscellaneous | FPS
 
     

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

 

 

Time to see how fast your game really is!

Introduction
Welcome to the FPS (Frames Per Second) tutorial. We'll start off by using the previous tutorial (Smart Key Movement) as a base. You don't have to do the tutorial, but just be sure you take a look at it and know what it does.

Before getting into graphics, I'll explain to you what Environment.TickCount does.

Environment.Tickcount
The best way to learn things is to experiment with them.  First, in the form designer, set form1's WindowState property to Maximized.

In form1_keydown, at the end:
Me.Text = Environment.TickCount

Run it and hold down a key. it might freak you out a bit. You should get some huge number. Still can't figure out what it does? Try this:

Me.Text = Environment.TickCount / 1000

Hold down a key and stare at the number you see for about 10 seconds. You should see the pattern. The number before the decimal point is being increased every second.

Now you see what's going on? Environment.TickCount is the number of milliseconds your program has been running (We divided by 1000 to get the number of seconds)
However, it seems to start with a random number and work it's way up from there. This is not the case however. I'm not 100% sure where this 'random starting point' comes from, but it has something to do with:
a) How long you've had .NET open... or
b) The total number of milliseconds that you've run [any] program.

Don't worry about the random number, however. It will start with something "Random", but we can get the number of elapsed seconds like so:
Dim start As Single

In form1_load:
start = Environment.TickCount

Back in form1_KeyDown:
Me.Text = "Elapsed time: " & (Environment.TickCount - start) / 1000  'Divide by 1000 to get the number of seconds.

Hold down a key and you should see the number of seconds the program has been running (it must be annoying to hold down a key, but we'll get to looping it so it does it automatically, soon).

Well. Now you know what Environment.TickCount does. Here's the code you will be working with for the remainder of the tutorial (just paste it in)

Code Base
This code was taken from the previous tutorial (Smart Key Movement - "Method 1")

    Dim zx As Single
    Dim zy As Single
    Dim hLeft As Boolean
    Dim hRight As Boolean
    Dim hdown As Boolean
    Dim hup As Boolean

    Dim FormClosed As Boolean

    Dim Start As Single

   '<Windows Form Designer Generated Code>   <-- Use your own.

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Start = Environment.TickCount
        Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or ControlStyles.DoubleBuffer Or ControlStyles.UserMouse, True)
        Me.Show()
        Me.Focus()
        LoopMovement()
    End Sub
    Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyDown
        Select Case e.KeyCode
            Case Keys.Down
                hdown = True
            Case Keys.Up
                hup = True
            Case Keys.Left
                hLeft = True
            Case Keys.Right
                hRight = True
        End Select
    End Sub

    Private Sub Form1_KeyUp(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyUp
        Select Case e.KeyCode
            Case Keys.Down
                hdown = False
            Case Keys.Up
                hup = False
            Case Keys.Left
                hLeft = False
            Case Keys.Right
                hRight = False
        End Select
    End Sub

    Public Sub LoopMovement()
        Do While Not FormClosed
            Application.DoEvents()
            Me.Text = "Elapsed time: " & (Environment.TickCount - start) / 1000 'Divide by 1000 to get the number of seconds
            If hup Then
                zy -= 1
            End If
            If hdown Then
               zy += 1
            End If
            If hLeft Then
               zx -= 1
            End If
            If hRight Then
               zx += 1
            End If
            Me.Invalidate()
        Loop
    End Sub

    Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
        e.Graphics.FillRectangle(Brushes.Red, zx, zy, 30, 30)
    End Sub

    Private Sub Form1_Closed(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Closed
        FormClosed = True
    End Sub

That should start you off. Run it and be sure it works. Adjust the increment/decrement of zx and zy (in the LoopMovement sub) to what you're comfortable with. Oh note one change, I moved the Me.Text line into LoopMovement() so that it will do it automatically.

Getting the FPS
FPS stands for Frames Per Second. The number of Frames Per Second basically means how many times it renders per second (aka the number of times it goes through the LoopMovement loop - whether you're moving or not, it renders the form).

To do this, we'll need to (obviously) count the number of frames it renders, (one per loop) and we'll need an FPS variable as well.
Dim FPS As Integer
Dim FrameCount  As Integer

We'll also need a variable which counts when you started the loop.
Dim LoopStart As Integer
*When one second has elapsed from the time you started the loop, we count the number of Frames (FrameCount) and store that into our FPS variable.*
You really ought to reread that sentence and make sure you fully understand what I'm talking about.

To figure out if one second has elapsed since you started the loop:(Don't type this in yet)
If Environment.TickCount - LoopStart >= 1000 Then
'One second has passed
End If

The reason you use >= instead of = is to "play it safe" basically. Things could happen and it might not 'hit' 1000 (it could be 1000.00000001 or something).

After you store the FPS variable, you reset LoopStart and reset FrameCount (so we can measure the FPS again).

So here's the code to do that:
Do this right in LoopMovement right after Do While Not FormClosed

'One second has passed
If Environment.TickCount - LoopStart >= 1000 Then
 
  'Store the FPS.
    FPS = FrameCount
   
'Reset LoopStart (one second has passed)
    LoopStart = Environment.TickCount
   
'Reset FrameCount because it's time to count the FPS again
   FrameCount = 0
End If

And after Me.Invalidate:
'Another frame has been rendered
FrameCount += 1

Now in form1_Paint (after you draw the rectangle)
e.Graphics.DrawString("FPS: " & FPS, New Font("Courier New", 16, FontStyle.Bold, GraphicsUnit.Pixel), Brushes.Blue, 100, 100)

Run it. Don't worry if you see a 0 or a 1 at first. I get around 65 FPS (with Frontpage, Windows Media Player, and about 3 different .NET IDEs running).
But before we continue, be sure you understand the code up there. It can really mess with your mind if you think too hard. Just pretend you're the compiler and interpret the data like a machine and you might get it (to understand the machine, you gotta BE the machine)

Be sure that your Form1.WindowState property is Maximized, because when it's normal sized (300x300), I get 721 FPS, so don't brag about your "high FPS of 120" if you haven't maximized it.
I'm running at 1280x1024 resolution, so my "Maximized" size might be considerably bigger (or in the future, smaller) than yours.

(Ye with an AMD FX-55 and Dual 6800 Ultras, I bow down to thee).

Hmm. Something to think about, the latest and greatest thing out there on 1/24/05 is:
FX-55 (~$900)
4 GB RAM (Not sure. 1 GB of good ram costs 250, so 4 GB should cost $1000)
Dual 6800 Ultra (400*2 = 800)
I wonder how much this will change as time goes on. I'm not too concerned with hard drive space though, most people use multiple drives (My friend has 3 * 250 GB HDs). Maybe by then the 6800 Ultras will become crap and integrated.

One day I'll look at this website again and remember how good this was "way back then."

Oh now it's time to do some speed tests.

"Benchmarks"
Why are we doing this? Because I feel like it. Feel free to post your specs and FPS on vbProgramming just for the heck of it.
My specs:

Pentium 4 3.0c GHz (Northwood, socket 478). Max overclock (with stock cooling) 3.6 ghz. Planning to get better cooling.
ASUS P4P800-e Deluxe with built-in ASUS WiFi-b.
Crucial XMS Low Latency PC3200 2 * 512 MB RAM.
Sapphire ATi Radeon 9800 Pro (Max overclock: to XT speeds).
<NZXT Guardian Case, DVD Burner, DVD Drive, 80 GB, more fans...etc>

I'm sure most of you have better specs than me.
I built this on June 2004 for $1400, right now (1/24/05) these parts are around $1000.

Remember earlier I told you that overriding the onpaintbackground sub will increase the speed? In case you forgot how to do this, just select Overrides (instead of Base Class events) on the left dropdown menu and select OnPaintBackground on the right dropdown menu.

After overriding the OnPaintBackGround, I got a quick boost up to 85 FPS.

Oh I hope you realize that double buffering will slow down your program too (double buffering stores what you are about to render to memory and then renders it).
Comment out this line (form1_load:)
 Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or ControlStyles.DoubleBuffer Or ControlStyles.UserPaint, True)

Run it.....
<There's no background>
Move around, have some fun. There's no background - it's all transparent. Move around, what you draw is "added" on to the screen. It's kind of weird, but fun.
I get a whopping 1375 FPS. However the only reason for this is because there's no background.

Delete the override to OnPaintbackground and now give it a try (so you shouldn't have any OnPaintBackground, nor any SetStyle lines), I get 585 FPS, but it's flicker mania.

Alright, we learned one thing:
If you want to override onpaintbackground, you need to enable DoubleBuffer/AllPaintinginWmPaint/UserPaint.

Since I have a big resolution, 1280x1024 (again, in the future you guys will probably have like 32768x16384 with huge 200" monitors - maybe i'm being a little too optimistic). Since I have a certain resolution, Maximized for me is one size, but for you it's a different size.

Set your form1.WindowStyled property to Normal, and change your form1 size to 640x480 (I'm sure we can all handle that - developing in .NET with 640x480 is a PAIN... give it a try, but don't complain saying your eyes hurt :D).

With the overrides and the Setstyle, I get 305 FPS.

I think I'm going a little too crazy with benchmarks in this tutorial - we'll have a lot more in the Optimization tutorial, where this information will benefit you :).


Uniformity - Method 1: Adjusting Movement Speed
Change things back to the way they were before. Maximized, SetStyle, Overrides...etc.

In this section, we'll learn how to make the game run the speed on all computers (except for the ones who are still stuck in 1985 somewhere .... sorry lol).
The first method (which has it's weak points), is to adjust the movement speed with the FPS. In our example, we want our guy to move about 30 pixels per second. To do this:

MovementIncrement = 30 / FPS

So apply the changes like so:

If hup Then
zy -= 30/FPS
End If
If hdown Then
zy += 30/FPS
End If
If hLeft Then
zx -= 30/FPS
End If
If hRight Then
zx += 30/FPS
End If


I'll explain why we did that with a quick example. Let's say our Frames Per Second is 15. To move him 30 pixels per second, then we would have to move him 2 units per frame (because there are 15 frames per second). Using variables, it's:
PixelsYouWantTheGuyToMovePerSecond / FPS

So, run it BUT WAIT! Wait a full second before moving the character. It works .. etc.

Now, run it again, except push right IMMEDIATELY.  See what happens?  He goes CRAZY and off the screen. This is because the FPS isn't really 1 (in the beginning, the FPS is usually 1). It was 1 in the VERY BEGINNING when you loaded, but within that second, the system became 'stabilized'. So between that first second and the 2nd second, the FPS changed (dramatically), but during that first second, it thought the FPS was 1 (when it was really 85, for me), so it basically moved the guy 30 pixels per frame. That's HUGE.  See the problem is, we can't get the FPS until after that second has elapsed.

Sure, you could keep the player occupied by giving him something to read, or hold off key movement until after the second second (... the 2nd second), but that's dumb. What if the player opens Media Player and starts playing some music during that second and pushed right - things would go crazy (well, first of all that guy would have to be crazy to do all that within a second). It's just... too "risky."  Besides, keeping track of the character's position will be a pain.

Uniformity - Method 2: Limiting the FPS
I like this method a little better. I'll start you off with the code:
      Dim DesiredFrameRate as Integer = 30

    Public Sub LoopMovement()
        Application.DoEvents()
        Do While Not FormClosed
            Application.DoEvents()
            If Environment.TickCount - LoopStart >= 1000 / DesiredFrameRate Then
                If hup Then
                    zy -= 1
                End If
                If hdown Then
                    zy += 1
                End If
                If hLeft Then
                    zx -= 1
                End If
                If hRight Then
                    zx += 1
                End If
                LoopStart = Environment.TickCount
                Me.Invalidate()
            End If
        Loop
    End Sub

I made only a few changes. I just added the / 30 (the 30 is our desired frames per second), and since it's 30 frames per second, we only have to move him 1 unit per 
frame. 

You can get rid of the FPS, Start, and FrameCount variables, because the FPS will always be 30. I recommend you do it this way instead of setting the DesiredFrameRate
to 1500 and making the move increment 30/1500 (which is .02) - not every computer can handle 1500 frames per second. By the way, in either case, if you 
set the DesiredFrameRate too high (higher than the FPS that the player gets), then it'll max out ... at the real FPS, so set it somewhat low, but not rediculously low (for
example, don't set the desiredframerate to 1 and set the move increment to 1/30.... that just won't work)

Change the form size and push right(or any button actually), nothing happens, it all goes the same speed. 

By the way, System.Environment.TickCount isn't 100% accurate (on slower systems), but it is certainly much faster and less of a memory hog than using timers to 
control your frame rate.

I hope you've enjoyed this tutorial. 

I'll just post the code below instead of uploading it to the forums for easy access.

Source code for finding the FPS:
Public Class Form1
    Inherits System.Windows.Forms.Form
    Dim zx As Single
    Dim zy As Single
    Dim hLeft As Boolean
    Dim hRight As Boolean
    Dim hdown As Boolean
    Dim hup As Boolean
    Dim FormClosed As Boolean
    Dim Start As Single
    Dim LoopStart As Single
    Dim FrameCount As Integer
    Dim FPS As Integer
    '<Windows Form Designer Generated Code>
    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Start = Environment.TickCount
        Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or ControlStyles.DoubleBuffer Or ControlStyles.UserPaint, True)
        Me.Show()
        Me.Focus()
        LoopMovement()
    End Sub
    Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyDown
        Select Case e.KeyCode
            Case Keys.Down
                hdown = True
            Case Keys.Up
                hup = True
            Case Keys.Left
                hLeft = True
            Case Keys.Right
                hRight = True
        End Select
    End Sub
    Private Sub Form1_KeyUp(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyUp
        Select Case e.KeyCode
            Case Keys.Down
                hdown = False
            Case Keys.Up
                hup = False
            Case Keys.Left
                hLeft = False
            Case Keys.Right
                hRight = False
        End Select
    End Sub
    Public Sub LoopMovement()
        Do While Not FormClosed
            Application.DoEvents()
            'One second has passed
            If Environment.TickCount - LoopStart >= 1000 Then
                'Store the FPS.
                FPS = FrameCount
                'Reset LoopStart (one second has passed)
                LoopStart = Environment.TickCount
                'Reset FrameCount because it's time to count the FPS again
                FrameCount = 0
            End If
            If hup Then
                zy -= 1
            End If
            If hdown Then
                zy += 1
            End If
            If hLeft Then
                zx -= 1
            End If
            If hRight Then
                zx += 1
            End If
            Me.Invalidate()
            'Another frame has been rendered
            FrameCount += 1
        Loop
    End Sub
    Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
        e.Graphics.FillRectangle(Brushes.Red, zx, zy, 30, 30)
        e.Graphics.DrawString("FPS: " & FPS, New Font("Courier New", 16, FontStyle.Bold, GraphicsUnit.Pixel), Brushes.Blue, 0, 0)
    End Sub
    Private Sub Form1_Closed(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Closed
        FormClosed = True
    End Sub

    Protected Overrides Sub OnPaintBackground(ByVal pevent As System.Windows.Forms.PaintEventArgs)
    End Sub
End Class

Source code for limiting FPS:
Public Class Form1
    Inherits System.Windows.Forms.Form
    Dim zx As Single
    Dim zy As Single
    Dim hLeft As Boolean
    Dim hRight As Boolean
    Dim hdown As Boolean
    Dim hup As Boolean

    Dim DesiredFrameRate as Integer
    Dim FormClosed As Boolean
    Dim Start As Single
    Dim LoopStart As Single
    Dim FrameCount As Integer
    Dim FPS As Integer
    '<Windows Form Designer Generated Code>
    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Start = Environment.TickCount
        Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or ControlStyles.DoubleBuffer Or ControlStyles.UserPaint, True)
        Me.Show()
        Me.Focus()
        LoopMovement()
    End Sub
    Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyDown
        Select Case e.KeyCode
            Case Keys.Down
                hdown = True
            Case Keys.Up
                hup = True
            Case Keys.Left
                hLeft = True
            Case Keys.Right
                hRight = True
        End Select
    End Sub
    Private Sub Form1_KeyUp(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyUp
        Select Case e.KeyCode
            Case Keys.Down
                hdown = False
            Case Keys.Up
                hup = False
            Case Keys.Left
                hLeft = False
            Case Keys.Right
                hRight = False
        End Select
    End Sub
    Public Sub LoopMovement()
        Application.DoEvents()
        Do While Not FormClosed
            Application.DoEvents()
            Me.Text = zx & "," & zy & " Time Elapsed: " & (Environment.TickCount - Start) / 1000
            If Environment.TickCount - LoopStart >= 1000 / DesiredFrameRate Then
                If hup Then
                    zy -= 1
                End If
                If hdown Then
                    zy += 1
                End If
                If hLeft Then
                    zx -= 1
                End If
                If hRight Then
                    zx += 1
                End If
                LoopStart = Environment.TickCount
                Me.Invalidate()
            End If
        Loop
    End Sub
    Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
        e.Graphics.FillRectangle(Brushes.Red, zx, zy, 30, 30)
    End Sub
    Private Sub Form1_Closed(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Closed
        FormClosed = True
    End Sub

    Protected Overrides Sub OnPaintBackground(ByVal pevent As System.Windows.Forms.PaintEventArgs)
    End Sub
End Class

Look up.