Unity3d QuickTip – czyli szybkie porady, rozwiązania częstych problemów i sztuczki w Unity3d!

Dzisiejszy odcinek: Jak wykrywać kolizje i zdarzenia?

Uwaga! Jest to poradnik typu QuickTip. Zatem skupia się on na osiągnięciu założonego celu. Zatem zakładamy że użytkownik zna na tyle program Unity3d, aby samodzielnie wykonać najprostsze czynności, jak np. dodanie modelu kostki do sceny czy dodanie modelowi jakiegoś komponentu. Jeżeli brakuje Ci tej podstawowej wiedzy, zapraszam do tutoriala:
Unity Tutorial – Podstawy

Teoria

W świecie Unity3d mamy 3 metody wykrywania kolizji. Warto się zapoznać z każdą z nich, ponieważ mają one różne zastosowania. Wyróżniamy 3 główne typy wykrywania kolizji:

  • Kolizja (Collision) – Jest to zwykłe “zderzenie”. Tego typu kolizji użyjemy np. na ścianie, tak by gracz w nią wbiegając został zatrzymany.
  • Wyzwalacz (Trigger) – Jest to rodzaj kolizji w której wiemy że znaleźliśmy się w obszarze kolizji, ale nie zostajemy przez nią brutalnie zatrzymani. Taki collider można sobie wyobrazić jako fotokomórkę. Wchodzimy w jej “pole widzenia”, więc zapala światło. Ten typ kolizji można też wykorzystać np. przy tworzeniu pola widzenia przeciwnika.
  • Rzucanie promienia (Raycasting) – Chyba najdziwniejsze wykrywanie kolizji. Jedyne którego nie otrzymamy przez dodanie komponentu. Działa to tak, że “strzelamy” promieniem z naszej postaci do przodu na pewien dystans. I możemy wykryć czy coś na linii “strzału” się znalazło. I jak się łatwo domyślić, jako zasięg działania broni palnej, najczęściej się to wykorzystuje.

Same kolizje są narzędziem kluczowym. Możemy ich używać dzięki komponentowi Collider (Dostępny w menu: Component->Physics). Jak łatwo można zauważyć mamy klika typów colliderów: Box, Sphere, Capsule, Mesh, Wheel, Terrain.

  • Box (Sześcian), Sphere (Kula), Capsule (Kapsuła) – Odnoszą się oczywiście do kształtu.
  • Wheel (Koło) – Daje nam coś w rodzaju pierścienia
  • Terrain (Teren) – Kluczowy dla obiektu terenu, ponieważ daje nam możliwość dodania colliderów do wygenerowanych drzew.
  • Mesh – Jest to typ collidera, który dopasowuje się idealnie do kształtu obiektu

Nasuwa się pytanie: Jakiego typu collidera użyć? Jeżeli chodzi o kolizje zwykłe, naturalnym wyborem wydawałby się Mesh Collider. Ale jednak nie zawsze będzie to wybór najlepszy. Czemu? Bo zużywa więcej zasobów komputera.Więc jeśli mamy drzwi o wymyślnej fakturze i nałożymy na nie zwykłego box collidera, gracz nawet nie zwróci uwagi, a my zmniejszamy wymagania naszej gry. Ogólnie powinniśmy dążyć do jak największego uproszczenia kształtów colliderów.

Jeżeli chodzi o wyzwolenia. Tutaj Mesh collider również się nie sprawdzi. Ponieważ collidery tego typu są z reguły większe od samego obiektu. Jeżeli wykorzystujemy go np. do zbierania przedmiotów z ziemi, powinien być nawet przesadnie za duży, aby gracz mógł łatwo zebrać przedmiot, a nie musiał jak głupi biegać w kółko, żeby jakimś cudem trafić w nasz collider.

Collidery w scenie Unity3d oznaczone są za pomocą zielonych linii na obiekcie.

Collider i Trigger Collider w Unity3d
Collider i Trigger Collider w Unity3d

Zwykła kolizja (Collision)

Wygląd komponentu Collider
Wygląd komponentu Collider

Najpierw omówmy sobie parametry naszego komponentu.

  • Is Trigger – Zmienia collider w wyzwalacz (omówiony później)
  • Material – Możemy dodać colliderowi jakiś typ materiału. Np. gumę, co nada mu fizyczne właściwości gumy.
  • Center – Pozycja collidera. Z racji że to komponent, współrzędne ustawiane są względem obiektu. Tzn. (0, 0, 0) to środek obiektu, a nie środek świata gry.
  • Size – Rozmiar collidera.

Korzystając z jednej z metod dodania komponentu, dodajemy sobie Box Collider do ustawionego na scenie obiektu (U mnie sześcian). I… właściwie tyle! Jeżeli uruchomimy grę i spróbujemy przejść przez nasz Sześcian, nie powinno nam się to udać.

Oczywiście to nie wszystko co możemy zrobić. Możemy przechwycić wydarzenia.

void OnCollisionEnter(Collision collision) {
    // Tutaj kod wykonywany po wykryciu kolizji
}

void OnCollisionStay(Collision collisionInfo) {
    // Tutaj kod wykonywany co klatkę, w czasie trwania kolizji
}

void OnCollisionExit(Collision collisionInfo) {
    // Tutaj kod wykonywany w momencie "opuszczania" kolizji
}
function OnCollisionEnter(collision : Collision) {
    // Tutaj kod wykonywany po wykryciu kolizji
}

function OnCollisionStay(collisionInfo : Collision) {
    // Tutaj kod wykonywany co klatkę, w czasie trwania kolizji
}

function OnCollisionExit(collisionInfo : Collision) {
    // Tutaj kod wykonywany w momencie "opuszczania" kolizji
}

Zdarzenie – wyzwalacz (Trigger)

Wygląd komponentu Collider
Wygląd komponentu Collider

Jak łatwo zauważyć, żeby zmienić zwykły collider w wyzwalający, wystarczy zaznaczyć checkboxa Is Trigger. Zwiększyłem też rozmiar, abyśmy wykrywali wejście w obszar, zanim dotkniemy obiektu. Reszta zostaje bez zmian.

Często zdarza się że dany obiekt posiada dwa collidery. Jeden zwykły, a drugi wyzwalający. Można takie rozwiązanie zastosować np. w kodzie automatycznych drzwi. Collider wyzwalający odpowiada za to żeby drzwi się automatycznie otwierały, kiedy zwykły odpowiada za to żeby nie dało się przez nie przeniknąć.

O ile w zwykłym colliderze jego obecność jest często wystarczająca, tutaj meritum jest kod, ponieważ wyzwolenia baz kodu robiącego cokolwiek, nawet nie zauważymy.

 

void OnTriggerEnter(Collider other) {
    // Tutaj kod wykonywany po wejściu w obszar
}

void OnTriggerStay(Collider other) {
    // Tutaj kod wykonywany co klatkę, w czasie obecności obiektu w obszarze
}

void OnTriggerExit(Collider other) {
    // Tutaj kod wykonywany w momencie opuszczania obszaru
}
function OnTriggerEnter(other : Collider) {
    // Tutaj kod wykonywany po wejścia w obszar
}

function OnTriggerStay(other : Collider) {
    // Tutaj kod wykonywany co klatkę, w czasie obecności obiektu w obszarze
}

function OnTriggerExit(other : Collider) {
    // Tutaj kod wykonywany w momencie opuszczenia obszaru
}

Mamy 3 decydujące zdarzenia. Enter – wejście, Stay – w czasie, Exit – wyjście. Każda funkcja jako parametr otrzymuje obiekt typu Collider. Teraz pod zmienną other mamy obiekt, który wszedł w interakcję. Załóżmy że nasz kod przypisujemy przeciwnikowi i chcemy żeby po zauważeniu gracza, obrócił się w jego stronę. Oczywiście zauważenie to funkcja OnTriggerEnter. Teraz możemy przypisać jakiś tag naszemu graczowi i rozpoznać go po tagu:

void OnTriggerEnter(Collider other) {
    if(other.tag == "Player") {
        gameObject.LookAt(other.transform);
    }
}

Jest to prosty przykład. Ale za pomocą odpowiedniego oprogramowaniach tych zdarzeń, możemy zbudować coś w rodzaju sztucznej inteligencji dla naszych przeciwników.

Rzucanie promieni (Raycasting)

Ostatni typ odbywa się wyłącznie w kodzie. Jeżeli trzymamy się przykładu przeciwnika. W poprzednim przykładzie kod jak collider przypisany byłby do przeciwnika. Jeśli teraz tworzymy np. strzelanie, to kod z użyciem raycastingu znajdzie się w kodzie gracza.

RaycastHit hit;
if(Physics.Raycast(transform.position, transform.forward, out hit, 3)) {
    if(hit.collider.gameObject.tag == "opponent") {
        // Tutaj kod w przypadku trafienia promieniem w obiekt o tagu opponent
    }
}
var hit : RaycastHit;
if(Physics.Raycast(transform.position, transform.forward, hit, 3)) {
    if(hit.collider.gameObject.tag == "opponent") {
        // Tutaj kod w przypadku trafienia promieniem w obiekt o tagu opponent
    }
}

Najpierw tworzymy promień w postaci zmiennej hit typu RaycastHit. Następnie wykonujemy rzutowanie promienia. Dzięki temu że wstawiliśmy go w ifa, dalszy ciąg kodu wykona się tylko jeśli w coś trafiliśmy.

Szybki rzut oka na parametry funkcji Physics.Raycast():

  • Początek promienia – Czyli gdzie ma się zacząć. Najczęściej będzie to pozycja obiektu z którego rzutujemy.
  • Kierunek promienia – Czyli w którym kierunku od punktu początku ma się rozwijać. (transform.forward – kierunek w którym spogląda obiekt)
  • Informacje o rzucanym promieniu – Zmienna o strukturze RaycastHit
  • Długość promienia – Odległość na jaką ma się ciągnąć promień w jednostkach ustalonych w grze

W C# przed hit dodajemy słowo kluczowe out. Pojawia się ono dlatego, abyśmy mogli otrzymać w zmiennej dane, które uzyskała wewnątrz funkcji Physics.Raycast().

Podsumowanie

Poznaliśmy dziś 3 metody wykrywania kolizji, oraz możliwości związane z tym. Jest to jeden z najczęściej wykorzystywanych mechanizmów w środowisku Unity3d jak i ogólnie w grach. Warto jednak mądrze korzystać z tego mechanizmu żeby nie przeciążyć zasobów komputera.

Jeżeli masz jakieś pytanie zadaj je w komentarzu. :)

32 thoughts

  1. Siema, chcialbym zapytac w jaki sposob wykrywac kolizje bez uzycia fizyki. Tzn po wbiegnieciu w sciane chcialbym zatrzymac gracza, ale w zaden inny sposob na niego nie oddzialywac(np nie chcialbym zeby sie od tej sciany odbijal).

    1. Aby na obiekty oddziaływały Collidery, oba muszą mieć collider, albo jeden mieć collider lub Rigidbody. Rigidbody odpowiada za fizykę. Więc aby wykryć kolizję bez symulacji fizyki, wystarczą Ci dwa obiekty posiadające Collider.

    2. jak wtedy poruszac postacia? Do tej pory uzywalem velocity. Nie odpowiada mi dynamiczna zmiana pozycji poprzez przypisywanie transform.position, bo dochodzi do sytuacji, gdzie obiekt wchodzi w inny obiekt i zostaje wypchniety(chcialbym zeby zostal zatrzymany przed sciana, a nie z niej wypchniety gdy znajduje sie w srodku niej).

    3. Możesz wykorzystywać funkcję Translate, przy czym jest szansa, że napotkasz na podobny problem.

      Jeśli stworzenie poruszania się, nie jest dla Ciebie tutaj najważniejsze, to polecam skorzystać ze skryptu na poruszanie się, ze standardowych assetów. W paczce Characters, masz postać pierwszoosobową bez Rigidbody wzbogaconą o skrypt na poruszanie. Możesz wykorzystać to w całości bądź sam skrypt.

    4. Z tego co wiem to Translate poprostu zmienia wartosc position.

      Poradzilem sobie w inny sposob. Dalej korzystam z velocity i rigidbody, ale zablokowalem wszystkie osie, na które ingeruje fizyka.

    5. Możesz użyć promieni, jeżeli jeden z nich zostanie przerwany to:

      rigidbody.velicity = new Vector3(0,0,0)
      Jeżeli o to ci chodzi

  2. Witam, chciałbym zrobić obiekt, który po kolizji z graczem byłby usuwany, problem w tym że musi kolidować z podłożem i kilkoma innymi obiektami. Co mam dopisać żeby nie znikał po upadku na ziemie?

    1. Wystarczy zrobić dokładnie to co jest w przykładzie:
      – Dodać graczowi jakiś tag
      – W czasie kolizji sprawdzać tag obiektu z którym kolidujemy
      – Usunąć obiekt, jeśli tag pokrywa się z tagiem nadanym graczowi.

    2. Zapis jest dobry, więc błąd jest gdzie indziej. To co mi przychodzi na myśl:
      1) Usunąłeś całą zawartość skryptu i te podane linijki to wszystko co masz w skrypcie. Wtedy to nie zadziała.
      2) Skrypt nie został dodany do obiektu, albo został dodany do złego obiektu.
      3) Obiekt gracza nie ma tagu Player – wielkość liter ma znaczenie.
      4) Kolizja w ogóle nie zachodzi. Aby to sprawdzić, wstaw sobie wewnątrz OnCollisionEnter, ale przed ifem: Debug.Log(“cos”); i sprawdź, czy po kolizji coś się wyświetli w konsoli.

    3. W konsoli wywala error ‘other’ nie istnieje(…). A nie trzeba czegoś przypisać do other – jakiejś wartości, zmiennej?

    4. Nie. Other to nic innego jak obiekt z którym zachodzi kolizja. Ale zakładamy, że ma on Collider. Więc jeśli obiekt gracza, nie ma collidera, to może nie zadziałać.

    5. Teraz zrobiłem literówkę
      Jak poprawiłem to error wygląda tak

      Script error: OnCollisionEnter
      This message parameter has to be of type:
      The message will be ignored.

      Pojawia się to po uruchomieniu gry

  3. Cześć, sorki za laicke pytanie ale dopiero zaczynam zabawę z unity. Co za różnica przy oncolision… Void i funkcja nie rozumie. Może ktoś mnie uświadomić.

    1. Może chodzi tutaj o te function i void? No bo function to jest do javy, a void to C#. O to chodzi?

  4. witam mam pytanie przy raycastingu gdy na scenie mam obiekt cube z tagiem opponent i ustawiłem sobie w skrypcie że gdy trafię w obiekt z tym tagiem to na konsoli wyświetla napis i
    gdy strzelam z krótkiej odległości nic się nie dzieje a gdy stoję i coliduję z nim to dopiero wtedy coś się dzieje próbowałem zwiększyć range promienia do nawet 1000 ale jest tak samo i nie wiem jak to zmienić proszę o pomoc
    z góry dziękuję

    1. W funkcji Update dopisz sobie Debug.DrawRay:
      Debug.DrawRay(transform.position, forward, Color.green);

      Gdzie pierwsze dwa parametry to te same, które określają Twój kierunek RayCasta. Trzecia to kolor, więc możesz zostawić jak podałem.

      Teraz odpal grę, zostawiając sobie widok na okienko Scene, gdzie zostanie wyrysowana linia, która prezentuje, jak faktycznie wygląda Twój promień strzału.

      Najprawdopodobniej błąd masz w określeniu kierunku (czyli drugiego parametru) i Twój promień nie leci tam gdzie patrzysz, tylko gdzieś w bok.

    2. promień wyświetla z przodu postaci ale nie podąża gdy ruszam myszką w górę lub w dół i mogę strzelać w przeciwnika gdy patrze się w ziemie

  5. Cześć! Mam pytanie (tak wiem, data) ale mi nie działa kod z raycatingiem (dopiero zaczynam) Mam ją wrzucić do funkcji update czy jakiej?

  6. Jeszcze jedno. Bo robię drzwi. I jak zrobić żeby się otwierały, a jak są otwarte to po naciśnięciu tego samego przycisku zamykały? plz pomóż.

    1. Zrobiłem, Tam działa że po wciśnięciu “E” zmienia się tekst (podpowiedź)CZASAMI działa animacja, ale najczęściej jakby włącza się i otwieranie i zamykanie równocześnie.

    2. To musiałbym zobaczyć kodzik, który napisałeś, bo tak nie mam pojęcia. (Tylko jak chcesz go wkleić to przez pastebin, albo coś takiego. Nigdy bezpośrednio w komentarz, bo się tego przeczytać nie da ;)

    3. Problemem może być lokalizacja linijki 63. Ponieważ po kliknięciu zmieniasz stan drzwi a potem wykonujesz jakąś akcję w zależności od stanu.
      Załóżmy że masz zmienną czyOtwarte na true, czyli drzwi są otwarte.
      Gracz klika E
      czyOtwarte zmienia się na false i dopiero potem wybierasz, którą animację odtworzyć. Czyli dla drzwi zamkniętych, odtworzysz animację zamykania drzwi.
      Wrzuć zmianę stanu za wybór animacji i powinno być OK.

  7. Takie jeszcze jedno pytanie. Bo nigdzie nie mogę znaleźć. Da się jakoś “pokazać” int na ekranie? Chodzi o to, że na przykład zbieram jakieś przedmioty to w eq pojawia się ich liczba?

  8. void OnTriggerEnter(Collider other) {
    if(other.tag == “Player”) {
    gameObject.LookAt(other.transform);
    }
    }
    Wpisałem i nie działa u mnie
    i pokazuje “Type `UnityEngine.Collision’ does not contain a definition for `tag’ and no extension method `tag’ of type `UnityEngine.Collision’ could be found (are you missing a using directive or an assembly reference?)”
    Co mam robić.

Leave a Reply

Your email address will not be published. Required fields are marked *