[Лог #7] Класс рисовальщика.

Добавлен , опубликован

Рендер

Темой этого лога станет рендер. Расскажу немного о том как я себе представляю виртуального художника и почему лучше реализовать интерфейс IRender и класс-реализацию CRenderOGL, наример.
Хочется провести некоторые аналогии, которые по моему очень чётко вписываются.
Класс "рисовальщик" именовал я раньше как RenderDevice, но что это такое вообще? Зачем какие-то классы городить, когда есть, например, OpenGL? Конечно для удобства использования.
Класс IRenderDevice можно назвать художником, причём очень хорошо ему это имя подходит.
Можно сказать художнику "нарисуй мне вот тут квадрат", собственно это было бы эквивалентно такому вот вызову в коде "pRenderDevice->DrawQuad( .... )".
Художник не может нарисовать ничего, если у него нет холста, красок и кистей, и тут вновь можно провести аналогию.
Роль холста будет играть "контекст устройства"( как я понимаю это просто особая видеопамять, вернее её часть) - имеется у всех окон. Тут всё ясно холст - окно.
Кисти или их эквивалент - модели и геометрия, даже не имеет значения 2Д или 3Д. Возможно, кто-то знает, что геометрия(статичная?) в играх на движке Source, idTech и UnrealEngine называется Brush ( кисть ) - наверно просто совпадение, не думаю.
Кисть по холсту художник проведёт, но ничего не нарисует, поэтому нужны краски - текстуры, материалы, шейдеры (shaders). Думаю многие знают что из себя представляет шейдер - это подпрограмма для видеокарты, которая говорит как будет рисоваться 3Д модель. Экранный пост-процессы \ пост-эффекты - это тоже шейдер, например, свечение (glow, bloom), переход картинки в серость\цвет ( такой эффект был в игре Saboteur ).
Аналогии очень хорошо описывают набор классов графической библиотеки, однако, надо заметить, что нет конкретики. Это некая абстракция.
IRenderDevice - художник, а CRenderDeviceOGL - какой-нибудь Вася Пупкин, который вообще то тоже художник, но как видно уже конкретный.
Примерно вот так может выглядеть класс, вернее интерфейс для "художника":
class IRenderDevice
{
public:
	// Первичная инициализация GAPI-устройства
	virtual TResult	Init( WindowT *Window, int param ) = 0;

	// Инициализировать графический контекст для конкретного окна приложения 
	virtual TResult 	InitContextForWindow( WindowT *Window ) = 0;

	// Освободить граф.контекст окна
	virtual TResult 	CleanupContextForWindow( WindowT *Window ) = 0;

	// Сделать текущим(главным) контекст окна
	virtual TResult 	MakeCurrent( WindowT *Window ) = 0;

	// Сменить буфер изображения окна
	virtual TResult 	SwapBuffer( WindowT *Window ) = 0;

	// Финальная очистка граф.устройства
	virtual TResult 	Cleanup() = 0;

	virtual void ApplyView( GView * View ) = 0;
        virtual void SetMaterial( GMaterial * material ) = 0;
	virtual void DrawMesh( GMesh * Mesh ) = 0;
};
У методов этого класса нет реализации (конкретного кода) и они все виртуальные. Видно, что несколько первых методов - это инициализация, обновление кадра и освобождение ресурсов ( Init, SwapBuffer и Cleanup соответственно) - эти функции платформозависимые, что не есть хорошо вообще то. Но можно чуть-чуть избавится от зависимости "спрятав" зависимые от платформы типы и функции, например, тип переменной WindowT - произвольный (определённый мной, а не платформой), внутри которого есть платформозависимые переменные. Это сделано для того, чтобы не менять вызов функции, например:
Этот код не нужно будет менять для каждой платформы
pRenderDevice->Init( pMainWindow, 0 );
Для каждой ОС придётся лишь менять\написать код для класса WindowT и некоторые части от RenderDevice ( и\или других систем ).
Насколько это правильное решение - не знаю точно, возможно это вообще плохое решение.
Последние три функции интерфейса IRenderDevice - "применить вид", "установить материал" и "нарисовать модель". Конечно это не полный список функций, который пригодится, но для очень простых игр думаю даже такого хватит.
Функция ApplyView, которая принимает указатель на некий объект класса GView - особая штука, которой я можно сказать горжусь (хотя на самом деле нечем). GView - это что-то вроде виртуальной камеры, границ вида (вьюпорт \ viewport ) и матрицы проекции. Хранит в себе две матрицы - проекции (перспективная\ортогональная) и вида (положение и направление камеры).
Классы материала и сетки(mesh) вообще могут быть какими угодно, вернее информация которая в них хранится может быть различной и зависеть от "старшей" графической библиотеки (OpenGL \ DirectX).
Но не только материал и 3Д модель зависит от выбора библиотеки, собственно вся реализация интерфейса зависит от неё. Вот например если взять OpenGL, то может быть что-то вроде этого:
class GRenderDevice : public IRenderDevice
{
public:
	GRenderDevice();
	~GRenderDevice();

	// Первичная инициализация GAPI-устройства
	TResult		Init( WindowT *Window, int param );
	// Инициализировать графический контекст для конкретного окна приложения 
	TResult 	InitContextForWindow( WindowT *Window );
	// Освободить граф.контекст окна
	TResult 	CleanupContextForWindow( WindowT *Window );
	// Сделать текущим(главным) контекст окна
	TResult 	MakeCurrent( WindowT *Window );
	// Сменить буфер изображения окна
	TResult 	SwapBuffer( WindowT *Window );
	// Финальная очистка граф.устройства
	TResult 	Cleanup();

	void ApplyView( GView * View );
	void DrawMesh( GMesh * Mesh );

	void DrawVText( float x, float y, const char *strText, float size, bool bReversY = false );

	// Flags = 1 - color, 2 - depth, 4 - stencil
	enum{
		BIT_COLOR = 0x1,
		BIT_DEPTH = 0x2,
		BIT_STENCIL=0x4
	};
	void ClearScreen( int Flags = 0 );
	void ClearScreenColor( const float r, const float g, const float b, const float a );

	void BindTexture( GTexture * Texture );
	void SetWorldMatrix( const float * matrix );
	void SetWireframe( bool show = true );

	void	DrawRect( float x, float y, float xx, float yy );

protected:
	TResult		InitExtensions();
	HGLRC		m_hContext;      // На самом деле зависимая от платформы переменная
	WindowT		*m_pOwnerWindow;
};
Видно, что функций больше, да и переменные появились. (не забывайте, что это примеры, а в реальном проекте будет иначе). Можно создавать экземпляр этого класса и пользоваться всеми этими функциями, однако, если нужна независимость от OpenGL\DirectX, то нужно использовать IRenderDevice у которого функций меньше. Зачем тогда использовать этот урезанный класс? Дело в том, что реализацию художника можно загружать из DLL (если это реализовано, конечно), например, так сделано в Quake2 - рендер и механика игры загружаются из DLL.
Ну вот скажем та же функция ApplyView для OpenGL реализации будет выглядеть примерно так:
void GRenderDevice::ApplyView( GView * View )
{
	if( !View ) return;
// Задать вьюпорт
	View->SetViewport( 0, 0, GetBufferWidth(), GetBufferHeight() );

	glViewport( View->GetX(), View->GetY(), View->GetWidth(), View->GetHeight() );

// Утсановить матрицы (OpenGL-специичные функции)
	glMatrixMode( GL_PROJECTION );
	glLoadMatrixd( &View->GetProjectionMatrix().x[0][0] );
	glMatrixMode( GL_MODELVIEW );
	glLoadMatrixd( &(View->GetViewMatrix()).x[0][0] );
}
Или вот функция рисования сетки:
void GRenderDevice::DrawMesh( GMesh * Mesh )
{
	if( !Mesh ) return;

	GVertex3D * V		= Mesh->GetVertexArray();
	unsigned int Count	= Mesh->GetCountVertex();

// вновь специфичные функции
	glVertexPointer(	3,	GL_FLOAT, sizeof(GVertex3D), &(V[0].x) );
	glNormalPointer(		GL_FLOAT, sizeof(GVertex3D), &(V[0].nx) );
	glTexCoordPointer(	2,	GL_FLOAT, sizeof(GVertex3D), &(V[0].tu) );

// получить количество под-сеток
	unsigned int count_batch = Mesh->GetCountBatches();

// для каждой под-сетки ...
	for( unsigned int i=0; i< count_batch; ++i )
	{
		GBatch *b = 0;
		if( b = Mesh->GetBatch(i) )
		{

			if( b->m_MaterialID != -1 )
			{
//                             установить текстуру 
				BindTexture( FindTextureByID( b->m_MaterialID ) );
			}

//                     Нарисовать треугольники 
			glDrawElements( GL_TRIANGLES, b->GetCount(), GL_UNSIGNED_INT, b->GetPtr() );
		}
	}
}
Для DirectX реализации были бы совсем другие функции, но смыл бы не изменился.
Поэтому вызывая pRenderDevice->ApplyView( pView ) будет срабатывать специфичный код.
Честно говоря не очень хорошо получается у меня объяснить весь смысл, да что там - вообще не понятно что я тут написал :D
Конечно хотелось бы больше конкретного кода, но не учитель я :(
Да ещё и версия OpenGL, которую я ранее и использовал устарела, теперь придётся чуть обновить свои знания и подтянуть версию хотя бы OpenGL 3.3.

Что дальше ?

Думаю либо чуть увеличить разрыв между логами, либо небольшую паузу взять. Думаю начать уже конкретно проектировать само приложение (не писать код) и сделать небольшой альфа-прототип на каком-нибудь Unity (хотел вообще на GameMaker, но похоже с новым обновлением они отказались от WinXP - Steam отказывается запускать GM).
Заметьте ни одной картинки D: !
`
ОЖИДАНИЕ РЕКЛАМЫ...
0
14
9 лет назад
0
Я это называю графическим движком. А отрисовщик, это тот, кто конкретные игровые сущности на экран рисует, используя графический движок. Тот самый, который говорит "нарисуй мне здесь круг".
0
29
9 лет назад
0
Думаю либо чуть увеличить разрыв между логами, либо небольшую паузу взять. Думаю начать уже конкретно проектировать само приложение (не писать код) и сделать небольшой альфа-прототип на каком-нибудь Unity (хотел вообще на GameMaker, но похоже с новым обновлением они отказались от WinXP - Steam отказывается запускать GM).
Ну и делал бы дальше на Юнити, не?)
0
15
9 лет назад
0
Я это называю графическим движком
Слишком мощно звучит для моих классов :) Но вообще там функции уровня DrawQuad, DrawMesh, а для конкретных игровых объектов можно вообще ещё один класс написать (или реализовать в этих самых объектах), типа DrawSupergun или DrawBee.
Ну и делал бы дальше на Юнити, не?)
Дальше? Но ведь я даже не начинал ) Хотел GameMaker использовать т.к. быстрее получилось бы (наверно). Сейчас уже начал потихоньку делать прототип на юньке.
Кстати, вот подумал о сюжете и персонажах и пришёл к очень интересной на мой взгляд штуке - продолжить историю другой игры, но долго сомневался надо ли сделать про кого-то нового или оставить старого перса. Изначально бродилка и задумывалась как продолжение. Короче вот небольшая игруля сделанная на конкурс Gaminator15 (но выкладываю пост-конкурсную версию)- ссылка на архив , кто пройдёт от начала и до конца - тому "респект и уващужа вообще!" :)
0
29
9 лет назад
0
Слишком мощно звучит для моих классов :) Но вообще там функции уровня DrawQuad, DrawMesh, а для конкретных игровых объектов можно вообще ещё один класс написать (или реализовать в этих самых объектах), типа DrawSupergun или DrawBee.
лучше завести что-то типа IDrawableObject с методом Draw в который будет передаваться твой IRenderDevice или что-то типа того, с помощью которого можно будет рисовать примитивы на экране
0
15
9 лет назад
0
лучше завести что-то типа IDrawableObject с методом Draw в который будет передаваться твой IRenderDevice или что-то типа того, с помощью которого можно будет рисовать примитивы на экране
Как раз думал об этом, а то у меня раньше было, что GameObject имел метод Draw куда и передавался этот RenderDevice, вообще там была очень лихая цепочка передачи этого "девайса" - сначала GameState, потом CWorld, затем GameObject ( и наследники), но думаю во всех самописных движках что-то вроде этого ))
2
14
9 лет назад
2
но думаю во всех самописных движках что-то вроде этого ))
Нет. У меня, например, объекты сами себя рисовать не умеют. За них это делает жирный клас отрисовщика, который их сортирует, группирует, солит по вкусу и только потом рисует. Но можно и с методом Draw у каждого. Каждый онанирует, как ему импонирует.
0
24
9 лет назад
Отредактирован prog
0
Kozinaka:
У меня, например, объекты сами себя рисовать не умеют. За них это делает жирный клас отрисовщика, который их сортирует, группирует, солит по вкусу и только потом рисует.
Жирный плюс за разделение логики объекта и его отображения.
0
15
9 лет назад
0
... жирный клас отрисовщика, который их сортирует, группирует ...
Ну и RenderDevice можно аналогичной штуке научить ) Хотел вот добавить методы BeginScene \ EndScene (как в DirectX) там бы сбрасывались\ выводились списки на отрисовку.
А можешь небольшой примерчик кода как эта штука работает? С жирным отрисовщиком и конкретным объектом.
0
14
9 лет назад
Отредактирован Kozinaka
0
Жирный плюс за разделение логики объекта и его отображения.
Спасибо :) На самом деле можно разделять и с методом Draw, просто получается морока с двумя параллельными иерархиями объектов. Одна иерархия в игровом движке - это сугубо логика, а вторая иерархия уже с объектами отрисовки. Причём иерархии разные. Я в одном своём проекте так заморачивался, пользуясь возможностями C++ по множественному наследованию, но в текущем, "Несыти", не стал. Оставил только иерархию объектов в игровом движке + жирный класс отрисовщика, который всё про то, как рисовать игровые объекты знает.
А можешь небольшой примерчик кода как эта штука работает? С жирным отрисовщиком и конкретным объектом.
Кода дать не могу, окно треснет, а словами опишу. У меня движок спрайтовый, сцена слоёная. Например, есть слой с ландшафтом, есть слой с тенями наземных существ, есть слой с наземными существами, дальше тени высоких объектов, отбрасываемые на наземных существ и т.д. Некоторые объекты занимают несколько слоёв - у фонаря есть основание со своей тенью, спрайт света и верхняя часть. Внутри некоторых слоёв требуется сортировка спрайтов по тому или иному признаку - иногда просто по координате Y, иногда сложнее - звенья у червя, например, должны отрисовываться от хвоста к голове, чтобы наложения корректные были.
Имеем коллекцию объектов из игрового движка, которые нам нужно отрисовать в виде сцены. Есть полтора десятка слоёв - таких же коллекций объектов.
  1. В зависимости от типа объекта заталкиваем его в каждый слой, в котором он отрисовывается.
  2. Сортируем нужные слои нужным способом
  3. Отрисовываем слои последовательно снизу вверх, рисуя от объекта в слое только его соответствующую часть.
Этим всем занимается отрисовщик. Пока различных объектов и слоёв было немного, я всё разруливал на switch'ах, но, в итоге, пришел к пачке методов у отрисовщика типа DrawXXX(Layer layer), где XXX - имя объекта. Короче, к одному методу Draw я и пришел, только при отсутствии иерархии отрисовываемых объектов - методы отрисовки плавают в воздухе, а применяются к объектам из игрового движка, в которые отрисовка не вмешивается, это вообще другая сборка.
0
15
9 лет назад
0
Kozinaka:
Хмм .. ну со слоями вроде понятно - без них в 2Д сложновато. Но есть вопрос - по сути рисование объектов как-то вот так делается ?
void SuperGame_MainLoop_Draw( )
{
    // ...
    // собственно pDrawer - это тот, кто всех рисует
    // DrawWorm - это рисование "червя"
    // pWorm - объект самого "червя"
    pDrawer->DrawWorm( pWorm );
    // ...
}
Я правильно понял идею ?
0
14
9 лет назад
Отредактирован Kozinaka
0
Я правильно понял идею?
Почти. Идея такая:
pDrawer->DrawAll(); 
А внутри что-то типа такого:
pSceneDrawer->DrawScene();
pGuiDrawer->DrawGUI();
А внутри DrawScene() что-то такое:
DrawLandscape();

for(auto &obj : pGameEngine->Scene->Objects)
{
     switch(obj->Type)
     {
          ...  
          case Worm: DrawWorm(obj);
          ...
     }
}
0
15
9 лет назад
0
Kozinaka:
Ооо, понятно ) Я бы сделал почти аналогично, но вот вместо switch обычно наследование использовал и по этой причине всё рисование было реализовано в конкретных классах игровых объектов.
А по слоям раскидывает функция, например, DrawWorm или слой задаётся раньше ?
0
14
9 лет назад
0
По слоям разбрасывается раньше, тут этого просто нет. DrawWorm просто рисует червя на текущем слое и вызывается ровно только раз, на скольких слоях есть объект Worm. А вся кухня по слоению, бантчингу и сортировке находится внутри метода DrawScene() до прохода по слоям и вызовов DrawXXX().
Я бы сделал почти аналогично, но ... наследование использовал и ... всё рисование ... в классах игровых объектов
В данном случае "почти", это перемешивание логики с представлением. Покайся, пока не поздно! :)
0
15
9 лет назад
0
Kozinaka:
Каюсь! ) Вообще хотел когда-то сделать как советуют - разбить всё т.е. всякие там IRenderable, IMovable и т.д. но как-то не пошло :(
0
14
9 лет назад
Отредактирован Kozinaka
0
По идее, если для отрисовки объекта не нужно хранить состояния переменных специфичных для отрисовки, то в игровом движке никаких забот о рендере и быть не должно. Просто есть набор игровых объектов, по которым пробегается отрисовщик и так или иначе решает вопросы рисования.
Если хранить состояние нужно, то придётся чуть-чуть испачкаться. В минимальном варианте игровой объект должен уметь прицепить к себе внешнюю ссылку и предоставлять её по требованию. Я бы назвал это IBindable (потому как слов IRenderable в игровом движке не должно быть). Пробегаясь по объектам отрисовщик приделывает к игровым объектам объекты отрисовки из параллельной иерархии. Если у игрового объекта уже ассоциирован объект отрисовки - тогда не трогать, если нет (значит только что создали его) - создать и прицепить.
Например:
Игровой объект - зомби, у него есть координата и направление движения. Объект его отрисовки хранит у себя номер текущей анимации ходьбы и при отрисовке выводит нужный спрайт с нужным углом поворота.
Теперь представляем пачку зомби на карте. Как только в отрисовщик приходит игровой объект "Зомби" без ассоциированного объекта отрисовки, к нему приделывается свежый объект отрисовки, начинающий считать кадры анимации. Зомбаков много, ои находятся в разных стадиях анимации, которые хранятся в ассоциированных с ними объектами отрисовки. Мы разделили логику (координата, направление движения) от представления (выбор спрайта, номер кадра анимации) ценой одной внешней ссылки в игровом объекте.
1
24
9 лет назад
1
Kozinaka, более того, бывает достаточно создать не прямую ссылку от объекта логики к объекту отрисовки, а связать их, например, через какой-нибудь Map во внешнем менеджере. Это более ресурсоемкий вариант, зато позволяет полностью абстрагировать логику от представления.
1
29
9 лет назад
1
Почитал, подумал о вашей логике разделения и пришел вот к такой вот мысли, а почему бы не сделать вот так?
public interface IDrawer {
    // Методы для рисования
}
// Этот интерфейс отвечает за отрисовку объекта
public interface IObjectView {
    public void Draw(IDrawer drawer);
}
// Этот интерфейс отвечает за игровую логику объекта
public interface IObjectModel {
    public void OnTick(float dt);
    // Другие различные события игрового мира
}
// Этот интерфейс отвечает за обработку пользовательского ввода для заданного объекта
public interface IObjectController {
    public void OnKeyPress(char keyCode);
    // Другие события пользовательского ввода
}
// Этот класс представляет собой игровой объект с тремя сущностями, отвечающими за рендер, логику и управление этим объектом
public abstract class AGameObject {
    public IDataStorage Data { get; set; } // Область для хранения данных игрового объекта, которая должна быть общая между компонентами
    public IObjectView View { get; set; }
    public IObjectModel Model { get; set; }
    public IObjectController Controller { get; set; }
    // Другие сущности игрового объекта, например сущность физики
}
public interface IDataStorage {
    public object Get(string key);
    public void Set(string key, object value);
}
// А тут типа универсальная такая штука для базовых объектов
public static class GameObjectDataStorageExtension {
    public static T Get<T>(this IDataStorage storage, string key) {
        return (T)storage.Get(key);
    }
    public static void Set<T>(this IDataStorage storage, string key, T value) {
        storage.Set(key, value);
    }
    public static float GetX(this IDataStorage storage) {
        return storage.Get<float>("position.X");
    }
    public static float GetY(this IDataStorage storage) {
        return storage.Get<float>("position.Y");
    }
    // и т.д.
}
2
14
9 лет назад
Отредактирован Kozinaka
2
alexprey, потому что это перемешивание логики и представления. Разнос этого всего по интерфейсам делает перемешивание ещё более явным, AGameObject говорит нам прямым текстом: я и модель, и отрисовщик, и даже пользовательский ввод разбирать умею! Плевал я на вашу инкапсуляцию с четвёртого этажа. :)
...впрочем что-то такое вполне можно использовать в качестве сборной хранилки данных об объекте из разных мест. Т.е. после того, как мы сопоставили модель и её отрисовщик, можно положить это всё в подобную структурку. Но, конечно, не абстрактную, без публичных сеттеров и без разбора объектом конкретных нажатых кнопок клавиатуры - этим должен заниматься кто-то другой.
Map во внешнем менеджере. Это более ресурсоемкий вариант, зато позволяет полностью абстрагировать логику от представления.
Боюсь, при отрисовке придётся шерстить этот несчастный map'а для получения каждого объекта. Линейный рост трудоёмкости с увеличением количества объектов умножается на логарифмический рост сложности выборки из map'а. Я бы предпочёл за это не платить вовсе, сопоставив модель и представление только один раз при создании представления.
0
29
9 лет назад
0
Kozinaka, нее, не понимаешь, AGameObject сам ничего не умеет делать, он просто хранит данные, на самом деле его можно и не делать абстрактным. А в основном цикле уже можно будет пробегаться, смотреть что объект умеет делать и передавать управление уже в эти контроллеры. Как видишь тут как раз таки логика полностью разделена. Хочешь повесил отрисовку червя, хочешь повесил отрисовку жука, хочешь поставил управление клавиатурой, хочешь мышью, хочешь повесил ИИ. В общем то вот. Хотя на самом деле я считаю, что модель и вьюха этого игрового объекта не разделима. Управление, физика, ИИ это да, можно навешивать из-вне уже
0
14
9 лет назад
0
alexprey, ага, понял. Ты как хранилку его и задумывал. Меня абстрактность сбила. Самое интересное как раз в том, как его собрать - как сопоставить модели с остальными потрохами, если они динамически появляются и исчезают в процессе игры.
>Хотя на самом деле я считаю, что модель и вьюха этого игрового объекта не разделима.
Гради Буч смотрит на тебя с укором. :)
1
29
9 лет назад
1
Kozinaka, кст, я немного разобрался с мыслями. Model - хранит данные игрового объекта, Controller - реакция на различные события, ну а View - соответсвенно отрисовка. Соответсвенно сама модель ничего не должна знать о контроллере и о вьюхе, контроллер ничего не знает о вьюхе, он только управляет данными в модели, ну а вьюха сама берет модель и по данным из нее уже рисует объект
1
24
9 лет назад
Отредактирован prog
1
Kozinaka, если гнаться за производительностью и позволяет архитектура, то можно вобще параллельные списки/массивы использовать. Впрочем, архитектура позволяет далеко не всегда, особенно если логическому объекту может и не быть назначено графического объекта (объект полностью невидим или, например, его графика выгружена т.к. объект очень далеко от камеры, но просчитывать его еще нужно), а хуже всего когда у обьекта то есть вьюха, то нет её.
0
14
9 лет назад
Отредактирован Kozinaka
0
модель ничего не должна знать о контроллере и о вьюхе, контроллер ничего не знает о вьюхе, он только управляет данными в модели, ну а вьюха сама берет модель и по данным из нее уже рисует объект
Ага! То, что доктор прописал: ru.wikipedia.org/wiki/Model-View-Controller
prog, согласен. Но я даже не стал выбирать между способами реализации связи модели и представления, а просто отказался от отдельных объектов представления и в жирном отрисовщике жестко распределяю кого как рисовать. Дёшево и сердито. :)
0
29
9 лет назад
0
Kozinaka, дык да, я знаю что такое MVC, просто практика построения MVC архитектуры еще не очень прокачена
0
15
9 лет назад
0
Вот помню как-то спрашивал на gamedev.ru про компонентную систему объектов ( хотя вопрос был в другом, но немного и этот задели ). И был у меня такой пример - планируется игры типа Twisted Metal какой-нибудь (гонки на выживание). Как будет выглядеть класс-объявление машины и взрывной бочки и их "взаимоотношение"(столкновение, например ).
Имхо, дробление игрового объекта - спорная вещь, ведь можно так разделить, что аж жуть и вместо тройки классов будет 100500. Возможно сможете меня переубедить ? )
Чтобы оставить комментарий, пожалуйста, войдите на сайт.