Budujemy silnik: architektura, renderer
20.12.2008 21:14 in grafika 3D, cg, OpenGL, programowanie, C++, silnik
Mój silnik składa się z kilku modułów. Każdy moduł to osobny plik DLL (tutaj wielkie brawa dla VS, które radzi sobie z solution z kilkoma projektami DLL i dwoma projektami gier tak, jakby wszystko było w tym samym projekcie -- powiem szczerze, że nie spodziewałem się, że w C++ coś takigo się uda). Modułem jest na przykład obsługa danych, wejścia, dźwięku, grafiki, fizyki, etc. Przykładowy kod gry wygląda następująco:
void uGame::Run()
{
try
{
uDataManager dm("data.utd");
uWorld world;
uRenderer renderer(dm, world);
renderer.Init();
uLight *l = new uLight();
l->SetPosition(...);
...
world.AddObject(l);
uModel *m = new uModel();
m->SetFilename("car.utm");
world.AddObject(m);
uCamera *c = new uCamera;
c->SetPosition(...);
...
world.AddObject(c);
renderer.SetCamera(c);
world.OnLogic += UDELEGATE(&MyLogic);
renderer.StartLoop();
}
catch (uException e)
{
...
}
}
void uGame::MyLogic(uWorld &world)
{
...
}
Jak widać, użycie renderera zostało sprowadzone do trzech bardzo prostych operacji. Ważne rzeczy:
- renderer nie jest bezpośrednio sterowany przez użytkownika (w tej wersji ma pomocniczą funkcję SetCamera, która zostanie jednak zastąpiona mechanizmem ustawień)
- wszystkie opcje (rozdzielczość, detale czy w przyszłości aktywna kamera) są przekazywane jako obiekty typu RendererSettings (dziedziczącym po abstrakcyjnym obiekcie, tak samo jak kamera czy model)
- renderer sam ,,wpina'' się w świat, dodając swojego delegata do obsługi zdarzenia AddObject
- przy okazji: prosty kod do obsługi delegatów w stylu C#:
#define UDELEGATE(a) fastdelegate::MakeDelegate(this, a);
template <class T>
class uEvent1
{
typedef fastdelegate::FastDelegate1<T> _delegate;
std::list<_delegate> mDelegates;
public:
void operator +=(_delegate &d)
{
mDelegates.push_back(d);
}
void operator -=(_delegate &d)
{
mDelegates.remove(d);
}
void clear()
{
mDelegates.clear();
}
void operator() (T &value)
{
for (std::list<_delegate>::iterator
it = mDelegates.begin(); it != mDelegates.end(); it++)
{
(*it)(value);
}
}
};
- jak było wspomniane wcześniej, użytkownik nie ,,obsługuje'' samemu renderera. To renderer, korzystając z modułu danych, wczytuje niezbędne modele, tekstury i inne zasoby
Takie podejście to pełne oddzielenie wyświetlania danych od ich logicznego znaczenia. Pozwala na równoległą pracę nad rendererem i innymi modułami w przyjemny i bezpieczny sposób. Umożliwia kompletną wymianę renderera. Na koniec można zauważyć, że w ten sposób w silniku 3D realizujemy wzorzec MVP (model-widok-kontroler). :)
Comments:
-
Reg:
Dlaczego robisz DLL zamiast LIB?
21.12.2008 13:02:28
-
LIB-ów używam tylko do importowania funkcji (żeby samemu nie GetProc-ować). Natomiast używam DLL, by w przyszłości móc dołożyć np. nowy renderer do starego projektu.
21.12.2008 15:29:47