用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);

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

 

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

时间: 2024-10-13 11:14:20

用C#编写游戏脚本的相关文章

【Unity 3D】学习笔记二十七:unity游戏脚本(七)

使用C#编写游戏脚本 在前面提到,unity支持三种语言编写脚本:js,C#,boo.入门的时候建议只用js,因为js比较简单易懂,语法也不是很严格.但后来晋级的时候推荐使用C#,因为它比较符合unity的编程思想,执行效率更高.下面总结下怎么使用C#编写脚本. 继承MonoBehaviour类 在unity中,任何一个脚本,包括上述三种语言都需要去继承MonoBehaviour这个类.为什么我们之前写JS代码的时候没有继承咧?因为在创建JS代码的时候,系统会将其类名与继承关系隐藏起来. 在pr

游戏脚本的笔记

使用脚本是把游戏代码和主引擎分离开最理想的方法,甚至在处理游戏中的动画.游戏引擎允许玩家四处行走,探索游戏地图.和其他玩家进行对话.以及打斗.游戏中的故事情节和游戏角色则用脚本编写,如果直接用代码编写,这样不确定性高,每次修改都需要重新编译引擎. 将游戏代码和游戏内容加以分离,编写游戏引擎代码时不用考虑物品描述,反之亦然.这是物品描述和游戏引擎分别存放在不同的文件里面,在游戏代码中与描述数据唯一有联系的只是读取数据的部分.游戏引擎不关心数据具体是什么,它只负责将它读出来,放在特定的地方交给外部程

《游戏脚本的设计与开发》-(RPG部分)3.8 通过脚本来自由控制游戏(一)

注意:本系列教程为长篇连载无底洞,半路杀进来的朋友,如果看不懂的话,请从第一章开始看起,文章目录请点击下面链接. http://blog.csdn.net/lufy_legend/article/details/8888787 一,内容预览 算起来,游戏脚本系列文章已经很久没更新了,虽然该系列文章更新缓慢,但是确实还是能够帮到一些朋友,前段时间,仅仅因为做毕业设计通过邮件联系我的就有4位学生.有鉴于此,我还是挤点儿时间来继续慢慢更新一下了.另外,我想再声明一下,目前该脚本引擎还处在移植开发阶段,

从此编写 Bash 脚本不再难【转】

从此编写 Bash 脚本不再难 原创 Linux技术 2017-05-02 14:30 在这篇文章中,我们会介绍如何通过使用 bash-support vim 插件将 Vim 编辑器安装和配置 为一个编写 Bash 脚本的 IDE. -- Aaron Kili 本文导航 -什么是 bash-support.vim 插件? …… 05% -如何在 Linux 中安装 Bash-support 插件 …… 10% -如何在 Vim 编辑器中使用 Bash-support 插件 …… 17% -如何为

从游戏脚本语言说起,剖析Mono所搭建的脚本基础

0x00 前言 在日常的工作中,我偶尔能遇到这样的问题:“为何游戏脚本在现在的游戏开发中变得不可或缺?”.那么这周我就写篇文章从游戏脚本聊起,分析一下游戏脚本因何出现,而mono又能提供怎样的脚本基础.最后会通过模拟Unity3D游戏引擎中的脚本功能,将Mono运行时嵌入到一个非托管(C/C++)程序中,实现脚本语言和“引擎”之间的分离. 回到目录 0x01 Why?从为何需要游戏脚本开始 首先聊聊为何现在的游戏开发需要使用游戏脚本这个话题. 为何需要有脚本系统呢?脚本系统又是因何而出现的呢?其

编写shell脚本和执行

这次的学习内容: 认识shell,如何编写shell脚本和执行Shell 其实就是一个解释执行命令的程序,所谓shell编程其实就是用一定的语法将各种基本的命令组合起来,让shell程序去解释执行.如果对windows的dos有了解,可以这样理解,其实shell脚本文件和.bat批处理文件差不多.然而linux下的shell比起windows的dos强大很多,呵呵.为了安全起见,创建一个普通的账号进行学习]# useradd cnetsa]# passwd cnetsa然后使用 cnetsa 这

漏洞扫描 -- 编写Nmap脚本

漏洞扫描 -- 编写Nmap脚本 2006年12月份,Nmap4.21 ALPHA1版加入脚本引擎,并将其作为主线代码的一部分.NSE脚本库如今已经有400多个脚本,覆盖了各种不同的网络机制(从SMB漏洞检测到Stuxnet探测,及中间的一些内容).NSE的强大,依赖它强大的功能库,这些库可以非常容易的与主流的网络服务和协议,进行交互. 挑战 我们经常会扫描网络环境中的主机是否存在某种新漏洞,而扫描器引擎中没有新漏洞的检测方法,这时候我们可能需要自己开发扫描工具. 你可能已经熟悉了某种脚本(例如

Linux系统编写shell脚本批量创建和删除用户

一.编写shell脚本批量添加用户 实现方法:判断用户是否存在,存在则返回错误提示,同时判断用户文件是否存在,不存在则退出 1.创建添加用户脚本 [[email protected] ~]# vim useradd.sh #!/bin/bashif [ $# -eq 0 ];then        echo "你没有输入任何文件!"        exit 1fi if [ ! -f $1 ];then        echo "输入有误!"        exit

【Unity 3D】学习笔记二十六:unity游戏脚本(六)

在3D游戏世界中,任何一个游戏对象在创建的时候都会附带Transform(变换)组件,并且该组件是无法删除的,也不应该删除.在unity中,Transform面板一共有3个属性: Position  (位置) Rotation(旋转) Scale(缩放) 这三个值都是用来调整游戏对象在游戏界面中的位置,状态等相关参数. Position  (位置) 任何一个游戏对象的三维坐标都保存在Vector3容器中,该容器记录对象在X轴,Y轴,Z轴的坐标.一旦Vector33容器中的坐标发生变化,那么Sce