V tomto díle článku si ukážeme třetí způsob, jak vytvořit aplikaci pod rozhraním DirectX. V prvním dílu jsme si ukázali postup pomocí časovače, což bylo příliš neobratné. V minulém dílu jsme používali herní smyčku, která má ovšem také své neduhy. Nejlepší cesta je využívat překreslovací událost formuláře Paint. V ní zavoláme proceduru Render a pak zavoláme Me.Invalidate, což je metoda, která oznámí systému, že formulář se trošku změnil a že bude třeba jej v budoucnu překreslit. Nepotřebujeme Application.DoEvents, protože Invalidate nevykresluje hned, ale až když na to má systém čas.
Vytvořte si tedy nový projekt s názvem Snow typu Windows Appliaction. Formulář přejmenujte v Solution Exploreru na formSnow. Přidejte naše známé knihovny do referencí, otevřete okno kódu formuláře, vymažte jeho obsah, a nakopírujte tam základní procedury z minulého dílu.
Imports Microsoft.DirectX
Imports Microsoft.DirectX.DirectDraw
Public Class formSnow
Dim dev As Device
Dim backBuffer As Surface, backBufferDesc As SurfaceDescription
Dim frontBuffer As Surface, frontBufferDesc As SurfaceDescription
Dim clip As Clipper
Private Sub formSnow_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
Static mm As Integer = 0
mm += 1
If mm > 5 Then Me.Close()
End Sub
Private Sub formSnow_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Click
Me.Close()
End Sub
Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
Me.Close()
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
Me.SetStyle(ControlStyles.Opaque, True)
createDevice()
createBuffers()
loadContents()
End Sub
Public Sub createDevice()
dev = New Device()
dev.SetCooperativeLevel(Me, CooperativeLevelFlags.FullscreenExclusive)
End Sub
Public Sub createBuffers()
If Not Me.Focused Then Exit Sub
dev.SetCooperativeLevel(Me, CooperativeLevelFlags.FullscreenExclusive)
If frontBuffer IsNot Nothing Then
frontBuffer.Restore()
Else
frontBufferDesc = New SurfaceDescription()
frontBufferDesc.SurfaceCaps.PrimarySurface = True
frontBufferDesc.SurfaceCaps.Flip = True
frontBufferDesc.SurfaceCaps.Complex = True
frontBufferDesc.BackBufferCount = 1
frontBuffer = New Surface(frontBufferDesc, dev)
End If
If backBuffer IsNot Nothing Then
backBuffer.Restore()
Else
backBufferDesc = New SurfaceDescription()
backBufferDesc.SurfaceCaps.BackBuffer = True
backBuffer = frontBuffer.GetAttachedSurface(backBufferDesc.SurfaceCaps)
End If
clip = New Clipper(dev)
clip.Window = Me
frontBuffer.Clipper = clip
End Sub
Public Sub loadContents()
End Sub
Public Sub animateSnow()
End Sub
Public Sub Render()
If Not Me.Created Then Exit Sub
If frontBuffer Is Nothing Or backBuffer Is Nothing Then Exit Sub
If Not Me.Focused Then Exit Sub
Try
backBuffer.ColorFill(Color.Black)
animateSnow()
frontBuffer.Flip(backBuffer, FlipFlags.Wait)
Catch ex As Exception
If ex.GetType() Is GetType(SurfaceLostException) Then
createBuffers()
End If
End Try
End Sub
Private Sub formSnow_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
Render()
Me.Invalidate()
End Sub
V tomto bloku kódu je již vše, co je třeba k vykreslení prázdného okna. Na začátek jsem přidal procedury Form_KeyDown, Form_MouseMove a Form_Click, které při jejich zavolání ukončí náš spořič. Procedura MouseMove má čítač, který počítá, kolikrát se zavolala, a pokud se zavolala alespoň 5x, ukončí program. To proto, že tato procedura má ve zvyku spouštět se, i když se myš ani nehne, zejména při startu programu ve starších verzích Windows.
Úplně na konci je procedura Paint, která renderuje a připravuje obrazovku pro překreslení. Ta zastupuje smyčku, kterou jsme použili v minulém díle.
Načtení obrázku snéhové vločky
Snéhovou vločku si stáhněte tak, že pravým tlačítkem klikněte na tento obrázek
a vyberete volbu
Uložit obrázek. V Solution Exploreru klikněte dvakrát na položku
My Project, rozbalí se okno vlastností projektu.
Přejděte na záložku Resources a rozbalte nabídku tlačítka Add Resource, kde vyberete položku Add Existing File.... Vyberte stažený soubor a potvrďte volbu. Tím jsme obrázek zaintegrovali do projektu a nemusíme jej dodávat spolu s aplikací, je již přibalen v EXE souboru. To právě potřebujeme, spořič obrazovky by měl být pouze jediný soubor, který se nahraje do složky System32 v adresáři s nainstalovaným operačním systémem. Přípona se musí změnit na SCR, aby se soubor objevil v nabídce spořičů obrazovky.
Na první záložce v okně vlastností projektu zaškrtněte uprostřed volbu Make single instance application, která zajistí, že tato aplikace nepůjde spustit dvakrát za sebou. To má totiž Windows občas také ve zvyku, spouštět spořiče hned několikrát. Asi aby se víc uspořilo :-).
Nyní k načtení obrázku sněhové vločky, je to opakování, známe to již z minulého dílu. Do procedury LoadContents vložte tento kód:
Dim desc As New SurfaceDescription()
flake = New Surface(My.Resources.vlocka, desc, dev)
Dim ck As New ColorKey()
flake.SetColorKey(ColorKeyFlags.SourceDraw, ck)
Nezapomeňte do deklarací nahoře vložit tento řádek, kde deklarujeme proměnnou flake:
Dim flake As Surface
Princip generování a pohybu sněhových vloček
Algoritmus, který jsem vymyslel a zvolil, je trochu komplikovaný, K zachování věrnějšího efektu budeme generovat vločky různých velikostí, které budou padat různou rychlostí (která do určité míry bude záviset na jejich velikosti), a tyto vločky se budou ještě pohybovat vodorovně, aby byl jejich pohyb zajímavější.
Každá vločka si při vytvoření automaticky zvolí pozici X, na které začíná. Pozice Y se nastaví kousek nad horní okraj obrazovky.
Dále se nastaví velikost vločky. Minimální je 8x8 pixelů, maximální je 36x36 pixelů. Protože chceme, aby větších vloček bylo více, použijeme kvadratickou závistost namísto lineární. Vygenerujeme náhodné číslo od 0 do 24x24 a to odmocníme, dostaneme číslo od 0 do 24. Přičteme k němu 8 a máme velikost vločky. Větších vloček bude více, protože vyšší čísla budou pravděpodobnější. Například na číslo menší nebo rovno 12 se odmocní jen čísla od 0 do 144, ale na číslo větší nebo rovno 12 se odmocní hodnoty od 144 do 576, což je výrazně větší interval a tudíž i výrazně větší pravděpodobnost. Tím pádem bude menších vloček méně než těch větších.
Rychlost spočítáme podle převrácené hodnoty druhé mocniny velikosti vločky, od které odečtěme 7. K tomu přidáme ještě náhodné číslo vhodně vynásobené, aby poměr byl vyvážený. Pokud by rychlost vyšla moc velká, rušilo by to, takže ji funkcí Math.Min případně zmenšíme na nějaký limit. Tento poměrně komplikovaný vztah zajistí, že menší vločky se budou pohybovat rychleji než vločky větší. Ke zvětšení účinku používáme opět druhou mocninu velikosti.
Každá vločka bude také harmonicky kmitat podél vodorovné osy, abychom docílili zajímavějšího efektu. Harmonický pohyb se počítá funkcí sinus. Tato funkce, jak jistě všichni víme, vrací hodnoty od -1 do 1. Protože pohyb o 1 pixel není téměř vidět, musíme hodnotu vhodně vynásobit, a to tzv. amplitudou, což je maximální výchylka. Tu určíme pro každou vločku náhodně. Dále potřebujeme vygenerovat úhlovou rychlost, tj. jak rychle se mení úhel, pomocí nějž počítáme sinus. Tento úhel neustále roste, a hodnoty funkce kmitají tam a zpět. No a samozřejmě si každá vločka musí pamatovat svůj aktuální úhel, abychom k němu mohli přičíst v dalším snímku úhlovou rychlost a přepočítat vodorovnou pozici.
A na závěr má každá vločka svůj lifetime, tedy čas v milisekundách, za jaký se rozpustí. V praxi to vypadá tak, že některé vločky zmizí ještě před tím, než zajedou za okraj obrazovky.
Celé to vypadá složitě, pokud algoritmus nechápete, nevadí, jednodušší (bez kmitání a se stejnou velikostí vloček, ale náhodnou rychlostí) byste jistě zvládnuli.
Do deklarací (mimo procedury) přidejte tento kód. Obsahuje datovou strukturu pro data vločky, pole pro tisíc sněhových vloček, generátor náhodných čísel a proměnné pro počítání délky posledního snímku.
Structure Vlocka
Dim tX, X, Y As Single
Dim Speed As Single
Dim Lambda As Single
Dim LambdaSpeed As Single
Dim LambdaFactor As Single
Dim LifeTime As Single
Dim Size As Integer
End Structure
Dim v(999) As Vlocka
Dim LastTime As Integer = Environment.TickCount()
Dim DeltaTime As Integer
Dim Generated As Single = 0
Dim r As New Random()
V kódu máme již připravenou proceduru animateSnow, která se spustí jedno za každý snímek. O kousek posune sněhové vločky, pokud to jde, tak vygeneruje nové, a nakonec vločky vykreslí. Proměnná Generated určuje, kolik vloček se za snímek již vygenerovalo, aby se vločky generovaly postupně. Zde je tedy tato procedura:
DeltaTime = Environment.TickCount - LastTime
LastTime = Environment.TickCount
Generated -= 2
For i As Integer = 0 To v.Length - 1
With v(i)
If (.LifeTime <= 0 Or .Y > 768) And Generated < 10 Then
.X = r.Next(dev.DisplayMode.Width)
.Y = -36
.Lambda = 0
.LambdaFactor = r.NextDouble() * 10 + 2
.LambdaSpeed = r.NextDouble() * 0.001 + 0.001
.LifeTime = (r.Next(9000) + 9000)
.Size = CInt(Math.Sqrt(r.Next(24 * 24) + 8 * 8))
.Speed = Math.Min(1 / (.Size * .Size - 14 * .Size + 49) + 0.1 + r.NextDouble() * 0.05, 0.2)
Generated += 1
End If
If .LifeTime > 0 And .Y < dev.DisplayMode.Height Then
.LifeTime -= DeltaTime
.Y += .Speed * DeltaTime
.Lambda += .LambdaSpeed * DeltaTime
.tX = .X + .LambdaFactor * Math.Sin(.Lambda)
BltStretch(flake, .tX, .Y, .Size, .Size)
End If
End With
Next
Téměř posledním momentem je procedura BltStretch, která vykresluje obrázky se zvětšením. Stará se o to, abychom nekreslili mimo buffer, což by vyvolávalo chyby, naše vločky tedy ořízne a vykreslí jen jejich viditelné části.
Public Sub BltStretch(ByRef s As Surface, ByVal x As Integer, ByVal y As Integer, ByVal imageWidth As Integer, ByVal imageHeight As Integer)
Dim left As Integer = 0, w As Integer = s.SurfaceDescription.Width, width As Integer = imageWidth
Dim top As Integer = 0, h As Integer = s.SurfaceDescription.Height, height As Integer = imageHeight
If y < 0 Then top = -y : height = imageHeight + y
If y + imageHeight > dev.DisplayMode.Height Then height = dev.DisplayMode.Height - y
If x < 0 Then left = -x : width = imageWidth + x
If x + imageWidth > dev.DisplayMode.Width Then width = dev.DisplayMode.Width - x
If x + imageWidth > 0 And y + imageHeight > 0 And _
x < dev.DisplayMode.Width - 1 And y < dev.DisplayMode.Height - 1 Then
backBuffer.Draw(New Rectangle(x + left, y + top, width, height), s, _
New Rectangle(left / imageWidth * w, top / imageHeight * h, width / imageWidth * w, height / imageHeight * h), DrawFastFlags.SourceColorKey)
End If
End Sub
Teď by nám mělo vykreslování padajícího sněhu fungovat.
To nejlepší nakonec
Nyní se musíme podívat, jak fungují spořiče obrazovky. Jak jsem již psal nahoře, jedná se o normální EXE soubor, který má ovšem příponu změněnou na SCR. Musí být ve složce System32 (případně System na Windows 95, 98 a ME), kterou najdete v adresáři s nainstalovaným operačním systémem Windows.
Pokud tento soubor spustíme jen tak, nebo s parametrem /c, musí se zobrazit formulář s nastavením. My však zatím nastavení podporovat nebudeme, takže bude vhodné pouze zobrazit hlášku, že tento spořič nemá žádné nastavení. Pokud spořič spustíme s parametrem /s, spustí se samotný spořič. Pokud jej spustíme s parametrem /p, náš spořič se ukončí, protože nebudeme podporovat jeho náhled v malém okénku v dialogu výběru spořiče obrazovky. Pokud bude parametr jiný, tak spořič také nespustíme.
Pro spouštění a manipulaci s událostmi na úrovni aplikace má Visual Basic speciální třídu MyApplication. Dostaneme se k ní přes okno vlastností projektu, kde hned na první záložce klikneme na okno Application Events. Otevře se příslušné okno kódu, do kterého vložíme tuto proceduru:
Private Sub MyApplication_Startup(ByVal sender As Object, ByVal e As Microsoft.VisualBasic.ApplicationServices.StartupEventArgs) Handles Me.Startup
If e.CommandLine.Count = 0 Then
e.Cancel = True
Else
Select Case e.CommandLine(0).ToLower.Trim
Case "/p"
e.Cancel = True
Case "/s"
Me.MainForm = formSnow
Case Else
MsgBox("Pro tento spořič není možné žádné nastavení.", MsgBoxStyle.Information, "Snow")
e.Cancel = True
End Select
End If
End Sub
A tím je náš spořič obrazovky hotov. Stačí jen v menu Build vybrat příkaz Build Project a vygeneruje se nám spustitelný soubor. Ten najdete nejspíše v Dokumentech, kde je složka Visual Studio 2005\Projects. V ní by měly být všechny vaše projekty, pokud je tedy neukládáte jinam. Najděte si složku svého projektu a v podsložce Bin\Release byste měli najít svůj soubor EXE. Ten zkopírujte do systémové složky Windows, změňte příponu na SCR, a to je celé.
Pravým tlačítkem klikněte na plochu, vyberte Vlastnosti a na kartě Spořič obrazovky si vyberte Snow. Můžete dát náhled, a pak se už jen kochat krásným padajícím sněhem.
V příštím díle se již vrhneme na 3D grafiku.