控制台应用程序鼠标单击XY坐标检测/比较

我有一个游戏,我正在C#控制台应用程序中工作,纯粹作为练习,然后继续更好的方法。 与使用内置按钮function的Windows窗体应用程序相反,我正在努力抓住光标位置(我知道该怎么做)并将其与控制台应用程序内部的一些区域进行比较,如下所示:也许像素位置,但我也不知道是否有某种内置单位的空间而不是像素(这最后一位是我无法想象的部分)。

PS我知道这是一般性的,没有代码已经提供,但我不觉得它是需要的,因为我要求的是如何在控制台应用程序中获取XY坐标的简要说明,并将它们粘贴在int中变量。

提前谢谢了! :d

什么@Frank Krueger说。 你真的想这样做吗? Windows窗体旨在使这容易。

如果这样做,则需要将PInvoke用于低级Windows API。 试着这个作为起点 – 但请注意,这比Windows Forms应用程序要复杂得多。

经过很长一段时间的搜索,我终于找到了这个例子 。 下载页面上的示例程序 。 除了其他function外,它还为您提供了控制台窗口中的鼠标位置(基于字符)。

编辑:这是我的ConsoleListener类(包含我的NativeMethods类的一部分)。
您可以将处理程序附加到MouseEvent (在调用Start()方法之后)。

 using System; using System.Runtime.InteropServices; using System.Threading; using static ConsoleLib.NativeMethods; namespace ConsoleLib { public static class ConsoleListener { public static event ConsoleMouseEvent MouseEvent; public static event ConsoleKeyEvent KeyEvent; public static event ConsoleWindowBufferSizeEvent WindowBufferSizeEvent; private static bool Run = false; public static void Start() { if (!Run) { Run = true; IntPtr handleIn = GetStdHandle(STD_INPUT_HANDLE); new Thread(() => { while (true) { uint numRead = 0; INPUT_RECORD[] record = new INPUT_RECORD[1]; record[0] = new INPUT_RECORD(); ReadConsoleInput(handleIn, record, 1, ref numRead); if (Run) switch (record[0].EventType) { case INPUT_RECORD.MOUSE_EVENT: MouseEvent?.Invoke(record[0].MouseEvent); break; case INPUT_RECORD.KEY_EVENT: KeyEvent?.Invoke(record[0].KeyEvent); break; case INPUT_RECORD.WINDOW_BUFFER_SIZE_EVENT: WindowBufferSizeEvent?.Invoke(record[0].WindowBufferSizeEvent); break; } else { uint numWritten = 0; WriteConsoleInput(handleIn, record, 1, ref numWritten); return; } } }).Start(); } } public static void Stop() => Run = false; public delegate void ConsoleMouseEvent(MOUSE_EVENT_RECORD r); public delegate void ConsoleKeyEvent(KEY_EVENT_RECORD r); public delegate void ConsoleWindowBufferSizeEvent(WINDOW_BUFFER_SIZE_RECORD r); } public static class NativeMethods { public struct COORD { public short X; public short Y; public COORD(short x, short y) { X = x; Y = y; } } [StructLayout(LayoutKind.Explicit)] public struct INPUT_RECORD { public const ushort KEY_EVENT = 0x0001, MOUSE_EVENT = 0x0002, WINDOW_BUFFER_SIZE_EVENT = 0x0004; //more [FieldOffset(0)] public ushort EventType; [FieldOffset(4)] public KEY_EVENT_RECORD KeyEvent; [FieldOffset(4)] public MOUSE_EVENT_RECORD MouseEvent; [FieldOffset(4)] public WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent; /* and: MENU_EVENT_RECORD MenuEvent; FOCUS_EVENT_RECORD FocusEvent; */ } public struct MOUSE_EVENT_RECORD { public COORD dwMousePosition; public const uint FROM_LEFT_1ST_BUTTON_PRESSED = 0x0001, FROM_LEFT_2ND_BUTTON_PRESSED = 0x0004, FROM_LEFT_3RD_BUTTON_PRESSED = 0x0008, FROM_LEFT_4TH_BUTTON_PRESSED = 0x0010, RIGHTMOST_BUTTON_PRESSED = 0x0002; public uint dwButtonState; public const int CAPSLOCK_ON = 0x0080, ENHANCED_KEY = 0x0100, LEFT_ALT_PRESSED = 0x0002, LEFT_CTRL_PRESSED = 0x0008, NUMLOCK_ON = 0x0020, RIGHT_ALT_PRESSED = 0x0001, RIGHT_CTRL_PRESSED = 0x0004, SCROLLLOCK_ON = 0x0040, SHIFT_PRESSED = 0x0010; public uint dwControlKeyState; public const int DOUBLE_CLICK = 0x0002, MOUSE_HWHEELED = 0x0008, MOUSE_MOVED = 0x0001, MOUSE_WHEELED = 0x0004; public uint dwEventFlags; } [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)] public struct KEY_EVENT_RECORD { [FieldOffset(0)] public bool bKeyDown; [FieldOffset(4)] public ushort wRepeatCount; [FieldOffset(6)] public ushort wVirtualKeyCode; [FieldOffset(8)] public ushort wVirtualScanCode; [FieldOffset(10)] public char UnicodeChar; [FieldOffset(10)] public byte AsciiChar; public const int CAPSLOCK_ON = 0x0080, ENHANCED_KEY = 0x0100, LEFT_ALT_PRESSED = 0x0002, LEFT_CTRL_PRESSED = 0x0008, NUMLOCK_ON = 0x0020, RIGHT_ALT_PRESSED = 0x0001, RIGHT_CTRL_PRESSED = 0x0004, SCROLLLOCK_ON = 0x0040, SHIFT_PRESSED = 0x0010; [FieldOffset(12)] public uint dwControlKeyState; } public struct WINDOW_BUFFER_SIZE_RECORD { public COORD dwSize; } public const uint STD_INPUT_HANDLE = unchecked((uint)-10), STD_OUTPUT_HANDLE = unchecked((uint)-11), STD_ERROR_HANDLE = unchecked((uint)-12); [DllImport("kernel32.dll")] public static extern IntPtr GetStdHandle(uint nStdHandle); public const uint ENABLE_MOUSE_INPUT = 0x0010, ENABLE_QUICK_EDIT_MODE = 0x0040, ENABLE_EXTENDED_FLAGS = 0x0080, ENABLE_ECHO_INPUT = 0x0004, ENABLE_WINDOW_INPUT = 0x0008; //more [DllImportAttribute("kernel32.dll")] public static extern bool GetConsoleMode(IntPtr hConsoleInput, ref uint lpMode); [DllImportAttribute("kernel32.dll")] public static extern bool SetConsoleMode(IntPtr hConsoleInput, uint dwMode); [DllImportAttribute("kernel32.dll", CharSet = CharSet.Unicode)] public static extern bool ReadConsoleInput(IntPtr hConsoleInput, [Out] INPUT_RECORD[] lpBuffer, uint nLength, ref uint lpNumberOfEventsRead); [DllImportAttribute("kernel32.dll", CharSet = CharSet.Unicode)] public static extern bool WriteConsoleInput(IntPtr hConsoleInput, INPUT_RECORD[] lpBuffer, uint nLength, ref uint lpNumberOfEventsWritten); } } 

为了使其正常工作,您可能希望首先执行此代码:

 IntPtr inHandle = GetStdHandle(STD_INPUT_HANDLE); uint mode = 0; GetConsoleMode(inHandle, ref mode); mode &= ~ENABLE_QUICK_EDIT_MODE; //disable mode |= ENABLE_WINDOW_INPUT; //enable (if you want) mode |= ENABLE_MOUSE_INPUT; //enable SetConsoleMode(inHandle, mode); 

使用此文件头:

 using System; using static ConsoleLib.NativeMethods; 

此外,控制台不仅适用于文本处理。 你可以为它编写相当不错的窗口管理器。 你可以用它做任何事情。 这更难了。

但速度较慢。 我使用用户界面的控制台在C#中实现了一个虚拟机。 它不会一个接一个地打印文本行; 它[界面]的行为就像一个GUI。

如果你想在控制台上输入鼠标,试试这个钩子: http : //blogs.msdn.com/b/toub/archive/2006/05/03/589468.aspx?PageIndex=2#comments

当你在不使用事件的情况下编写游戏时……你真正在做的就是自己实现事件。 这是有利的,因为你可以比使用语言的内置事件更有效率。 如果您知道自己在做什么,以这种方式编写的游戏不易出错。

例如,当我试图教我的兄弟如何编写游戏时,我为他写了一个简单的蛇游戏。 我在一个线程中有一个主循环,移动蛇并在一个循环中将它绘制在它的新位置。 我会在同一时间运行一个线程,不断检查4件事:

1)如果蛇撞到了自己(比赛结束); 如果游戏结束,停止更新蛇的主要位置的主线程,将游戏打印到屏幕上,等待键输入,然后重新开始游戏。

2)如果蛇吃了一个苹果; 增加计数器变量,该变量表示已经吃了多少苹果,并在屏幕上打印这个新值,覆盖以前的数据。

3)如果蛇吃掉了可以被10整除的苹果(蛇长了1个细胞,从等待变量中减去,该变量说明蛇在每次运动之间应该经过多长时间)

4)如果按下了箭头键。 如果向左,则将move设置为0,如果右边设置移动到1,如果向下设置移动到2,如果向上设置移动到3.存储的int是一个指向4个代表数组的指针,使蛇移动在正确的方向。

更新蛇的位置的主循环将告诉线程检查这4个事情蛇正在做什么。 我这样做的方法是让屏幕上的每个单元格都会让蛇的头移动来指代一个二维的代表arrays。 关于这个代表数组:

游戏以控制台模式编写,并使用控制台颜色。 控制台设置为80×50个字符。 代表如下:“delegate void ptr()”; 然后我创建数组:“ptr [,] pos = new ptr [80,50]”。 假设蛇的头部位于屏幕上的位置(4,5),在它移动之后,主循环将执行“pos [4,5] .Invoke();”。

其中之一:当蛇移动到一个新位置时,主循环线程将获得蛇在屏幕上覆盖的每个单元格,并将该代表设置为指向一个名为“void gameover()”的函数将gameover_变量设置为true。 因此,当检查游戏状态的循环线程检查游戏时,它会冻结游戏并在屏幕上打印游戏。

另一个:当在屏幕上绘制苹果时,它被绘制的代表位置(随机化)被设置为指向“void increment_apple()”,其增加apple计数器,从视图中移除当前苹果,并绘制新的苹果在屏幕上,将旧苹果位置设置为指向“无效的nop()”,它不执行任何操作,并将新的苹果位置指向“void increment_apple()”。

这基本上是游戏的运作方式。 正如你所看到的,蛇移动到屏幕上的这些位置,并且它没有执行任何明确的检查,如“if(snake_position == some_position)”,游戏会自动执行游戏中发生的所有事情,就像当您单击表单上的按钮时,自动执行分配给该事件的操作,而无需您自己检查事件。

所以你看,我可以使用一个表单和C#提供的默认事件,但我没有。 我使用了控制台界面,并实现了自己的事件系统。

这就是它在幕后的工作原理:表单应用程序的主循环将在一个线程中运行,该线程检查屏幕上所有按钮等的输入。 这些项中的每一项都会将它们使用的布尔变量设置为true。 单击此按钮时,运行循环的另一个线程会检查您按下的内容,并按下一个名为“button1”的按钮,该按钮将为其分配一个委托; 然后该委托随其指向的任何内容执行。

有点难以解释,但这对你有意义吗?

经过深入研究,我找到了解决方案。 使用Button类和我创建的GUI,可以创建一个按钮,然后单击或鼠标(它不能完美地工作)。 您需要导入System.Windows.Forms和System.Drawing。

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Traingames.NetElements; //using System.Windows.Forms; using System.Drawing; namespace ConsoleTools.NET { class Program { static ConsoleFramework c = new ConsoleFramework(); static public Point MousePos; static Button One = new Button(); static Pixel Mouse = new Pixel(); static void Main(string[] args) { Console.ForegroundColor = ConsoleColor.White; // t.Draw(10, 40, ConsoleColor.Gray); One.Set(0, 10, "░░1░░", ConsoleColor.Gray); GUI.Add(One); GUI.CalculateOnStart(); for (;;) { MousePos = new Point(System.Windows.Forms.Control.MousePosition.X / (Console.LargestWindowWidth / 24), System.Windows.Forms.Control.MousePosition.Y / (Console.LargestWindowHeight / 7)); if (One.Pressed(MousePos)) { Console.Write("1"); } // Console.Clear(); } } } } namespace Traingames.NetElements { public class ConsoleFramework { public char[] chars = { '█', '▓', '▒', '░' }; Point MousePos() { return new Point((System.Windows.Forms.Control.MousePosition.X / (Console.LargestWindowWidth / 24)) - 100, System.Windows.Forms.Control.MousePosition.Y / (Console.LargestWindowHeight / 7)); } public void SetPixel(int x, int Y, ConsoleColor color) { int y = (int)Math.Floor(Y / 1.5f); for (int i = 0; i < y; i++) { Console.WriteLine(""); } for (int i = 0; i < x - 1; i++) { Console.Write(" "); } Console.BackgroundColor = color; Console.Write(" "); Console.BackgroundColor = ConsoleColor.Black; } } public class Pixel : GUI { public void Set(int X, int Y, string text) { ConsoleColor backColor = ConsoleColor.Black; BackColor = backColor; int yyyyyy = (int)Math.Floor(Y / 1.5f); Text = text; y = Y; x = X; } } public class GUI { public int x, y; public static GUI[,] GraphicalUserInterfaces = new GUI[1000, 1000]; public ConsoleColor BackColor; public string Text; public void Draw() { int X = x; int Y = y; ConsoleColor backColor = BackColor; string text = Text; for (int i = 0; i < y; i++) { Console.WriteLine(""); } for (int i = 0; i < x - 1; i++) { Console.Write(" "); } Console.BackgroundColor = BackColor; Console.Write("[" + text + "]"); Console.BackgroundColor = ConsoleColor.Black; Point M = ConsoleTools.NET.Program.MousePos; // return MX >= xx && MX <= (xx + Text.Length + 1) && MY >= yy && MY <= yy + 2 && Control.MouseButtons == MouseButtons.Left; } static GUI Last; public static void Add(GUI gui) { GraphicalUserInterfaces[gui.x, gui.y] = gui; } public static void CalculateOnStart() { for (int x = 0; x < 1000; x++) { for (int y = 0; y < 1000; y++) { if (GraphicalUserInterfaces[x, y] != null) { if (Last != null && y < Last.y) { GraphicalUserInterfaces[x, y].x = Last.x - GraphicalUserInterfaces[x, y].x; GraphicalUserInterfaces[x, y].y = Last.y - GraphicalUserInterfaces[x, y].y; } GraphicalUserInterfaces[x, y].Draw(); GraphicalUserInterfaces[x, y].x = x; GraphicalUserInterfaces[x, y].y = y; Last = GraphicalUserInterfaces[x, y]; } } } } } public class Button : GUI { public bool Over(Point M) { int yy = ((y * 2) - y / 3) + 2; int xx = (x / (Console.LargestWindowWidth / 24)) + Text.Length; if (MX >= xx && MX <= (xx + Text.Length + 1) && MY >= yy && MY <= yy + 2) Console.BackgroundColor = ConsoleColor.DarkBlue; return MX >= xx && MX <= (xx + Text.Length + 1) && MY >= yy && MY <= yy + 2; } public bool Pressed(Point M) { int yy = ((y * 2) - y / 3) + 1; int xx = (x / (Console.LargestWindowWidth / 24)); return MX >= xx && MX <= (xx + Text.Length * 1.5f) && MY >= yy && MY <= yy + 2 && System.Windows.Forms.Control.MouseButtons == System.Windows.Forms.MouseButtons.Left; } public void CalculateClick(Point M) { if (Pressed(M)) { Console.Clear(); Draw(); } } public void Set(int X, int Y, string text, ConsoleColor backColor) { BackColor = backColor; int yyyyyy = (int)Math.Floor(Y / 1.5f); Text = text; y = Y; x = X; int xx = (x / (Console.LargestWindowWidth / 24)) + Text.Length; } } }