PDA

Просмотр полной версии : [Статья] Рисуем поверх графики в окне Perfect World


dwa83
22.04.2012, 11:53
Многие интересуются проблемой вывода информации прямо в окно PW. Есть примеры, где перехватываются события окончания прорисовки сцены Direct3D и перед сменой буфера прорисовки кадра прорисовывается всё нужное. Мне показалось это слишком замороченным, и потому я попытался найти альтернативный способ вывода своей инфы поверх игры. Это привело меня к способу, которым можно вывести информацию даже без применения DirectX. Я например прикрутил сюда отображение с помощью OpenGL(не люблю Direct3D из-за сложности, а тут всё просто и понятно:))

Опишу в чём заключается мой способ на примере программы, сделанной в среде Borland C++ Builder 6.
Для вывода информации нам понадобится окно(буду называть его фреймом), которое мы присоединим к окну PW как дочернее и будем выводить в него то что нам нужно. Для работы нам понадобятся хэндл окна Perfect World и хэндл нашего фрейма.
Опишем их в глобальных переменных. Так же определимся с размерами нашего фрейма, хотя это можно сделать и не здесь.

HWND win_pw; // Хэндл окна PW
HWND frame; // Хэндл нашего фрейма
int frLeft=300; // Левый отступ фрейма от границы окна-родителя
int frTop=300; // Отступ сверху от границы окна-родителя
int frWidth=200; // Ширина фрейма
int frHeight=200; // Высота фрейма

// Так же я здесь объявлю класс, который я использую для прорисовки с помощью OpenGL.
// Вы будете использовать свой способ отображения
Tglwindow win;

Далее разместим на форме кнопку для нашего примера. При нажатии на неё будет происходить создание фрейма и присоединение к окну PW как дочернего элемента. Также разместим компонент Timer. Он будет отвечать за прорисовку изображения во фрейме. Нужно по умолчанию его отключить. Включаться он будет при нажатии на кнопку после создания фрейма, когда рисовать уже можно.

[Ссылки могут видеть только зарегистрированные и активированные пользователи]

Приступим к созданию фрейма. В обработчике клика по кнопке создаём наш компонент.

void __fastcall TForm1::Button1Click(TObject *Sender)
{

// Для начала находим хэндл окна PW и помещаем его в нашу глобальную переменную
win_pw=FindWindow(0,"Perfect World");


// Функцией CreateWindowEx мы динамически создаём наш фрейм(по сути окно)
// функция возвращает хэндл созданного окна
frame = CreateWindowEx
(
WS_EX_LAYERED, // "многослойное" окно
"STATIC", // класс окна
"Здравствуй, далёкий Perfect World !", // заголовок окна
WS_VISIBLE|WS_CHILD|WS_POPUP, // стиль окна
100, // левая граница окна(размеры мы потом поменяем на нужные)
100, // верхняя граница окна
60, // ширина
20, // высота
win_pw, // хэндл родительского окна(окна Perfect World)
(HMENU)NULL , // хэндл меню(не нужен)
NULL, // хэндл экземпляра приложения(не указываем)
NULL // pointer to window-creation data
);

// Установим чёрный цвет в роли прозрачного(не отображаемого)
SetLayeredWindowAttributes(frame, RGB(0, 0, 0), 0x0, LWA_COLORKEY);

// присоединим OpenGL к нашему фрейму для отрисовки графики
win.glcreate(frame);

// Включаем таймер, отвечающий за прорисовку
Timer1->Enabled=true;
}


Сделаю несколько пояснений. Я использую функцию CreateWindowEx вместо CreateWindow, так как в ней указываются дополнительные стили, а именно нужный нам WS_EX_LAYERED. Создав окно с указанием этого стиля мы сможем далее использовать такое дополнительное свойство как прозрачность определённого цвета. Это мы делаем для того, чтобы наш фрейм отображался с прозрачным фоном.

указав класс "STATIC" мы создаём окно на подобие компанента Panel, вобщем просто прямоугольную рабочую область. Хотя запросто можно использовать например класс "BUTTON", тогда в окне Perfect World будет отображаться обычная кнопка, в которую так же можно что-либо нарисовать или напечатать.

[Ссылки могут видеть только зарегистрированные и активированные пользователи]


"Здравствуй, далёкий Perfect World !" - заголовок окна, либо свойство Caption элементов.
стиль окна
WS_VISIBLE - видимое окно
WS_CHILD - является дочерним
WS_POPUP - а это самое главное. Указав это свойство, мы говорим, что наше окно распологается поверх всех элементов родительского окна. Поэтому наш фрейм будет выводиться поверх графики игры. Но здесь появляется одно "НО". Хотя наше дочернее окно и является дочерним от окна игры, сворачивается вместе с ним и т.д., но оно не ограничено областью окна игры и его координаты начинают определяться относительно всего экрана. Таким образом оно может выходить за границы родительского окна. Но это проблему мы решим далее.

[Ссылки могут видеть только зарегистрированные и активированные пользователи]

Далее приступим к рисованию в нашем фрейме. Пишем код в событии таймера.
Нам нужно решить проблему с вылезанием нашего фрейма за границу окна игры. Мы поступим следующим образом, считаем значения размеров и положения окна игры, и будем позиционировать наш фрейм относительно них. В этом нам поможет функция GetWindowRect, которая получит в структуру типа tagRECT положение нужного окна по его хэндлу. Создадим структуру tagRECT в которую и считаем нужные значения

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{

tagRECT rect;
GetWindowRect(win_pw,&rect);

Итак координаты у нас есть, установим положение нашего фрейма относительно полученных координат функцией SetWindowPos

SetWindowPos(frame,
HWND_BOTTOM,
rect.left + frLeft,
rect.top + frTop,
frWidth,
frHeight,
SWP_NOZORDER);

Теперь наш фрейм позиционирован относительно окна игры. Вот вроде бы и всё готово, можно приступать к рисованию. Я использую OpenGL

glClearColor(0.0,0.0,0.0,0.0); // задаём цвет очистки буфера кадра чёрным
win.clear(); // очищаем буфер
glLoadIdentity(); // сбросим матрицу
glTranslatef(0.0,0.0,-6.0); // "отодвинем" изображение от зрителя на 6 единиц

glBegin(GL_TRIANGLES); // начало сцены
// GL_TRIANGLES - каждые 3 вершины образуют треугольник
glVertex3f( 0.0f, 1.0f, 0.0f); // верх
glVertex3f(-1.0f,-1.0f, 0.0f); // слева снизу
glVertex3f( 1.0f,-1.0f, 0.0f); // справа снизу
glEnd(); // конец сцены

win.swapbuffer(); // смена буферов, делаем буфер кадра видимым

}


Я очищаю буфер кадра в чёрный цвет, таким образом при выводе на наш фрейм этот цвет будет прозрачным. Далее я для примера просто рисую белый треугольник на чёрном фоне(который станет прозрачным).

[Ссылки могут видеть только зарегистрированные и активированные пользователи]

Вот собственно и всё, что я хотел поведать. В работе с WinAPI я разбираюсь плохо, потому может быть куча недочётов в примере. Например, знающие люди могут создать фрейм изначально с чёрным фоном и просто средствами WinAPI печатать в него текст, тоесть вообще обойтись без графических библиотек и использовать только вывод стедствами Windows. Так же можно растянуть наш фрейм по размерам всего окна PW, и мы будем рисовать в него так, как будто рисовали бы в окно игры относительно его координат. Так же в данном примере присутствуют некоторые побочные артефакты, например игра реагирует на поворот камеры правой кнопкой мыши как-то "необычно". Возможно этот способ можно реализовать проще и он нуждается в доработке, но здесь я просто показал суть способа.