Почему, уже рассказав в первой части статьи, о принципах создания кланов, я взялся за написание второй части статьи? Половина, а то и больше, игр пишется под DirectX, поэтому я просто счел своим долгом осветить технические особенности все тех же операций из первой части статьи, но уже под DirectX. Ну, давайте начнем….
Практически любое игровое приложение связано с понятиями видеорежимов, разрешения экрана, глубины цвета и другими. В нашем случае, мы тесно связанны с машинным представлением цвета, поэтому сперва я расскажу об используемых в большинстве игр видеорежимах. Обычно в своих опусах я пишу тот материал, который Вы не найдете в книгах, поскольку программисты почему-то предпочитают не освещать подобные проблемы, но следующий материал взят (вернее не взят, т.к. я излагаю его своим языком) из книги Стена Трухильо "Графика для Windows средствами DirectDraw" - настоятельно рекомендую.
В DirectX предусмотренно 4 видеорежима, соответственно 8 бит, 16 бит, 24 бита и 32 бита.
Итак, рассмотрим восьмибитный режим. В общем-то, на нем не стоило останавливаться, потому что современные игровые приложения его уже не используют, но поскольку в заголовке статьи указана игра, которая была написана именно в этом режиме, я решил все же немного рассказать и о нем.
Восмибитный режим кое в чем удобен - для определения цвета в нем используется один байт. Максимальное количество цветов соответственно - 256. Само значение, заносимое в этот байт, не является цветом, а лишь ссылкой на палитру цветов, где хранятся RGB цветовые компоненты ( индексом в массиве цветов ). Так вот, когда писали игру Warcraft, скорее всего пошли следующим путем: Часть палитры определили под изменяющиеся цвета, например: У Вас есть два клана - синий и зеленый, выделяем на синие и зеленые цвета по 16 элементов палитры с номерами 0-15 и 16-31 соответственно. Рисуем все спрайты, делая изменяющиеся их части одним из этих цветов, скажем синим. При выводе спрайта на экран, смотрим какого цвета его клан должен быть. Допустим он должн быть зеленым. Затем попиксельно просматриваем спрайт. Если находим пиксели с номерами 0-15 (синий цвет), меняем их на соответствующие с номерами (16-31). К примеру это может выглядеть так:
Color : Byte; // Цвет пикселя Const Blue = 0; Green = 1; If (Color div 16) = Blue then Color = Green*16 + ( Color mod 16 ); |
Эта информация не сочетается с той, что я выдал в первой части статьи, но это обусловлено особенностью именно восьмибитного режима.
Теперь, перепрыгнув через 16-битный режим, рассмотрим режим 24-бита. О 16-битном режиме разговор особый. В 24-битном режиме цвет представляется тремя байтами, каждый из каторых содержит одну цветовую компоненту - RGB соотоветственно. С этим режимом работать достаточно легко. Создание кланов происходит так же, как я писал в первой части статьи, за тем лишь исключением, что обращаться за пикселями приходится не к Tbitmap, а к TdirectDrawSurface, поскольку мы имеем дело с DirectX. Что такое TdirectDrawSurface я объяснять не буду, для этого Вам придется прочесть специальную литературу (ниже указан список), а как с этим работать поясню в примере для 16-битных поверхностей.
Теперь мы подошли к самому распространенному и самому сложному режиму - 16 бит. Как Вы уже поняли, цвет в этом режиме представлен двумя байтами. Это 64 кб цветов. Этого достаточно для самой взыскательной игры и занимает на одну треть меньше памяти, чем 24-битный цвет. Но за все надо платить - возни с ним побольше.
Во-первых, есть два типа этих режимов: в одном из них RGB компоненты в цвете занимают 15 бит, в другом 16 бит. Это иллюстрирует следующий рисунок:
Верхний вариант, который не использует один бит, иногда обозначают 555, нижний - где у зеленой компоненты на один бит больше, иногда обозначают 565.
Но это еще не все, помимо этих режимов еще существуют два таких же, но, с переставленными красной и синей компонентами, то есть вместо RGB - BGR. Причем Вы никогда не угадаете, какой из этих режимов будет у компьютера активизирован, это зависит от конкретного видеоустройства. Иногда не требуется знать, как именно представлен цвет в компьютере, но не в нашем случае, мы ведь выполняем побитовые операции с цветом. К счастью в DirectX реализованна возможность узнать с каким режимом мы работаем в данный момент.
Кроме этой проблемы, существует еще проблемы ширины картинки. В DirectX картинки хранятся на поверхностях TdirectDrawSurface. Так вот, оказывается, что ширина поверхности не всегда соответствует ширине картинки. Это тоже зависит от конкретного видеоустройства. Иными словами иногда DirectDraw выделяет памяти немного больше, чем надо - некоторые видеоустройства требуют чтобы ширина поверхности была кратна 12, если у вашей картинки ширина не кратна 12, то все равно под поверхность будет выделен участок памяти с шириной кратной 12. Это, при попиксельном доступе к поверхности, тоже необходимо учитывать, если Вы не хотите видеть Ваш спрайт перекошенным. К счастью DirectX также дает возможность узнать шаг (ширину поверхности) в байтах.
Ну, вот, в кратце, я рассказал про видеорежимы, теперь рассмотрим исходный текст демонстрационной прграммы.
Примечание: Прграмма написана на Delphi 5 с использованием компонент DelphiX, скачать можно тут.
Картинка со спрайтом помещается в компонент DXImageList под именем Sprite, картинка с изображением серой маски помещается под именем Mask.
Для начала нам понадобятся битовые цветовые маски красного, синего, зеленого, желтого, сиреневого, голубого цветов.
var ... RMask,GMask,BMask, YMask,FMask,AMask : Word; ... // Получить их можно следующим образом: RMask := DXDraw1.Surface.SurfaceDesc.ddpfPixelFormat.dwRBitMask; GMask := DXDraw1.Surface.SurfaceDesc.ddpfPixelFormat.dwGBitMask; BMask := DXDraw1.Surface.SurfaceDesc.ddpfPixelFormat.dwBBitMask; // Маска желтого цвета получается сложением по системе OR зеленой и красной маски: YMask := DXDraw1.Surface.SurfaceDesc.ddpfPixelFormat.dwRBitMask or DXDraw1.Surface.SurfaceDesc.ddpfPixelFormat.dwGBitMask; // Маска сиреневого цвета получается сложением по системе OR синей и красной маски: FMask := DXDraw1.Surface.SurfaceDesc.ddpfPixelFormat.dwRBitMask or DXDraw1.Surface.SurfaceDesc.ddpfPixelFormat.dwBBitMask; // Маска голубого цвета получается сложением по системе OR зеленой и синей маски: AMask := DXDraw1.Surface.SurfaceDesc.ddpfPixelFormat.dwGBitMask or DXDraw1.Surface.SurfaceDesc.ddpfPixelFormat.dwBBitMask; |
Теперь для получения нужного цвета клана необходимо сложить каждый пиксель серой маски с цветовой маской соответсвующего цвета и скопировать маску на спрайт учитывая черный цвет как прозрачный. Для этого в моем примере создана процедура CloneSprite, в качестве аргументов ей передается цветовая маска, того цвета, который мы хотим получить. Вот текст этой процедуры:
procedure TForm1.CloneSprite( ColorMask : Word); var //Объект - поверхность DirectDraw SurfaceDescSprite SpriteSurface, MaskSurface : TDirectDrawSurface; SurfaceDescMask : TDDSurfaceDesc; // Структура описывающая поверхность pBitsSprite, pBitsMask : PWordArray; // Указатель на начало области памяти поверхности SurfaceHeight: Integer; SurfaceWidth: Integer; // Размеры поверхности i,j : Integer; // Циклические переменные MaskColor : Word; // Цвет пикселя на серой маске (временная переменная) begin DXTimer.Enabled := False; // Отключить таймер ответственный за перерисовку // Здесь происходит присваивание ссылок на поверхность временным переменным SpriteSurface := DXImageList.Items.Find('Sprite').PatternSurfaces[0]; MaskSurface := DXImageList.Items.Find('Mask').PatternSurfaces[0]; // Для получения прямого доступа к поверхности ее надо заблокировать, // в параметрах передается прямоугольник на поверхности к которому // требуется получить доступ и структура с информационными полями SpriteSurface.Lock(SpriteSurface.ClientRect,SurfaceDescSprite); MaskSurface.Lock(MaskSurface.ClientRect,SurfaceDescMask); // После блокировки поля структуры будут содержать необходимую нам информацию // Получить высоту поверхности SurfaceHeight := SurfaceDescSprite.dwHeight; // Получить ширину поверхности в байтах, напомню, что она может отличаться от // ширины нашей картинки (спрайта), этот параметр надо разделить на 2, т.к. у // нас цвет кодируется двумя байтами SurfaceWidth := SurfaceDescSprite.lPitch div 2; // Получить указатели на поверхности спрайта и серой маски pBitsSprite := SurfaceDescSprite.lpSurface; pBitsMask := SurfaceDescMask.lpSurface; // В цикле по строкам и столбцам изображения производим сложение пикселей // серой маски с цветовой маской и присваиваем полученное пикселям спрайта for j := 0 to SurfaceHeight - 1 do for i := 0 to SurfaceWidth - 1 do begin // Получить пиксель серой маски MaskColor := pBitsMask[j*SurfaceWidth + i]; // Если он не черный, то if MaskColor <> 0 then // Сложить с цветовой маской и присвоить пикселю спрайта pBitsSprite[j*SurfaceWidth + i] := MaskColor AND ColorMask; end; // Не забыть разблокировать поверхности иначе компьютер зависнет в мертвую SpriteSurface.UnLock; MaskSurface.UnLock; DXTimer.Enabled := True; // Включить таймер перерисовки end; |
Вывод спрайта на экран осуществляется в обработчике события OnTimer, компонента TDXTimer:
procedure TForm1.DXTimerTimer(Sender: TObject; LagCount: Integer); begin DXDraw1.Surface.Fill(0);// Очистить буфер // Нарисовать спрайт DXImageList.Items.Find('Sprite').Draw(DXDraw1.Surface,0,0,0); // Вывести информацию о частоте кадров with DXDraw1.Surface.Canvas do begin Brush.Style := bsClear; Font.Color := clWhite; Font.Size := 12; Textout(0, 0, 'FPS: '+inttostr(DXTimer.FrameRate)); Release; end; // Переключить поверхности DXDraw1.Flip; end; |
Вот, собственно и все. Для полного понимания смотрите тексты примера. Если возникнут какие-нибудь вопросы пишите мне на email, который указан в Copyright. Примеры для данной статьи качать тут