Как сделать неинтерактивное графическое наложение поверх другой программы на C#?

Чтобы дать некоторое представление, я разрабатываю программное обеспечение, которое помогает игрокам в игре Star Wars: The Old Republic. Игра имеет очень ограниченные возможности пользовательского интерфейса, поэтому я разрабатываю внешнее приложение, которое будет анализировать журнал в режиме реального времени и выводить визуальные подсказки, чтобы помочь пользователю максимизировать свою игровую производительность. Например, если персонаж получает определенный "бафф", его показывает боевой журнал, и я хочу разместить на экране визуальную подсказку (чтобы пользователю не нужно было обращать внимание на маленькие значки по периметру экран).

Прежде чем я начну, я хотел создать несколько сценариев "для проверки концепции", чтобы понять, как я собираюсь передать основные части. Я застрял там, где у меня есть вопрос:

Мне нужно, чтобы я мог показывать графику, скорее всего, файл PNG с прозрачностью, на экране над игрой. Пользователь должен иметь возможность кликать по этому изображению, чтобы он мог продолжать взаимодействовать с игрой. Я немного растерялся, как это сделать. Требования будут:

  • Показать изображение (или несколько изображений, если на то пошло)
  • Имейте это изображение наклеить поверх другого приложения, даже без приложения "фокус"
  • Пусть это изображение будет неинтерактивным (или доступным по клику).
  • Я занимаюсь разработкой приложения на C#

Любое руководство о том, с чего начать, будет очень цениться!

2 ответа

Я начал изучать некоторые подобные вещи, так что это может дать вам старт.

Для первой версии вы можете начать с просмотра свойств "AllowTransparency", "TransparencyKey" и "TopMost" формы.

(Я обнаружил, что TransparencyKey не работает с белым (255,255,255), но что определенные небелые цвета работают нормально... не знаю почему).

Это работало бы как форма, реагирующая на клики, которая стояла бы над другими формами... но поскольку она прозрачная, вы не можете отображать изображения в прозрачной части. Но если все, что вам нужно, это hud, который подходит для целевого приложения, это может быть самый простой способ.

Если эта форма верхнего уровня не появится перед игрой... вы можете попробовать перевести игру в оконный режим.

При запуске в полноэкранном режиме игры обычно выводятся на экран напрямую через ActiveX, Direct3D, OpenGL, DirectDraw и т. Д.

Рисование поверх этого потребовало бы внедрения кода в DirectX, OpenGL или другую функцию рисования / обновления / обновления другого движка (в основном, чтобы DirectX3D рисовал ваши вещи в конце каждого цикла рисования). Есть некоторые существующие программы, которые делают это: например, Steam Overlay, fraps, xfire.

Быстрый поиск в Google нашел " Game Overlay", который, хотя я не загружал и не пробовал, говорит, что он может перекрывать формы приложений поверх игр для вас.

(Кажется, что эта программа находится в компании, которая была только что распущена, и я все равно не мог заставить ее работать на меня...)

Можно создать форму, которая не является полностью прозрачной, но может быть обработана щелчком мыши при помощи собственных вызовов Windows. Я посмотрю, смогу ли я создать пример в течение следующих нескольких дней.


Я нашел старый тестовый проект и немного его почистил.

В основном, при запуске он рисует 500 случайных красных линий в передней части экрана, которые могут проходить по клику. Затем он рисует 1000 случайных белых линий (т.е. стирает). Потом повторяется.

При написании кода я хотел получить подтверждение концепции для нескольких вещей: как уметь рисовать на полной поверхности формы, как программно сделать форму полноразмерной на нескольких экранах, как использовать Фоновые работники, и Как это доказательство концепции может работать как прозрачное наложение.

Инструкции:

  • Создайте новый проект Windows Forms с именем TranparentOverlay_simpleExample
  • В режиме конструктора установите следующие свойства в форме Form1:
    • BackColor: Белый
    • FormBorderStyle: Нет
    • Расположение: -1280, 0 (т. Е. В левом верхнем углу экрана, с одним экраном, вероятно, просто 0,0)
    • TopMost: True
    • ПрозрачностьКлюч: Белый
    • WindowState: развернуто

Теперь введите представление кода для Form1 и замените его следующим:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.InteropServices;
namespace TransparentOverlay_simpleExample
{

    public partial class Form1 : Form
    {
        BackgroundWorker bw = new BackgroundWorker();
        Random rand = new Random(DateTime.Now.Millisecond);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool BringWindowToTop(IntPtr hWnd);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool SetForegroundWindow(IntPtr hWnd);

        [DllImport("user32.dll", EntryPoint = "SetWindowPos")]
        public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);



        [DllImport("user32.dll")]
        static extern int SendMessage(IntPtr hWnd, uint wMsg, UIntPtr wParam, IntPtr lParam); //used for maximizing the screen

        const int WM_SYSCOMMAND = 0x0112; //used for maximizing the screen.
        const int myWParam = 0xf120; //used for maximizing the screen.
        const int myLparam = 0x5073d; //used for maximizing the screen.


        int oldWindowLong;

        [Flags]
        enum WindowStyles : uint
        {
            WS_OVERLAPPED = 0x00000000,
            WS_POPUP = 0x80000000,
            WS_CHILD = 0x40000000,
            WS_MINIMIZE = 0x20000000,
            WS_VISIBLE = 0x10000000,
            WS_DISABLED = 0x08000000,
            WS_CLIPSIBLINGS = 0x04000000,
            WS_CLIPCHILDREN = 0x02000000,
            WS_MAXIMIZE = 0x01000000,
            WS_BORDER = 0x00800000,
            WS_DLGFRAME = 0x00400000,
            WS_VSCROLL = 0x00200000,
            WS_HSCROLL = 0x00100000,
            WS_SYSMENU = 0x00080000,
            WS_THICKFRAME = 0x00040000,
            WS_GROUP = 0x00020000,
            WS_TABSTOP = 0x00010000,

            WS_MINIMIZEBOX = 0x00020000,
            WS_MAXIMIZEBOX = 0x00010000,

            WS_CAPTION = WS_BORDER | WS_DLGFRAME,
            WS_TILED = WS_OVERLAPPED,
            WS_ICONIC = WS_MINIMIZE,
            WS_SIZEBOX = WS_THICKFRAME,
            WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW,

            WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
            WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU,
            WS_CHILDWINDOW = WS_CHILD,

            //Extended Window Styles

            WS_EX_DLGMODALFRAME = 0x00000001,
            WS_EX_NOPARENTNOTIFY = 0x00000004,
            WS_EX_TOPMOST = 0x00000008,
            WS_EX_ACCEPTFILES = 0x00000010,
            WS_EX_TRANSPARENT = 0x00000020,

            //#if(WINVER >= 0x0400)

            WS_EX_MDICHILD = 0x00000040,
            WS_EX_TOOLWINDOW = 0x00000080,
            WS_EX_WINDOWEDGE = 0x00000100,
            WS_EX_CLIENTEDGE = 0x00000200,
            WS_EX_CONTEXTHELP = 0x00000400,

            WS_EX_RIGHT = 0x00001000,
            WS_EX_LEFT = 0x00000000,
            WS_EX_RTLREADING = 0x00002000,
            WS_EX_LTRREADING = 0x00000000,
            WS_EX_LEFTSCROLLBAR = 0x00004000,
            WS_EX_RIGHTSCROLLBAR = 0x00000000,

            WS_EX_CONTROLPARENT = 0x00010000,
            WS_EX_STATICEDGE = 0x00020000,
            WS_EX_APPWINDOW = 0x00040000,

            WS_EX_OVERLAPPEDWINDOW = (WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE),
            WS_EX_PALETTEWINDOW = (WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST),
            //#endif /* WINVER >= 0x0400 */

            //#if(WIN32WINNT >= 0x0500)

            WS_EX_LAYERED = 0x00080000,
            //#endif /* WIN32WINNT >= 0x0500 */

            //#if(WINVER >= 0x0500)

            WS_EX_NOINHERITLAYOUT = 0x00100000, // Disable inheritence of mirroring by children
            WS_EX_LAYOUTRTL = 0x00400000, // Right to left mirroring
            //#endif /* WINVER >= 0x0500 */

            //#if(WIN32WINNT >= 0x0500)

            WS_EX_COMPOSITED = 0x02000000,
            WS_EX_NOACTIVATE = 0x08000000
            //#endif /* WIN32WINNT >= 0x0500 */

        }

        public enum GetWindowLongConst
        {
            GWL_WNDPROC = (-4),
            GWL_HINSTANCE = (-6),
            GWL_HWNDPARENT = (-8),
            GWL_STYLE = (-16),
            GWL_EXSTYLE = (-20),
            GWL_USERDATA = (-21),
            GWL_ID = (-12)
        }

        public enum LWA
        {
            ColorKey = 0x1,
            Alpha = 0x2,
        }

        [DllImport("user32.dll", SetLastError = true)]
        static extern int GetWindowLong(IntPtr hWnd, int nIndex);

        [DllImport("user32.dll")]
        static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

        [DllImport("user32.dll")]
        static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);

        /// <summary>
        /// Make the form (specified by its handle) a window that supports transparency.
        /// </summary>
        /// <param name="Handle">The window to make transparency supporting</param>
        public void SetFormTransparent(IntPtr Handle)
        {
            oldWindowLong = GetWindowLong(Handle, (int)GetWindowLongConst.GWL_EXSTYLE);
            SetWindowLong(Handle, (int)GetWindowLongConst.GWL_EXSTYLE, Convert.ToInt32( oldWindowLong | (uint)WindowStyles.WS_EX_LAYERED | (uint)WindowStyles.WS_EX_TRANSPARENT));
        }

        /// <summary>
        /// Make the form (specified by its handle) a normal type of window (doesn't support transparency).
        /// </summary>
        /// <param name="Handle">The Window to make normal</param>
        public void SetFormNormal(IntPtr Handle)
        {
            SetWindowLong(Handle, (int)GetWindowLongConst.GWL_EXSTYLE, Convert.ToInt32(oldWindowLong | (uint)WindowStyles.WS_EX_LAYERED));
        }

        /// <summary>
        /// Makes the form change White to Transparent and clickthrough-able
        /// Can be modified to make the form translucent (with different opacities) and change the Transparency Color.
        /// </summary>
        public void SetTheLayeredWindowAttribute()
        {
            uint transparentColor = 0xffffffff;

            SetLayeredWindowAttributes(this.Handle, transparentColor, 125, 0x2);

            this.TransparencyKey = Color.White;
        }

        /// <summary>
        /// Finds the Size of all computer screens combined (assumes screens are left to right, not above and below).
        /// </summary>
        /// <returns>The width and height of all screens combined</returns>
        public static Size getFullScreensSize()
        {
            int height = int.MinValue;
            int width = 0;

            foreach (Screen screen in System.Windows.Forms.Screen.AllScreens)
            {
                //take largest height
                height = Math.Max(screen.WorkingArea.Height, height);

                width += screen.Bounds.Width;
            }

            return new Size(width, height);
        }

        /// <summary>
        /// Finds the top left pixel position (with multiple screens this is often not 0,0)
        /// </summary>
        /// <returns>Position of top left pixel</returns>
        public static Point getTopLeft()
        {
            int minX = int.MaxValue;
            int minY = int.MaxValue;

            foreach (Screen screen in System.Windows.Forms.Screen.AllScreens)
            {
                minX = Math.Min(screen.WorkingArea.Left, minX);
                minY = Math.Min(screen.WorkingArea.Top, minY);
            }

            return new Point( minX, minY );
        }

        public Form1()
        {
            InitializeComponent();

            MaximizeEverything();

            SetFormTransparent(this.Handle);

            SetTheLayeredWindowAttribute();

            BackgroundWorker tmpBw = new BackgroundWorker();
            tmpBw.DoWork += new DoWorkEventHandler(bw_DoWork);

            this.bw = tmpBw;

            this.bw.RunWorkerAsync();
        }

        private void MaximizeEverything()
        {
            this.Location = getTopLeft();
            this.Size = getFullScreensSize();

            SendMessage(this.Handle, WM_SYSCOMMAND, (UIntPtr)myWParam, (IntPtr)myLparam);
        }

        private void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;

            int numRedLines = 500;
            int numWhiteLines = 1000;

            Size fullSize = getFullScreensSize();
            Point topLeft = getTopLeft();

            using (Pen redPen = new Pen(Color.Red, 10f), whitePen = new Pen(Color.White, 10f)) {
                using (Graphics formGraphics = this.CreateGraphics()) {

                    while (true) {

                        bool makeRedLines = true;

                        for (int i = 0; i < numRedLines + numWhiteLines; i++)
                        {

                            if (i > numRedLines)
                            {
                                makeRedLines = false;
                            }

                            //Choose points for random lines...but don't draw over the top 100 px of the screen so you can 
                            //still find the stop run button.
                            int pX = rand.Next(0, (-1 * topLeft.X) + fullSize.Width);
                            int pY = rand.Next(100, (-1 * topLeft.Y) + fullSize.Height);

                            int qX = rand.Next(0, (-1 * topLeft.X) + fullSize.Width);
                            int qY = rand.Next(100, (-1 * topLeft.Y) + fullSize.Height);

                            if (makeRedLines)
                            {
                                formGraphics.DrawLine(redPen, pX, pY, qX, qY);
                            }
                            else
                            {
                                formGraphics.DrawLine(whitePen, pX, pY, qX, qY);
                            }

                            Thread.Sleep(10);
                        }
                    }
                }
            }
        }
    }
}

Списки перечислений являются значениями, используемыми при вызовах собственных окон, и преобразование цветов RGB, таких как белый, в уинты делает работу с родной Windows немного трудной.

Но, наконец, у нас теперь есть невидимый холст, который покрывает все экраны, и мы можем рисовать на нем так же, как и с любым другим графическим объектом (поэтому рисовать текст или рисунки так же легко, как и линии).

(Я думаю, что если вы рисуете полупрозрачную картинку для графического объекта, вы могли бы сделать себя полупрозрачным наложением, а не полностью непрозрачным / прозрачным наложением).

В этом примере нельзя наложить оверлеи на полноэкранные 3d-игры, но он отлично работает для тех же игр, запущенных в оконном режиме.

(PS Я только что проверил это в Team Fortress 2, он рисует поверх него в оконном режиме, но не в полноэкранном режиме, поэтому я предполагаю, что Старая Республика будет похожа).


Следующие ссылки могут быть полезны всем, кто пытается подключиться к процедуре рисования для Direct3D версий 9, 10 и 11.

http://spazzarama.com/2011/03/14/c-screen-capture-and-overlays-for-direct3d-9-10-and-11-using-api-hooks/

https://github.com/spazzarama/Direct3DHook

Он не обеспечивает полнофункционального наложения, но приведенный выше пример проекта успешно записывает кадры в секунду поверх Team Fortress 2 для меня. Там есть хорошие инструкции о том, как начать его использовать. Он должен помочь вам в настройке SlimDX Runtime и EasyHook.

Способ, которым я делал это в прошлом, состоит в том, чтобы получить дескриптор главного окна, часто единственный способ сделать это - просмотреть весь список окон и найти тот, который вам нужен. Это проблематично, если одновременно открыто два экземпляра игры с одинаковым названием.

Когда у вас есть этот дескриптор окна, вы можете добавить визуальные элементы поверх того, что уже есть, указав, сколько пикселей больше, сколько пикселей вниз и сколько слоев "наружу" (z-индекс) вы хотите, чтобы оно было относительным в верхний левый пиксель окна.

Один из подходов к множественному окну состоит в том, чтобы запустить вашу программу на C#, которая проверяет, есть ли экземпляр игры, в которую уже играли, выдает сообщение и завершает работу, если есть, если нет, то запускает экземпляр вашей игры как дочерний процесс. Я думаю, что вы можете вернуть hwin в это время, но если нет, вы можете поискать в списке окон целевой заголовок после запуска игры.

Другие вопросы по тегам