Kod jest moim zdaniem nie jest aż tak źle zaprojektowany, a Ty stanąłeś po prostu przed problemem wielowyboru ^^. Przyznam, nie ma jednego dobrego rozwiązania w tej sytuacji. RTTI w grach zazwyczaj się nie używa, gdyż jest za wolne; kiedy pojawia się konieczność jego użycia, to albo przeprojektowuje się wszystko (ale to zły znak, jeżeli trzeba coś przeprojektować już podczas pisania), albo robi się własne. W tym drugim przypadku nie musi to być nic trudniejszego, niż jakaś cyfra, ale lepiej zrobić identyfikację typu po nazwie. Ale po kolei:
MarcinEC, napisałeś: "Posłuchaj, pozbywasz się całej informacji o typie zmiennej poprzez to, że wrzucasz wszystkie obiekty gry do jednego worka - zostaje Ci jedynie CObject, a Ty męczysz się później z typowaniem tego...". Tak się akurat składa, że robi się tak [prawie] zawsze. Tj. przechowujesz listę na wskaźniki do CObject, i w pętli głównej, tudzież innych miejscach gry, robisz update za pomocą funkcji wirtualnych. Oczywiście, w niektórych miejscach warto też prowadzić inne, równoległe listy - np. lista wskaźników na CItem, żeby sprawdzać tylko te obiekty, czy gracz może je podnieść. Generalnie jednak największa, główna lista powinna zawierać obiekty, które mogą indywidualnie przemieszczać się po świecie gry, czyli CObject, CActor, czy jak się tam je nazwie. Chodzi o to, żeby udostępniały jakiś prosty interfejs do uaktualniania fizyki i rysowania.
Teraz musisz dokładnie przemyśleć hierarchię klas w swojej grze (czy co tam piszesz ^^) - np. klasy CPlayer, CMonster, CFlyingMonster, itp. to klasy istot obdarzonych jakąś tam inteligencją, która pozwala im na chodzenie, podnoszenie przedmiotów, atakowanie, etc. [przy czym CPlayer jest oczywiście kierowany przez gracza) - już masz dobre miejsce na wspólną klasę bazową, np. CPawn (pawn - ang. pionek w szachach). Z tej klasy będą dziedziczyć się wszystkie postacie. Wszystkie przedmioty dziedziczyć będą po CItem, jakieś kamienie, etc. po CDecoration, a wszystko to razem po CActor / CObject [jak tam sobie nazwiesz]. Teraz co to dało - załóżmy, że chcesz zaimplementować sprawdzanie zderzenia pomiędzy CPlayer a CACID - gdzie ta druga klasa to bajoro z kwasem ^^. Wiesz, że kwas zadaje uszkodzenia wszystkim postaciom. Z użyciem własnego RTTI mogłoby to wyglądać tak:
void CACID::Kolizja(CActor * z_czym)
{
if(z_czym.IsA("Pawn"))
{
z_czym.ZadajUszkodzenia(this, USZKODZENIE_POPARZENIE, ile_uszkodzen);
}
}
Idea tego jest taka, że sprawdzasz, czy przekazany obiekt jest postacią (potworem, graczem). Jeżeli tak, to zadajesz mu uszkodzenia (funkcja wirtualna z klasy CActor). Oczywiście zostanie też wywołana prawdopodobnie druga funkcja - CPlayer::Kolizja, z przekazanym do niej bajorkiem z kwasem, ale ta funkcja już nie musi sprawdzać, czy obiekt jest bajorkiem [w tym miejscu wkracza dobry projekt!!]. Tak na marginesie dodam, że taki właśnie kod steruje grą Unreal Tournament ^^. Jak działa takie RTTI? Masz następującą funkcję: bool IsA(std::string nazwa);. Jeżeli obiekt jest klasą o podanej nazwie, lub dziedziczy po czymś o tej nazwie (np. CACID dziedziczy po CDecoration, która dziedziczy po CActor), to zwraca prawdę. W przeciwnym wypadku zwraca fałsz. Jeszcze się wytłumaczę z tej funkcji ZadajUszkodzenia - dla przykładu przyjmuje ona trzy parametry - wskaźnik do obiektu, który zadał uszkodzenia (CActor*), typ uszkodzeń (int, stała tekstowa, jak tam chcesz), oraz ile uszkodzeń zadano (int, float). Jakbyś chciał mieć potwora odpornego na ataki gracza, to sprawdzasz w jego implementacji tej funkcji
void CIndestructableMonster::ZadajUszkodzenia(CActor* od_kogo, int typ, int ile)
{
if(od_kogo.IsA("Player"))
{
return; //nie zadajemy uszkodzen
}
//...
}
W innym przypadku będziesz chciał, żeby potwór był odporny na kwas; to piszesz linijkę:
if(typ == USZKODZENIE_KWAS) return;
I tak dalej. Wszystko zależy od dobrego projektu. RTTI używasz własnego, i tylko po to, żeby sprawdzić, czy dany obiekt jest graczem, potworem, etc. NIE używasz go natomiast przy rysowaniu na ekran, czy sprawdzaniu fizyki - od tego masz klasę podstawową CActor, funkcje wirtualne, i garść zmiennych - każde RTTI jest zbyt wolne, żeby się go opłacało wywoływać co klatkę x razy dla wszystkich obiektów. Jeśli wydaje się, że jednak trzeba, to przeprojektuj ^^.
Teraz - jak napisać takie RTTI? No, z tym jest więcej zabawy. Dobry kod był w II tomie Perełek Programowania Gier (jak będziesz w pobliżu księgarni, to przeglądnij); wymaga lekkich modyfikacji i okrojenia. Nie wiem, czy nie zamieszczę przerobionej wersji gdzieś na sieci, ale jak chcesz, to Ci mogę wysłać mailem.
Ok, rozpisałem się, mam nadzieje, że chociaż zrozumiale :)
Pozdrawiam wszystkich,
TeMPOraL