DDFer 发表于 2020-1-1 00:00

[Python] 【Python3】教你写页游自动化Python脚本 3.取色,大漠识别和后台点击

本帖最后由 DDFer 于 2020-1-1 23:05 编辑

十分抱歉,咕咕了一段时间
最近要期考和上篇帖子的模型不完善,无法实现进程的二次启动,尽管那个进程已经完成它的生命周期并完美退出(努力完善ing~)
(为什么删除线只有在编辑时可以看到,保存后就没了。。。)
更新:修复进程无法在关闭后重新启动
不多说了,下面开始教程



熟悉按键精灵的大佬们都应该用过一个叫大漠的插件
但先讲不依赖大漠的情况下,用微软官方的指令来实现脚本的操作

在上篇帖子的代码基础上,来增加主要操作的代码
来看看我已经在用的完整脚本代码

import win32com.client as wc,win32gui as wg,threading as xc,time,tkinter as tk,win32api as wa,win32con as wn,multiprocessing as jc



def init_window():
    global cs,wd
    wd = tk.Tk()
    cs = tk.Canvas(wd,
                   width = 800,
                   height = 500,
                   bg = 'white')
    wd.minsize(800, 500)   # 最小尺寸
    wd.maxsize(800, 500)
    wd.title('DDTHelper')
    pic = tk.PhotoImage(file="pic.png")
    cs.create_image(400,250,image = pic)
    cs.pack()
    bt = tk.Button(wd,
                   text='初始化',
                   bg=('white'),
                   font=('微软雅黑',20),
                   width=155,
                   height=48,
                   command=BT_onCreat)
    bt.pack()
    cs.create_window(530,70,
                     width=155,
                     height=48,
                     window=bt)
    wd.mainloop()
def init_control(Znum,name):
    global v1,v2,v3,tx1,t2,tx2,t3,tx3,txn1,txn2,txn3
    if Znum==1:
      v1=tk.IntVar()
      tx1=tk.StringVar()
      #txn1=tk.StringVar()
    elif Znum==2:
      v2=tk.IntVar()
      tx2=tk.StringVar()
      #txn2=tk.StringVar()
    elif Znum==3:
      v3=tk.IntVar()
      tx3=tk.StringVar()
      #txn3=tk.StringVar()
    exec('tx{}.set("未运行")'.format(Znum))
    exec('lb{} = tk.Label(wd,text="{}",bg=("#ffffff"),font=("微软雅黑",20))'.format(Znum,name))
    #exec('lbn{} = tk.Label(wd,textvariable=txn{},bg=("#ffffff"),font=("微软雅黑",10))'.format(Znum,Znum))
    exec('cb{} = tk.Checkbutton(wd,textvariable=tx{},bg=("#ffffff"),font=("微软雅黑",10),variable = v{}, height=5,width = 0,command=BT_onRun{})'.format(Znum,Znum,Znum,Znum))
    exec('cb{}.pack()'.format(Znum))
    exec('lb{}.pack()'.format(Znum))
    #exec('lbn{}.pack()'.format(Znum))
    Ytmp=Znum*100
    Ytmp=Ytmp+70
    exec('cs.create_window(630,{},width=0,height=0,window=lb{})'.format(Ytmp,Znum))
    Ytmp=Ytmp+40
    #exec('cs.create_window(630,{},width=35,height=25,window=lbn{})'.format(Ytmp,Znum))
    exec('cs.create_window(710,{},width=70,height=25,window=cb{})'.format(Ytmp,Znum))

def BT_onCreat():
    global Znum,D1,D2,D3,conT
    Znum = 0
    wg.EnumWindows(get_all_hwnd, 0)
    conT = jc.Manager().Array("i",)
    for h,t in hwnd_title.items():
      if "4399" in t:
            hwnd = t.split("|")
            name = t.split("|")
            print("账号:" + name + "句柄:" + hwnd)
            Znum = Znum + 1
            hwnd = int(hwnd)
            init_control(Znum,name)
            if Znum == 1:
                D1 = jc.Manager().Array("i",)
            elif Znum == 2:
                D2 = jc.Manager().Array("i",)
            elif Znum == 3:
                D3 = jc.Manager().Array("i",)

def get_all_hwnd(hwnd,mouse):
    if wg.IsWindow(hwnd) and wg.IsWindowEnabled(hwnd) and wg.IsWindowVisible(hwnd):
      hwnd_title.update({hwnd:wg.GetWindowText(hwnd)})
def all_run(Znum):
    while Znum >0:
      exec('t{}.start()'.format(Znum))
      Znum = Znum - 1


#操作类--------------------------------------------------------------------------------------------------------------
def climb(hwnd,jl,fx):
    if fx==1:#右边
      #适应方向及防止无效
      wa.SendMessage(hwnd,wn.WM_KEYDOWN,68,None)
      wa.SendMessage(hwnd,wn.WM_KEYUP,68,None)
      #1.3=1屏距
      wa.SendMessage(hwnd,wn.WM_KEYDOWN,68,None)
      time.sleep(jl*1.3)
      wa.SendMessage(hwnd,wn.WM_KEYUP,68,None)
    else:
      #适应方向及防止无效
      wa.SendMessage(hwnd,wn.WM_KEYDOWN,65,None)
      wa.SendMessage(hwnd,wn.WM_KEYUP,65,None)
      #1.3=1屏距
      wa.SendMessage(hwnd,wn.WM_KEYDOWN,65,None)
      time.sleep(jl*1.3)
      wa.SendMessage(hwnd,wn.WM_KEYUP,65,None)
def doAngle(hwnd,jd):
    for i in range(jd):
      time.sleep(0.05)
      wa.SendMessage(hwnd,wn.WM_KEYDOWN,87,None)
      wa.SendMessage(hwnd,wn.WM_KEYUP,87,None)
def doClick(hwnd,cx,cy):
    long_position = wa.MAKELONG(cx, cy)
    wa.SendMessage(hwnd, wn.WM_LBUTTONDOWN, wn.MK_LBUTTON, long_position)
    wa.SendMessage(hwnd, wn.WM_LBUTTONUP, wn.MK_LBUTTON, long_position)
def doFire(hwnd,ld):
    wa.SendMessage(hwnd,wn.WM_KEYFIRST,66,None)#先摁大
    wa.SendMessage(hwnd,wn.WM_KEYFIRST,69,None)#先摁技能
    wa.SendMessage(hwnd,wn.WM_KEYFIRST,97,None)
    wa.SendMessage(hwnd,wn.WM_KEYFIRST,98,None)
    wa.SendMessage(hwnd,wn.WM_KEYFIRST,97,None)#11大招
    wa.SendMessage(hwnd,wn.WM_KEYFIRST,100,None)
    wa.SendMessage(hwnd,wn.WM_KEYDOWN,32,None)
    time.sleep(ld * 0.04)
    wa.SendMessage(hwnd,wn.WM_KEYUP,32,None)


#游戏流程处理类---------------------------------------------------------------------------------------------------------
def Chose_FB(hwnd,hdc):
    doClick(hwnd,600,200)#打开菜单
    time.sleep(1)
    doClick(hwnd,626,188)#单人副本
    time.sleep(1)
    while True:
      doClick(hwnd,5,5)
      if str(wg.GetPixel(hdc,244,237))==str(2041582):
            doClick(hwnd,289,243)#魔石
            FBn=1
            break
      elif str(wg.GetPixel(hdc,337,278))==str(13298869):
            doClick(hwnd,292,299)#技能丹
            FBn=2
            break
    time.sleep(1)
    doClick(hwnd,726,501)#难度
    time.sleep(1)
    doClick(hwnd,504,563)#确定
    time.sleep(1)
    doClick(hwnd,951,491)
    return(FBn)
def FB_MS(hwnd,hdc):
    time.sleep(24)
    while str(wg.GetPixel(hdc,497,169))!=str(5418993):#回合检测
      doClick(hwnd,5,5)
      time.sleep(0.5)
    while True:
      doClick(hwnd,5,5)
      colx=wg.GetPixel(hdc,917,486)
      if str(colx)==str(36645):
            print("位置1")
            JD=18
            break
      else:
            print("位置2")
            climb(hwnd,0.5,0)
            JD=25
            break
    wa.SendMessage(hwnd,wn.WM_KEYFIRST,69,None)#波谷专用
    wa.SendMessage(hwnd,wn.WM_KEYFIRST,80,None)#第一次pass
    time.sleep(5)
    for i in range(2):
      while str(wg.GetPixel(hdc,497,169))!=str(5418993):#回合检测
            doClick(hwnd,5,5)
            time.sleep(0.5)
      wa.SendMessage(hwnd, wn.WM_KEYDOWN, 65, None)
      wa.SendMessage(hwnd, wn.WM_KEYUP, 65, None)
      doFire(hwnd,20)
    time.sleep(6)
    doAngle(hwnd,JD)
    time.sleep(10)
    while True:
      #回合循环
      cs = 0
      while str(wg.GetPixel(hdc,497,169))!=str(5418993):#回合检测
            if cs>=20:#超时退出
                break
            else:
                doClick(hwnd,5,5)
                time.sleep(1)
                cs=cs+1
      #退出
      if cs==20:
            print("退出副本")
            break
      else:
            doFire(hwnd,20)      
def FB_JD(hwnd,hdc):
    while True:
      cs = 0
      cg = 0
      while str(wg.GetPixel(hdc,497,169))!=str(5418993):#回合检测
            if cs>=20:#超时退出
                cg=1
                cs=0
                break
            else:
                doClick(hwnd,5,5)
                time.sleep(1)
                cs=cs+1
      if cg==1:
            break
      else:
            doFire(hwnd,60)

#程序流程模块类----------------------------------------------------------------------------------------------------------
def RunMain(hwnd):
    RM=0
    hdc=wg.GetWindowDC(hwnd)
    while True:
      while str(wg.GetPixel(hdc,919,280))!=str(10248996):#房间检测
            print("房间")
            doClick(hwnd,5,5)
            time.sleep(1)
      if Chose_FB(hwnd,hdc) == 1:
            FB_MS(hwnd,hdc)
      else:
            FB_JD(hwnd,hdc)
      RM = RM + 1
def Con(Data,conT):
    #设置守护线程
    Znum = Data
    print(str(Data))
    hwnd = Data
    time.sleep(1)
    exec('t{} = xc.Thread(target=RunMain,args=(hwnd,))'.format(Znum))
    exec('t{}.setDaemon(True)'.format(Znum))
    exec('t{}.start()'.format(Znum))
    while True:
      if conT == 0:
            time.sleep(1)
      else:
            break
    print('进程' + str(Znum) +':已退出')


def onRunMan(Znum):
    if onRunMan2(Znum) == 1:
      conT=0
      exec('tx{}.set("运行中")'.format(Znum))
      exec('p{} = jc.Process(target=Con,args=(D{},conT))'.format(Znum,Znum))
      exec('p{}.daemon=True'.format(Znum))
      exec('p{}.start()'.format(Znum))
    else:
      conT=1
      #exec('del p{}'.format(Znum))
      exec('tx{}.set("未运行")'.format(Znum))
def onRunMan2(Znum):
    if Znum ==1:
      return v1.get()
    elif Znum == 2:
      return v2.get()
    elif Znum ==3:
      return v3.get()
def onRunMan3(Znum):
    if Znum ==1:
      if p1.is_alive:
            return(1)
      else:
            return(0)
    elif Znum == 2:
      if p2.is_alive:
            return(1)
      else:
            return(0)
    elif Znum ==3:
      if p3.is_alive:
            return(1)
      else:
            return(0)
def BT_onRun1():
    onRunMan(1)
def BT_onRun2():
    onRunMan(2)
def BT_onRun3():
    onRunMan(3)

if __name__ == '__main__':
    hwnd_title = dict()
    init_window()



我已经将模块代码用--区分开来
之前我们讲过了   窗口界面和 程序线程

重点在于 操作类

负责向指定游戏窗口发生鼠标点击命令的方法
def doClick(hwnd,cx,cy):
    long_position = wa.MAKELONG(cx, cy)#模拟鼠标指针 传送到指定坐标
    wa.SendMessage(hwnd, wn.WM_LBUTTONDOWN, wn.MK_LBUTTON, long_position)#模拟鼠标按下
    wa.SendMessage(hwnd, wn.WM_LBUTTONUP, wn.MK_LBUTTON, long_position)#模拟鼠标弹起
这个方法把原本复杂的代码压缩了,于是我们要点击游戏界面的时候,就可以调用该方法来实现,比如
doClick(目标窗口句柄,x坐标,y坐标)
是不是就有内味了?

再看看其他方法
def climb(hwnd,jl,fx):
    if fx==1:#右边
      #适应方向及防止无效
      wa.SendMessage(hwnd,wn.WM_KEYDOWN,68,None)
      wa.SendMessage(hwnd,wn.WM_KEYUP,68,None)
      #1.3秒=1屏距
      wa.SendMessage(hwnd,wn.WM_KEYDOWN,68,None)
      time.sleep(jl*1.3)
      wa.SendMessage(hwnd,wn.WM_KEYUP,68,None)
    else:
      #适应方向及防止无效
      wa.SendMessage(hwnd,wn.WM_KEYDOWN,65,None)
      wa.SendMessage(hwnd,wn.WM_KEYUP,65,None)
      #1.3=1屏距
      wa.SendMessage(hwnd,wn.WM_KEYDOWN,65,None)
      time.sleep(jl*1.3)
      wa.SendMessage(hwnd,wn.WM_KEYUP,65,None)
def doAngle(hwnd,jd):
    for i in range(jd):
      time.sleep(0.05)
      wa.SendMessage(hwnd,wn.WM_KEYDOWN,87,None)
      wa.SendMessage(hwnd,wn.WM_KEYUP,87,None)
def doFire(hwnd,ld):
    wa.SendMessage(hwnd,wn.WM_KEYFIRST,66,None)#先摁大招
    wa.SendMessage(hwnd,wn.WM_KEYFIRST,69,None)#先摁技能
    wa.SendMessage(hwnd,wn.WM_KEYFIRST,97,None)
    wa.SendMessage(hwnd,wn.WM_KEYFIRST,98,None)#如果有大招,
    wa.SendMessage(hwnd,wn.WM_KEYFIRST,97,None)#11大招
    wa.SendMessage(hwnd,wn.WM_KEYFIRST,100,None)
    wa.SendMessage(hwnd,wn.WM_KEYDOWN,32,None)#空格蓄力
    time.sleep(ld * 0.04)#每蓄力1力度约用时0.04秒,受游戏延迟和电脑性能会有误差,总体可以接受,也可以改成识别力度条(更精准,但因为力度条颜色不纯干扰暂且搁置方案)
    wa.SendMessage(hwnd,wn.WM_KEYUP,32,None)#松开空格

这里的方法基本都是发送一些键盘操作的集合
比如说
方法climb是用来控制游戏中人物的爬行,
方法doAngle是用来调整游戏中人物发射炮弹的角度
方法doFire就是操作游戏人物发动攻击
总结以上方法,模拟键盘按键有3条指令
wa.SendMessage(游戏窗口句柄,wn.WM_KEYDOWN,按键码,None)
   wa.SendMessage(游戏窗口句柄,wn.WM_KEYUP,按键码,None)
wa.SendMessage(游戏窗口句柄,wn.WM_KEYFIRST,按键码,None)
它们分别是向游戏窗口发送摁下指定按键   弹起指定按键和集合摁下和弹起一体的点击指定按键
但需要注意的是
如果需要重复点击一个按键的时候,千万不要用点击指定按键 这个代码
这样会产生一个bug,相当于你摁下了按键却没有弹起,导致失控
需要像doAngle方法那样,使用按下和弹起来保证不会出bug

然后再到游戏取色
因为没有提取的必要,我就没有单独分离出来
取色需要用到hdc(想知道hdc的可以去百度 hdc和hwnd)
hdc=wg.GetWindowDC(int(hwnd))
↑利用hwnd来获取hdc
color = wg.GetPixel(hdc,x坐标,y坐标)
↑获取指定点的颜色
细心的小伙伴们可以发现
我在每个获取颜色的代码附近都有doClick的调用
那是因为防止用户点击了游戏界面后又点击了其他地方,导致游戏窗口失焦,所以使用doClick强制激活窗口

这里需要注意一点
因为这个游戏官方允许使用脚本,所以微软官方的指令是可以用的
否者的话可以尝试用大漠插件或者别的插件来发送硬件级别的模拟按键信息

下面讲解调用大漠插件的方法
大漠插件下载:点我下载
注意:大漠插件是32位的,所以调用时必须使用32为的py,不然会报错
下载好后把里面的dm.dll放在和脚本同一个目录下
使用
import win32com.client


dm = win32com.client.Dispatch('dm.dmsoft')#调用大漠插件
print(dm.ver())#输出版本号
就可以成功的调用大漠插件并输出版本号
绑定窗口
dm_ret = dm.BindWindow(hwnd,"gdi", "windows", "windows", 0)

绑定字典
dm.setDict(0, '字典.txt')#把字典文件放到和脚本同一个目录下
dm.useDict(0)

可以说,在成功注册了大漠插件后
它的使用代码基本和它里面自带的说明书里面的使用代码一致了
需要的小伙伴可以多看看它自带的说明书{:301_1001:}
不过dm.dll经常被defender报毒。。。导致我想用都用不了

虽然大漠的识别系统很强大,但毕竟是闭源付费,还强制得换成32位python。。
还是少用为妙

最后祝福大家
新年快乐

豆虫 发表于 2020-1-2 22:49

import win32api,win32gui,win32con

hwnd=win32gui.FindWindow(None,'计算器')
win32gui.SetForegroundWindow(hwnd)
def doClick(hwnd,cx,cy):
    long_position = win32api.MAKELONG(cx, cy)#模拟鼠标指针 传送到指定坐标
    win32api.SendMessage(hwnd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, long_position)#模拟鼠标按下
    win32api.SendMessage(hwnd, win32con.WM_LBUTTONUP, win32con.MK_LBUTTON, long_position)#模拟鼠标弹起

doClick(hwnd=hwnd,cx=120,cy=571)

不知道您测试过没有,我试了一下点击电脑中的计算器,并不能点击。

1983 发表于 2020-1-1 00:04

感谢分享,看了一下,小白觉得迷迷糊糊。。。

wobuaiqq 发表于 2020-1-1 00:11

新年快乐

MOEYU_VANILLA 发表于 2020-1-1 00:16

感谢分享

nzkboy 发表于 2020-1-1 00:21

新年的第一帖

wan456 发表于 2020-1-1 00:29

我的新年第一帖 、祝吾爱所有新年快乐、心想事成,
楼主是怎么确定官方指令的、分享一下方法、不限于这个帖子

plutolugia 发表于 2020-1-1 00:58

感谢分享

euking 发表于 2020-1-1 02:23

不错 支持

wdz2018 发表于 2020-1-1 04:35

看起来很厉害的样子,但还是不明白

5LIJIAJIE 发表于 2020-1-1 06:28

可以的哈哈哈
页: [1] 2 3 4 5
查看完整版本: [Python] 【Python3】教你写页游自动化Python脚本 3.取色,大漠识别和后台点击