zoukankan      html  css  js  c++  java
  • 用C#编写游戏脚本

      大学宿舍玩游戏的时候,为了简化重复的键鼠动作,有学习过按键精灵和TC脚本开发工具,并做了一些小脚本,基本达到了当时的需求。不知不觉,已经毕业了3年了,无聊之余又玩起了游戏,对于一些无趣的重复行为,于是又想写个脚本来处理下。比如跑任务,自动补血等,没想到现在的游戏对于按键精灵和TC基本上都是封杀。对于我这种小白,过游戏安全检测这种棘手的事,也许花费很多时间,都没有结果。经常测试,发现游戏不会对自己写的C#脚本进行检测,所以决定用C#来写。

      研究了几天,突然间又不想玩游戏了,所以把这几天的研究成果分享给大家,希望对后来的人有启发。我玩的是一款QQ的游戏,我想要做的脚本就是 扫货脚本(当有人摆摊价格低于自己预设的价格时,自动购买下来,倒卖)。

      经过分析,最难的步骤是怎么识别摊位上的价格,第一感觉,这不就是文字识别吗,于是找了一个.Net 唯一开源的Tesseract-ocr。经过测试,发现Tesseract-ocr只适合白底黑字的文字识别,于是对图片进行了以下处理

    1.  变灰度图
    2. 增加亮度100
    3. 增加对比度100
    4. 变黑白
    5. //反向  游戏文字是白色的
            /// <summary>
            /// 反像
            /// </summary>
            /// <param name="bitmapImage"></param>
            /// <returns></returns>
            public static Bitmap ApplyInvert(Bitmap source)
            {
                //create a blank bitmap the same size as original
                Bitmap newBitmap = new Bitmap(source.Width, source.Height);
    
                //get a graphics object from the new image
                Graphics g = Graphics.FromImage(newBitmap);
    
                // create the negative color matrix
                ColorMatrix colorMatrix = new ColorMatrix(new float[][]
                {
            new float[] {-1, 0, 0, 0, 0},
            new float[] {0, -1, 0, 0, 0},
            new float[] {0, 0, -1, 0, 0},
            new float[] {0, 0, 0, 1, 0},
            new float[] {1, 1, 1, 0, 1}
                });
    
                // create some image attributes
                ImageAttributes attributes = new ImageAttributes();
    
                attributes.SetColorMatrix(colorMatrix);
    
                g.DrawImage(source, new Rectangle(0, 0, source.Width, source.Height),
                            0, 0, source.Width, source.Height, GraphicsUnit.Pixel, attributes);
    
                //dispose the Graphics object
                g.Dispose();
    
                return newBitmap;
            }
    
      /// <summary>
            /// 图片变成灰度
            /// </summary>
            /// <param name="b"></param>
            /// <returns></returns>
            public static Bitmap ToGray(Bitmap b)
            {
                for (int x = 0; x < b.Width; x++)
                {
                    for (int y = 0; y < b.Height; y++)
                    {
                        Color c = b.GetPixel(x, y);
                        int luma = (int)(c.R * 0.3 + c.G * 0.59 + c.B * 0.11);//转换灰度的算法
                        b.SetPixel(x, y, Color.FromArgb(luma, luma, luma));
                    }
                }
                return b;
            }
    
        /// <summary>
            /// 图像变成黑白
            /// </summary>
            /// <param name="b"></param>
            /// <returns></returns>
            public static Bitmap ToBlackWhite(Bitmap b)
            {
                for (int x = 0; x < b.Width; x++)
                {
                    for (int y = 0; y < b.Height; y++)
                    {
                        Color c = b.GetPixel(x, y);
                        if (c.R < (byte)255)
                        {
                            b.SetPixel(x, y, Color.FromArgb(0, 0, 0));
                        }
                    }
                }
                return b;
            }
    
     /// <summary>
            /// 图像亮度调整
            /// </summary>
            /// <param name="b"></param>
            /// <param name="degree"></param>
            /// <returns></returns>
            public static Bitmap KiLighten(Bitmap b, int degree)
            {
    
                if (b == null)
                {
    
                    return null;
    
                }
    
                if (degree < -255) degree = -255;
    
                if (degree > 255) degree = 255;
    
                try
                {
    
                    int width = b.Width;
    
                    int height = b.Height;
    
                    int pix = 0;
    
                    BitmapData data = b.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
    
                    unsafe
                    {
                        byte* p = (byte*)data.Scan0;
    
                        int offset = data.Stride - width * 3;
    
                        for (int y = 0; y < height; y++)
                        {
    
                            for (int x = 0; x < width; x++)
                            {
    
                                // 处理指定位置像素的亮度
    
                                for (int i = 0; i < 3; i++)
                                {
    
                                    pix = p[i] + degree;
    
    
    
                                    if (degree < 0) p[i] = (byte)Math.Max(0, pix);
    
                                    if (degree > 0) p[i] = (byte)Math.Min(255, pix);
    
    
    
                                } // i
    
                                p += 3;
    
                            } // x
    
                            p += offset;
    
                        } // y
    
                    }
    
    
    
                    b.UnlockBits(data);
    
    
    
                    return b;
    
                }
    
                catch
                {
    
                    return null;
    
                }
    
    
    
            }
      /// <summary>
            /// 图像对比度调整
            /// </summary>
            /// <param name="b">原始图</param>
            /// <param name="degree">对比度[-100, 100]</param>
            /// <returns></returns>
    
            public static Bitmap KiContrast(Bitmap b, int degree)
            {
    
                if (b == null)
                {
    
                    return null;
    
                }
    
    
    
                if (degree < -100) degree = -100;
    
                if (degree > 100) degree = 100;
    
    
    
                try
                {
    
    
    
                    double pixel = 0;
    
                    double contrast = (100.0 + degree) / 100.0;
    
                    contrast *= contrast;
    
                    int width = b.Width;
    
                    int height = b.Height;
    
                    BitmapData data = b.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
    
                    unsafe
                    {
    
                        byte* p = (byte*)data.Scan0;
    
                        int offset = data.Stride - width * 3;
    
                        for (int y = 0; y < height; y++)
                        {
    
                            for (int x = 0; x < width; x++)
                            {
    
                                // 处理指定位置像素的对比度
    
                                for (int i = 0; i < 3; i++)
                                {
    
                                    pixel = ((p[i] / 255.0 - 0.5) * contrast + 0.5) * 255;
    
                                    if (pixel < 0) pixel = 0;
    
                                    if (pixel > 255) pixel = 255;
    
                                    p[i] = (byte)pixel;
    
                                } // i
    
                                p += 3;
    
                            } // x
    
                            p += offset;
    
                        } // y
                    }
                    b.UnlockBits(data);
                    return b;
                }
                catch
                {
                    return null;
                }
            }
    

      经过以上处理,发现识别率高了很多,可是不知道什么原因对单个价格,如9,6,5 这种无法识别,而且对于3,8,0,很容易混淆,对于这种扫货的脚本来说,价格识别率必须是100%对的。后来又去学习怎么训练字库,花了很多时间,最终得出一个结论,OCR训练识别率的前提是 文字能被识别,但是识别错了,如果连文字都识别不出,那么没有训练的必要了,就这样,放弃了。

      当天晚上,看了一篇别人识别网站验证码的文章,又看了国内的脚本开发的文字识别,看到大漠插件的字库,是一个个像素组成的字。灵光一闪,每个价格的笔画不同,位置不同,同样大小的图片,Base64值肯定不一样啊,第二天做了实验,证明自己的想法是对的,哪怕一个像素不对,都是不一样的。于是写了个脚本,把摊位里1-2000的价格都抓下来,然后处理成黑白后分割成小图片。

            /// <summary>
            /// 图像转Base字符串
            /// </summary>
            /// <returns></returns>
            public static string ToBaseMd5(this Bitmap img)
            {
                if (img == null)
                    return string.Empty;
                else
                    return Convert.ToBase64String(ToByte(img));
            }
    

      做脚本嘛,最重要的截取指定区域的图片嘛,直接上代码。

              Bitmap image = new Bitmap(26, 18);
              Graphics imgGraphics = Graphics.FromImage(image);
               //设置截屏区域    
              imgGraphics.CopyFromScreen(X, Y, 0, 0, new Size(26, 18));
             image.Save(path, ImageFormat.Tiff);
    

    以上的技术,基本上可以把识别文字的价格问题解决了,当然中途花了很多时间来做重复的事。

      接下来有个问题,怎么定位价格啊,各种按钮的位置,因此要找个参照物,简单的说就是,截取一个参考物的图片,然后其他元素的位置相对这个参照物进行设置。转化成技术来说,就是一张小图在另一张大图里面找到位置,并返回相对坐标。尝试了几种方法,最终使用 AForge 这个开源项目来处理,代码如下

            /// <summary>
            /// 判断图像是否存在
            /// </summary>
            /// <param name="template"></param>
            /// <param name="bmp"></param>
            /// <returns></returns>
            public static bool ContainsImg(this Bitmap template, Bitmap bmp)
            {
                // create template matching algorithm's instance // (set similarity threshold to 92.1%) 
                ExhaustiveTemplateMatching tm = new ExhaustiveTemplateMatching(0.921f); // find all matchings with specified above similarity 
                TemplateMatch[] matchings = tm.ProcessImage(template, bmp); // highlight found matchings
    
                return matchings.Length > 0;
            }
            /// <summary>
            /// 判断图像是否存在另外的图像中,并返回坐标
            /// </summary>
            /// <param name="template"></param>
            /// <param name="bmp"></param>
            /// <returns></returns>
            public static Point ContainsGetPoint(this Bitmap template, Bitmap bmp)
            {
                // create template matching algorithm's instance // (set similarity threshold to 92.1%) 
                ExhaustiveTemplateMatching tm = new ExhaustiveTemplateMatching(0.921f); // find all matchings with specified above similarity 
                TemplateMatch[] matchings = tm.ProcessImage(template, bmp); // highlight found matchings
                BitmapData data = template.LockBits(new Rectangle(0, 0, template.Width, template.Height), ImageLockMode.ReadWrite, template.PixelFormat);
                Point p = new Point();
    
                if (matchings.Length > 0)
                {
                    Drawing.Rectangle(data, matchings[0].Rectangle, Color.White);
                    p = matchings[0].Rectangle.Location;
                    template.UnlockBits(data);
                }
    
                return p;
            }
    

      现在价格可以识别了,通过找图,界面的各个坐标都确定了,现在就是写模拟鼠标和键盘的操作了。这个网上很多,我的很简单

    对于我的游戏来说鼠标操作,就是移动和左击

        public class MouseHelper
        {
            [DllImport("user32.dll")]
            private static extern bool SetCursorPos(int X, int Y);
    
            [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
            private static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint cButtons, UIntPtr dwExtraInfo);
    
            /// <summary>
            /// 鼠标左击
            /// </summary>
            public static void LeftClick()
            {
                mouse_event(0x02, 0, 0, 0, UIntPtr.Zero);
                mouse_event(0x04, 0, 0, 0, UIntPtr.Zero);
            }
            /// <summary>
            /// 鼠标移动到指定的位置
            /// </summary>
            /// <param name="x"></param>
            /// <param name="y"></param>
            public static void MovePoint(Point p)
            {
                SetCursorPos(p.X, p.Y);
            }
        }
    

      键盘可以用C#自带的方法 SendKeys

    SendKeys.Send("输入文本");//用于输入文字
    SendKeys.SendWait("{ENTER}");用于输入按键命令
    

      基本上就这些了,另外附上一些可能会用到的技能

    找到游戏句柄

       /// <summary>
            /// 获取游戏句柄
            /// </summary>
            /// <returns></returns>
            public static int GetFFoHandle()
            {
                Process[] processes = Process.GetProcessesByName("进程名称");
    
                var p = processes.FirstOrDefault();
    
                if (p == null)
                {
                    return 0;
                }
                else
                {
                    return p.MainWindowHandle.ToInt32();
                }
            }
    

    根据句柄获取游戏的位置

        [DllImport("user32.dll", SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
            [StructLayout(LayoutKind.Sequential)]
            public struct RECT
            {
                public int Left;
                public int Top;
                public int Right;
                public int Bottom;
            }
    

     根据句柄将游戏窗体移动到某个位置

            /// <summary>
            /// 根据句柄移动窗体
            /// </summary>
            /// <param name="hWnd"></param>
            /// <param name="hWndInsertAfter"></param>
            /// <param name="x"></param>
            /// <param name="Y"></param>
            /// <param name="cx"></param>
            /// <param name="cy"></param>
            /// <param name="wFlags"></param>
            /// <returns></returns>
            [DllImport("user32.dll", EntryPoint = "SetWindowPos")]
            public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
    

    其他什么快捷键啊,啥的,网上一大堆就不写了。

     

    好了,就这些,通过以上的代码,可以完成大部分简单的前台脚本了,写的比较乱,但是对于正在研究中的人,我想一定省了不少事。

  • 相关阅读:
    [calss*="col-"]匹配类名中包含col-的类名,^以col-开头,$以col-结尾
    插件写法之脚本运行环境,mac和window检测
    @media only screen and (max-width:640px)中的问题,响应式布局
    webpack2的配置属性说明entry,output,state,plugins,node,module,context
    npm ERR! missing script: dev 报错解决
    [jshint] 'import' is only available in ES6 (use 'esversion: 6'). (W119) 提示import等ES6语法的jshint错误的,在代码前加一行 /* jshint esversion: 6 */
    Uncaught TypeError: (intermediate value)(...) is not a function 上一个方法结束没有加分号; 代码解析报错
    LeetCode 1. two sum
    redis集群尝试
    服务器搭建私人Git
  • 原文地址:https://www.cnblogs.com/seethrough/p/5568107.html
Copyright © 2011-2022 走看看