Na przykład mam klasę gier, która prowadzi int
śledzenie życia gracza. Mam warunkowe
if ( mLives < 1 ) {
// Do some work.
}
Jednak ten warunek trwa nadal i praca jest wykonywana wielokrotnie. Na przykład chcę ustawić minutnik, aby wyjść z gry za 5 sekund. Obecnie ustawia go na 5 sekund na każdą klatkę, a gra nigdy się nie kończy.
To tylko jeden przykład i mam ten sam problem w kilku obszarach mojej gry. Chcę sprawdzić jakiś warunek, a następnie zrobić coś raz i tylko raz, a następnie ponownie nie sprawdzać ani nie wykonywać kodu w instrukcji if. Niektóre z możliwych rozwiązań, które przychodzą na myśl, obejmują ustawienie bool
dla każdego warunku i ustawienie czasu bool
uruchomienia warunku. Jednak w praktyce robi się to bardzo nieładnie bools
, ponieważ muszą być przechowywane jako pola klasy lub statycznie w samej metodzie.
Jakie jest właściwe rozwiązanie tego problemu (nie dla mojego przykładu, ale dla domeny problemowej)? Jak byś to zrobił w grze?
Odpowiedzi:
Myślę, że możesz rozwiązać ten problem, po prostu zachowując ostrożniejszą kontrolę nad możliwymi ścieżkami kodu. Na przykład, jeśli sprawdzasz, czy liczba żyć gracza spadła poniżej jednego, dlaczego nie sprawdzić tylko wtedy, gdy gracz traci życie, zamiast każdej klatki?
To z kolei zakłada, że
subtractPlayerLife
będzie wywoływane tylko w określonych przypadkach, co być może wynikałoby z warunków, w których musisz sprawdzić każdą klatkę (być może kolizje).Starannie kontrolując sposób działania kodu, można uniknąć niechlujnych rozwiązań, takich jak statyczne booleany, a także stopniowo zmniejszać ilość kodu wykonywanego w pojedynczej ramce.
Jeśli jest coś, co po prostu wydaje się niemożliwe do refaktoryzacji, a naprawdę musisz sprawdzić tylko raz, to zaznacz (jak
enum
) lub statyczne booleany. Aby uniknąć samodzielnego deklarowania statyki, możesz użyć następującej sztuczki:co dałoby następujący kod:
wydrukuj
something
isomething else
tylko raz. Nie daje najlepiej wyglądającego kodu, ale działa. Jestem też całkiem pewien, że istnieją sposoby na jego poprawę, być może poprzez lepsze zapewnienie unikalnych nazw zmiennych. Działa#define
to tylko raz podczas działania. Nie ma sposobu, aby go zresetować i nie ma sposobu, aby go zapisać.Pomimo tej sztuczki zdecydowanie zalecamy lepszą kontrolę przepływu kodu w pierwszej kolejności i używanie go
#define
w ostateczności lub wyłącznie do celów debugowania.źródło
To brzmi jak klasyczny przypadek użycia wzorca stanu. W jednym stanie sprawdzasz swój stan życia <1. Jeśli warunek ten się spełni, przejdziesz w stan opóźnienia. Ten stan opóźnienia czeka określony czas, a następnie przechodzi do stanu wyjścia.
W najbardziej trywialnym podejściu:
Można rozważyć użycie klasy State wraz z StateManager / StateMachine do obsługi zmiany / wypychania / przejścia między stanami zamiast instrukcji switch.
Możesz sprawić, by Twoje rozwiązanie do zarządzania stanami było tak wyrafinowane, jak chcesz, w tym wiele stanów aktywnych, pojedynczy aktywny stan nadrzędny z wieloma aktywnymi stanami potomnymi przy użyciu pewnej hierarchii itp. Oczywiście używaj tego, co ma dla ciebie sens, ale naprawdę uważam, że Rozwiązanie stanu wzorca sprawi, że kod będzie znacznie łatwiejszy do ponownego użycia oraz łatwiejszy w utrzymaniu i przestrzeganiu.
źródło
Jeśli martwisz się wydajnością, wydajność wielokrotnego sprawdzania zwykle nie jest znacząca; to samo dotyczy warunków boolowych.
Jeśli interesuje Cię coś innego, możesz użyć czegoś podobnego do wzorca projektowego o wartości zerowej, aby rozwiązać ten problem.
W takim przypadku załóżmy, że chcesz wywołać metodę,
foo
jeśli gracz nie ma już życia. Zaimplementowałbyś to jako dwie klasy, jedną wywołującąfoo
i drugą, która nic nie robi. Zadzwońmy do nichDoNothing
iCallFoo
tak:To jest trochę „surowy” przykład; kluczowe punkty to:
FooClass
które mogą byćDoNothing
lubCallFoo
. Może to być klasa podstawowa, klasa abstrakcyjna lub interfejs.execute
metodę tej klasy.DoNothing
instancja).CallFoo
instancję).Zasadniczo jest to połączenie strategii i zerowych wzorców.
źródło
if
czeku doFooClass
samej instancji, więc jest ona wykonywana tylko raz i to samo w celu utworzenia instancjiCallFoo
.