Dzisiaj zrobimy sobie bardzo dobrze znaną grę „Snake” znaną głównie ze starych telefonów. Grę taką można udoskonalić o elementy wizualne, jak i elementy rozgrywki, możemy konkurować o tokeny, grać na czas, kreować poziomy z przeszkodami, tylko od nas zależy, co w takiej grze umieścimy. To, co ja zrobiłem, prezentuje w grze do pobrania poniżej, można kreować poziomy, konkurować ze sobą na jednym komputerze z kolegą lub przy użyciu sieci komputerowej:
Snake Neon 1.0
Zaczniemy od stworzenia formy. Nie będziemy dodawać żadnych obiektów, ponieważ wszystko, co będzie potrzebne, będziemy generować na bieżąco.
Rodzaj elementu | Nazwa elementu | Ustawienia |
---|---|---|
Form | Form1 | Name: Form1 Text: Wąż VisualMonsters.cba.pl Size: 416; 439 BackgroundImageLayout: Center DubleBuffered: True |
Timer1 | Timer1 | Interval: 110 |
Opcja BackgroundImageLayout jest opcjonalna, ponieważ forma wizualna gry, będzie wyświetlana jako obrazek tła formy, sposób jej ułożenia jest dosyć istotny. Jeśli wybierzemy opcje, Tile (sąsiadujący) nasza forma wizualna będzie powielona i gracz może nie wiedzieć, gdzie kończy się obszar gry:
No to zaczynamy:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
Public Class Form1 'Realizowe kierunki podczas działania Timera Private Enum Kierunek wPrawo wDol wLewo wGore Pauza End Enum 'Struktura węża Private Structure StrukturaWeza Dim rect As Rectangle Dim x As Integer Dim y As Integer Dim KolorWeza As Color End Structure 'Określa wielkość gry, przyjmujemy kwadrat Dim wielkoscGry As Integer = 20 'Tablica przechowująca pola gry Private pola(,) As Rectangle 'Tablica przechowująca zajęte pola, gra skończy się jeśli wąż ugryzie samego siebie 'ta tablica czuwa nad tym Private zajetepola(,) As Boolean 'zmienna przechowuje aktualny kierunek Private aktKierunek As Kierunek Dim punkty As Integer = 0 Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load 'inicjuje elementy gry, dzięki temu będzie można zagrać ponownie gdy gra się skończy initialize() End Sub Private Sub initialize() 'kierunek startowy aktKierunek = Kierunek.Pauza punkty = 0 'startowe punkty 'metoda określa 'GenerujPlanszeGry() 'GenerujWeza() 'GenerujToken() 'Timer1.Start() End Sub End Class |
Na tym etapie, za dużo wyjaśniać nie trzeba. Będziemy przerabiać sobie poszczególne etapy w sekcji Initialize(). Najpierw wygenerujemy plansze, odblokujmy element:
1 2 3 4 5 6 7 8 9 10 |
Private Sub initialize() 'kierunek startowy aktKierunke = Kierunek.Pauza punkty = 0 'startowe punkty 'metoda określa GenerujPlanszeGry() 'GenerujWeza() 'GenerujToken() 'Timer1.Start() End Sub |
Generowanie planszy jest bardzo zbliżone do tego, które robiliśmy podczas tworzenia gry w statki.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
'główna plansza gry na której będzie wyświetlana grafika Private PlanszaGlowna As New Bitmap(400, 400) Dim kolorPola As New SolidBrush(Color.FromArgb(50, 255, 0, 0)) Private Sub GenerujPlanszeGry() Dim g As Graphics = Graphics.FromImage(PlanszaGlowna) 'określamy wysokość i szerokość planszy Dim szerokosc As Integer = (400 / wielkoscGry) - 1 Dim wysokosc As Integer = (400 / wielkoscGry) - 1 ''powtarzamy inicjacje tablic aby zmienić ich wielkość ReDim pola(wielkoscGry, wielkoscGry) ReDim zajetepola(wielkoscGry, wielkoscGry) 'pętla ustawia wstępne wartości obiektów For j As Integer = 0 To wielkoscGry - 1 For i As Integer = 0 To wielkoscGry - 1 pola(i, j) = New Rectangle((szerokosc + 1) * j, (wysokosc + 1) * i, szerokosc, wysokosc) zajetepola(i, j) = False g.FillRectangle(kolorPola, pola(i, j)) 'dodaje grafikę do bitmapy Next Next 'zmienia tło formy Me.BackgroundImage = PlanszaGlowna g.Dispose() Me.Refresh() End Sub |
Efektem będzie wyświetlenie siatki kwadratów:
Mamy już scenę, na której będzie poruszał się nasz wąż. Odblokujemy teraz element generujący naszego węża, nie będzie on chwilowo widoczny. Ponieważ ożywi go nam dopiero Timer:
1 2 3 4 5 6 7 8 9 10 |
Private Sub initialize() 'kierunek startowy aktKierunke = Kierunek.Pauza punkty = 0 'startowe punkty 'metoda określa GenerujPlanszeGry() GenerujWeza() 'GenerujToken() 'Timer1.Start() End Sub |
Po odblokowaniu dodajemy metodę generującą węża:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
Private snake As Collection 'Kolekcja przechowujaca elementy naszego węża Private DlugoscStartowa As Integer = 3 'Startowa dlugość węża Private BiezacaDlugoscWeza As Integer 'Aktualna dlugość węża Private Sub GenerujWeza() 'generujemy strukturę i tworzymy nową kolekcję Dim sWaz As StrukturaWeza snake = New Collection 'określamy punkt startowy w którym pojawi się nasz wąż Dim x As Integer = 5 Dim y As Integer = 5 'kolekcje liczone są nie od 0 a od jeden For i As Integer = 0 To DlugoscStartowa - 1 'okreslamy parametry startowe dla kazdego elementu struktury sWaz.rect = pola(x, y) sWaz.x = x sWaz.y = y sWaz.KolorWeza = Color.Red 'dodajemy strukturę do kolekcji snake.Add(sWaz) Next 'zajmujemy pole startowe na którym znajduje sie waz zajetepola(x, y) = True BiezacaDlugoscWeza = DlugoscStartowa End Sub |
Powyższa metoda wygeneruje trzy struktury, o tej samej lokalizacji i kolorze. Czas ożywić naszego węża, odblokowujemy timer:
1 2 3 4 5 6 7 8 9 10 |
Private Sub initialize() 'kierunek startowy aktKierunke = Kierunek.Pauza punkty = 0 'startowe punkty 'metoda określa GenerujPlanszeGry() GenerujWeza() 'GenerujToken() Timer1.Start() End Sub |
Teraz przechodzimy to zdarzenia TimerTick:
Mamy zdarzenie i możemy dodać ruch węża. Będzie to metoda PoruszWeza(), która będzie poruszała naszymi obiektami, zgodnie z aktualnie wybranym kierunkiem (zmienna aktKierunek):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick PoruszWeza() Application.DoEvents() End Sub Private poswiata As Brush = New SolidBrush(Color.FromArgb(255, 255, 255)) 'KOLOR ZNIKAJĄCEGO KWADRACIKA POZOSTAWIAJACY OGON Dim jestemWruchu As Boolean = False Dim token As New Rectangle Private Sub PoruszWeza() 'tworzymy czystą planszę o równych wymiarach jak plansza gry Dim podgladgry As New Bitmap(400, 400) Dim g As Graphics = Graphics.FromImage(podgladgry) 'ZATRZYMUJE TIMER Timer1.Enabled = False 'deklarujemy strukturę Dim sWaz As StrukturaWeza 'usówamy ostatnią strukturę z kolekcji, sWaz = snake(snake.Count) 'wybieramy ostatni element g.FillRectangle(poswiata, sWaz.rect) 'element opcjonalny zostawia poświatę ogona snake.Remove(snake.Count) 'usówamy go zajetepola(sWaz.x, sWaz.y) = False 'zwalniamy zajęte pole 'teraz wybieramy pierszy element węża sWaz = snake.Item(1) 'deklarujemy zmienne w których umieścimy lokalizację pierwszego elementu węża Dim x As Integer = sWaz.x Dim y As Integer = sWaz.y 'sprawdzamy czy po dodaniu 1 zgodnie z ustalonym kierunkiem nasz wąż nie uderzył w krawędź planszy Select Case aktKierunek Case Kierunek.wPrawo x = x + 1 If x > wielkoscGry - 1 Then 'gracz uderzył w ścianę po prawej stronie Timer1.Enabled = False 'zatrzymuje timer If MessageBox.Show("Uderzyłeś w krawędź planszy, czy chcesz zacząć grę od nowa?", "Gra skończona!", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1) = Windows.Forms.DialogResult.Yes Then 'Jeśli gracz przycisną "Tak" gra zacznie się od nowa initialize() Exit Sub Else Exit Sub End If End If Case Kierunek.wGore y = y - 1 If y < 0 Then Timer1.Enabled = False If MessageBox.Show("Uderzyłeś w krawędź planszy, czy chcesz zacząć grę od nowa?", "Gra skończona!", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1) = Windows.Forms.DialogResult.Yes Then initialize() Exit Sub Else Exit Sub End If End If Case Kierunek.wDol y = y + 1 If y > wielkoscGry - 1 Then Timer1.Enabled = False If MessageBox.Show("Uderzyłeś w krawędź planszy, czy chcesz zacząć grę od nowa?", "Gra skończona!", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1) = Windows.Forms.DialogResult.Yes Then initialize() Exit Sub Else Exit Sub End If End If Case Kierunek.wLewo x = x - 1 If x < 0 Then Timer1.Enabled = False If MessageBox.Show("Uderzyłeś w krawędź planszy, czy chcesz zacząć grę od nowa?", "Gra skończona!", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1) = Windows.Forms.DialogResult.Yes Then initialize() Exit Sub Else Exit Sub End If End If End Select 'ten element odpowiedziany jest za przesunięcie pierwszego elementu węża 'jeśli wciśnięta jest Pauza(Spacja) wąż nie zostanie przesunięty If Not aktKierunek = Kierunek.Pauza Then If zajetepola(x, y) = True Then 'wąż zderzył się z samym sobą Timer1.Enabled = False If MessageBox.Show("Zjadłeś własny ogon, czy chcesz zacząć grę od nowa?", "Gra skończona!", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1) = Windows.Forms.DialogResult.Yes Then initialize() Exit Sub Else Exit Sub End If End If 'na podstawie kierunku, zmienna x lub y została zmieniona 'ta struktura (sWaz) nie jest jeszcze dodana do kolekcji, dlatego jej zmiennymi 'możemy jeszcze manipulować sWaz.x = x sWaz.y = y sWaz.rect = pola(x, y) zajetepola(x, y) = True 'zajmuje nowe pole End If 'nowa struktura koloru wężą, nie dodana jeszcze głowa 'można ten element pominąć wstawiając w tym miejscu snake.Add(sWaz, , 1) 'lub można zrobić inny kolor dla głowy węża g.FillRectangle(New SolidBrush(sWaz.KolorWeza), sWaz.rect) 'koloruje leszcze elementów kolekcji For t As Integer = 1 To snake.Count g.FillRectangle(New SolidBrush(snake(t).KolorWeza), snake(t).rect) Next 'dodaje strukture do kolekcji na 1-wszej pozycji snake.Add(sWaz, , 1) 'jeśli wąż łyknie token If pola(x, y).Location = token.Location Then punkty += 1 'uaktualnia punktację snake.Add(sWaz, , , snake.Count) ' dodaje kawałek ogona na koniec BiezacaDlugoscWeza = snake.Count Timer1.Interval -= 2 'zwiększa szybkość gry If Timer1.Interval < 0 Then Timer1.Interval = 1 GenerujToken() 'generuje nowy token End If 'dodaje niezajęte elementy i token For j As Integer = 0 To wielkoscGry - 1 For i As Integer = 0 To wielkoscGry - 1 If zajetepola(i, j) = False Then g.FillRectangle(kolorPola, pola(i, j)) End If If token = pola(i, j) Then g.FillRectangle(New SolidBrush(Color.Yellow), pola(i, j)) End If Next Next ' ustawia nowy obrazek tła Me.BackgroundImage = podgladgry 'informuje użytkownika o aktualnym poziomie trudności (szybkości) gry i punktacji Me.Text = "Zebrane punkty: " + punkty.ToString + " ,szybkość gry: " + (100 + (((100 - Timer1.Interval) / 100) * 100)).ToString g.Dispose() Refresh() 'wznawia działanie timera Timer1.Enabled = True 'odblokowuje możliwość zmiany kierunku jestemWruchu = False End Sub |
Bez metody GenerujToken() nasza plansza będzie czysta tylko z jednym kwadratem czerwonym symbolizującym naszego węża:
Zostało nam dodanie aktualizacji kierunków i generowanie tokena. Oba te elementy są w miarę proste:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
Private Sub Form1_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyDown 'Zapobiega auto zderzeniu, gdyby nie było tego ograniczenia moglibyśmy zderzyć się wewnątrz węża 'zanim zmieni się tło formy If jestemWruchu = False Then Select Case e.KeyCode Case Keys.Down 'nie wykona operacji w przeciwnym kierunku która spowoduje wewnętrzne zderzenie elementów If Not (aktKierunek = Kierunek.wDol Or aktKierunek = Kierunek.wGore) Then aktKierunek = Kierunek.wDol jestemWruchu = True End If Case Keys.Left If Not (aktKierunek = Kierunek.wLewo Or aktKierunek = Kierunek.wPrawo) Then aktKierunek = Kierunek.wLewo jestemWruchu = True End If Case Keys.Right If Not (aktKierunek = Kierunek.wPrawo Or aktKierunek = Kierunek.wLewo) Then aktKierunek = Kierunek.wPrawo jestemWruchu = True End If Case Keys.Up If Not (aktKierunek = Kierunek.wGore Or aktKierunek = Kierunek.wDol) Then aktKierunek = Kierunek.wGore jestemWruchu = True End If Case Keys.Space aktKierunek = Kierunek.Pauza End Select End If 'Można usunąć "zajetepola(x, y) = True 'zajmuje nowe pole" w metodzie PoruszWeza() (linia 212) 'po usunięcia ograniczenia da to możliwość zawracania wężowi End Sub Private Sub GenerujToken() Dim rand As New Random Dim x As Integer Dim y As Integer Do x = rand.Next(0, wielkoscGry) y = rand.Next(0, wielkoscGry) If zajetepola(x, y) = False Then Exit Do End If Loop token = pola(x, y) End Sub |
Gotowe, teraz gra działa, a my możemy rozkoszować się możliwością manipulacji. Można zwiększać i zmniejszać szybkość gry manipulując przy Intervale Timera1. Sprawdźcie sobie grę Snake Neon z początku tego posta. Takie rzeczy można zrobić, modyfikując i dodając elementy do tego algorytmu.
Do pobrania:
kod źródłowy: Waz_kodZrodlowy_Visualmonsters.cba.pl
pełen projekt: Snake_VisualMonsters.cba.pl