使用SharpDX的HUD基础知识:如何绘制屏幕表面?

脚本

我打算学习如何为第三方PC游戏(免费,合法)开发自定义HUD的基础知识,我的目的是开发一个应用程序,在屏幕上绘制/显示其他信息(通过读取mem地址)。

所以我研究了专业项目,我发现使用SharpDX库的TurboHUD使用基于DirectX的库在屏幕上绘制对象/覆盖/文本,结果非常好(在绘图的任何时候都不会失去性能)屏幕上的多个对象),但是,由于该项目的作者没有提供理解他们如何做到这一点的来源,所以我试图自己学习使用相同的专业技术。

如果我走错路或者我错过了一些比SharpDX更好的替代方案来开发它,请告诉我。

我的主要问题是:

在C#或最好是VB.Net中,我如何使用SharpDX在屏幕上有效地绘制自定义字符串?

请注意,我可以将Form的难度设置为0,但我认为它应该存在正确的方式,并且我要求知道在“桌面”屏幕上直接绘制的正确方法。

我期望推出PC游戏,然后启动我的自定义HUD,它将在“桌面”屏幕上绘制以添加游戏的其他信息,我希望你理解我。

研究

我应该澄清一下,我对这种DirectX库完全缺乏经验,我正在使用SharpDX示例包来尝试学习它的用法。

由于所有样本都在C#中,因此在VB.Net下学习它的用法更加困难。

SharpDX示例包中有一个’ AdvancedTextRenderingApp’C #项目,但正如它的名字所说它是一个高级示例,它也是一个自定义表单( SharpDX.Windows.RenderForm )来绘制该表单。

这是我提到的C#项目的VB.Net代码翻译:

http://pastebin.com/KG2c3v09


更新:

只是打算对我所做的研究发表评论:

我最近发现了这个有用的GitHub存储库 ,然而,它无法在Visual Studio 2015中编译(因为缺少命名空间使用,添加它们会产生更多编译器错误),它也面向高级SharpDX用户,并在分析完整样本后仍然不知道如何在第三部分流程窗口的表面上编写/绘制…而且C#语法让我对SharpDX的使用有了全面的了解,因为作者也做了大量的SharpDX定制实现成员,然后……我不仅仅是失去了所有这些例子。

SharpDX官方样本是另一个看似非常有用的东西……可能适用于高级用户。 一些示例似乎演示了如何渲染自定义窗口/表面(使用500个繁琐且难以理解的代码行来完成它。而且他们的“ Hello world ”示例对我来说是一场噩梦。)但是,我想要做的就是我在我的问题中解释的是在另一个进程的现有窗口的表面上绘制,并且我知道可能的是,我需要使用SharpDX从头开始渲染“表面”,然后将其定位在目标窗口中,然后使表面看不见,然后画上它,但我不知道该怎么做。

我花了一段时间才找到如何在XNA中加载字体来绘制文本,但一切正常。

你需要4件事:

  1. 使表格最顶层
  2. 将航空玻璃风格延伸至整体forms(透明工作)
  3. 初始化XNA, Microsoft XNA Game Studio 4.0
  4. 绘制纹理和文本

一个限制

游戏不得处于全屏模式。 与TurboHUD限制相同

Form1.cs

 using System; using System.Collections.Generic; using System.ComponentModel; using System.Text; using System.IO; using System.Windows.Forms; using System.Threading; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using System.Runtime.InteropServices; using System.Drawing; namespace XNATransparentWindow { public partial class Form1 : Form { private ContentBuilder contentBuilder; public Form1() { InitializeComponent(); TopMost = true; FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; MARGINS margins = new MARGINS(); margins.leftWidth = 0; margins.rightWidth = 0; margins.topHeight = this.Width; margins.bottomHeight = this.Height; // Extend aero glass style to whole form DwmExtendFrameIntoClientArea(this.Handle, ref margins); //Load XNA directX this.contentBuilder = new ContentBuilder(); graphicsDeviceService = GraphicsDeviceService.AddRef(Handle, ClientSize.Width, ClientSize.Height); //Register the service, so components like ContentManager can find it. services.AddService(graphicsDeviceService); //Get the Graphics device dev = graphicsDeviceService.GraphicsDevice; if(dev == null){/*error message*/} //Load texture int bufferSize; System.IO.MemoryStream memoryStream; Bitmap img; using (img = new Bitmap(@"C:\...\.png")) { bufferSize = img.Height * img.Width * 4; memoryStream = new System.IO.MemoryStream(bufferSize); img.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png); memoryStream.Seek(0, SeekOrigin.Begin); texture = Texture2D.FromStream(dev, memoryStream, img.Width, img.Height, false); memoryStream.Close(); if(texture == null){/*error message*/} } //Create sprite s_Batch = new SpriteBatch(dev); if(s_Batch == null){/*error message*/} FontPos = new Vector2(270.0F, 110.0F); //Load font contentManager = new ContentManager(services, this.contentBuilder.OutputDirectory); this.contentBuilder.Clear(); this.contentBuilder.Add(@"C:\...\my_font1.spritefont","my_font1", "FontDescriptionImporter", "FontDescriptionProcessor"); //Build spritefont to get the .xbn file string error = this.contentBuilder.Build(); //If build fail if (!String.IsNullOrEmpty(error)) { MessageBox.Show(error); return; } //Load the .xbn file Font1 = contentManager.Load("my_font1"); if(Font1 == null){/*error message*/} } [DllImport("dwmapi.dll")] static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS margin); [DllImport("user32.dll")] static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, Int32 wParam, Int32 lParam); [StructLayout(LayoutKind.Sequential)] public struct MARGINS { public int leftWidth; public int rightWidth; public int topHeight; public int bottomHeight; } public ServiceContainer Services { get { return services; } } ServiceContainer services = new ServiceContainer(); GraphicsDevice dev; SpriteFont Font1; Vector2 FontPos; SpriteBatch s_Batch; Texture2D texture; ContentManager contentManager; GraphicsDeviceService graphicsDeviceService; private const UInt32 WM_NCLBUTTONDOWN = 0xA1; private const Int32 HTCAPTION = 0x2; private void Form1_MouseDown(object sender, MouseEventArgs e) { if (e.Button == System.Windows.Forms.MouseButtons.Right) { this.Close(); } else //to move the form { this.Capture = false; SendMessage(this.Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0); } } private void Form1_Paint(object sender, PaintEventArgs e) { //There are two buffers. One offscreen-backbuffer and the //frontbuffer which is the actual form in this example. The //drawings are done to the backbuffer and in the end the two //buffers flip. The backbuffer becomes frontbuffer and the //frontbuffer becomes backbuffer. //The drawing should start when the last resource is loaded. //Since Font1 is the last one in this example I check for this if (Font1 == null) { return; } //clear the backbuffer with transparent color. dev.Clear(Microsoft.Xna.Framework.Color.Transparent); //Do all your drawings here //draw texture and text with the sprite s_Batch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend); s_Batch.Draw(texture, new Microsoft.Xna.Framework.Rectangle(0, 0, this.Width, this.Height), Microsoft.Xna.Framework.Color.White); s_Batch.DrawString(Font1, @"XNA FRAMEWORK", FontPos, Microsoft.Xna.Framework.Color.Black); s_Batch.End(); //here the flip is performed dev.Present(); } //Release resources private void Form1_FormClosing(object sender, FormClosingEventArgs e) { graphicsDeviceService.GraphicsDevice.Dispose(); graphicsDeviceService.Release(true); s_Batch.Dispose(); texture.Dispose(); } } } 

以下类只是我找到的示例XNA 4.0内容编译器的复制粘贴(稍作调整)。 它们仅用于加载用于绘制文本的字体:

GraphicsDeviceService.cs

 #region File Description //----------------------------------------------------------------------------- // GraphicsDeviceService.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #endregion #region Using Statements using System; using System.Threading; using Microsoft.Xna.Framework.Graphics; #endregion // The IGraphicsDeviceService interface requires a DeviceCreated event, but we // always just create the device inside our constructor, so we have no place to // raise that event. The C# compiler warns us that the event is never used, but // we don't care so we just disable this warning. #pragma warning disable 67 namespace XNATransparentWindow { ///  /// Helper class responsible for creating and managing the GraphicsDevice. /// All GraphicsDeviceControl instances share the same GraphicsDeviceService, /// so even though there can be many controls, there will only ever be a single /// underlying GraphicsDevice. This implements the standard IGraphicsDeviceService /// interface, which provides notification events for when the device is reset /// or disposed. ///  class GraphicsDeviceService : IGraphicsDeviceService { #region Fields // Singleton device service instance. static GraphicsDeviceService singletonInstance; // Keep track of how many controls are sharing the singletonInstance. static int referenceCount; #endregion ///  /// Constructor is private, because this is a singleton class: /// client controls should use the public AddRef method instead. ///  GraphicsDeviceService(IntPtr windowHandle, int width, int height) { parameters = new PresentationParameters(); parameters.BackBufferWidth = Math.Max(width, 1); parameters.BackBufferHeight = Math.Max(height, 1); parameters.BackBufferFormat = SurfaceFormat.Vector4; // SurfaceFormat.Color; parameters.DeviceWindowHandle = windowHandle; parameters.PresentationInterval = PresentInterval.Immediate; parameters.IsFullScreen = false; graphicsDevice = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.Reach, parameters); } ///  /// Gets a reference to the singleton instance. ///  public static GraphicsDeviceService AddRef(IntPtr windowHandle, int width, int height) { // Increment the "how many controls sharing the device" reference count. if (Interlocked.Increment(ref referenceCount) == 1) { // If this is the first control to start using the // device, we must create the singleton instance. singletonInstance = new GraphicsDeviceService(windowHandle, width, height); } return singletonInstance; } ///  /// Releases a reference to the singleton instance. ///  public void Release(bool disposing) { // Decrement the "how many controls sharing the device" reference count. if (Interlocked.Decrement(ref referenceCount) == 0) { // If this is the last control to finish using the // device, we should dispose the singleton instance. if (disposing) { if (DeviceDisposing != null) DeviceDisposing(this, EventArgs.Empty); graphicsDevice.Dispose(); } graphicsDevice = null; } } ///  /// Resets the graphics device to whichever is bigger out of the specified /// resolution or its current size. This behavior means the device will /// demand-grow to the largest of all its GraphicsDeviceControl clients. ///  public void ResetDevice(int width, int height) { if (DeviceResetting != null) DeviceResetting(this, EventArgs.Empty); parameters.BackBufferWidth = Math.Max(parameters.BackBufferWidth, width); parameters.BackBufferHeight = Math.Max(parameters.BackBufferHeight, height); graphicsDevice.Reset(parameters); if (DeviceReset != null) DeviceReset(this, EventArgs.Empty); } ///  /// Gets the current graphics device. ///  public GraphicsDevice GraphicsDevice { get { return graphicsDevice; } } GraphicsDevice graphicsDevice; // Store the current device settings. PresentationParameters parameters; // IGraphicsDeviceService events. public event EventHandler DeviceCreated; public event EventHandler DeviceDisposing; public event EventHandler DeviceReset; public event EventHandler DeviceResetting; } } 

ServiceContainer.cs

 #region File Description //----------------------------------------------------------------------------- // ServiceContainer.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #endregion #region Using Statements using System; using System.Collections.Generic; #endregion namespace XNATransparentWindow { ///  /// Container class implements the IServiceProvider interface. This is used /// to pass shared services between different components, for instance the /// ContentManager uses it to locate the IGraphicsDeviceService implementation. ///  public class ServiceContainer : IServiceProvider { Dictionary services = new Dictionary(); ///  /// Adds a new service to the collection. ///  public void AddService(T service) { services.Add(typeof(T), service); } ///  /// Looks up the specified service. ///  public object GetService(Type serviceType) { object service; services.TryGetValue(serviceType, out service); return service; } } } 

ContentBuilder.cs

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using System.Diagnostics; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; using Microsoft.Build.Framework; namespace XNATransparentWindow { public class ContentBuilder : IDisposable { #region Fields // What importers or processors should we load? const string xnaVersion = ", Version=4.0.0.0, PublicKeyToken=842cf8be1de50553"; static string[] pipelineAssemblies = { "Microsoft.Xna.Framework.Content.Pipeline.FBXImporter" + xnaVersion, "Microsoft.Xna.Framework.Content.Pipeline.XImporter" + xnaVersion, "Microsoft.Xna.Framework.Content.Pipeline.TextureImporter" + xnaVersion, "Microsoft.Xna.Framework.Content.Pipeline.EffectImporter" + xnaVersion, "Microsoft.Xna.Framework.Content.Pipeline.AudioImporters" + xnaVersion, "Microsoft.Xna.Framework.Content.Pipeline.VideoImporters" + xnaVersion, // If you want to use custom importers or processors from // a Content Pipeline Extension Library, add them here. // // If your extension DLL is installed in the GAC, you should refer to it by assembly // name, eg. "MyPipelineExtension, Version=1.0.0.0, PublicKeyToken=1234567812345678". // // If the extension DLL is not in the GAC, you should refer to it by // file path, eg. "c:/MyProject/bin/MyPipelineExtension.dll". }; // MSBuild objects used to dynamically build content. Project buildProject; ProjectRootElement projectRootElement; BuildParameters buildParameters; List projectItems = new List(); //ErrorLogger errorLogger; // Temporary directories used by the content build. string buildDirectory; string processDirectory; string baseDirectory; // Generate unique directory names if there is more than one ContentBuilder. static int directorySalt; // Have we been disposed? bool isDisposed; #endregion #region Properties /// Gets the output directory, which will contain the generated .xnb files. public string OutputDirectory { get { return Path.Combine(buildDirectory, "bin/Content"); } } #endregion #region Initialization /// Creates a new content builder. public ContentBuilder() { CreateTempDirectory(); CreateBuildProject(); } /// Finalizes the content builder. ~ContentBuilder() { Dispose(false); } /// Disposes the content builder when it is no longer required. public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// Implements the standard .NET IDisposable pattern. protected virtual void Dispose(bool disposing) { if (!isDisposed) { isDisposed = true; DeleteTempDirectory(); } } #endregion #region MSBuild /// Creates a temporary MSBuild content project in memory. void CreateBuildProject() { string projectPath = Path.Combine(buildDirectory, "content.contentproj"); string outputPath = Path.Combine(buildDirectory, "bin"); // Create the build project. projectRootElement = ProjectRootElement.Create(projectPath); // Include the standard targets file that defines how to build XNA Framework content. projectRootElement.AddImport("$(MSBuildExtensionsPath)\\Microsoft\\XNA Game Studio\\" + "v4.0\\Microsoft.Xna.GameStudio.ContentPipeline.targets"); buildProject = new Project(projectRootElement); buildProject.SetProperty("XnaPlatform", "Windows"); buildProject.SetProperty("XnaProfile", "Reach"); buildProject.SetProperty("XnaFrameworkVersion", "v4.0"); buildProject.SetProperty("Configuration", "Release"); buildProject.SetProperty("OutputPath", outputPath); // Register any custom importers or processors. foreach (string pipelineAssembly in pipelineAssemblies) { buildProject.AddItem("Reference", pipelineAssembly); } // Hook up our custom error logger. //errorLogger = new ErrorLogger(); buildParameters = new BuildParameters(ProjectCollection.GlobalProjectCollection); //buildParameters.Loggers = new ILogger[] { errorLogger }; } /// Adds a new content file to the MSBuild project. The importer and /// processor are optional: if you leave the importer null, it will /// be autodetected based on the file extension, and if you leave the /// processor null, data will be passed through without any processing. public void Add(string filename, string name, string importer, string processor) { ProjectItem item = buildProject.AddItem("Compile", filename)[0]; item.SetMetadataValue("Link", Path.GetFileName(filename)); item.SetMetadataValue("Name", name); if (!string.IsNullOrEmpty(importer)) item.SetMetadataValue("Importer", importer); if (!string.IsNullOrEmpty(processor)) item.SetMetadataValue("Processor", processor); projectItems.Add(item); } /// Removes all content files from the MSBuild project. public void Clear() { buildProject.RemoveItems(projectItems); projectItems.Clear(); } /// Builds all the content files which have been added to the project, /// dynamically creating .xnb files in the OutputDirectory. /// Returns an error message if the build fails. public string Build() { // Create and submit a new asynchronous build request. BuildManager.DefaultBuildManager.BeginBuild(buildParameters); BuildRequestData request = new BuildRequestData(buildProject.CreateProjectInstance(), new string[0]); BuildSubmission submission = BuildManager.DefaultBuildManager.PendBuildRequest(request); submission.ExecuteAsync(null, null); // Wait for the build to finish. submission.WaitHandle.WaitOne(); BuildManager.DefaultBuildManager.EndBuild(); // If the build failed, return an error string. if (submission.BuildResult.OverallResult == BuildResultCode.Failure) { //return string.Join("\n", errorLogger.Errors.ToArray()); } return null; } #endregion #region Temp Directories /// Creates a temporary directory in which to build content. void CreateTempDirectory() { // Start with a standard base name: // // %temp%\WinFormsContentLoading.ContentBuilder baseDirectory = Path.Combine(Path.GetTempPath(), GetType().FullName); // Include our process ID, in case there is more than // one copy of the program running at the same time: // // %temp%\WinFormsContentLoading.ContentBuilder\ int processId = Process.GetCurrentProcess().Id; processDirectory = Path.Combine(baseDirectory, processId.ToString()); // Include a salt value, in case the program // creates more than one ContentBuilder instance: // // %temp%\WinFormsContentLoading.ContentBuilder\\ directorySalt++; buildDirectory = Path.Combine(processDirectory, directorySalt.ToString()); // Create our temporary directory. Directory.CreateDirectory(buildDirectory); PurgeStaleTempDirectories(); } ///  /// Deletes our temporary directory when we are finished with it. ///  void DeleteTempDirectory() { Directory.Delete(buildDirectory, true); // If there are no other instances of ContentBuilder still using their // own temp directories, we can delete the process directory as well. if (Directory.GetDirectories(processDirectory).Length == 0) { Directory.Delete(processDirectory); // If there are no other copies of the program still using their // own temp directories, we can delete the base directory as well. if (Directory.GetDirectories(baseDirectory).Length == 0) { Directory.Delete(baseDirectory); } } } ///  /// Ideally, we want to delete our temp directory when we are finished using /// it. The DeleteTempDirectory method (called by whichever happens first out /// of Dispose or our finalizer) does exactly that. Trouble is, sometimes /// these cleanup methods may never execute. For instance if the program /// crashes, or is halted using the debugger, we never get a chance to do /// our deleting. The next time we start up, this method checks for any temp /// directories that were left over by previous runs which failed to shut /// down cleanly. This makes sure these orphaned directories will not just /// be left lying around forever. ///  void PurgeStaleTempDirectories() { // Check all subdirectories of our base location. foreach (string directory in Directory.GetDirectories(baseDirectory)) { // The subdirectory name is the ID of the process which created it. int processId; if (int.TryParse(Path.GetFileName(directory), out processId)) { try { // Is the creator process still running? Process.GetProcessById(processId); } catch (ArgumentException) { // If the process is gone, we can delete its temp directory. Directory.Delete(directory, true); } } } } #endregion } } 

您要加载的字体是xml文件: any_name.spritefont

例:

      Segoe UI Mono  14  0  true          ~     

如果您希望表单是单击,则需要添加随机透明度键颜色以形成和:

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

public Form1()

 IntPtr initialStyle = GetWindowLongPtr(this.Handle, -20); SetWindowLong(this.Handle, -20, (IntPtr)((int)initialStyle | 0x20)); 

随意纠正我所犯的任何错误。