This article has been localized into Polish by the community.
In this article series, we're building a complete Snake game from scratch. It makes sense to start with the Introduction and then work your way through the articles one by one, to get the full understanding.
If you want to get the complete source code for the game at once, to get started modifying and learning from it right now, consider downloading all our samples!
Ciągły ruch dzięki użyciu DispatcherTimer
W poprzednich artykułach stworzyliśmy ładnie wyglądający obszar gry dla naszego węża, a następnie dodaliśmy kod, aby wykonać faktyczne tworzenie i ruch węża. Jednak, jak już wspomniano, kod ruchu nie powinien być wywoływany tylko raz - zamiast tego powinien być wywoływany wielokrotnie, aby utrzymać węża w ruchu tak długo, jak gra jest uruchomiona. Innymi słowy, będziemy potrzebować timera.
Ogólnie rzecz biorąc, w programowaniu Timer jest zwykle mechanizmem, który pozwala na wielokrotne powtarzanie zadania w oparciu o interwał. Innymi słowy, za każdym razem, gdy timer "tyka", wykonywany jest fragment kodu, a timer tyka w oparciu o zdefiniowany interwał. To jest dokładnie to, czego potrzebujemy, aby utrzymać naszego węża w ruchu, więc dodamy funkcję DispatcherTimer do naszego okna:
public partial class SnakeWPFSample : Window
{
private System.Windows.Threading.DispatcherTimer gameTickTimer = new System.Windows.Threading.DispatcherTimer();
....
Mając to na miejscu, musimy teraz zasubskrybować jego jedyne zdarzenie: Zdarzenie Tick. Dokonamy tego w konstruktorze okna:
public SnakeWPFSample()
{
InitializeComponent();
gameTickTimer.Tick += GameTickTimer_Tick;
}
A oto realizacja wydarzenia:
private void GameTickTimer_Tick(object sender, EventArgs e)
{
MoveSnake();
}
Tak więc za każdym razem, gdy timer tyka, wywoływane jest zdarzenie Tick, które w zamian wywołuje metodę MoveSnake(), którą zaimplementowaliśmy wcześniej. Aby w końcu zobaczyć rezultat naszej ciężkiej pracy i mieć wizualnego, poruszającego się węża, w zasadzie musimy tylko stworzyć początkowe części węża, a następnie uruchomić licznik czasu. Stworzymy więc metodę o nazwie StartNewGame(), której użyjemy do rozpoczęcia zarówno pierwszej gry, jak i dowolnej liczby dodatkowych nowych gier, gdy gracz zginie. Zaczniemy jednak od bardzo podstawowej wersji, a następnie rozszerzę ją o więcej funkcji w miarę postępów - na razie po prostu wprawmy tego węża w ruch!
Pierwszym krokiem będzie dodanie kolejnego zestawu stałych, których użyjemy do uruchomienia nowej gry:
public partial class SnakeWPFSample : Window
{
const int SnakeSquareSize = 20;
const int SnakeStartLength = 3;
const int SnakeStartSpeed = 400;
const int SnakeSpeedThreshold = 100;
......
W tym momencie używane są tylko trzy pierwsze stałe, aby kontrolować rozmiar, długość i prędkość początkową węża. Próg SnakeSpeedThreshold wykorzystamy później, ale na razie, zgodnie z obietnicą, dodajmy prostą implementację metody StartNewGame():
private void StartNewGame()
{
snakeLength = SnakeStartLength;
snakeDirection = SnakeDirection.Right;
snakeParts.Add(new SnakePart() { Position = new Point(SnakeSquareSize * 5, SnakeSquareSize * 5) });
gameTickTimer.Interval = TimeSpan.FromMilliseconds(SnakeStartSpeed);
// Draw the snake
DrawSnake();
// Go!
gameTickTimer.IsEnabled = true;
}
Zaczynamy od ustawienia snakeLength oraz snakeDirection w oparciu o wartości początkowe. Następnie dodajemy pojedynczy element do snakeParts Listy (więcej na ten temat później), dając mu dobrą pozycję startową do poruszania się w prawo - ponownie użyjemy SnakeSquareSize stałej aby otrzymać pomóc w obliczeniu prawidłowej pozycji. Mając to na miejscu, możemy narysować węża, wywołując metodę DrawSnake(), a następnie włączyć timer, który zasadniczo rozpocznie ruch węża.
Wreszcie jesteśmy w punkcie, w którym możemy niemal usiąść i cieszyć się pierwszą wersją czegoś, co faktycznie wygląda jak gra - w rzeczywistości wszystko, co musimy teraz zrobić, to wywolać metode StartNewGame(). Powinno to oczywiście zostać zrobione, gdy użytkownik będzie gotowy, ale na razie, aby sprawdzić, czy wszystko działa, po prostu zrobimy to, gdy tylko wszystko inne zostanie zainicjowane - ponownie będziemy polegać na ContentRendered zdarzeniu Window, którą dodaliśmy w jednym z pierwszych artykułów. Wystarczy dodać wywołanie do naszego StartNewGame() metody i w końcu jesteśmy gotowi do kompilacji i uruchomienia:
private void Window_ContentRendered(object sender, EventArgs e)
{
DrawGameArea();
StartNewGame();
}
Jeśli zrobiłeś wszystko zgodnie z opisem, powinieneś być teraz w stanie uruchomić grę i zobaczyć, jak wąż jest tworzony i natychmiast zaczyna się poruszać:
Zauważ, jak wąż pojawia się z niczego, jako pojedynczy kwadrat, a następnie rośnie do długości trzech kwadratów. Dzieje się tak, ponieważ dodajemy tylko jedną część do listy snakeParts, ale za każdym razem gdy MoveSnake() metoda jest wywoływana przez timer, dodawana jest nowa część (aby wąż rósł), a części ogonowe są usuwane tylko wtedy, gdy aktualna długość jest bliska przekroczenia pożądanej długości węża, która zaczyna się od 3 (o czym decyduje SnakeStartLength stała).
Podsumowanie
Mamy teraz poruszającego się węża, co jest naprawdę niesamowite! Ale jak widać na animowanym obrazku powyżej, są jeszcze rzeczy do zrobienia - nie ma jedzenia dla węża, a kiedy wąż uderza w ścianę, nic się nie dzieje. Nad tymi aspektami będziemy pracować w kolejnych artykułach.