在开始之前,我想为自己创建一些“概念证明”脚本,以弄清楚我将如何处理主要部分.我坚持的是我有问题的地方:
我需要能够在游戏的屏幕上显示一个图形,可能是一个透明的PNG文件.用户需要能够点击该图像,以便他们可以继续与游戏进行交互.我对如何去做有点迷茫.要求是:
>显示图像(或多个图像,就此而言)
>即使没有应用程序“焦点”,该图像仍然覆盖其他应用程序
>让图像成为非交互式(或点击式).
>我正在用C#开发应用程序
任何关于从哪里开始的指导将非常感谢!
解决方法
对于第一个版本,您可以首先查看Form的“AllowTransparency”和“TransparencyKey”以及“TopMost”属性.
(我发现TransparencyKey不适用于White(255,255,255),但是特定的非白色工作正常……不确定原因).
这可以作为一种可以点击其他形式的点击式表格…但由于它是透明的,因此无法在透明部分中显示图像.但是,如果你需要的只是一个适合目标应用程序的hud,这可能是最简单的方法.
如果这个顶级形式不会在游戏前结束……你可能会尝试将游戏置于窗口模式.
在全屏模式下运行时,游戏通常直接通过ActiveX,Direct3D,OpenGL,DirectDraw等绘制到屏幕上.
在这些之上绘图需要将代码注入DirectX,OpenGL或其他引擎的绘制/更新/刷新功能(基本上告诉DirectX3D在每个绘制周期结束时绘制您的东西).有一些现有的软件可以做到这一点:例如,Steam Overlay,fraps,xfire.
一个快速的谷歌搜索发现“Game Overlay”虽然我没有下载或试过,说它可以覆盖表格应用程序在游戏之上.
(似乎那个程序是在一家刚刚解散的公司之下,无论如何我似乎无法让它为我工作……)
可以创建一个不完全透明但通过本机Windows调用可以点击的表单.我会看看是否可以在接下来的几天内创建一个示例.
我找到了一个旧的测试项目,并对其进行了一些清理.
基本上,当它运行时,它将在屏幕的前面绘制500条随机红线,这些红线是可点击的.然后它绘制1000条随机白线(即擦除).然后重复.
在编写代码时,我希望获得一些概念证明:如何能够在表单的整个表面上绘制,如何以编程方式使表单在多个屏幕上变为完整大小,如何使用背景工作者,以及这个概念证明如何作为透明叠加层工作.
说明:
>创建名为TranparentOverlay_simpleExample的新Windows窗体项目
>在设计视图中,在Form1上设置以下属性:
> BackColor:白色
> FormBorderStyle:无
>位置:-1280,0(即屏幕的左上角,一个屏幕可能只有0,0)
> TopMost:是的
> TransparencyKey:白色
> WindowState:最大化
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,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,qY); } Thread.Sleep(10); } } } } } } }
Enums列表是在本机Windows调用中使用的值,并将像White这样的RGB颜色转换为uints使得处理本机Windows有点痛苦.
但是,最后,我们现在有一个覆盖所有屏幕的隐形画布,我们可以像任何其他图形对象一样绘制它(所以它就像在线条上一样容易地绘制文本或图片).
(我认为如果你将半透明的图片绘制到图形对象上,那么你可以自己制作半透明的叠加而不是完全不透明/透明的叠加).
此示例不能在全屏3D游戏上放置叠加层,但对于在窗口模式下运行的相同游戏可以正常工作.
(P.S.我刚刚在Team Fortress 2中对它进行了测试,它在Windowed模式下绘制它,但不是全屏,所以我猜测旧共和国将是类似的).
以下链接可能对试图挂钩Direct3D版本9,10和11的绘图例程的任何人都有用.
https://github.com/spazzarama/Direct3DHook
它不提供全功能覆盖,但上面的示例项目为我成功地在Team Fortress 2上每秒写入帧.它有关于如何开始使用它的良好说明.它应该指导您完成设置SlimDX Runtime和EasyHook的过程.