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