Создание в Delphi эффекта пламени c использованием DirectX |
||||
---|---|---|---|---|
Существует мнение, что демки необходимо делать на ассемблере или, в крайнем случае, на Си. Якобы, хорошая демо должна иметь маленький размер, чего нельзя добиться в Delphi или Visual Basic . На самом деле, это не так. Главное в демке ни размер, ни алгоритмы, ни язык, на котором она написана, а выполняемые ей действия. В этой статье я опишу графический эффект, который, наверное, делали все демо-дизайнеры – эффект пламени. Создавать его будем в Delphi с использованием DirectX и WinAPI. Итак, начнем. Первое, что необходимо сделать – это создать в Delphi новой проект (Меню: File/New/Application). Так как мы не будем использовать VCL , то удалим из проекта форму (Project/Remove from Project). Откроем файл проекта (Project/View Source) и перепишем его: program Project1; uses windows,messages,directdraw; {$R *.res} type tacc=array[0..307200] of byte; var WindowClass: TWndClass; Instance: HWnd; Handle: HWnd; msg: TMsg; DDResult : HResult; DDrawObject : IDirectDraw; PrimarySurface : IDirectDrawSurface; SecondarySurface : IDirectDrawSurface; DDPal: IDirectDrawPalette; SurfaceDescription: TDDSurfaceDesc; BackCaps: TDDSCaps; palen:array[0..255] of PaletteEntry; b1,b2:^tacc; begin InitApp; InitDirectDraw; while GetMessage( msg, 0, 0, 0) do begin translatemessage(msg); dispatchmessage(msg); end; end. В секции uses мы прописали directdraw. Этот модуль для работы с библиотекой
DirectDraw, которого нет в стандартной поставке Delphi. Я использую адаптацию
C++ заголовков, сделанную Erik Unger. Затем идет описание глобальных переменных,
необходимых в работе программы. Первое, что должна делать программа – это создать окно в Windows. procedure InitApp; begin instance := GetModuleHandle(nil); with WindowClass do begin style := CS_HRedraw or CS_VRedraw; lpfnWndProc := @windowproc; Hinstance := Instance; LpszClassName := 'DXF'; hbrBackground:=0; hIcon := 0; end; RegisterClass(WindowClass); Handle:=CreateWindowEx(0, 'DXF', 'Flame', WS_POPUP, 5, 5, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), 0, 0, instance, nil); UpdateWindow(Handle); ShowWindow(Handle, CmdShow); SetTimer(Handle, 1, 1, nil); end; Сначала, мы заполняем структуру WindowsClass, необходимую для регистрации
класса окна. Затем, с помощью RegisterClass, регистрируем класс в Windows. После
этого, создаем окно и выводим его на экран. При регистрации класса окна мы указали, что процедура windowproc будет обрабатывать все события поступающие окну. function windowproc (Hwn,msg,wpr,lpr: longint): longint; stdcall; begin result := defwindowproc(hwn,msg,wpr,lpr); case msg of WM_DESTROY: begin FreeAll; Halt; end; WM_SETCURSOR: SetCursor(0); WM_KEYDOWN: begin if wpr=VK_ESCAPE then begin FreeAll; Halt; end; end; WM_TIMER: redraw; end; end; При получении сообщения WM_DESTROY или нажатии кнопки Esc программа вызывает FreeAll, отвечающею за освобождение системных ресурсов, занятых нами, и завершает работу. При сообщении WM_TIMER, вызывается процедура redraw, выполняющая вывод очередного кадра на экран. Но перед тем, как выводить что-либо на экран нужно инициализировать DirectDraw, и создать необходимые поверхности и буферы, а также установить палитру. procedure InitDirectDraw; var I: Integer; begin DDResult := DirectDrawCreate (nil, DDrawObject, nil); DDResult := DDrawObject.SetCooperativeLevel(handle, DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN); DDResult := DDrawObject.SetDisplayMode(640, 480, 8); with SurfaceDescription do begin dwSize := sizeof(SurfaceDescription); dwFlags := DDSD_CAPS or DDSD_BACKBUFFERCOUNT; ddsCaps.dwCaps := DDSCAPS_PRIMARYSURFACE or DDSCAPS_FLIP or DDSCAPS_COMPLEX; DwBackBufferCount := 1; end; DDResult := DDrawObject.CreateSurface (SurfaceDescription,PrimarySurface,nil); BackCaps.dwCaps := DDSCAPS_BACKBUFFER; PrimarySurface.GetAttachedSurface(BackCaps, SecondarySurface); for i:=0 to 25 do begin palen[i].peRed := (i)*10; palen[i].peGreen := 0; palen[i].peBlue := 0; end; for i:=26 to 85 do begin palen[i].peRed := 255; palen[i].peGreen := round((i-25)*4.2); palen[i].peBlue := 0; end; for i := 86 to 141 do begin palen[i].peRed := 255; palen[i].peGreen := 255; palen[i].peBlue := round((i-86)*4.51); end; for i := 142 to 255 do begin palen[i].peRed := 255; palen[i].peGreen := 255; palen[i].peBlue := 255; end; DDrawObject.CreatePalette(DDPCAPS_8BIT or DDPCAPS_ALLOW256, @palen, DDPal, nil); PrimarySurface.SetPalette(DDPal); new(b1); new(b2); end; Во-первых, создаем объект DirectDraw. Устанавливаем приложению эксклюзивный
полноэкранный уровень и разрешения экрана 640x480x8. Теперь можно рассмотреть и генерацию кадров. procedure redraw; var i, j, k: Integer; swp: Pointer; diracc:^tacc; begin i := 0; while (i<640) do begin b1[i+0] := random(2)*255; b1[i+640] := random(2)*255; inc(i, random(5)); end; for i := 1280 to 307200 do b2[i] := ((b1[i-1]+b1[i+1]+b1[i]+b1[i-639]+b1[i-641]+b1[i-1279]+b1[i-1280]+b1[i-1281]) shr 3); try SecondarySurface.Lock(nil, SurfaceDescription, DDLOCK_SURFACEMEMORYPTR or DDLOCK_WRITEONLY or DDLOCK_WAIT, 0); diracc := SurfaceDescription.lpSurface; j := 0; k := 306560; for i := 0 to 477 do begin Move(b2[k], diracc[j], 640); inc(j, SurfaceDescription.lPitch); dec(k, 640); end; finally SecondarySurface.Unlock(nil); end; swp := b1; b1 := b2; b2 := swp; PrimarySurface.Flip(nil, DDFLIP_WAIT); end; Первый цикл – это создание так называемых "hotspots", случайных точек
максимального цвета в начале первого буфера. Последнее, что необходимо написать - это освобождение системных ресурсов перед выходом из программы. procedure FreeAll; begin Dispose(b2); Dispose(b1); DDPal._Release; pointer(DDPal) := nil; SecondarySurface._Release; pointer(SecondarySurface) := nil; PrimarySurface._Release; pointer(PrimarySurface) := nil; DDrawObject._Release; pointer(DDrawObject) := nil; end; | ||||