W każdej kolizji występują dwa GameObjects, prawda? Chcę wiedzieć, jak zdecydować, który obiekt powinien zawierać mój OnCollision*
?
Jako przykład załóżmy, że mam obiekt Player i obiekt Spike. Moją pierwszą myślą jest umieszczenie w odtwarzaczu skryptu zawierającego taki kod:
OnCollisionEnter(Collision coll) {
if (coll.gameObject.compareTag("Spike")) {
Destroy(gameObject);
}
}
Oczywiście taką samą funkcjonalność można uzyskać, zamiast tego mając skrypt na obiekcie Spike, który zawiera kod taki jak ten:
OnCollisionEnter(Collision coll) {
if (coll.gameObject.compareTag("Player")) {
Destroy(coll.gameObject);
}
}
Choć obie są poprawne, to więcej sensu dla mnie mieć skrypt na odtwarzaczu , ponieważ w tym przypadku, gdy w wyniku zderzenia się dzieje, akcja jest wykonywana na odtwarzaczu .
Jednak wątpię w to, że w przyszłości możesz chcieć dodać więcej obiektów, które zabiją gracza podczas kolizji, takich jak wróg, lawa, wiązka laserowa itp. Te obiekty prawdopodobnie będą miały inne znaczniki. Zatem skrypt w odtwarzaczu stałby się:
OnCollisionEnter(Collision coll) {
GameObject other = coll.gameObject;
if (other.compareTag("Spike") || other.compareTag("Lava") || other.compareTag("Enemy")) {
Destroy(gameObject);
}
}
Podczas gdy w przypadku, gdy skrypt znajdował się na Spike, wystarczy, że dodasz ten sam skrypt do wszystkich innych obiektów, które mogą zabić Gracza, i nadasz mu nazwę skryptu KillPlayerOnContact
.
Ponadto, jeśli masz kolizję między Graczem a wrogiem, prawdopodobnie chcesz wykonać akcję na obu . Więc w takim przypadku, który obiekt powinien poradzić sobie z kolizją? A może obie powinny poradzić sobie z kolizją i wykonywać różne czynności?
Nigdy wcześniej nie budowałem żadnej gry o rozsądnych rozmiarach i zastanawiam się, czy kod może stać się niechlujny i trudny w utrzymaniu, ponieważ rośnie, jeśli na początku coś takiego się nie powiedzie. A może wszystkie sposoby są prawidłowe i to naprawdę nie ma znaczenia?
Każdy wgląd jest mile widziany! Dziękuję za Twój czas :)
źródło
Tag.SPIKE
zamiast tego?Odpowiedzi:
Ja osobiście wolę tak projektować systemy, aby żaden obiekt biorący udział w kolizji nie obsługiwał go, a zamiast tego niektóre proxy obsługujące kolizję mogłyby obsłużyć go za pomocą wywołania zwrotnego
HandleCollisionBetween(GameObject A, GameObject B)
. W ten sposób nie muszę myśleć o tym, gdzie powinna być logika.Jeśli nie ma takiej opcji, na przykład gdy nie masz kontroli nad architekturą reagowania na kolizje, sprawy stają się nieco rozmyte. Ogólnie odpowiedź brzmi „to zależy”.
Ponieważ oba obiekty otrzymają wywołania zwrotne kolizji, umieściłem kod w obu, ale tylko w kodzie, który jest istotny dla roli tego obiektu w świecie gry , jednocześnie starając się zachować jak największą rozszerzalność. Oznacza to, że jeśli jakakolwiek logika ma jakikolwiek sens w obu obiektach, wolę umieścić ją w obiekcie, który prawdopodobnie doprowadzi do mniejszej liczby zmian kodu na dłuższą metę wraz z dodaniem nowej funkcjonalności.
Innymi słowy, pomiędzy dowolnymi dwoma typami obiektów, które mogą kolidować, jeden z tych typów obiektów generalnie ma taki sam wpływ na więcej typów obiektów niż drugi. Umieszczenie kodu dla tego efektu w typie, który wpływa na więcej innych typów, oznacza mniej konserwacji na dłuższą metę.
Na przykład obiekt gracza i obiekt pocisku. Prawdopodobnie „przyjmowanie obrażeń w wyniku zderzenia z kulami” ma logiczny sens jako część gracza. „Zadawanie obrażeń trafionej rzeczy” ma logiczny sens jako część kuli. Jednak kula prawdopodobnie spowoduje uszkodzenie bardziej różnych rodzajów obiektów niż gracz (w większości gier). Gracz nie odniesie obrażeń z bloków terenu lub postaci niezależnych, ale kula nadal może wyrządzić im obrażenia. W takim razie wolałbym umieścić obsługę kolizji w celu spowodowania uszkodzenia pocisku.
źródło
TL; DR Najlepszym miejscem do umieszczenia detektora kolizji jest miejsce, w którym uważasz, że jest to aktywny element w kolizji, zamiast elementu pasywnego w kolizji, ponieważ być może będziesz potrzebować dodatkowego zachowania, aby wykonać taką aktywną logikę (i przestrzeganie dobrych wytycznych OOP, takich jak SOLID, jest obowiązkiem aktywnego elementu).
Szczegółowe :
Zobaczmy ...
Moje podejście, gdy mam te same wątpliwości, niezależnie od silnika gry , polega na ustaleniu, które zachowanie jest aktywne, a które pasywne w stosunku do akcji, którą chcę uruchomić podczas zbierania.
Uwaga: Używam różnych zachowań jako abstrakcji różnych części naszej logiki, aby zdefiniować szkody i - jako kolejny przykład - inwentarz. Oba są osobnymi zachowaniami, które dodam później do Gracza, i nie zagracają mojego niestandardowego Gracza osobnymi obowiązkami, które byłyby trudniejsze do utrzymania. Używając adnotacji, takich jak RequireComponent, upewniłem się, że moje zachowanie odtwarzacza ma również zachowania, które teraz definiuję.
To tylko moja opinia: unikaj wykonywania logiki w zależności od znacznika, ale zamiast tego używaj różnych zachowań i wartości, takich jak wyliczenia, aby wybierać .
Wyobraź sobie te zachowania:
Zachowanie określające obiekt jako stos na ziemi. Gry RPG używają tego do zbierania przedmiotów w ziemi.
Zachowanie w celu określenia obiektu zdolnego do zadawania obrażeń. Może to być lawa, kwas, każda substancja toksyczna lub latający pocisk.
W tym zakresie rozważ
DamageType
iInventoryObject
jako wyliczenia.Zachowania te nie są aktywne , ponieważ będąc na ziemi lub latając, nie działają one same. Oni nic nie robią. Np. Jeśli masz kulę ognistą lecącą w pustym miejscu, nie jest ona gotowa do wyrządzenia obrażeń (być może inne zachowania powinny być uważane za aktywne w kuli ognistej, np. Kula ognista pochłania swoją moc podczas podróży i zmniejsza moc zadawania obrażeń w trakcie procesu kiedy potencjalne
FuelingOut
zachowanie może zostać opracowane, pozostaje to inne zachowanie, a nie to samo zachowanie DamageProducer), a jeśli widzisz obiekt w ziemi, nie sprawdza on wszystkich użytkowników i nie myśli, czy mogą mnie wybrać?Potrzebujemy do tego aktywnych zachowań. Odpowiednikami byłyby:
Jedno zachowanie, by przyjąć obrażenia (wymagałoby zachowania, które poradziłoby sobie z życiem i śmiercią, ponieważ obniżanie życia i otrzymywanie obrażeń są zwykle osobnymi zachowaniami, a ponieważ chcę, aby te przykłady były jak najprostsze) i być może opierać się obrażeniom.
Ten kod jest nieprzetestowany i powinien działać jako koncepcja . Jaki jest cel Uszkodzenie jest czymś, co ktoś odczuwa i jest związane z uszkodzeniem obiektu (lub formy żywej), jak się uważa. Oto przykład: w zwykłych grach RPG miecz cię uszkadza , podczas gdy w innych grach RPG go implementujących (np. Summon Night Swordcraft Story I i II ) miecz może również otrzymać obrażenia, trafiając w twardy element lub blokując zbroję, i może potrzebować naprawy . Ponieważ więc logika uszkodzeń odnosi się do odbiorcy, a nie do nadawcy, umieszczamy tylko odpowiednie dane w nadawcy, a logikę w odbiorcy.
To samo dotyczy posiadacza ekwipunku (inne wymagane zachowanie dotyczyłoby obiektu znajdującego się na ziemi i możliwości jego usunięcia z tego miejsca). Być może jest to właściwe zachowanie:
źródło
Jak widać na podstawie różnorodnych odpowiedzi tutaj, w podejmowaniu tych decyzji jest dość osobisty styl, a ocena oparta na potrzebach konkretnej gry - nie ma absolutnie najlepszego podejścia.
Moim zaleceniem jest przemyślenie tego pod kątem skalowania twojego podejścia wraz z rozwojem gry , dodawaniem i powtarzaniem funkcji. Czy umieszczenie logiki obsługi kolizji w jednym miejscu doprowadzi później do bardziej złożonego kodu? Czy obsługuje bardziej modularne zachowanie?
Masz więc kolce, które powodują obrażenia gracza. Zapytaj siebie:
Oczywiście nie chcemy dać się ponieść emocjom i zbudować nadmiernie skonstruowany system, który może obsłużyć milion skrzynek zamiast tylko jednego przypadku, którego potrzebujemy. Ale jeśli w obu przypadkach jest to równa praca (tj. Umieść te 4 wiersze w klasie A w porównaniu z klasą B), ale jedna skaluje się lepiej, jest to dobra ogólna zasada przy podejmowaniu decyzji.
W tym przykładzie, w szczególności w Unity, skłaniam się ku umieszczeniu logiki zadawania obrażeń w kolcach , w postaci czegoś w rodzaju
CollisionHazard
komponentu, który po zderzeniu nazywa metodę przyjmowania obrażeń wDamageable
komponencie. Dlatego:Damageable
na nich komponent (jeśli potrzebujesz odporności na same kolce, możesz dodać rodzaje uszkodzeń i pozwól, abyDamageable
komponent obsługiwał immunitety)źródło
Naprawdę nie ma znaczenia, kto poradzi sobie z kolizją, ale bądź spójny z czyją to pracą.
W moich grach staram się wybrać
GameObject
coś, co „robi” coś drugiemu obiektowi, jako tego, który kontroluje kolizję. IE Spike uszkadza gracza, więc obsługuje kolizję. Kiedy oba obiekty „robią” coś ze sobą, niech każdy z nich poradzi sobie z ich działaniem na drugim obiekcie.Proponuję również spróbować zaprojektować kolizje tak, aby kolizje były obsługiwane przez obiekt, który reaguje na kolizje za pomocą mniejszej liczby rzeczy. Spike musi tylko wiedzieć o kolizjach z odtwarzaczem, dzięki czemu kod kolizji w skrypcie Spike jest ładny i zwarty. Obiekt odtwarzacza może kolidować z wieloma innymi rzeczami, co spowoduje rozdęty skrypt kolizji.
Na koniec postaraj się, aby procedury obsługi kolizji były bardziej abstrakcyjne. Kolec nie musi wiedzieć, że zderzył się z graczem, musi tylko wiedzieć, że zderzył się z czymś, czemu musi zadać obrażenia. Powiedzmy, że masz skrypt:
Możesz
DamageController
podklasować w GameObject gracza, aby poradzić sobie z uszkodzeniem, a następnie wprowadzićSpike
kod kolizjiźródło
Miałem ten sam problem, kiedy zaczynałem, więc oto: W tym konkretnym przypadku użyj wroga. Zwykle chcesz, aby kolizją zajmował się obiekt, który wykonuje akcję (w tym przypadku - wróg, ale jeśli twój gracz miał broń, broń powinna poradzić sobie z kolizją), ponieważ jeśli chcesz poprawić atrybuty (na przykład obrażenia wroga) zdecydowanie chcesz to zmienić w enemyscript, a nie w scripts (szczególnie jeśli masz wielu graczy). Podobnie, jeśli chcesz zmienić obrażenia jakiejkolwiek broni, której używa Gracz, na pewno nie chcesz jej zmieniać u każdego wroga twojej gry.
źródło
Oba obiekty poradzą sobie z kolizją.
Niektóre obiekty zostaną natychmiast zdeformowane, złamane lub zniszczone w wyniku kolizji. (kule, strzały, balony z wodą)
Inni po prostu odbijali się i przewracali na bok. (skały) Mogą one nadal otrzymywać obrażenia, jeśli trafiona rzecz była wystarczająco trudna. Skały nie otrzymują obrażeń od uderzającego w nie człowieka, ale mogą pochodzić od młota.
Niektóre miękkie przedmioty, takie jak ludzie, odniosą obrażenia.
Inni byliby ze sobą związani. (magnesy)
Obie kolizje zostaną umieszczone w kolejce obsługi zdarzeń dla aktora działającego na obiekcie w określonym czasie i miejscu (i prędkości). Pamiętaj tylko, że program obsługi powinien zajmować się tylko tym, co dzieje się z obiektem. Możliwe jest nawet, że moduł obsługi umieści innego modułu obsługi w kolejce, aby obsłużyć dodatkowe efekty kolizji w oparciu o właściwości aktora lub obiektu, na którym działa. (Bomba wybuchła, a wynikająca z niej eksplozja musi teraz przetestować kolizje z innymi obiektami).
Podczas pisania skryptów używaj znaczników z właściwościami, które zostałyby przetłumaczone na implementacje interfejsu przez Twój kod. Następnie zapoznaj się z interfejsami w kodzie obsługi.
Na przykład miecz może mieć znacznik Walki Wręcz, co przekłada się na interfejs o nazwie Walka Wręcz, który może rozszerzyć interfejs Obrażeń, który ma właściwości dla rodzaju obrażeń i wielkości obrażeń określonych za pomocą stałej wartości lub zasięgu itp. A bomba może mieć znacznik wybuchowy, którego interfejs wybuchowy rozszerza również interfejs DamageGiving, który ma dodatkową właściwość dla promienia i możliwie jak szybko rozproszenie obrażeń.
Twój silnik gry kodowałby następnie interfejsy, a nie określone typy obiektów.
źródło