Tämä koodi antaa mahdollisuuden laittaa pelikoodin pyörimään tiettyjä kertoja sekunnissa, joka on erittäin hyödyllinen peleissä. Koodi on joustava ja tapahtumia voi laittaa pyörimään useita yhtäaikaisesti, esimerkkikoodissa on kolme tapahtumaa. Kontrolli on todella tarkka ja koodi pitää hyvin huolen siitä, että pelikoodit pyörivät sen tietyn määrän sekunnissa. Samalla koodi pitää automaattisesti huolen siitä, että prosessorikäyttö pysyy minimissä (tämä esimerkki näyttää kokoajan 0% huolimatta siitä, että ohjelma on jatkuvassa loopissa).
Esimerkkikoodissa on siis kolme eri tapahtumaa:
1. Sisäinen koodinkäsittely (240 kertaa sekunnissa, TICK_INTERNAL)
2. Ruudunpäivitysnopeus (60 kertaa sekunnissa, TICK_DISPLAY)
3. Sekunttipäivitys (kerran sekunnissa, TICK_SECOND)
Nämä tapahtumat on määritelty TICK_INDEX enumiin modTicks.bas-tiedostossa. Voit lisätä tähän omia tapahtumiasi tai muuttaa vanhoja, tyylillä TICK_OMA = 3 ja sitten lisätä päivitystahdin Init_Ticks-proseduuriin ja myös koodinpätkän Main-proseduuriin. Suosittelen lisäämään arvoa varten oman constantin esimerkin mukaisesti, jotta arvoa on helppo muuttaa suoraan moduulin koodin alusta.
Esimerkkikoodi ei tee mitään muuta kuin sen, että se kertoo sekunnin välein Form1.Captioniin kuinka monesti koodit on suoritettu ja kuinka kauan koodi on ollut toiminnassa.
Näin saat koodin toimintaan:
- Aloita uusi projekti.
- Luo uusi moduuli, nimeä se nimelle modTicks ja kopioi ensimmäinen koodilistaus siihen.
- Luo toinen moduuli, nimeä se nimelle modMain ja kopioi toinen koodilistaus siihen.
- Kopioi kolmas koodilistaus Form1:een.
- Tärkeää! Projekti pysyy käynnissä Main-proseduurin kautta. Katso siis valikosta Project > Project1 Properties ja aseta Startup Objectiksi Sub Main.
Loppusanoiksi vielä se, että kannattaa pitää suoritettava koodi mahdollisimman kevyenä. Nopeudelle on itsemurhaa alkaa esimerkiksi pelleilemään variant-muuttujilla, joten määrittele Dimeillä tarkasti mitä muuttujia haluat käyttää. Kaikkein eniten luuppaavissa tapahtumissa kannattaa välttää myös string-muuttujan käyttöä, koska muuten prosessorikäyttö ampaisee helposti korkealle. On myös hyvä pitää Main-proseduurin koodi lyhyenä, siispä tee uusiin moduuleihin proseduureja tai peräti class moduuleja, joissa suoritetaan pääkoodia.
Tapahtumien moniajo vaatii myös uudenlaista ajattelutapaa: on mahdollista hajauttaa usein kutsuttujen proseduurien loopit toimimaan useamman kutsun aikana sen sijaan, että joka kutsulla looppi suoritettaisiin kokonaisena (joka olisi hyvin tappavaa suorituskyvylle, varsinkin jos kyse on esimerkiksi useamman vihollisen tekoälystä). Tämä on kuitenkin jo aivan eri aihe, tässä pääasia on näyttää tarkasti aikataulussa pysyvä tapahtumien hallinta ja itse rakennettu moniajo. Todellinen Windowsin tarjoama moniajo mahdollistaisi sen, että peli ei pysähtyisi esimerkiksi formia liikuteltaessa kuten nyt tapahtuu, mutta sekin on oma aiheensa.
modTicks.bas
Option Explicit
Private Const FRAMES_PER_SECOND = 60
Private Const MOVES_PER_SECOND = 240
Public Enum TICK_INDEX
TICK_INTERNAL = 0 ' for internal processing (moving objects, checking keyboard state etc.)
TICK_DISPLAY = 1 ' for updating the screen
TICK_SECOND = 2 ' for displaying FPS information once a second
End Enum
Private Type TICKS
Count As Long ' number of ticks done
Ending As Currency ' the next ending tick
Freq As Currency ' tick frequency
End Type
Private dblFreqToMS As Double ' to convert frequency to millisecond
Private curLateTick As Currency ' start skipping when more late than this value
Private udtTicks(2) As TICKS ' the array to hold tick information
' the API declarations
Private Declare Function QueryPerformanceCounter Lib "kernel32" (lpPerformanceCount As Currency) As Long
Private Declare Function QueryPerformanceFrequency Lib "kernel32" (lpFrequency As Currency) As Long
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
' this function will wait for the next tick and returns the index that is about to happen
Public Function GetNextTickIndex() As TICK_INDEX
Dim lngA As Long, curDifference As Currency, enmIndex As TICK_INDEX
Dim curTick As Currency
' we skip the first item, because we default for it
For lngA = 1 To UBound(udtTicks)
' check if this tick will be processed next
If udtTicks(lngA).Ending < udtTicks(enmIndex).Ending Then enmIndex = lngA
Next lngA
' increase tick index
udtTicks(enmIndex).Count = udtTicks(enmIndex).Count + 1
' get current tick
QueryPerformanceCounter curTick
' because of the falling behind prevention, we need to set this to correct value
If udtTicks(enmIndex).Ending = 0 Then udtTicks(enmIndex).Ending = curTick
' then wait for the tick
curDifference = udtTicks(enmIndex).Ending - curTick
If curDifference >= 0 Then
lngA = CLng(CDbl(curDifference) * dblFreqToMS)
' do we need to sleep? (this prevents 100% processor usage!)
If lngA > 0 Then
Sleep lngA
' prevent being late
QueryPerformanceCounter curTick
curDifference = udtTicks(enmIndex).Ending - curTick
' set the next tick
SetNextTick enmIndex, -curDifference
Else
' set the next tick
SetNextTick enmIndex
End If
Else
If curDifference > curLateTick Then
' WE ARE LATE, but not too badly
SetNextTick enmIndex, -curDifference
Else
' WE ARE BADLY LATE, so we have to skip processing :/
' otherwise on slow computers the things just get worse and worse...
SetNextTick enmIndex
End If
End If
' return the tick index
GetNextTickIndex = enmIndex
End Function
Public Function GetTickCount(ByVal Index As TICK_INDEX) As Long
' return the current tick count
GetTickCount = udtTicks(Index).Count
End Function
Public Sub Init_Ticks()
Dim curFreq As Currency, curTick As Currency
' we need a good timer: use queryperformancecounter
QueryPerformanceFrequency curFreq
' calculate frequency to second value
dblFreqToMS = 1000 / CDbl(curFreq)
' when we skip stuff?
curLateTick = -(curFreq / 20)
' set frequencies
udtTicks(TICK_INTERNAL).Freq = curFreq / MOVES_PER_SECOND
udtTicks(TICK_DISPLAY).Freq = curFreq / FRAMES_PER_SECOND
udtTicks(TICK_SECOND).Freq = curFreq
End Sub
Public Sub ResetTickCount(ByVal Index As TICK_INDEX)
udtTicks(Index).Count = 0
End Sub
Private Sub SetNextTick(ByVal Index As TICK_INDEX, Optional ByVal curFallBehind As Currency = 0)
Dim curTick As Currency
' get current tick
QueryPerformanceCounter curTick
' set next event to future
udtTicks(Index).Ending = curTick + udtTicks(Index).Freq - curFallBehind
End SubmodMain.bas
Option Explicit
' nämä kutsut ovat parempaa DoEventsin hallintaa varten
' mitä sitä turhaan hidasta DoEventsiä kutsumaan, jos sille ei ole tarvetta...
Public Const QS_KEY = &H1&
Public Const QS_MOUSEMOVE = &H2&
Public Const QS_MOUSEBUTTON = &H4&
Public Const QS_POSTMESSAGE = &H8&
Public Const QS_TIMER = &H10&
Public Const QS_PAINT = &H20&
Public Const QS_SENDMESSAGE = &H40&
Public Const QS_HOTKEY = &H80&
Public Const QS_ALLINPUT = (QS_SENDMESSAGE Or QS_PAINT Or QS_TIMER Or QS_POSTMESSAGE Or QS_MOUSEBUTTON Or QS_MOUSEMOVE Or QS_HOTKEY Or QS_KEY)
Public Const QS_MOUSE = (QS_MOUSEMOVE Or QS_MOUSEBUTTON)
Public Const QS_INPUT = (QS_MOUSE Or QS_KEY)
Public Const QS_ALLEVENTS = (QS_INPUT Or QS_POSTMESSAGE Or QS_TIMER Or QS_PAINT Or QS_HOTKEY)
Public Declare Function GetQueueStatus Lib "user32" (ByVal qsFlags As Long) As Long
' kun tämän muuttaa arvoon True, ohjelma sulkeutuu
Public Quit As Boolean
Private Sub Main()
' alusta päivityksenhallinta
Init_Ticks
' näytä pääformi
Form1.Show
' jatketaan kunnes Quit on asetettu arvoon True
Do While Not Quit
' odota kunnes seuraava tapahtuma tapahtuu
' GetNextTickIndex löytyy modTicks.bas-tiedostosta
' ohjelma on pysähtynyt GetNextTickIndexiin kunnes jotain pitäisi tapahtua
' funktio palauttaa arvon, joka kertoo mikä koodi pitäisi nyt suorittaa
Select Case GetNextTickIndex
' sisäisen koodin hallinta
Case TICK_INTERNAL
' tänne voi kirjoittaa esim.:
' - ammusten ja pelaajien liikuttelun
' - törmäysten tunnistamisen
' - näppäimistön ja hiiren lukemisen
' ruudunpäivityksen hallinta
Case TICK_DISPLAY
' tänne voi kirjoittaa ruudunpäivityksen, esim.:
' - hae kopio taustabufferista (jossa ei ole objekteja yms.)
' - piirrä ruudulla näkyvät ammukset, pelaajat ja viholliset kopiobufferiin
' - piirrä bufferi ruudulle näkyviin
' sekunttipäivitys
Case TICK_SECOND
' näytä formin otsikossa sekuntin välein kuinka paljon on tapahtumia tapahtunut
Form1.Caption = _
"FPS: " & Str$(GetTickCount(TICK_DISPLAY)) & _
" | MPS: " & Str$(GetTickCount(TICK_INTERNAL)) & _
" | " & Format$(TimeSerial(0, 0, CInt(GetTickCount(TICK_SECOND) And &HFFFF)), "n:ss")
' nollaa laskurit
ResetTickCount TICK_DISPLAY
ResetTickCount TICK_INTERNAL
End Select
' estä ohjelmaa jäämästä jumiin antamalla Windowsille aikaa suorittaa moniajoa
' tämä pitää huolen siitä, että DoEventsiä kutsutaan vain silloin kun sille on tarvetta
' tämä on tärkeää, koska DoEvents on hidas ja tätä koodirykelmää suoritetaan todella monesti sekuntia kohden
If GetQueueStatus(QS_SENDMESSAGE Or QS_ALLEVENTS) <> 0 Then DoEvents
Loop
' poista Form1 muistista
Set Form1 = Nothing
End SubForm1
Option Explicit
Private Sub Form_Unload(Cancel As Integer)
' kertoo että nyt lopetetaan
Quit = True
End SubKaipaako kukaan jotakin laajennettua esimerkkiä siitä, miten tuota voi käyttää hyväksi?
Tossa osoitteessa on tuolla koodilla tehtynä yksi Sprite juttu :)
http://koti.mbnet.fi/joresoft/Download/Esim/
Kuva => http://koti.mbnet.fi/joresoft/images/Kuva-SpritenSisaan.png
nopeni hieman, kun muutin timerin tuohon koodiin :)
Koodissa on nyt se Quit = True :) muutin ennen kuin meen nukkumaan :)
Hieno koodinpätkä. DoEvents-kikka on aika ovela. GetTickCount on vähän hämäävä nimi samannimisen API-funktion takia.
Jeps, se sattui vahingossa, mutta en sitten jaksanut keksiä sille muutakaan nimeä kun koodi oli jo levityksessä. Eikä GetTickCountille ole käsittääkseni tarvetta tätä käyttävässä ohjelmassa :)
Ja kiitokset Jorelle graafisen esimerkin väsäämisestä!
Lisäkorjausta tuohon Joren uuteen koodiin: Main-proseduurin loppuun pitää lisätä
Unload f1 Unload f2
Muuten ohjelma jää muistiin kykkimään, koska kaikkia formeja ei ladata pois muistista.
Aihe on jo aika vanha, joten et voi enää vastata siihen.