Pacman

Gra polega na sterowaniu Pac-Manem, który porusza się po labiryncie i zjada wszystkie kropki (Pellets), unikając jednocześnie czterech Duchów. Zjedzenie Dużej Kropki (Power Pellet) odwraca sytuację, tymczasowo pozwalając Pac-Manowi na zjedzenie Duchów, co daje dodatkowe punkty. Utrata wszystkich żyć (lub zjedzenie przez Ducha w trybie normalnym) kończy grę.

Written By Coder Matthew

Last updated 6 months ago


1) Grafiki użyte w Packman

Tak to się prezentuje już w samym unity
  • 1. Ghost (duchy)

    • Warianty Kolorystyczne: Grafiki dla każdego z czterech Duchów w ich normalnym stanie, różniące się kolorem (np. czerwony, różowy).

    • Tryb Przestraszenia: Grafiki niebieskiego Ducha oraz migający wariant biało-niebieski, używane, gdy Pac-Man zje Dużą Kropkę (PowerPellet). Logikę zmiany wyglądu kontroluje skrypt GhostFrightened.

    • Oczy: Grafiki samych oczu, które wskazują kierunek ruchu Ducha i są wyświetlane, gdy Duch został zjedzony i wraca do domu.

  • 2. Pac-Man (gracz)

    • Animacja Ruchu: Kilka klatek, które po cyklicznym przełączaniu symulują otwieranie i zamykanie ust Pac-Mana.

    • Sekwencja Śmierci: Seria klatek, które odtwarzają animację znikania/eksplozji Pac-Mana po utracie życia.

  • 3. Wall (labirynt)

    • Kafelki Ścian: Duża liczba (Wall_00 do Wall_37) małych grafik (Tile, zebranych razem w Tilemape), które są używane do budowania struktury labiryntu.

    • Warstwa Kolizji: Kafelki te stanowią wizualną warstwę przeszkód, z którą Duchy i Pac-Man kolidują.

  • 4. Pellet (obiekty do zbierania)

    • Mała Kropka (Pellet_...): Standardowa kropka do zbierania. Stanowi większość obiektów na planszy.

    • Duża Kropka (PowerPellet): Większy, często migający obiekt, który po zjedzeniu aktywuje tryb Frightened u Duchów


2) Hierarchia w Unity

Wszystkie obiekty
  • 1. Game Manager

    • Reprezentuje fizyczny obiekt kontrolujący globalny stan gry oraz jej dynamikę.\

    • Utrzymuje wzorzec Singleton (GameManager.Instance), będąc centralnym punktem zarządzania grą.

    • Jest to obiekt, do którego przypięty jest skrypt GameManager. Kontroluje stan gry, naliczanie punktów, żyć oraz logikę wyzwalania GameOver().

  • 2. Main Camera

    • Odpowiada za widok gry, czyli to, co gracz faktycznie widzi. Ustawia pole widzenia i projekcję, aby poprawnie wyświetlać labirynt 2D.

    • Jest statyczna i skupiona na obszarze labiryntu. Umożliwia wizualizację wszystkich akcji gry.

  • 3. Grid (siatka)

    • Kontener nadrzędny dla wszystkich wizualnych i logicznych elementów labiryntu. Organizuje i grupuje wszystkie elementy statyczne na planszy.

    • Jest używany jako rodzic dla labiryntu, ułatwiając zarządzanie jego pozycją i skalą.

  • 3.1 Walls (wewnątrz Grid)

    • Reprezentuje fizyczne ściany labiryntu.

    • Zawiera wszystkie kafelki (Tile) labiryntu, które są przeszkodami dla postaci.

    • Obiekt ten posiada komponenty kolizji, z którymi skrypt Movement (używany przez Pac-Mana i Duchy) koliduje, uniemożliwiając przejście.

  • 3.2 Pellets (wewnątrz Grid)

    • Kontener dla wszystkich kropek na planszy.

    • Jest to obiekt przechowujący wszystkie prefabrykaty Pellet i PowerPellet .

    • Skrypt GameManager odwołuje się do tego obiektu (pellets) w celu resetowania rundy (aktywacja wszystkich kropek) i sprawdzania, czy pozostały jeszcze jakieś kropki do zjedzenia.

  • 3.3 Nodes (wewnątrz Grid)

    • Kontener dla wszystkich węzłów decyzyjnych (skrzyżowań) w labiryncie.

    • Obiekty te zawierają skrypt Node, który definiuje dostępne kierunki ruchu.

    • Duchy używają tych węzłów jako punktów nawigacyjnych, aby określić, w którym kierunku mają się poruszyć zgodnie ze swoim algorytmem (Chase, Scatter, Frightened).

  • 3.3.1 Inside / Outside (wewnątrz Nodes)

    • Specjalne węzły reprezentujące pozycje wejścia/wyjścia z Domu Duchów.

    • Używane jako punkty docelowe przez skrypt GhostHome do animacji wchodzenia i wychodzenia Duchów z centralnej klatki.

  • 4. Canvas (Kontener UI)

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

    • Wymagany do hostowania elementów takich jak tekst i obrazy, które muszą pozostać na stałym miejscu na ekranie.

  • 4.1 GameOverText / ScoreText (wewnątrz Canvas)

    • Elementy tekstowe wyświetlające wynik oraz komunikat "Game Over".

    • Umożliwiają graczowi monitorowanie postępu i stanu gry.

    • Skrypt GameManager bezpośrednio odwołuje się do tych obiektów, aby aktualizować wyświetlane wartości

  • 5. Pacman

    • Reprezentuje postać sterowaną przez gracza.

    • Jest to główny obiekt gry, który ma przypięty skrypt Pacman i moduł ruchu Movement.

    • Kontroluje input gracza, rotację postaci, zderzenia (z duchami) oraz cykl życia (reset, sekwencja śmierci).

  • 6. Ghost (Blinky, Inky, Pinky, Clyde)

    • Ghost_Blinky, Ghost_Inky, Ghost_Pinky, Ghost_Clyde (i ich komponenty: Body, Eyes, Blue, White)

    • Reprezentują czterech przeciwników o unikalnych osobowościach i algorytmach pościgu.

    • Każdy z Duchów ma przypięty skrypt Ghost oraz wiele skryptów behawioralnych (Chase, Scatter, Frightened, Home).

    • Body, Eyes, Blue, White: To są różne graficzne komponenty Ducha. Skrypty behawioralne (np. GhostFrightened) włączają i wyłączają te komponenty, aby wizualnie zmienić stan Ducha (np. ukrycie Body i Eyes, a pokazanie Blue w trybie przestraszenia).


3) Skrypty użyte w Packman

Tak właśnie wyglądają gotowe skrypty w unity
  • 1. GameManager

    • Centralna jednostka kontrolna całej gry, zarządzająca stanem, punktacją i cyklami rund. Utrzymuje wzorzec Singleton, życiami, wynikiem i logiką Game Over.

    • Kontroluje sekwencje NewGame() i NewRound(), aktywując i resetując wszystkie obiekty.

    • Zawiera logikę reakcji na kluczowe zdarzenia: zjedzenie Kropki, zjedzenie Dużej Kropki (Power Pellet) oraz zjedzenie Pac-Mana lub Ducha.

  • 2. AnimatedSprite

    • Uniwersalny silnik animacji klatkowej dla wszystkich postaci i elementów. Odpowiada za cykliczne przełączanie grafik (sprites) w stałym interwale (animationTime).

    • Używany do ożywiania Pac-Mana (ruch ust), Duchów (ruch ciała), a także do animacji specjalnych (np. miganie trybu przestraszenia).

  • 3. Ghost

    • Główny skrypt Duchów, będący bazą i kontenerem dla wszystkich ich zachowań

    • Zarządza ogólnym stanem Ducha (Reset, Aktywacja) oraz punktami, jakie daje za zjedzenie.

    • Przechowuje referencje do wszystkich skryptów behawioralnych (Home, Chase, Scatter, Frightened) i wykrywa kolizje z Pac-Manem, decydując, kto kogo zjada.

  • 4. GhostBehavior

    • Abstrakcyjna klasa bazowa (szablon) dla wszystkich specyficznych zachowań Duchów.

    • Zapewnia wspólną funkcjonalność: włączanie/wyłączanie zachowania oraz zarządzanie czasem jego trwania (duration).

    • Umożliwia płynne przełączanie między trybami Ducha (np. z Chase na Scatter), dziedzicząc po niej cała logika AI.

  • 5. GhostChase

    • Implementuje logikę trybu "Pościg"

    • Odpowiada za algorytm, który kieruje Ducha w stronę najkrótszej drogi do Pac-Mana

    • W każdym Węźle (Node) oblicza, który dostępny kierunek najbardziej przybliży go do celu, a po wyłączeniu automatycznie aktywuje tryb Scatter.

  • 6. GhostEyes

    • Kontroluje grafikę oczu Ducha, tak aby patrzyły w kierunku, w którym się porusza. Jest to wizualny wskaźnik, który zwiększa czytelność ruchu Duchów.

    • Na podstawie wektora ruchu (Movement.direction) na bieżąco zmienia wyświetlany sprite oczu (góra, dół, lewo, prawo).

  • 7. GhostFrightened

    • Implementuje logikę trybu "Przestraszony" (Frightened), w którym Pac-Man może zjeść Ducha. Kontroluje zmianę wyglądu (na niebieski i migający) oraz logikę ucieczki od Pac-Mana.

    • Wprowadza mnożnik do prędkości (zwalnia Ducha) oraz w Węzłach wybiera kierunek, który prowadzi najdalej od Pac-Mana (logika ucieczki).

  • 8. GhostHome

    • Zarządza logiką pobytu Ducha w "Domu" (centralnej klatce) oraz animacją jego wejścia i wyjścia. Odpowiada za płynną sekwencję ExitTransition(), w której Duch opuszcza klatkę.

    • Używa zdefiniowanych punktów (inside, outside) jako celów animacji, a podczas pobytu w Domu powoduje "odbijanie się" Ducha od ścian klatki.

  • 9. GhostScatter

    • Implementuje logikę trybu "Rozproszenie" (Scatter). Powoduje, że Duchy na zmianę z trybem Pościgu (Chase) uciekają do swojego bezpiecznego rogu mapy.

    • W Węzłach podejmuje decyzje o losowym kierunku, ale z preferencją unikania zawracania, a po wyłączeniu automatycznie aktywuje tryb Chase.

  • 10. Movement

    • Podstawowy moduł fizyki i ruchu dla Pac-Mana i Duchów.

    • Odpowiada za ciągłe przemieszczanie obiektów, zarządzanie prędkością (speedMultiplier) i sprawdzanie kolizji ze ścianami.

    • Używa metody SetDirection() do zarządzania aktualnym kierunkiem i kierunkiem w kolejce (nextDirection), co zapewnia płynne i responsywne sterowanie.

  • 11. Node

    • Definiuje punkty decyzyjne (skrzyżowania) w labiryncie. Określa, w których z czterech kierunków (góra/dół/lewo/prawo) nie ma ścian.

    • Skrypty Duchów (Chase, Scatter, Frightened) wchodzą w ten obiekt, aby uzyskać listę dostępnych dróg i na tej podstawie podjąć decyzję o ruchu.

  • 12. Pacman

    • Główny skrypt Gracza, zarządzający jego interakcjami i cyklem życia. Odpowiada za odczytywanie inputu (klawisze kierunkowe) i obracanie postaci.

    • Wywołuje moduł ruchu (Movement.SetDirection()) na podstawie klawiszy, a także uruchamia DeathSequence() w momencie zderzenia z nieprzestraszonym Duchem.

  • 13. Pellet

    • Baza dla wszystkich jadalnych kropek (obiektów do zbierania). Definiuje wartość punktową i logikę zjedzenia.

    • W momencie kolizji z Pac-Manem, wywołuje GameManager.PelletEaten(), aby naliczyć punkty i usunąć kropkę ze sceny.

  • 14. PowerPellet

    • Dziedziczy po Pellet i dodaje specjalną funkcjonalność Power-up'a. Przekazuje czas trwania efektu (duration).

    • Nadpisuje standardową metodę Eat() i wywołuje GameManager.PowerPelletEaten(), co uruchamia tryb Frightened u wszystkich Duchów.


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 UnityEngine; using UnityEngine.Networking; using UnityEngine.UI; using System.Collections; [DefaultExecutionOrder(-100)] public class GameManager : MonoBehaviour { // --- Singleton i Parametry Gry --- public static GameManager Instance { get; private set; } [SerializeField] private Ghost[] ghosts; // Referencje do wszystkich duchów. [SerializeField] private Pacman pacman; // Referencja do gracza. [SerializeField] private Transform pellets; // Kontener z wszystkimi kropkami. [SerializeField] private Text gameOverText; [SerializeField] private Text scoreText; [SerializeField] private Text livesText; public int score { get; private set; } = 0; public int lives { get; private set; } = 1; // Zmodyfikowana liczba żyć. private int ghostMultiplier = 1; // Mnożnik punktów za zjedzone duchy. // --- Inicjalizacja Singletonu --- private void Awake() { // [BLOK KOMENTARZY: Zarządzanie Instancją] // Zapewnienie, że istnieje tylko jeden GameManager na scenie. if (Instance != null) { DestroyImmediate(gameObject); } else { Instance = this; } } private void OnDestroy() { if (Instance == this) { Instance = null; } } private void Start() { // Rozpoczęcie nowej gry po starcie sceny. NewGame(); } private void Update() { // [BLOK KOMENTARZY: Logika Restartu Po Przegranej] // Jeśli nie ma żyć i gracz naciśnie dowolny klawisz, rozpoczyna nową grę. if (lives <= 0 && Input.anyKeyDown) { NewGame(); } } private void NewGame() { // [BLOK KOMENTARZY: Inicjalizacja Nowej Gry] // Ustawia wynik na 0, żyć na 1 i rozpoczyna nową rundę. SetScore(0); SetLives(1); NewRound(); } private void NewRound() { // [BLOK KOMENTARZY: Przygotowanie Nowej Rundy] // Ukrywa tekst Game Over. Aktywuje wszystkie kropki, aby można było je zjeść od nowa. gameOverText.enabled = false; foreach (Transform pellet in pellets) { pellet.gameObject.SetActive(true); } ResetState(); // Resetuje pozycje Pac-Mana i Duchów. } private void ResetState() { // [BLOK KOMENTARZY: Resetowanie Stanu Postaci] // Resetuje stany (pozycje, kierunki, zachowania) Duchów i Pac-Mana. for (int i = 0; i < ghosts.Length; i++) { ghosts[i].ResetState(); } pacman.ResetState(); } private void GameOver() { // [BLOK KOMENTARZY: Koniec Gry] // Wyświetla komunikat Game Over i deaktywuje wszystkie postacie. gameOverText.enabled = true; for (int i = 0; i < ghosts.Length; i++) { ghosts[i].gameObject.SetActive(false); } pacman.gameObject.SetActive(false); } // --- Logika Zmiany Żyć i Wyniku --- private void SetLives(int lives) { // [BLOK KOMENTARZY: Zarządzanie Życiami] // Aktualizuje licznik żyć i wyświetla go w UI. this.lives = lives; livesText.text = "x" + lives.ToString(); } private void SetScore(int score) { // [BLOK KOMENTARZY: Zarządzanie Wynikiem i Komunikacją Sieciową] // Aktualizuje wynik, formatuje go (dwie cyfry) i wysyła na serwer. this.score = score; scoreText.text = score.ToString().PadLeft(2, '0'); StartCoroutine( SendScore( 19, // id gry score // wynik ) ); } // --- Obsługa Zdarzeń Gry --- public void PacmanEaten() { // [BLOK KOMENTARZY: Pac-Man Zjedzony] // Uruchamia sekwencję śmierci Pac-Mana, zmniejsza życia i albo resetuje stan, albo wywołuje Game Over. pacman.DeathSequence(); SetLives(lives - 1); if (lives > 0) { Invoke(nameof(ResetState), 3f); } else { GameOver(); } } public void GhostEaten(Ghost ghost) { // [BLOK KOMENTARZY: Duch Zjedzony] // Nalicza punkty (z użyciem mnożnika), zwiększa mnożnik i wysyła ducha do domu. int points = ghost.points * ghostMultiplier; SetScore(score + points); ghostMultiplier++; // Mnożnik rośnie z każdym zjedzonym duchem. } public void PelletEaten(Pellet pellet) { // [BLOK KOMENTARZY: Kropka Zjedzona] // Wyłącza zjedzoną kropkę, nalicza punkty i sprawdza warunek zakończenia rundy. pellet.gameObject.SetActive(false); SetScore(score + pellet.points); if (!HasRemainingPellets()) { // Jeśli wszystkie kropki zostały zjedzone, rozpoczyna nową rundę. pacman.gameObject.SetActive(false); Invoke(nameof(NewRound), 3f); } } public void PowerPelletEaten(PowerPellet pellet) { // [BLOK KOMENTARZY: Duża Kropka Zjedzona (Power-up)] // Uruchamia tryb 'Frightened' (przestraszony) dla wszystkich duchów. for (int i = 0; i < ghosts.Length; i++) { ghosts[i].frightened.Enable(pellet.duration); } PelletEaten(pellet); // Normalne naliczenie punktów za kropkę. // Resetuje mnożnik punktów za duchy po upływie czasu trwania Power-up'a. CancelInvoke(nameof(ResetGhostMultiplier)); Invoke(nameof(ResetGhostMultiplier), pellet.duration); } // --- Funkcje Pomocnicze --- private bool HasRemainingPellets() { // [BLOK KOMENTARZY: Sprawdzanie Końca Rundy] // Iteruje przez wszystkie kropki, sprawdzając, czy jakakolwiek jest wciąż aktywna. foreach (Transform pellet in pellets) { if (pellet.gameObject.activeSelf) { return true; } } return false; } private void ResetGhostMultiplier() { // [BLOK KOMENTARZY: Resetowanie Mnożnika] // Ustawia mnożnik punktów za duchy z powrotem na 1. ghostMultiplier = 1; } // --- 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; 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 ? r.downloadHandler.text : "ERR: " + r.error); } } }
Example
using System.Collections; using UnityEngine; public class GhostHome : GhostBehavior { public Transform inside; // Pozycja wewnątrz Domu Ducha. public Transform outside; // Pozycja wyjściowa z Domu Ducha. private void OnEnable() { // [BLOK KOMENTARZY: Włączenie] // Zatrzymuje wszystkie korutyny, aby nie kolidowały. StopAllCoroutines(); } private void OnDisable() { // [BLOK KOMENTARZY: Wyjście z Domu] // Uruchamia sekwencję animacji wyjścia, gdy Duch ma opuścić Dom. if (gameObject.activeInHierarchy) { StartCoroutine(ExitTransition()); } } private void OnCollisionEnter2D(Collision2D collision) { // [BLOK KOMENTARZY: Odbijanie się w Domu] // Gdy Duch w Domu zderzy się ze ścianą, odwraca swój kierunek, // tworząc efekt "odbijania się" w klatce. if (enabled && collision.gameObject.layer == LayerMask.NameToLayer("Obstacle")) { ghost.movement.SetDirection(-ghost.movement.direction); } } private IEnumerator ExitTransition() { // [BLOK KOMENTARZY: Sekwencja Wyjścia z Domu] // Ręczna kontrola pozycji, aby Duch płynnie przeszedł od środka do wyjścia. // 1. Wyłączenie normalnego ruchu. ghost.movement.SetDirection(Vector2.up, true); ghost.movement.rb.isKinematic = true; ghost.movement.enabled = false; Vector3 position = transform.position; float duration = 0.5f; float elapsed = 0f; // 2. Animacja do punktu startowego (wewnątrz klatki). while (elapsed < duration) { ghost.SetPosition(Vector3.Lerp(position, inside.position, elapsed / duration)); elapsed += Time.deltaTime; yield return null; } elapsed = 0f; // 3. Animacja wyjścia z klatki (do outside.position). while (elapsed < duration) { ghost.SetPosition(Vector3.Lerp(inside.position, outside.position, elapsed / duration)); elapsed += Time.deltaTime; yield return null; } // 4. Przywrócenie kontroli ruchu (losowy kierunek lewo/prawo) i włączenie Rigidbody. ghost.movement.SetDirection(new Vector2(Random.value < 0.5f ? -1f : 1f, 0f), true); ghost.movement.rb.isKinematic = false; ghost.movement.enabled = true; } }