Snake

Gra polega na sterowaniu rosnącym wężem, który porusza się po dwuwymiarowej planszy. Celem jest zjedzenie jabłek, co powoduje wydłużenie węża. Gracz musi unikać kolizji ze ścianami oraz, co kluczowe, zderzenia z własnym ogonem kończy grę.

Written By Coder Matthew

Last updated 6 months ago


1) Grafiki użyte w Snake

Tak to się prezentuje już w samym unity
  • Apple_0

    • Prefabrykat reprezentujący obiekt do zbierania (jedzenia). Po zderzeniu z głową węża, jabłko zwiększa jego długość i zostaje przeniesione w losowe miejsce.

    • Jest używany przez skrypt AppleSpawner. Zawsze jest tylko jedna aktywna instancja tego prefaba na scenie.

  • SnakeSegment (Segment Ciała Węża)

    • Prefabrykat reprezentujący pojedynczy segment ciała/ogona węża.

      Jest instancjonowany za każdym razem, gdy wąż zjada jabłko (Grow()).

    • Jest używany przez skrypt SnakeMovement (metoda ResetSnake i Grow). Obiekt ten posiada tag "SnakeBody", co pozwala głowie węża na wykrycie kolizji z własnym ciałem.

  • Walls

    • Prefabrykat reprezentujący fizyczne granice planszy lub inne przeszkody.

      Służy do zdefiniowania obszaru gry i uniemożliwienia wężowi opuszczenia planszy.

    • Obiekt ten posiada tag "Wall" i jest używany do wykrywania zderzenia. Kolizja z tym obiektem wywołuje funkcję HandleDeath() w skrypcie SnakeMovement.

  • MySquare

    • Obiekt/wypełniacz planszy, używany do stworzenia siatki (Grid).

    • Jest to tak zwana Tilemap ‘a, która tworzy palete kafelków i w łatwy sposób można za jej pomocą dowolnie wypełnić w danych brawach tło sceny.


2) Hierarchia w Unity

Wszystkie obiekty
  • SnakeHead

    • Reprezentuje głowę węża, czyli postać sterowaną przez gracza. Obiekt ten zawiera skrypt SnakeMovement, który kontroluje całą logikę ruchu i kolizji.

    • Punkt startowy dla całego węża. Jest on również pierwszym elementem na liście segmentów (segments) i to on wykrywa kolizje z jabłkiem, ścianami i własnym ciałem.

  • MainCamera

    • Odpowiada za widok gry, czyli za wyświetlanie planszy i ruch węża. Ustawia perspektywę dla świata 2D.

    • Jest statyczna i skupiona na obszarze gry, umożliwiając graczowi obserwację całej planszy.

  • Walls

    • Obiekt nadrzędny grupujący wszystkie granice planszy. Organizuje cztery obiekty ścian.

    • Umożliwia łatwe zarządzanie wizualizacją i fizyką wszystkich krawędzi naraz.

  • Grid

    • Główna Siatka Unity, która jest kontenerem dla Tilemapy. Definiuje globalny system współrzędnych oparty na kafelkach (kafelki są używane do wizualizacji tła).

    • Używany do organizowania elementów opartych na siatce, takich jak ewentualna wizualizacja tła labiryntu.

    • Tilemap używany do rysowania tła planszy. W przypadku Snake'a, Tilemap służy do rysowania siatki (wzoru tła), na którym wąż się porusza.

  • AppleSpawner

    • Obiekt kontrolujący logikę generowania i relokacji jabłka. Do tego obiektu przypięty jest skrypt AppleSpawner

    • W momencie zjedzenia jabłka, skrypt SnakeMovement odwołuje się do tego obiektu, aby wywołać funkcję RelocateApple(), umieszczając jabłko w nowym, losowym miejscu.

  • Canvas (Kontener UI)

    • Jest kontenerem nadrzędnym dla wszystkich elementów interfejsu użytkownika (UI). Zarządza skalowaniem i rozmieszczeniem tekstu na ekranie.

    • Niezbędny do hostowania tekstu wyniku (scoreText).

    • Element tekstowy (TextMeshPro) wyświetlający aktualny wynik gracza. Skrypt SnakeMovement (metoda UpdateScoreText()) na bieżąco aktualizuje ten obiekt.


3) Skrypty użyte w Snake

Tak właśnie wyglądają gotowe skrypty w unity
  • SnakeMovement

    • Główny kontroler węża (głowy i ciała), odpowiedzialny za fizykę, input, kolizje i zarządzanie śmiercią/resetem. Realizuje unikalną mechanikę ciągłego ruchu i animacji ogona opartej na historii pozycji (metoda "sznurka").

    • Kontroluje płynny ruch głowy do docelowej kratki, zapisuje jego ślad (positionHistory) i przesuwa segmenty ogona wzdłuż tego śladu z ustaloną odległością (followDistance).

    • Wykrywa zderzenia z Jabłkiem (wzrost), Ścianą (śmierć) oraz Ciałem Węża (śmierć, z ignorowaniem segmentów tuż za głową).

  • AppleSpawner

    • Odpowiada za generowanie i losowe umieszczanie jabłek na planszy. Zapewnia, że jabłko nie pojawia się na obszarach zablokowanych (ściany, segmenty węża).

    • Używa losowych indeksów siatki (grid) i Physics2D.OverlapCircle do weryfikacji, czy wylosowane miejsce jest wolne. Wywołuje metodę RelocateApple() po starcie gry i po każdym zjedzeniu jabłka.


4) Całe skrypty

Tutaj znajdziesz całe skrypty bez owijania w bawełne 😊, każdy blok kodu jest starannie zakomentowany tak żebyś w miarę mógł ogarnąć o co chodzi nawet jak nie znasz unity i C#.

Example
using System.Collections.Generic; using UnityEngine; using TMPro; using UnityEngine.Networking; using System.Collections; public class SnakeMovement : MonoBehaviour { // --- Konfiguracja Prędkości i Siatki --- public float moveSpeed = 6f; // Szybkość, z jaką wąż się przesuwa. public float gridSize = 1f; // Rozmiar jednej kratki na planszy (kluczowe do zsynchronizowania z jabłkiem). public float followDistance = 0.5f; // Odległość między segmentami ogona. // --- Stan Ruchu i Pozycja --- private Vector2Int gridPosition; // Pozycja głowy węża w jednostkach siatki (indeksach). private Vector2Int direction = Vector2Int.right; // Aktualny kierunek ruchu. private Vector2Int nextDirection; // Kierunek zaplanowany przez gracza (kolejka). private Vector3 targetPosition; // Docelowa pozycja w świecie (gdzie wąż ma dotrzeć). private const float reachThreshold = 0.001f; // Tolerancja dotarcia do celu. // --- UI i Punktacja --- [Header("UI")] public TextMeshProUGUI scoreText; private int score = 0; // --- Ciało Węża i Historia Ruchu --- [Header("Snake Body")] public GameObject segmentPrefab; // Prefabrykat segmentu ciała. public int startingSegments = 3; // Początkowa długość ogona. private List<Transform> segments = new List<Transform>(); // Lista wszystkich segmentów (włączając głowę). private List<Vector3> positionHistory = new List<Vector3>(); // Kluczowy element: zapis każdej pozycji głowy w czasie. private int historyLimit = 5000; // Maksymalna długość historii. // --- Konfiguracja Sieci --- [Header("Network")] public int gameId = 3; // ID gry dla API. void Start() { // [BLOK KOMENTARZY: Inicjalizacja Gry] // Ustawia węża w pozycji początkowej i zeruje wynik. ResetSnake(); UpdateScoreText(); } void Update() { // [BLOK KOMENTARZY: Główna Pętla Gry] // W każdej klatce sprawdza input i wykonuje ruch. HandleInput(); MoveSnake(); } private void MoveSnake() { // [BLOK KOMENTARZY: Ruch Głowy (ciągły ruch)] // Głowa płynnie przesuwa się w stronę docelowej kratki z ustaloną prędkością. transform.position = Vector3.MoveTowards(transform.position, targetPosition, moveSpeed * Time.deltaTime); // [BLOK KOMENTARZY: Zapis Śladu Głowy] // Aktualna pozycja głowy jest zapisywana na początku listy pozycji. positionHistory.Insert(0, transform.position); // [BLOK KOMENTARZY: Ruch Segmentów Ogona (logika "sznurka")] // Pętla przesuwa każdy segment wzdłuż zapisanego śladu ruchu głowy. // Segmenty szukają punktu w historii, który znajduje się w odpowiedniej odległości (followDistance) od segmentu poprzedzającego. // Używa interpolacji (Lerp) dla płynnego umieszczenia segmentu między dwoma punktami w historii. float totalDistance = 0f; int historyIndex = 0; for (int i = 1; i < segments.Count; i++) { float targetDistance = i * followDistance; // Odległość, jaką dany segment musi zachować od głowy. // ... (logika obliczania pozycji na podstawie historii) ... } // [BLOK KOMENTARZY: Czyszczenie Historii] // Ogranicza długość historii, aby uniknąć problemów z wydajnością. if (positionHistory.Count > historyLimit) positionHistory.RemoveAt(positionHistory.Count - 1); // [BLOK KOMENTARZY: Dotarcie do Kratki i Zmiana Kierunku] // Sprawdza, czy głowa dotarła do docelowej kratki. if (Vector3.Distance(transform.position, targetPosition) <= reachThreshold) { // Wymusza precyzyjne ustawienie głowy na środku kratki (zapobiega dryfowaniu). transform.position = targetPosition; // Zmienia kierunek ruchu, jeśli nowy kierunek nie jest przeciwny do obecnego. if (!IsOpposite(nextDirection, direction)) direction = nextDirection; // Oblicza nową pozycję docelową w świecie gry (następna kratka). gridPosition += direction; targetPosition = new Vector3(gridPosition.x * gridSize, gridPosition.y * gridSize, 0f); } } private void HandleInput() { // [BLOK KOMENTARZY: Obsługa Sterowania] // Sprawdza wciśnięcia strzałek lub klawiszy WASD i próbuje ustawić nowy kierunek. if (Input.GetKeyDown(KeyCode.UpArrow) || Input.GetKeyDown(KeyCode.W)) TrySetDirection(Vector2Int.up); if (Input.GetKeyDown(KeyCode.DownArrow) || Input.GetKeyDown(KeyCode.S)) TrySetDirection(Vector2Int.down); if (Input.GetKeyDown(KeyCode.LeftArrow) || Input.GetKeyDown(KeyCode.A)) TrySetDirection(Vector2Int.left); if (Input.GetKeyDown(KeyCode.RightArrow) || Input.GetKeyDown(KeyCode.D)) TrySetDirection(Vector2Int.right); } private void TrySetDirection(Vector2Int newDir) { // [BLOK KOMENTARZY: Walidacja i Kolejkowanie Kierunku] // Ustawia nowy kierunek jako 'nextDirection' (kolejka), o ile nie jest przeciwny do obecnego. if (!IsOpposite(newDir, direction)) nextDirection = newDir; } private bool IsOpposite(Vector2Int a, Vector2Int b) { // [BLOK KOMENTARZY: Sprawdzenie Przeciwnego Kierunku] // Prosta funkcja sprawdzająca, czy dwa wektory są przeciwne (np. prawo i lewo). return a.x == -b.x && a.y == -b.y; } private void OnTriggerEnter2D(Collider2D collision) { // [BLOK KOMENTARZY: Obsługa Kolizji] // 1. Zderzenie ze Ścianą: Koniec gry. if (collision.CompareTag("Wall")) { HandleDeath(); return; } // 2. Zderzenie z Jabłkiem: Zwiększenie wyniku, wzrost i relokacja jabłka. if (collision.CompareTag("Apple") || collision.gameObject.name == "Apple") { score++; UpdateScoreText(); Grow(); AppleSpawner spawner = FindObjectOfType<AppleSpawner>(); if (spawner != null) spawner.RelocateApple(); return; } // 3. Zderzenie z Własnym Ciałem: Koniec gry, chyba że z segmentami tuż za głową. if (collision.CompareTag("SnakeBody")) { // Ignorowanie zderzenia z pierwszymi segmentami, by wąż mógł się skręcać. bool isCloseToHead = false; int segmentsToIgnore = 4; // ... (logika ignorowania) ... if (!isCloseToHead) { HandleDeath(); return; } } } private void HandleDeath() { // [BLOK KOMENTARZY: Obsługa Śmierci i Resetu] // Wysyła OSTATNI osiągnięty wynik, a następnie resetuje grę (węża, pozycję, wynik). StartCoroutine(SendScore(gameId, score)); ResetSnake(); score = 0; UpdateScoreText(); AppleSpawner spawner = FindObjectOfType<AppleSpawner>(); if (spawner != null) spawner.RelocateApple(); // Relokacja jabłka po resecie. } private void ResetSnake() { // [BLOK KOMENTARZY: Resetowanie Węża] // Czyści listę segmentów, niszczy stare segmenty, resetuje pozycję i kierunek głowy. for (int i = 1; i < segments.Count; i++) Destroy(segments[i].gameObject); segments.Clear(); segments.Add(transform); // Reset pozycji do (0,0) i kierunku na prawo. gridPosition = Vector2Int.zero; targetPosition = new Vector3(gridPosition.x * gridSize, gridPosition.y * gridSize, 0f); transform.position = targetPosition; direction = Vector2Int.right; nextDirection = direction; // [BLOK KOMENTARZY: Tworzenie Ciała i Historii] // Wypełnia listę pozycji startowych segmentami, a następnie tworzy segmenty startowe. positionHistory.Clear(); // ... (tworzenie segmentów startowych i wstępne wypełnianie historii) ... } private void Grow() { // [BLOK KOMENTARZY: Funkcja Wzrostu] // Tworzy nowy segment na pozycji ostatniego segmentu w ogonie. Vector3 newPos = segments[segments.Count - 1].position; GameObject segment = Instantiate(segmentPrefab, newPos, Quaternion.identity); segments.Add(segment.transform); } private void UpdateScoreText() { // [BLOK KOMENTARZY: Aktualizacja UI] if (scoreText != null) scoreText.text = "Score: " + score; } // --- Komunikacja Sieciowa (Wysyłanie Wyniku) --- public IEnumerator SendScore(int gameId, int score) { // [BLOK KOMENTARZY: Procedura HTTP] // Standardowa korutyna do wysyłania wyniku na zewnętrzny serwer. string url = "https://bit.elita.fun/execs/api/set_score.php?game_id=" + gameId + "&score=" + score; // ... (logika wysyłania) ... WWWForm f = new WWWForm(); f.AddField("pass", System.DateTime.Now.ToString("mm") + "EcS9YcvMtWNnW0N8yi1mVK"); using (UnityWebRequest r = UnityWebRequest.Post(url, f)) { yield return r.SendWebRequest(); Debug.Log(r.result == UnityWebRequest.Result.Success ? "Score Sent: " + r.downloadHandler.text : "ERR: " + r.error); } } }