← Март 2002 → | ||||||
1
|
2
|
3
|
||||
---|---|---|---|---|---|---|
4
|
5
|
6
|
7
|
8
|
9
|
10
|
11
|
12
|
13
|
14
|
15
|
16
|
17
|
18
|
19
|
20
|
21
|
22
|
23
|
24
|
26
|
27
|
28
|
29
|
30
|
За последние 60 дней ни разу не выходила
Открыта:
25-10-2000
Адрес
автора: comp.soft.prog.gamemaker-owner@subscribe.ru
Статистика
0 за неделю
Как самому создать компьютерную игру #13
Адрес для обратной связи - gamemaker@pisem.net 2. Новый проект на GameMaker.ru 3. Виртуальная реальность: fish-eye. 4. Партиклы. 5. Послесловие. Когда я в первый раз захотел разместить свой сайт на платном хостинге, меня ждало разочарование. Цены за месяц "обслуживания" были в среднем 10$. Дороговато. Пускай даже сейчас они упали до 8-6$, все равно для обычного пользователя интернета, захотевшего разместить свой сайт в сети это неприемлемо. Но сейчас появилось новое решение, которое, возможно, Вас заинтересует. Разместить свой сайт в сети за 0.85$ в месяц могут ТОЛЬКО подписчики рассылки "Как самому создать компьютерную игру", т.е. Вы. Кстати, одноименный сайт тоже выбрал этот хостинг. Если Вас хоть немного это заинтересовало, смотрите подробности на www.gamemaker.ru/host. Если вспомнить первые выпуски рассылки, то там всем желающим предоставлялась возможность участия в проекте разработки авиасимулятора. Тогда желающих не нашлось из-за малого числа подписчиков. Но после того как было объявлено о том, что данный проект будет отложен до лучших времен, пришло (и до сих пор приходит) много писем с заявками на участие в проектах. Надо сказать, что данное событие совершенно случайно совпало с новым подпроектом сайта "Как самому создать компьютерную игру". (www.gamemaker.ru) Не знаю, что из этого получиться, но предложение следующее: сделать с небольшой группой читателей игру по сценарию, который Вы предложите. СтарО? Тогда, чтобы было понятно, что такого еще не было, расскажу подробнее. Описание этой акции начнем с некоторого вступления. Вступление Вот уже на протяжении очень долгого времени различные сайты предлагали набирать команды для проектов. В лучшем случае после завершения проекта на эти сайты выкладывался исходный код. В самом начале формирования рассылки "Как самому создать компьютерную игру" была мысль сделать подробный отчет о действующих проектах, чтобы можно было "заглянуть внутрь" создаваемой игре. Опубликовать переписку и основные вопросы разработчиков, план проекта, техническое описание итд. Но эта идея получила свое продолжение на других сайтах, количество которых можно было сосчитать на пальцах одной руки. Но либо это начинание не было доведено до конца, либо вся эта идея свелась к выкладыванию исходника в нескольких стадиях разработки. Неужели не интересно, с какими проблемами сталкивались разработчики на том или ином этапе. Какие идеи были сразу, а какие пришли по ходу разработки. Наша акция состоит из двух частей. Поговорим о каждой в отдельности, а потом об этом подпроекте в целом. Разработка проекта в "прямом эфире" По-моему совсем неплохо было бы наблюдать за процессом создания игры в прямом эфире. Достигаться это будет за счет частого обновления сайта. Что на нем будет выкладываться? Все, что связано с конкретным проектом, начиная от различных рассуждений, переписки, договоренностей и заканчивая исходными кодами, которыми мы будем располагать на тот день. Т.е. каждый желающий может следить за разработкой проекта в "прямом эфире" и вникать в код программы по частям. Почему это все нельзя было сделать в рамках рассылки? Наверное потому, что обновить сайт легче, чем написать полноценный новый номер рассылки. К тому же на сайте обновления могут происходить неограниченное число раз в день. Конференция для этих целей тоже не подходит, т.к. в любом случае на каком-то этапе понадобиться выкладывать файлы и скриншоты, которые поместить в рассылку или конференцию будет проблематично. В любом случае, почему бы не попробовать предложенную систему. ;) Теперь немного о том, как будет происходить набор в команду. Первое, что требуется для проекта - это сценарий. Для этого существует специальный форум на сайте "Как самому создать компьютерную игру". (www.gamemaker.ru) К каждому сценарию можно добавлять свои замечания и предложения. Далее, требуется набрать команду для определенного проекта. О том, каким образом будут выбираться сценарии для игр будет написано ниже. А сейчас предположим, что сценарий для проекта уже выбран. Каждый может посмотреть на этот сценарий и оценить свои силы. Даже, скорее всего, не силы, а желание участвовать в проекте, учитывая то, насколько устраивает Вас данный сценарий. Если все в порядке, то можно подать заявку на зачисление в группу. По достижению определенного числа человек в группе проект создания выбранной игры будет считаться открытым. Для этого проекта открывается отдельный форум, где будут обсуждать все вопросы участники проекта, а также, все остальные могут читать эти бредни и оставлять свои комментарии. Замечу, что обновление базы происходит в реальном времени. Туда же будут добавляться тексты переписки по е-мейлу, только с небольшим опозданием. К тому же будет открыта специальная рубрика а сайте, где можно будет найти все коды и скриншоты, относящиеся к проекту. Какой сценарий выбрать? Теперь вернемся к этому вопросу. Представьте себе, что предыдущий проект мы каким-то образом "размножили". Сделали несколько проектов с одинаковой концепцией. Отличаются только сценарии. Так вот, число проектов неограниченно. Это означает, что в одно и то же время разные люди (хотя и необязательно) могут работать над созданием разных игр. А выбираются сценарии очень просто. Как только он готов на 45-50% и 5-10 человек готовы делать игру по этому сценарию, то проект считается открытым. И, наконец, самое главное! Конечно, я не могу Вас заставить оставлять то, на что Вы потратили огромное количество времени, но хотелось бы, чтобы все эти проекты были не только open source, но и какая-то информация или история разработки тоже была доступна. Все это более подробно описано в "Соглашении об участии в проекте", которое должны принять все участники проекта. Соглашение в скором времени будет доступно на сайте. В прошлом выпуске, рассказывая о ray casting, не успел упомянуть об одном из подводных камней этого алгоритма - fish eye (рыбий глаз). Внимательные читатели заметили упоминание об этом в исходниках. Но к сожалению таких, кто по этому поводу задал вопрос было очень мало. Поэтому либо это очевидно, либо незаметно. В чем смысл fish-eye. Изображение на экране сильно искажается и вместо прямых линий в местах соединения пола и потолка со стенами видны дуги. Причина - неправильное перспективное проектирование, т.к. при перспективном проектировании образом прямой линии всегда является прямая. В нашей программе это можно исправить путем коррекции фокусного расстояния d = d*cos угла между лучом и направлением взгляда. Это просто дополнение для любопытствующих. :) Введение Привет всем. Сегодня речь пойдёт о, так называемых, системах частиц (particle system, или партикловые системы (далее ПС) - кому как больше нравится). Думаю, некоторые из вас в курсе, что это такое. Для остальных поясню. Предположим, вы хотите сделать снег. А что такое снег? Снег - это множество частиц, имеющие схожие геометрические параметры, схожий вектор движения и ускорения. Таким образом, можно сделать объект, который будет генерировать каждую такую снежинку, задавать её базовые параметры, учитывая флуктуацию (отклонения от нормы), управлять движением каждой снежинки и, наконец, отображать все снежинки. Такой объект и назовем партикловой системой. В итоге мы получаем снег, который при правильном задание параметров будет в вашей игре выглядеть как настоящий. Лично проверял :). Постараюсь изложить основу идеи в наиболее простой форме. ПС являются мощным аппаратом реализации спецэффектов. С их помощью можно представить такие спецэффекты как дым, фонтан, взрыв (хотя обычно его делают по-другому, но по-хорошему надо так), листопад, огонь, осадки, трава, ну и т.д. и т.п. Ну, надеюсь, вы прониклись идеей и теперь приступим к реализации. Базовые классы Давайте подумаем, как это всё можно реализовать (для реализации будем использовать ООП, а для отображения OpenGL, и желательно иметь представление о том и другом J). Идем от простого к сложному. Первый вопрос, что приходит в голову, это как представить описание каждой частицы. Для этой цели используем простой класс, и, исходя из принципов ООП, пишем базовый класс: class CParticle { public: CParticle() {}; virtual ~CParticle() {}; }; Следующее что нам надо - это объект, который управлял бы этими частицами и выводил их на экран. Для этого дела напишем тоже пока только базовый класс: class CParticleSystem { public: CParticleSystem() {} ~CParticleSystem() {} virtual HRESULT ResetSystem() = 0; virtual void UpdateSystem(DWORD TimePassed) = 0; virtual HRESULT RenderSystem() = 0; virtual HRESULT PSSendMessage(DWORD param1, DWORD param2) { return TRUE; } }; Немного поясню. Т.к. этот класс является базовым для других, то здесь можно сделать так: virtual HRESULT Func() = 0; Это значит, что данный класс содержит указатель на функцию HRESULT Func(), но не содержит её саму. Это чисто виртуальная функция, т.е. функция, которая должна быть обязательно заменена во всех потомках этого класса. Теперь об этих функциях: 1. ResetSystem - обнуляет ПС. 2. UpdateSystem(DWORD TimePassed) - вызывается для обновления всех партиклов (н/п перемещения). Её параметр - время, прошедшее с момента последнего вызова этой функции. 3. RenderSystem - думаю и так понятно. 4. PSSendMessage - что-то типа реализации управления ПС'ами. Похоже на PostMessage из API. Вот основные базовые классы. Теперь давайте рассмотрим, как с помощью этого всего можно изобразить подобие фонтана. Подобие фонтана Рассмотрим теорию. Каждая капля имеет свою координату. Назовем её Position. Каждая капля имеет скорость (Velocity). Не забудем и про гравитацию (Acceleration). Полезно в некоторых случаях знать старую позицию (OldPos). В реальной жизни выполняются следующие условия: 1. Число частиц N устремляем в бесконечность. 2. Размер частицы S - к нулю. Но на наших ЭВМ мы не сможем воссоздать полноты нашей любимой реальности, и потому введём ограничения. На данном этапе введём размер частицы (Size). Далее, ведём время жизни (Age). Реализуем это: class CFountainParticle : public CParticle { public: CFountainParticle() {} VECTOR Position; VECTOR OldPos; VECTOR Velocity; VECTOR Acceleration; int Age; float Size; }; Теперь на тему ПС. У каждого фонтана должна быть точка, из которой "вылетают" все капли (m_SystemOrigin), и должна здесь быть информация для генерирования частиц. К этому относятся: 1. m_iTimeRandFactor и m_iMinTime - время каждой партиклины можно посчитать как Время_Существования = Основное_Время + Произвольное_Время. Вот это оно и есть. 2. m_iMaxParts - ограничение на кол-во частиц. 3. m_Velocity - начальный вектор направления и m_RandomFactor - флуктуация начального вектора скорости. 4. m_fPartSize - размер партикла. 5. m_Acceleration - гравитация. 6. Введём условие для того, чтоб при достижении партиклом некоторого уровня он уничтожался. Тут две переменные - ось, по которой проверяется условие достижения уровня (m_iKillDown), и само значение уровня (m_iKillValue). Вот, что у нас вышло: class CFountain : public CParticleSystem { public: int m_iTimeRandFactor; int m_iMinTime; DWORD m_iMaxParts; VECTOR m_Velocity; VECTOR m_Acceleration; VECTOR m_RandomFactor; VECTOR m_SystemOrigin; int m_iKillDown; float m_fPartSize; int m_iKillValue; CFountainParticle *Particles; Забыли самое главное - Particles, это указатель на массив партиклов. Итак. Перейдем к реализации функций. Конструктор. Задаем здесь все параметры ПС и создаем массив партиклов: CFountain :: CFountain() { m_Velocity = VECTOR(0, 0, 0.07f); m_Acceleration = VECTOR(0, 0, -0.001f); m_SystemOrigin = VECTOR(0, 0, 0); m_RandomFactor = VECTOR(0.03f, 0.03f, 0.05f); m_iMinTime = 2000; m_iTimeRandFactor = 500; m_iMaxParts = 200; m_fPartSize = 1; m_iKillValue = 0; m_iKillDown = AXE_Z; Particles = new CFountainParticle[m_iMaxParts]; } Деструктор. Только удаляем массив партиклов. CFountain :: ~CFountain() { delete Particles; } А вот реализация обнуления ПС: HRESULT CFountain :: ResetSystem() { for(DWORD i=0;i < m_iMaxParts;i++) SetParticleDefaults(i); return S_OK; } void CFountain :: SetParticleDefaults(int i) { Particles[i].Position = m_SystemOrigin; Particles[i].Velocity = VECTOR( m_Velocity.x + m_RandomFactor.x * RandFloatZ, m_Velocity.y + m_RandomFactor.y * RandFloatZ, m_Velocity.z + m_RandomFactor.z * RandFloatZ); Particles[i].Acceleration = m_Acceleration; Particles[i].Age = m_iMinTime + (int)(RandFloat * (float)m_iTimeRandFactor); } Двигаем частицы: void CFountain :: UpdateSystem(DWORD TimePassed) { for (DWORD i=0; i < m_iMaxParts; i++) { Particles[i].Position = Particles[i].Position + (Particles[i].Velocity * (float)TimePassed); Particles[i].Velocity = Particles[i].Velocity + Particles[i].Acceleration; if (CheckForDown(i)) SetParticleDefaults(i); Particles[i].Age -= TimePassed; if (Particles[i].Age < 0) SetParticleDefaults(i); } } #define AXE_X 1 #define AXE_Y 2 #define AXE_Z 3 int CFountain :: CheckForDown(int i) { switch(m_iKillDown) { case 0: return FALSE; break; case AXE_X: if(Particles[i].Position.x < m_iKillValue) return TRUE; break; case AXE_Y: if(Particles[i].Position.y < m_iKillValue) return TRUE; break; case AXE_Z: if(Particles[i].Position.z < m_iKillValue) return TRUE; break; default: break; } return FALSE; } И, наконец, рендеринг: HRESULT CFountain :: RenderSystem() { glEnable(GL_POINT_SMOOTH); glPointSize(3); glBegin(GL_POINTS); for( DWORD i=0; i < m_iMaxParts; i++ ) { glVertex3f( Particles[i].Position.x, Particles[i].Position.y, Particles[i].Position.z); } glEnd(); return S_OK; } Вот и всё. Думаю, объяснять подробно каждую функцию не имеет смысла. Так что давайте перейдём к разработке математического аппарата. Математика Как вы заметили, у нас есть упоминание неопределённого пока типа VECTOR, и несколько неизвестных макросов. Начнём с макросов: #define RandFloat ((float)(rand()%1000)/1000.0f) #define RandFloatZ ((float)((rand()%1000)-500)/1000.0f) #define RandFloatHalf ((float)(rand()%500)/500.0f+0.5f) А теперь о векторе. В данной программе используется радиус-вектор и простые операции с ним. Это можно изобразить достаточно простым классом: class VECTOR { public: float x, y, z; // координаты вектора VECTOR(float nx, float ny, float nz) // конструктор с заданием параметров { x = nx; y = ny; z = nz; } VECTOR(){} // конструктор без задания параметров inline VECTOR operator+(const VECTOR &src) // сложение векторов { VECTOR nv; nv.x = x + src.x; nv.y = y + src.y; nv.z = z + src.z; return nv; } inline VECTOR operator*(const float f) // умножение вектора на число { VECTOR nv; nv.x = x * f; nv.y = y * f; nv.z = z * f; return nv; } }; Менеджер Усё готово. Осталось добавить некоторые мелочи: создание окна, обработчик сообщений и инициализацию OpenGL. Но перед этим предлагаю такой вопрос. Здесь у нас одна ПС, а если их более сотни? Нам потребуется некий менеджер для управления всеми ПС. Без лишних комментариев привожу текст менеджера: // константы #define MAX_PS 100 #define MIN_TIME_LIVE 10 // описание ПС'ы typedef struct { CParticleSystem *ps; // указатель на ПС (вот зачем нужны базовые классы) BYTE blive; // определяет время жизни ПС DWORD TimeLive; }DescriptPS; //Описание класса class CParticleManager { private: // описания ПС DescriptPS m_PS[MAX_PS]; // количество созданных ПС int m_numPS; public: CParticleManager(); virtual ~CParticleManager(); // Non comments HRESULT Move(DWORD); void DeletePS(int numPS); HRESULT Render(); virtual HRESULT PSSendMessage(int nPS, DWORD param1, DWORD param2) { return m_PS[nPS].ps->PSSendMessage(param1,param2); } // А так создаем нашу, пока единственную, ПС. void AddPSFountain() { m_PS[m_numPS].ps = new CFountain(); m_PS[m_numPS].blive = FALSE; m_PS[m_numPS].AutoDraw = TRUE; m_numPS++; } }; // Описание функций CParticleManager::CParticleManager() { m_numPS = 0; } CParticleManager::~CParticleManager() { for(int i=0;i < m_numPS;i++) { delete m_PS[i].ps; } } HRESULT CParticleManager::Render() { glEnable(GL_BLEND); glDepthMask(0); glBlendFunc(GL_SRC_ALPHA, GL_ONE); for(int i=0;i < m_numPS;i++) { m_PS[i].ps->RenderSystem(); } glDepthMask(1); glDisable(GL_BLEND); return S_OK; } // Удаляем ПС void CParticleManager::DeletePS(int numPS) { m_numPS--; m_PS[numPS].ps = m_PS[m_numPS].ps; m_PS[numPS].blive = m_PS[m_numPS].blive; m_PS[numPS].TimeLive = m_PS[m_numPS].TimeLive; delete m_PS[numPS].ps; m_PS[numPS].ps = NULL; m_PS[numPS].blive = FALSE; m_PS[numPS].TimeLive = 0; } HRESULT CParticleManager::Move(DWORD tic) { for(int i=0;i < m_numPS;i++) { // учитывается возможность временного существования ПС if(m_PS[i].blive == TRUE) { m_PS[i].TimeLive -= tic; if(m_PS[i].TimeLive < 0) { DeletePS(i); i--; continue; } } m_PS[i].ps->UpdateSystem(tic); } return S_OK; } Как это применить Чтоб посмотреть это в действии, создадим в MS Visual C++ проект Win32Application. В WinMain меняем главный цикл: while (GetMessage(&msg, NULL, 0, 0) == TRUE) { TranslateMessage(&msg); DispatchMessage(&msg); } на: BOOL bGotMsg; PeekMessage( &msg, NULL, 0U, 0U, PM_NOREMOVE ); m_bActive = TRUE; while( WM_QUIT != msg.message ) { if( m_bActive ) bGotMsg = PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ); else bGotMsg = GetMessage( &msg, NULL, 0U, 0U ); if( bGotMsg ) { if( 0 == TranslateAccelerator( hWnd, NULL, &msg ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } } else { if( m_bActive && m_bReady ) { redraw(); } } } Это позволяет производить рендеринг во всё свободное время, т.е. когда приложение не получает "сообщений" (messages). На тему инициализации графики. Это можно сделать при создании окна (сообщение WM_CLOSE): case WM_CREATE: hDC = GetDC(hWnd); setupPixelFormat(hDC); hGLRC = wglCreateContext(hDC); wglMakeCurrent(hDC, hGLRC); Init(); m_bReady = TRUE; break; Сначала надо узнать контекст окна, затем устанавливаем формат пикселя: void setupPixelFormat(HDC hDC) { PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), /* size */ 1, /* version */ PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER, /* support double-buffering */ PFD_TYPE_RGBA, /* color type */ 16, /* prefered color depth */ 0, 0, 0, 0, 0, 0, /* color bits (ignored) */ 0, /* no alpha buffer */ 0, /* alpha bits (ignored) */ 0, /* no accumulation buffer */ 0, 0, 0, 0, /* accum bits (ignored) */ 16, /* depth buffer */ 0, /* no stencil buffer */ 0, /* no auxiliary buffers */ PFD_MAIN_PLANE, /* main layer */ 0, /* reserved */ 0, 0, 0, /* no layer, visible, damage masks */ }; int pixelFormat; pixelFormat = ChoosePixelFormat(hDC, &pfd); if (pixelFormat == 0) { MessageBox(WindowFromDC(hDC), "ChoosePixelFormat failed.", "Error", MB_ICONERROR | MB_OK); exit(1); } if (SetPixelFormat(hDC, pixelFormat, &pfd) != TRUE) { MessageBox(WindowFromDC(hDC), "SetPixelFormat failed.", "Error", MB_ICONERROR | MB_OK); exit(1); } } Потом создаём контекст OpenGL и делаем его активным. И, наконец, вызывается функция, устанавливающая камеру, некоторые параметры OpenGL и создает нашу ПС: void Init() { glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-64,64, -48,48, -700,700); gluLookAt(0,0,5, 0,0,0, 0,1,0); glMatrixMode(GL_MODELVIEW); glEnable(GL_DEPTH_TEST); PM.AddPSFountain(); } Вот так реализуется завершение проги: case WM_DESTROY: m_bReady = FALSE; if (hGLRC) { wglMakeCurrent(NULL, NULL); wglDeleteContext(hGLRC); } ReleaseDC(hWnd, hDC); PostQuitMessage(0); break; Можно добавить обработчик нажатия на клавиши: case WM_KEYDOWN: if(wParam == 'P') g_bPause = !g_bPause; break; Рисуем: void redraw() { static dwLastTime = timeGetTime(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); DWORD time = timeGetTime() - dwLastTime; dwLastTime += time; if(!g_bPause) PM.Move(time); PM.Render(); SwapBuffers(hDC); } Всё просто. Теперь давайте посмотрим, как всё это можно облагородить. Билбоарды Билбоарды - это спрайт в трёхмерном пространстве. В данном варианте у нас рисуются только точки, а частенько бывает нужно изобразить в качестве частицы спрайт. Но как представить 2D спрайт в 3D пространстве? Для этого рисуем прямоугольник (2 треугольника) с наложенной текстурой, т.е. нашим спрайтом. При этом этот прямоугольник должен быть всегда повёрнут к юзеру лицом. Для этого выполняем следующие операции: 1. Сохранить матрицы преобразования, привести их к единичным. 2. Для каждого партикла сделать следующее преобразования - НовоеПоложение = ПоложениеПартикла*МатрицаВида 3. Рисуем прямоугольник по координатам НовоеПоложение: pVertices[0].vec = TransPos+VECTOR(-PartSize, PartSize,0); pVertices[1].vec = TransPos+VECTOR( PartSize, PartSize,0); pVertices[2].vec = TransPos+VECTOR(-PartSize, -PartSize,0); pVertices[3].vec = TransPos+VECTOR( PartSize, -PartSize,0); 4. Рисуем этот прямоугольник. 5. Пока не отобразили все партиклы, повторять с 2. 6. Вернуть сохранёные в 1 матрицы. Вектор В языке C++ имеется очень интересное образование, зовущееся вектором. Но это не геометрический вектор, а последовательность указателей на какой-либо тип, что-то вроде вектора прерывания. Для понимания приведу простой пример: // простой пример класса, хранящий строку и функцию вывода этой строки class MyClass { MyClass() {} ~MyClass() {} char str[255]; void Print() { cout << str << '\n'; } } // определяем тип MyVector - как вектор с типом данных (MyClass*) typedef std::vector< MyClass* > MyVector; // Определяем переменную MyVector vec; // пихаем пару объектов в вектор MyClass *p = new MyClass; lstrcpy(p1->str, "str1"); vec.push_back(p); p = new MyClass; lstrcpy(p->str, "str2"); vec.push_back(p); p = new MyClass; lstrcpy(p->str, "str3"); vec.push_back(p); ┘ // выводим эти строки на экран for(MyClass **it = vec.begin(); it!=vec.end; it++) { (*it)->Print(); } Удобно, да? Для тех, кто не понял: вектор создает в памяти последовательность указателей на указатели на класс. Что можно добавить А добавить можно многое. Во-первых, ещё пару ПС. Во-вторых, в менеджер можно добавить использование этого самого вектора. В-третьих, сейчас параметры ПС задаются в самом классе ПС, а нужно их задавать извне. Не забывайте про билбоарды. Можно сделать количество партиклов не фиксированным числом. Тогда в класс описания каждого партикла добавится указатель на следующий и предыдущий партикл. Этим можно добиться того, что можно будет задавать появлением партиклов с помощью иммисии. Кому интересно посмотреть эти ПС в реальном применении, можете посетить сайт, и скачать дему: www.cod.far.ru/main.html. Anatol (Белов А.П.)
PS: Приглашаются молодые и в меру опытные бойцы для работы над проектом. Если Вы сценарист/дизайнер/художник или програмер, и хотите принять участие в твёрдо стоящем на ногах проекте, то пишите anatol@sstu.ru , или заходите на наш сайт : http://www.cod.far.ru/main.htm . Как Вы уже успели заметить, сегодня появилась статья нового автора. Anatol - прошу любить и жаловать. :) Кстати, большой специалист в области компьютерной графики. Вторая новость - у нас появился новый сайт. (www.gamemaker.ru) Так что не забудьте туда заглянуть! (хотя бы из любопытства) Еще, как всегда, ждем вопросы, предложения и критику по поводу рассылки и сайта. Не оставляйте пустым форум сайта. :) А то, для кого старались? :) Да, кстати, если кто-нибудь тоже хочет "публиковаться" - всегда пожалуйста. До скорой встречи! SlyMagic.
Использование любых материалов рассылки возможно только с разрешения автора. Тираж 6320 экземпляров. |
http://subscribe.ru/
E-mail: ask@subscribe.ru |
Отписаться
Убрать рекламу |
В избранное | ||