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