├── ADBHelper.py ├── Arknights ├── ArknightsRoguelike.py ├── Arknights_default.py ├── ResourceDictionary.py └── img │ ├── 1x_speed.png │ ├── buqieryu.png │ ├── choose_confirm.png │ ├── enter.png │ ├── enter_buqieryu.png │ ├── enter_game.png │ ├── enter_guiyixingshang.png │ ├── exit.png │ ├── exit_all.png │ ├── exit_confirm.png │ ├── exit_shop.png │ ├── fight_lipaoxiaodui.png │ ├── fight_xunshouxiaowu.png │ ├── fight_yiwai.png │ ├── fight_yuchongweiban.png │ ├── finish.png │ ├── giveup.png │ ├── giveup_confirm.png │ ├── guiyixingshang.png │ ├── kaishixingdong.png │ ├── landu.png │ ├── linguang.png │ ├── mujianyuxing.png │ ├── nazou.png │ ├── rg_start.png │ ├── shengming.png │ ├── signal_lost.png │ ├── start.png │ ├── start1.png │ ├── suanle.png │ ├── suanle2.png │ ├── success_pass.png │ ├── taopao.png │ ├── touzi_confirm.png │ ├── touzi_enter.png │ ├── touzirukou.png │ ├── xiwang.png │ ├── yanrong.png │ └── yuanshiding.png ├── CaptureMarkHelper.py ├── FunctionDoc.md ├── ImageProc.py ├── LICENSE ├── README.md ├── RaphaelScriptHelper.py └── settings.py /ADBHelper.py: -------------------------------------------------------------------------------- 1 | import os, time 2 | 3 | # 获取设备列表,每一个为deviceID 4 | def getDevicesList(): 5 | content = os.popen('adb devices').read() 6 | if "daemon not running" in content: 7 | content = os.popen('adb devices').read() 8 | row_list = content.split('List of devices attached\n')[1].split('\n') 9 | devices_list = [i for i in row_list if len(i) > 1] 10 | res = [] 11 | for d in devices_list: 12 | res.append(d.split('\t')[0]) 13 | return res 14 | 15 | # 杀死ADB进程 16 | def killADBServer(): 17 | os.system("adb kill-server") 18 | 19 | # 设备屏幕截图,需给定did和本机截图保存路径 20 | def screenCapture(deviceID, capPath): 21 | a = "adb -s " + deviceID + " shell screencap -p sdcard/adb_screenCap.png" 22 | b = "adb pull sdcard/adb_screenCap.png " + capPath 23 | for row in [a, b]: 24 | time.sleep(0.1) 25 | os.system(row) 26 | if os.path.exists(capPath) == True: 27 | return True 28 | else: 29 | return False 30 | 31 | # 模拟点击屏幕,参数pos为目标坐标(x, y) 32 | def touch(deviceID, pos): 33 | x, y = pos 34 | a = "adb -s " + deviceID + " shell input touchscreen tap {0} {1}".format(x, y) 35 | os.system(a) 36 | 37 | # 模拟滑动屏幕,posStart为起始坐标(x, y),posStop为终点坐标(x, y),time为滑动时间 38 | def slide(deviceID, posStart, posStop, time): 39 | x1, y1 = posStart 40 | x2, y2 = posStop 41 | a = "adb -s " + deviceID + " shell input swipe {0} {1} {2} {3} {4}".format(x1, y1, x2, y2, time) 42 | os.system(a) 43 | 44 | # 模拟长按屏幕,参数pos为目标坐标(x, y),time为长按时间 45 | def longTouch(deviceID, pos, time): 46 | x, y = pos 47 | a = "adb -s " + deviceID + " shell input swipe {0} {1} {2} {3} {4}".format(x, y, x, y, time) 48 | os.system(a) 49 | -------------------------------------------------------------------------------- /Arknights/ArknightsRoguelike.py: -------------------------------------------------------------------------------- 1 | # 请参考视频教程 https://www.bilibili.com/video/BV1u3411E7KD/ 改写此脚本后再运行 2 | # 请注意视频教程或文字教程中的相关注意事项 3 | 4 | import RaphaelScriptHelper as gamer 5 | import multiprocessing 6 | import ResourceDictionary as rd 7 | import settings 8 | from enum import Enum 9 | 10 | class Direction(Enum): 11 | UP = 0 12 | DOWN = 1 13 | LEFT = 2 14 | RIGHT = 3 15 | 16 | # 请在跑脚本之前参考教程修改这一部分 17 | # ======================================================================= 18 | 19 | # 安卓设备的DID 20 | gamer.deviceID = "127.0.0.1:62001" 21 | 22 | # 从点击开始以后到进入正式游戏界面之前的前期准备部分 23 | def init_front(): 24 | # 选择分队 25 | gamer.touch(rd.zhihuifendui) 26 | gamer.random_delay() 27 | gamer.touch(rd.zhihuifendui) 28 | gamer.random_delay() 29 | 30 | # 选择招募组合 31 | gamer.touch(rd.wenzhawenda) 32 | gamer.random_delay() 33 | gamer.touch(rd.wenzhawenda) 34 | gamer.random_delay() 35 | 36 | #选择第一个职业和干员 37 | gamer.touch(rd.zhongzhuang) 38 | gamer.random_delay() 39 | gamer.touch(rd.linguang) 40 | gamer.random_delay() 41 | gamer.touch(rd.querenganyuan) 42 | gamer.random_delay() 43 | gamer.touch(rd.skip) 44 | gamer.delay(1) 45 | gamer.touch(rd.skip) 46 | gamer.delay(1) 47 | gamer.touch(rd.skip) 48 | gamer.random_delay() 49 | gamer.delay(3) 50 | 51 | #选择第二个职业和干员 52 | gamer.touch(rd.juji) 53 | gamer.random_delay() 54 | gamer.touch(rd.landu) 55 | gamer.random_delay() 56 | gamer.touch(rd.querenganyuan) 57 | gamer.random_delay() 58 | gamer.touch(rd.skip) 59 | gamer.delay(1) 60 | gamer.touch(rd.skip) 61 | gamer.delay(1) 62 | gamer.touch(rd.skip) 63 | gamer.random_delay() 64 | gamer.delay(3) 65 | 66 | #选择第三个职业和干员 67 | gamer.touch(rd.shushi) 68 | gamer.random_delay() 69 | gamer.touch(rd.yanrong) 70 | gamer.random_delay() 71 | gamer.touch(rd.querenganyuan) 72 | gamer.random_delay() 73 | gamer.delay(3) 74 | gamer.touch(rd.skip) 75 | 76 | 77 | # 与虫为伴关卡所需时间,单位为秒 78 | fight_yu_chong_wei_ban_duration = 80 79 | 80 | # 与虫为伴打法 在此定义 请参考这个方法内的注释来编写 81 | def fight_yu_chong_wei_ban(): 82 | for i in range(4): # 循环做4次,以防中途有干员被打死然后就不部署了 83 | # 刚进游戏画面时的延时,这里不需要设置太高,因为此时已经是二倍速状态,如果一开始使用的干员费用较高可以适当增大此值 84 | gamer.delay(4) 85 | 86 | # 这一行代码的意思是将临光放在指定位置,朝向向上,下同,这些都可以自己替换掉 87 | fight_agent_arrange(rd.fight_icon_linguang, rd.yuchongweiban_linguang, Direction.UP) 88 | # 放完临光后延时5秒再放下一个干员(注意费用回复时间) 89 | gamer.delay(5) 90 | 91 | fight_agent_arrange(rd.fight_icon_landu, rd.yuchongweiban_landu, Direction.LEFT) 92 | gamer.delay(8) 93 | 94 | fight_agent_arrange(rd.fight_icon_yanrong, rd.yuchongweiban_yanrong, Direction.UP) 95 | gamer.delay(10) 96 | 97 | 98 | # 驯兽小屋关卡所需时间,单位为秒 99 | fight_xun_shou_xiao_wu_duration = 80 100 | 101 | # 驯兽小屋打法 在此定义 参考 与虫为伴的注释 102 | def fight_xun_shou_xiao_wu(): 103 | for i in range(4): 104 | gamer.delay(4) 105 | 106 | fight_agent_arrange(rd.fight_icon_linguang, rd.xunshouxiaowu_linguang, Direction.RIGHT) 107 | gamer.delay(5) 108 | 109 | fight_agent_arrange(rd.fight_icon_landu, rd.xunshouxiaowu_landu, Direction.LEFT) 110 | gamer.delay(8) 111 | 112 | fight_agent_arrange(rd.fight_icon_yanrong, rd.xunshouxiaowu_yanrong, Direction.LEFT) 113 | gamer.delay(10) 114 | 115 | # 礼炮小队关卡所需时间,单位为秒 116 | fight_li_pao_xiao_dui_duration = 80 117 | 118 | # 礼炮小队打法 在此定义 参考 与虫为伴的注释 119 | def fight_li_pao_xiao_dui(): 120 | for i in range(4): 121 | gamer.delay(4) 122 | 123 | fight_agent_arrange(rd.fight_icon_linguang, rd.lipaoxiaodui_linguang, Direction.RIGHT) 124 | gamer.delay(5) 125 | 126 | fight_agent_arrange(rd.fight_icon_landu, rd.lipaoxiaodui_landu, Direction.RIGHT) 127 | gamer.delay(8) 128 | 129 | fight_agent_arrange(rd.fight_icon_yanrong, rd.lipaoxiaodui_yanrong, Direction.RIGHT) 130 | gamer.delay(10) 131 | 132 | # 意外关卡所需时间,单位为秒 133 | fight_yi_wai_duration = 80 134 | 135 | # 意外打法 在此定义 参考 与虫为伴的注释 136 | def fight_yi_wai(): 137 | for i in range(4): 138 | gamer.delay(4) 139 | 140 | fight_agent_arrange(rd.fight_icon_landu, rd.yiwai_landu, Direction.DOWN) 141 | gamer.delay(5) 142 | 143 | fight_agent_arrange(rd.fight_icon_linguang, rd.yiwai_linguang, Direction.LEFT) 144 | gamer.delay(8) 145 | 146 | fight_agent_arrange(rd.fight_icon_yanrong, rd.yiwai_yanrong, Direction.DOWN) 147 | gamer.delay(10) 148 | 149 | 150 | # ======================================================================= 151 | # 以下部分请不要随意修改 152 | 153 | # ADB运行 154 | gamer.deviceType = 1 155 | 156 | # 全局标志位 勿改动 157 | isFightLose = False 158 | 159 | #屏幕分辨率 勿改动 请与此保持对齐 160 | screen_size = (2340, 1080) 161 | 162 | #战斗界面干员部署通用方法 三个参数分别是 干员 站位 朝向(0-3分别代表上下左右) 163 | def fight_agent_arrange(agent, pos, direction): 164 | screen_w, screen_h = screen_size 165 | x, y = pos 166 | shift = settings.touchPosRange 167 | 168 | if direction == Direction.UP: 169 | _y = y - 400 170 | if (_y < shift): 171 | _y = shift 172 | slide_final_pos = (x, _y) 173 | elif direction == Direction.DOWN: 174 | _y = y + 400 175 | if (_y > screen_h - shift): 176 | _y = screen_h - shift 177 | slide_final_pos = (x, _y) 178 | elif direction == Direction.LEFT: 179 | _x = x - 400 180 | if (_x < shift): 181 | _x = shift 182 | slide_final_pos = (_x, y) 183 | elif direction == Direction.RIGHT: 184 | _x = x + 400 185 | if (_x > screen_w - shift): 186 | _x = screen_w - shift 187 | slide_final_pos = (_x, y) 188 | else: 189 | return False 190 | 191 | if gamer.find_pic_slide(agent, pos): 192 | gamer.delay(0.5) 193 | gamer.slide((pos, slide_final_pos)) 194 | gamer.delay(0.5) 195 | return True 196 | 197 | return False 198 | 199 | # 跳过结算画面 200 | def skip_ending(): 201 | gamer.random_delay() 202 | gamer.touch(rd.bottom) 203 | gamer.delay(0.5) 204 | gamer.touch(rd.bottom) 205 | gamer.delay(0.5) 206 | gamer.touch(rd.bottom) 207 | gamer.delay(0.5) 208 | gamer.touch(rd.bottom) 209 | gamer.random_delay() 210 | gamer.touch(rd.bottom) 211 | 212 | # 战斗后处理 213 | def process_after_fight(): 214 | for i in range(5): # 避免某些关卡有特殊怪,打得比较慢,重试五次检查结果状态,每次间隔10s 215 | if gamer.find_pic_touch(rd.success_pass): 216 | gamer.random_delay() 217 | gamer.find_pic_touch(rd.nazou) 218 | gamer.delay(1) 219 | gamer.find_pic_touch(rd.exit) 220 | gamer.delay(0.5) 221 | if gamer.find_pic_touch(rd.exit_confirm): 222 | return True 223 | else: 224 | return False 225 | # 失败的情况考虑一下 226 | elif gamer.find_pic_touch(rd.signal_lost): 227 | gamer.random_delay() 228 | gamer.delay(5) 229 | skip_ending() 230 | global isFightLose 231 | isFightLose = True 232 | else: 233 | if i == 4: 234 | return False 235 | gamer.delay(10) 236 | 237 | # 战斗前处理 238 | def process_before_fight(): 239 | gamer.random_delay() 240 | gamer.find_pic_touch(rd.enter) 241 | gamer.random_delay() 242 | gamer.find_pic_touch(rd.kaishixingdong) 243 | gamer.delay(9) # 从点击开始行动按钮以后到进入游戏的延时9秒 244 | gamer.find_pic_touch(rd.speed_1x) # 二倍速 245 | 246 | # 普通战斗关卡 247 | def fight(): 248 | global isFightLose 249 | isFightLose = False 250 | if gamer.find_pic_touch(rd.fight_lipaoxiaodui): 251 | process_before_fight() 252 | t = multiprocessing.Process(target=fight_li_pao_xiao_dui) 253 | t.start() 254 | gamer.delay(fight_li_pao_xiao_dui_duration) 255 | t.terminate() 256 | elif gamer.find_pic_touch(rd.fight_yuchongweiban): 257 | process_before_fight() 258 | t = multiprocessing.Process(target=fight_yu_chong_wei_ban) 259 | t.start() 260 | gamer.delay(fight_yu_chong_wei_ban_duration) 261 | t.terminate() 262 | elif gamer.find_pic_touch(rd.fight_xunshouxiaowu): 263 | process_before_fight() 264 | t = multiprocessing.Process(target=fight_xun_shou_xiao_wu) 265 | t.start() 266 | gamer.delay(fight_xun_shou_xiao_wu_duration) 267 | t.terminate() 268 | elif gamer.find_pic_touch(rd.fight_yiwai): 269 | process_before_fight() 270 | t = multiprocessing.Process(target=fight_yi_wai) 271 | t.start() 272 | gamer.delay(fight_yi_wai_duration) 273 | t.terminate() 274 | else: 275 | return False 276 | 277 | process_after_fight() 278 | return True 279 | 280 | # 不期而遇节点处理 281 | def buqieryu(): 282 | if gamer.find_pic_touch(rd.buqieryu): 283 | gamer.random_delay() 284 | gamer.find_pic_touch(rd.enter_buqieryu) 285 | gamer.delay(8) #等待展示文本时间 286 | gamer.random_delay() 287 | 288 | for i in range(2): 289 | if gamer.find_pic_touch(rd.taopao): 290 | gamer.delay(1) 291 | gamer.find_pic_touch(rd.choose_confirm) 292 | break 293 | elif gamer.find_pic_touch(rd.xiwang): 294 | gamer.delay(1) 295 | gamer.find_pic_touch(rd.choose_confirm) 296 | break 297 | elif gamer.find_pic_touch(rd.shengming): 298 | gamer.delay(1) 299 | gamer.find_pic_touch(rd.choose_confirm) 300 | break 301 | elif gamer.find_pic_touch(rd.yuanshiding): 302 | gamer.delay(1) 303 | gamer.find_pic_touch(rd.choose_confirm) 304 | break 305 | else: 306 | #下滑一点然后重试一次,防止展示不完全 307 | gamer.slide(rd.right_slide_down) 308 | gamer.delay(3) 309 | gamer.touch(rd.bottom) 310 | gamer.random_delay() 311 | return True 312 | else: 313 | return False 314 | 315 | # 诡异行商节点处理(刷投资) 316 | def guiyixingshang(): 317 | if gamer.find_pic_touch(rd.guiyixingshang): 318 | gamer.random_delay() 319 | gamer.find_pic_touch(rd.enter_guiyixingshang) 320 | gamer.delay(3) 321 | gamer.random_delay() 322 | if gamer.find_pic_touch(rd.touzi_enter): 323 | gamer.find_pic_touch(rd.touzirukou) 324 | gamer.random_delay() 325 | pos = gamer.find_pic(rd.touzi_confirm, True) 326 | for i in range(0,20): #点20次 投资确认 327 | gamer.touch(pos) 328 | gamer.delay(0.5) 329 | gamer.find_pic_touch(rd.suanle) 330 | gamer.random_delay() 331 | gamer.find_pic_touch(rd.suanle2) 332 | gamer.random_delay() 333 | pos = gamer.find_pic(rd.exit_shop) 334 | gamer.touch(pos) 335 | gamer.random_delay() 336 | gamer.touch(pos) 337 | else: 338 | pos = gamer.find_pic(rd.exit_shop) 339 | gamer.touch(pos) 340 | gamer.random_delay() 341 | gamer.touch(pos) 342 | return True 343 | else: 344 | return False 345 | 346 | # 幕间余兴 这里直接选退出选项 347 | def mujianyuxing(): 348 | if gamer.find_pic_touch(rd.mujianyuxing): 349 | gamer.random_delay() 350 | gamer.find_pic_touch(rd.enter_buqieryu) 351 | gamer.delay(8) #等待展示文本时间 352 | gamer.random_delay() 353 | for i in range(2): 354 | if gamer.find_pic_touch(rd.taopao): 355 | gamer.delay(1) 356 | gamer.find_pic_touch(rd.choose_confirm) 357 | break 358 | else: 359 | #下滑一点然后重试一次,防止展示不完全 360 | gamer.slide(rd.right_slide_down) 361 | gamer.delay(3) 362 | gamer.touch(rd.bottom) 363 | gamer.random_delay() 364 | return True 365 | else: 366 | return False 367 | 368 | # 退出到主界面并放弃当前进度,重开 369 | def exit_game(): 370 | gamer.find_pic_touch(rd.exit_all) 371 | gamer.delay(2) 372 | gamer.random_delay() 373 | gamer.find_pic_touch(rd.giveup) 374 | gamer.random_delay() 375 | gamer.find_pic_touch(rd.giveup_confirm) 376 | skip_ending() 377 | 378 | # 干员编队部分,这里只要分辨率不变,操作是固定的 379 | def gan_yuan_bian_dui(): 380 | gamer.touch((2076,1026)) 381 | gamer.random_delay() 382 | gamer.touch((1846, 60)) 383 | gamer.random_delay() 384 | gamer.touch((987,242)) 385 | gamer.random_delay() 386 | gamer.touch((987, 446)) 387 | gamer.random_delay() 388 | gamer.touch((987, 656)) 389 | gamer.random_delay() 390 | gamer.touch((2078, 1022)) 391 | gamer.random_delay() 392 | gamer.touch((195, 52)) 393 | 394 | 395 | 396 | # 脚本从这里开始运行 397 | gamer.deviceType = 1 398 | 399 | while True: 400 | if gamer.find_pic_touch(rd.rg_start): 401 | gamer.random_delay() 402 | init_front() 403 | gamer.random_delay() 404 | if gamer.find_pic_touch(rd.enter_game): 405 | gamer.delay(5) 406 | gan_yuan_bian_dui() 407 | 408 | # 第一层只有四关,且第一关只能是战斗节点 409 | # 1 410 | fight() 411 | 412 | # TODO:可以考虑更智能的寻路算法,当前只支持按照固定优先级 413 | # 2 414 | gamer.random_delay() 415 | if buqieryu() is False: 416 | if fight() is False: 417 | if mujianyuxing() is False: 418 | exit_game() 419 | continue 420 | if isFightLose: 421 | continue 422 | 423 | # 中场滑屏到后面,避免重复识别 424 | gamer.slide(rd.bottom_slide_left) 425 | 426 | # 3 427 | gamer.random_delay() 428 | if buqieryu() is False: 429 | if fight() is False: 430 | if mujianyuxing() is False: 431 | exit_game() 432 | continue 433 | 434 | if isFightLose: 435 | continue 436 | 437 | # 第四关只能是诡异行商 438 | # 4 439 | guiyixingshang() 440 | gamer.delay(5) 441 | gamer.random_delay() 442 | exit_game() 443 | else: 444 | break 445 | -------------------------------------------------------------------------------- /Arknights/Arknights_default.py: -------------------------------------------------------------------------------- 1 | # 请将此脚本、img文件夹和ResourceDictionary.py文件复制到项目根目录下再运行! 2 | 3 | import ADBHelper, RaphaelScriptHelper, ResourceDictionary 4 | 5 | deviceList = ADBHelper.getDevicesList() 6 | i = 0 7 | for did in deviceList: 8 | print(str(i) + ": " + did) 9 | i = i + 1 10 | input_i = input("请输入需要执行脚本的设备编号\n") 11 | 12 | delayTime = input("请输入关卡所耗时间(单位:秒)\n") 13 | 14 | RaphaelScriptHelper.deviceType = 1 15 | RaphaelScriptHelper.deviceID = deviceList[int(input_i)] 16 | 17 | for i in range(0,100): 18 | j = 0 19 | while j < 5: 20 | RaphaelScriptHelper.find_pic_touch(ResourceDictionary.start) 21 | RaphaelScriptHelper.random_delay() 22 | if RaphaelScriptHelper.find_pic_touch(ResourceDictionary.start1): 23 | break 24 | j = j + 1 25 | RaphaelScriptHelper.random_delay() 26 | 27 | RaphaelScriptHelper.delay(int(delayTime)) 28 | 29 | j = 0 30 | while j < 5: 31 | if RaphaelScriptHelper.find_pic_touch(ResourceDictionary.finish): 32 | break 33 | j = j + 1 34 | RaphaelScriptHelper.delay(3) 35 | 36 | RaphaelScriptHelper.delay(3) 37 | RaphaelScriptHelper.random_delay() 38 | -------------------------------------------------------------------------------- /Arknights/ResourceDictionary.py: -------------------------------------------------------------------------------- 1 | start = "./img/start.png" 2 | start1 = "./img/start1.png" 3 | finish = "./img/finish.png" 4 | 5 | 6 | #指挥分队按钮,点两次,第二次是确认 7 | zhihuifendui = (335,820) 8 | 9 | #稳扎稳打选项,点两次,第二次是确认 10 | wenzhawenda = (960,820) 11 | 12 | zhongzhuang = (733,744) 13 | linguang = (1587,644) 14 | 15 | juji = (1598,731) 16 | landu = (928,424) 17 | 18 | shushi = (1168,686) 19 | yanrong = (1016,430) 20 | 21 | #选完人以后跳过 22 | skip = (2173,85) 23 | #选完干员以后确认 24 | querenganyuan = (2092,1024) 25 | 26 | speed_1x = "./img/1x_speed.png" 27 | bottom = (1169,920) 28 | 29 | fight_icon_linguang = "./img/linguang.png" 30 | fight_icon_landu = "./img/landu.png" 31 | fight_icon_yanrong = "./img/yanrong.png" 32 | 33 | yuchongweiban_linguang = (1096, 558) 34 | yuchongweiban_landu = (1214, 534) 35 | yuchongweiban_yanrong = (972, 664) 36 | kaishixingdong = "./img/kaishixingdong.png" 37 | success_pass = "./img/success_pass.png" 38 | nazou = "./img/nazou.png" 39 | exit = "./img/exit.png" 40 | exit_confirm = "./img/exit_confirm.png" 41 | signal_lost = "./img/signal_lost.png" 42 | fight_lipaoxiaodui = "./img/fight_lipaoxiaodui.png" 43 | fight_yuchongweiban = "./img/fight_yuchongweiban.png" 44 | fight_xunshouxiaowu = "./img/fight_xunshouxiaowu.png" 45 | fight_yiwai = "./img/fight_yiwai.png" 46 | enter = "./img/enter.png" 47 | 48 | buqieryu = "./img/buqieryu.png" 49 | enter_buqieryu = "./img/enter_buqieryu.png" 50 | choose_confirm = "./img/choose_confirm.png" 51 | taopao = "./img/taopao.png" 52 | xiwang = "./img/xiwang.png" 53 | shengming = "./img/shengming.png" 54 | yuanshiding = "./img/yuanshiding.png" 55 | 56 | guiyixingshang = "./img/guiyixingshang.png" 57 | enter_guiyixingshang = "./img/enter_guiyixingshang.png" 58 | touzi_enter = "./img/touzi_enter.png" 59 | touzirukou = "./img/touzirukou.png" 60 | touzi_confirm = "./img/touzi_confirm.png" 61 | suanle = "./img/suanle.png" 62 | suanle2 = "./img/suanle2.png" 63 | exit_shop = "./img/exit_shop.png" 64 | 65 | mujianyuxing = "./img/mujianyuxing.png" 66 | 67 | exit_all = "./img/exit_all.png" 68 | giveup = "./img/giveup.png" 69 | giveup_confirm = "./img/giveup_confirm.png" 70 | 71 | rg_start = "./img/rg_start.png" 72 | enter_game = "./img/enter_game.png" 73 | 74 | right_slide_down = ((1846, 844), (1846, 232)) 75 | bottom_slide_left = ((1760, 860), (350, 860)) 76 | quchangbuduan = (1380, 856) 77 | yiwai_landu = (1276, 366) 78 | yiwai_linguang = (1284, 490) 79 | yiwai_yanrong = (1402, 356) 80 | lipaoxiaodui_linguang = (972, 512) 81 | lipaoxiaodui_landu = (978, 398) 82 | lipaoxiaodui_yanrong = (960, 616) 83 | xunshouxiaowu_linguang = (1776, 522) 84 | xunshouxiaowu_landu = (1748, 374) 85 | xunshouxiaowu_yanrong = (1596, 390) 86 | -------------------------------------------------------------------------------- /Arknights/img/1x_speed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/1x_speed.png -------------------------------------------------------------------------------- /Arknights/img/buqieryu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/buqieryu.png -------------------------------------------------------------------------------- /Arknights/img/choose_confirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/choose_confirm.png -------------------------------------------------------------------------------- /Arknights/img/enter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/enter.png -------------------------------------------------------------------------------- /Arknights/img/enter_buqieryu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/enter_buqieryu.png -------------------------------------------------------------------------------- /Arknights/img/enter_game.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/enter_game.png -------------------------------------------------------------------------------- /Arknights/img/enter_guiyixingshang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/enter_guiyixingshang.png -------------------------------------------------------------------------------- /Arknights/img/exit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/exit.png -------------------------------------------------------------------------------- /Arknights/img/exit_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/exit_all.png -------------------------------------------------------------------------------- /Arknights/img/exit_confirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/exit_confirm.png -------------------------------------------------------------------------------- /Arknights/img/exit_shop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/exit_shop.png -------------------------------------------------------------------------------- /Arknights/img/fight_lipaoxiaodui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/fight_lipaoxiaodui.png -------------------------------------------------------------------------------- /Arknights/img/fight_xunshouxiaowu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/fight_xunshouxiaowu.png -------------------------------------------------------------------------------- /Arknights/img/fight_yiwai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/fight_yiwai.png -------------------------------------------------------------------------------- /Arknights/img/fight_yuchongweiban.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/fight_yuchongweiban.png -------------------------------------------------------------------------------- /Arknights/img/finish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/finish.png -------------------------------------------------------------------------------- /Arknights/img/giveup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/giveup.png -------------------------------------------------------------------------------- /Arknights/img/giveup_confirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/giveup_confirm.png -------------------------------------------------------------------------------- /Arknights/img/guiyixingshang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/guiyixingshang.png -------------------------------------------------------------------------------- /Arknights/img/kaishixingdong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/kaishixingdong.png -------------------------------------------------------------------------------- /Arknights/img/landu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/landu.png -------------------------------------------------------------------------------- /Arknights/img/linguang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/linguang.png -------------------------------------------------------------------------------- /Arknights/img/mujianyuxing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/mujianyuxing.png -------------------------------------------------------------------------------- /Arknights/img/nazou.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/nazou.png -------------------------------------------------------------------------------- /Arknights/img/rg_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/rg_start.png -------------------------------------------------------------------------------- /Arknights/img/shengming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/shengming.png -------------------------------------------------------------------------------- /Arknights/img/signal_lost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/signal_lost.png -------------------------------------------------------------------------------- /Arknights/img/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/start.png -------------------------------------------------------------------------------- /Arknights/img/start1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/start1.png -------------------------------------------------------------------------------- /Arknights/img/suanle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/suanle.png -------------------------------------------------------------------------------- /Arknights/img/suanle2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/suanle2.png -------------------------------------------------------------------------------- /Arknights/img/success_pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/success_pass.png -------------------------------------------------------------------------------- /Arknights/img/taopao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/taopao.png -------------------------------------------------------------------------------- /Arknights/img/touzi_confirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/touzi_confirm.png -------------------------------------------------------------------------------- /Arknights/img/touzi_enter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/touzi_enter.png -------------------------------------------------------------------------------- /Arknights/img/touzirukou.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/touzirukou.png -------------------------------------------------------------------------------- /Arknights/img/xiwang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/xiwang.png -------------------------------------------------------------------------------- /Arknights/img/yanrong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/yanrong.png -------------------------------------------------------------------------------- /Arknights/img/yuanshiding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanmin0822/RaphaelScriptHelper/865db81a79419533567b906bb2db22c8b00097f8/Arknights/img/yuanshiding.png -------------------------------------------------------------------------------- /CaptureMarkHelper.py: -------------------------------------------------------------------------------- 1 | # 标点截取工具 Author By Hanmin 2022.01 2 | # 请参考使用文档使用本工具 3 | 4 | import cv2, tkinter, os 5 | import tkinter.simpledialog 6 | import ADBHelper 7 | 8 | # 修改以下参数来运行 9 | 10 | # 原图缩放比例,用于展示在窗口里 11 | scale = 0.5 12 | 13 | # 截图保存路径,以/结束 14 | save_file_path = "./img/" 15 | 16 | # py变量字典文件 17 | pos_img_dict = "./testDict.py" 18 | 19 | # 动作类型 1=截图 2=标点 3=标线(取起终点组成向量) 4=标记区域 20 | action = 4 21 | 22 | # 图片来源替换输入你的did 23 | ADBHelper.screenCapture("did", "screen.png") 24 | img_file = "./screen.png" 25 | 26 | 27 | # =================================================== 28 | # 以下部分可以不改动 29 | 30 | def isVarExist(varName): 31 | if os.path.exists(pos_img_dict): 32 | with open(pos_img_dict, 'r', encoding='utf-8') as f: 33 | str = f.read() 34 | if varName in str: 35 | return True 36 | else: 37 | return False 38 | else: 39 | return False 40 | 41 | 42 | # type=动作类型 1=截图 2=标点 3=标线(取起终点组成向量) 4=标记区域 43 | def createVar(varName, value, type): 44 | with open(pos_img_dict, 'a+', encoding='utf-8') as f: 45 | if type == 1: 46 | f.write(varName + " = \"" + value + "\"\n") 47 | elif type == 2: 48 | f.write(varName + " = " + str(value) + "\n") 49 | elif type == 3: 50 | f.write(varName + " = " + str(value) + "\n") 51 | elif type == 4: 52 | f.write(varName + " = " + str(value) + "\n") 53 | 54 | def draw_Rect(event, x, y, flags, param): 55 | global drawing, startPos, stopPos 56 | if event == cv2.EVENT_LBUTTONDOWN: # 响应鼠标按下 57 | drawing = True 58 | startPos = (x, y) 59 | elif event == cv2.EVENT_MOUSEMOVE: # 响应鼠标移动 60 | if drawing == True: 61 | img = img_source.copy() 62 | cv2.rectangle(img, startPos, (x, y), (0, 255, 0), 2) 63 | cv2.imshow('image', img) 64 | elif event == cv2.EVENT_LBUTTONUP: # 响应鼠标松开 65 | drawing = False 66 | stopPos = (x, y) 67 | elif event == cv2.EVENT_RBUTTONUP: 68 | if startPos == (0, 0) and stopPos == (0, 0): 69 | return 70 | x0, y0 = startPos 71 | x1, y1 = stopPos 72 | cropped = img_source[y0:y1, x0:x1] # 裁剪坐标为[y0:y1, x0:x1] 73 | res = tkinter.simpledialog.askstring(title="输入", prompt="请输入图片变量名:(存储路径为" + save_file_path + ")", 74 | initialvalue="") 75 | if res is not None: 76 | if isVarExist(res): 77 | tkinter.simpledialog.messagebox.showerror("错误", "该变量名已存在,请更换一个或手动去文件中删除!") 78 | else: 79 | cv2.imwrite(save_file_path + res + ".png", cropped) 80 | createVar(res, save_file_path + res + ".png", 1) 81 | tkinter.simpledialog.messagebox.showinfo("提示", "创建完成!") 82 | elif event == cv2.EVENT_MBUTTONUP: 83 | if startPos == (0, 0) and stopPos == (0, 0): 84 | return 85 | x0, y0 = startPos 86 | x1, y1 = stopPos 87 | cropped = img_source[y0:y1, x0:x1] # 裁剪坐标为[y0:y1, x0:x1] 88 | cv2.imshow('cropImage', cropped) 89 | cv2.waitKey(0) 90 | 91 | 92 | def draw_Point(event, x, y, flags, param): 93 | global drawing, startPos, stopPos 94 | if event == cv2.EVENT_LBUTTONDOWN: # 响应鼠标按下 95 | drawing = True 96 | startPos = (x, y) 97 | img = img_source.copy() 98 | cv2.circle(img, startPos, 2, (0, 255, 0), 2) 99 | cv2.putText(img, "Point:" + str(startPos), (50, 100), cv2.FONT_HERSHEY_SIMPLEX, 2.0, (0, 255, 0), 3) 100 | print("Point:" + str(startPos)) 101 | cv2.imshow('image', img) 102 | elif event == cv2.EVENT_RBUTTONUP: 103 | if startPos == (0, 0): 104 | return 105 | res = tkinter.simpledialog.askstring(title="输入", prompt="请输入坐标 " + str(startPos) + " 变量名:", initialvalue="") 106 | if res is not None: 107 | if isVarExist(res): 108 | tkinter.simpledialog.messagebox.showerror("错误", "该变量名已存在,请更换一个或手动去文件中删除!") 109 | else: 110 | createVar(res, startPos, 2) 111 | tkinter.simpledialog.messagebox.showinfo("提示", "创建完成!") 112 | 113 | 114 | def draw_Line(event, x, y, flags, param): 115 | global drawing, startPos, stopPos 116 | if event == cv2.EVENT_LBUTTONDOWN: # 响应鼠标按下 117 | drawing = True 118 | startPos = (x, y) 119 | elif event == cv2.EVENT_MOUSEMOVE: # 响应鼠标移动 120 | if drawing == True: 121 | img = img_source.copy() 122 | cv2.line(img, startPos, (x, y), (0, 255, 0), 2) 123 | cv2.imshow('image', img) 124 | elif event == cv2.EVENT_LBUTTONUP: # 响应鼠标松开 125 | drawing = False 126 | stopPos = (x, y) 127 | print("startPoint:" + str(startPos) + " stopPoint:" + str(stopPos)) 128 | elif event == cv2.EVENT_RBUTTONUP: 129 | if startPos == (0, 0) and stopPos == (0, 0): 130 | return 131 | res = tkinter.simpledialog.askstring(title="输入", prompt="请输入开始坐标 " + str(startPos) + " 到结束坐标 " + str( 132 | stopPos) + " 组成向量的变量名:", initialvalue="") 133 | if res is not None: 134 | if isVarExist(res): 135 | tkinter.simpledialog.messagebox.showerror("错误", "该变量名已存在,请更换一个或手动去文件中删除!") 136 | else: 137 | createVar(res, (startPos, stopPos), 3) 138 | tkinter.simpledialog.messagebox.showinfo("提示", "创建完成!") 139 | 140 | 141 | def draw_Rect_Pos(event, x, y, flags, param): 142 | global drawing, startPos, stopPos 143 | if event == cv2.EVENT_LBUTTONDOWN: # 响应鼠标按下 144 | drawing = True 145 | startPos = (x, y) 146 | elif event == cv2.EVENT_MOUSEMOVE: # 响应鼠标移动 147 | if drawing == True: 148 | img = img_source.copy() 149 | cv2.rectangle(img, startPos, (x, y), (0, 255, 0), 2) 150 | cv2.imshow('image', img) 151 | elif event == cv2.EVENT_LBUTTONUP: # 响应鼠标松开 152 | drawing = False 153 | stopPos = (x, y) 154 | print("startPoint:" + str(startPos) + " stopPoint:" + str(stopPos)) 155 | elif event == cv2.EVENT_RBUTTONUP: 156 | if startPos == (0, 0) and stopPos == (0, 0): 157 | return 158 | x0, y0 = startPos 159 | x1, y1 = stopPos 160 | res = tkinter.simpledialog.askstring(title="输入", prompt="请输入矩形范围变量名:", 161 | initialvalue="") 162 | if res is not None: 163 | if isVarExist(res): 164 | tkinter.simpledialog.messagebox.showerror("错误", "该变量名已存在,请更换一个或手动去文件中删除!") 165 | else: 166 | createVar(res, (startPos, stopPos), 4) 167 | tkinter.simpledialog.messagebox.showinfo("提示", "创建完成!") 168 | elif event == cv2.EVENT_MBUTTONUP: 169 | if startPos == (0, 0) and stopPos == (0, 0): 170 | return 171 | x0, y0 = startPos 172 | x1, y1 = stopPos 173 | cropped = img_source[y0:y1, x0:x1] # 裁剪坐标为[y0:y1, x0:x1] 174 | cv2.imshow('cropImage', cropped) 175 | cv2.waitKey(0) 176 | 177 | drawing = False 178 | startPos = (0, 0) 179 | stopPos = (0, 0) 180 | img_source = cv2.imread(img_file) 181 | img = img_source.copy() 182 | 183 | root = tkinter.Tk() 184 | root.title('dialog') 185 | root.resizable(0, 0) 186 | root.withdraw() 187 | 188 | h_src, w_src, tongdao = img.shape 189 | w = int(w_src * scale) 190 | h = int(h_src * scale) 191 | cv2.namedWindow('image', cv2.WINDOW_NORMAL) 192 | cv2.resizeWindow("image", w, h) 193 | if action == 1: 194 | cv2.setMouseCallback('image', draw_Rect) 195 | elif action == 2: 196 | cv2.setMouseCallback('image', draw_Point) 197 | elif action == 3: 198 | cv2.setMouseCallback('image', draw_Line) 199 | elif action == 4: 200 | cv2.setMouseCallback('image', draw_Rect_Pos) 201 | 202 | cv2.imshow('image', img) 203 | cv2.waitKey(0) 204 | cv2.destroyAllWindows() 205 | 206 | root.destroy() 207 | -------------------------------------------------------------------------------- /FunctionDoc.md: -------------------------------------------------------------------------------- 1 | # 功能使用文档 2 | * [标点截取工具使用说明](#标点截取工具使用说明) 3 | 4 | * [settings文件配置说明](#settings文件配置说明) 5 | 6 | * [RaphaelScriptHelper](#RaphaelScriptHelper) 7 | 8 | * [ImageProc 图片处理类](#ImageProc-图片处理类) 9 | 10 | * [ADBHelper ADB助手类](#ADBHelper-ADB助手类) 11 | 12 |
13 | 14 |
15 | 16 | ## 标点截取工具使用说明 17 | 使用`CaptureMarkHelper.py`脚本 18 | 19 | 根据需要修改以下变量 20 | ```python 21 | # 修改以下参数来运行 22 | 23 | # 原图缩放比例,用于展示在窗口里 24 | scale = 0.5 25 | 26 | # 截图保存路径,以/结束 27 | save_file_path = "./img/" 28 | 29 | # py变量字典文件 30 | pos_img_dict = "./testDict.py" 31 | 32 | # 动作类型 1=截图 2=标点 3=标线(取起终点组成向量) 4=标记区域 33 | action = 4 34 | 35 | # 图片来源替换输入你的did 36 | ADBHelper.screenCapture("did", "screen.png") 37 | img_file = "./screen.png" 38 | ``` 39 | 40 | 其中各个变量的解释如下: 41 | 42 | * `scale`: 原图缩放比例,某些情况下需要标记或截图的图片尺寸很大,按照原图比例展示可能会超出屏幕边界,因此需要缩放,请根据实际情况填写此值,缩放比例仅作窗口展示用,不影响原图实际大小 43 | * `save_file_path`: 截图保存路径,当`action`为1,即截图功能时,脚本将把截图保存在此路径下 44 | * `pos_img_dict`: 变量字典文件,所有保存的图片路径名变量、点位置变量、向量变量等都以变量形式写入到此文件,之后只需要在其他脚本中引入此变量字典文件,就可以直接使用 45 | * `action` : 脚本功能类型,相见功能说明 46 | * `img_file`: 原图路径,如果需要ADB设备立即截图一张,可使用[screenCapture](#screenCapture)方法立即截图,并取截图结果 47 | 48 | **功能说明** 49 | 50 | * 1: 截图,在原图中按下鼠标左键并拖动鼠标,勾选出需要截图的区域,松开鼠标左键完成框选,点击鼠标滚轮(鼠标中键)预览截图效果,点击鼠标右键确认结果,在弹出的输入框中输入变量名并完成变量创建 51 | * 2: 标点,在原图中单击鼠标左键,在图片上标记一个点,左上角显示点位置,点击鼠标右键确认结果,在弹出的输入框中输入变量名并完成变量创建 52 | * 3: 标线,在原图中按下鼠标左键并拖动鼠标,画直线,松开鼠标左键完成画线,点击鼠标右键确认结果,在弹出的输入框中输入变量名并完成变量创建 53 | * 4: 标记矩形,在原图中按下鼠标左键并拖动鼠标,勾选出需要的区域,松开鼠标左键完成框选,点击鼠标滚轮(鼠标中键)预览效果,点击鼠标右键确认结果,在弹出的输入框中输入变量名并完成变量创建 54 | 55 |
56 | 57 | ## settings文件配置说明 58 | 59 | * `accuracy`: 图片匹配算法的置信度阈值,取值范围在0-1之间,默认0.93,在使用寻找图片类的功能时,如果匹配出错误目标则提高此值,如果要模糊匹配或高置信度无法匹配则降低此值 60 | * `cache_path`: 缓存文件路径,部分截图或其他缓存文件将存储在这个指定路径内 61 | * `randomDelayMin`: 调用[random_delay](#random_delay)方法时,延时取随机数的最小值,单位为秒 62 | * `randomDelayMax`: 调用[random_delay](#random_delay)方法时,延时取随机数的最大值,单位为秒 63 | * `touchPosRange`: 调用[touch](#touch)方法时,随机偏移量的最大值,单位为像素 64 | * `touchDelayRange`: 调用[touch](#touch)方法时,随机延时时长的最大值,单位为毫秒 65 | * `slideMinVer`: 调用[slide](#slide)方法时,滑屏所需时长取随机数的最小值,单位为毫秒 66 | * `slideMaxVer`: 调用[slide](#slide)方法时,滑屏所需时长取随机数的最大值,单位为毫秒 67 | 68 |
69 | 70 | ## RaphaelScriptHelper 71 | 引入 72 | ```python 73 | import RaphaelScriptHelper 74 | ``` 75 | 76 | > **使用以前,请参考[settings配置说明](#settings文件配置说明)配置好相关属性** 77 | 78 |
79 | 80 | ### random_delay 81 | 82 | 随机延时,随机范围从`randomDelayMin`到`randomDelayMax` 83 | 84 | **原型** 85 | 86 | ```python 87 | def random_delay() 88 | ``` 89 | **参数解释** 90 | 91 | 无入参 92 | 93 | **返回值** 94 | 95 | 无返回 96 | 97 | **注意** 98 | 99 | 对当前线程延时 100 | 101 |
102 | 103 | ### delay 104 | 105 | 延时指定时间 106 | 107 | **原型** 108 | 109 | ```python 110 | def delay(t) 111 | ``` 112 | **参数解释** 113 | 114 | `t`: 延时时间,单位为秒 115 | 116 | **返回值** 117 | 118 | 无返回 119 | 120 | **注意** 121 | 122 | 对当前线程延时 123 | 124 |
125 | 126 | ### touch 127 | 128 | 智能模拟点击某个点,将会随机点击以这个点为中心一定范围内的某个点,并随机按下时长,让操作更接近人为操作 129 | 130 | **原型** 131 | 132 | ```python 133 | def touch(pos) 134 | ``` 135 | **参数解释** 136 | 137 | `pos`: 点击位置,描述为一个二元组 (x, y) 138 | 139 | **返回值** 140 | 141 | 无返回 142 | 143 | **注意** 144 | 145 | 点击偏移的范围为settings配置中的`touchPosRange`(单位为像素),点击时长的范围为settings配置中的`touchDelayRange`(单位为毫秒,此值不建议设置太高,对于部分游戏较高的按下时长会被视为长按) 146 | 147 |
148 | 149 | ### find_pic 150 | 151 | 截取屏幕,在截图中寻找`target`图片,返回满足置信度要求的,置信度最高的区块的左上角坐标或中心坐标 152 | 153 | **原型** 154 | 155 | ```python 156 | def find_pic(target, returnCenter = False) 157 | ``` 158 | **参数解释** 159 | 160 | `target`: 欲寻找的图片路径 161 | `returnCenter`: 是否返回中心坐标,默认值为`False`,为`True`时返回满足置信度要求的,置信度最高的区块的中心坐标 162 | 163 | **返回值** 164 | 165 | 返回一个点坐标 (x,y),当没有任何满足要求的结果时,返回None 166 | 167 | **注意** 168 | 169 | 置信度阈值为settings配置中的`accuracy`,其余注意事项同[locate](#locate) 170 | 171 |
172 | 173 | ### find_pic_all 174 | 175 | 截取屏幕,在截图中寻找`target`图片,返回满足置信度要求的所有区块的左上角坐标 176 | 177 | **原型** 178 | 179 | ```python 180 | def find_pic_all(target) 181 | ``` 182 | **参数解释** 183 | 184 | `target`: 欲寻找的图片路径 185 | 186 | **返回值** 187 | 188 | 返回一个点坐标数组 \[(x1,y1), (x2,y2), ...\],当没有任何满足要求的结果时,返回一个空数组 189 | 190 | **注意** 191 | 192 | 置信度阈值为settings配置中的`accuracy`,其余注意事项同[locate_all](#locate_all) 193 | 194 |
195 | 196 | ### find_pic_touch 197 | 198 | 截取屏幕,在截图中寻找`target`图片,找到满足置信度要求的且置信度最高的区块,在其范围内进行一次智能模拟点击 199 | 200 | **原型** 201 | 202 | ```python 203 | def find_pic_touch(target) 204 | ``` 205 | **参数解释** 206 | 207 | `target`: 欲寻找的图片路径 208 | 209 | **返回值** 210 | 211 | 返回一个布尔值,如果成功寻找到满足要求的区块,则返回`True`,否则返回`False` 212 | 213 | **注意** 214 | 215 | 置信度阈值为settings配置中的`accuracy`,其余注意事项同[locate](#locate)及[touch](#touch) 216 | 217 |
218 | 219 | ### slide 220 | 221 | 智能模拟滑动,给定向量`vector`,将以随机速度,并取向量起终点附近的某个点为目标起终点进行一次滑动 222 | 223 | **原型** 224 | 225 | ```python 226 | def slide(vector) 227 | ``` 228 | **参数解释** 229 | 230 | `vector`: ,描述为一个二元组 ((x1, y1), (x2, y2)),其中第一个项是滑动起点,第二个项是滑动终点 231 | 232 | **返回值** 233 | 234 | 无返回 235 | 236 | **注意** 237 | 238 | 点击偏移的范围为settings配置中的`touchPosRange`(单位为像素),滑动所用时长随机范围从`slideMinVer`到`slideMaxVer`(单位为毫秒,滑屏操作不能太快,建议最小值设置在500ms以上) 239 | 240 |
241 | 242 | ### find_pic_slide 243 | 截取屏幕,在截图中寻找`target`图片,找到满足置信度要求的且置信度最高的区块,在其范围内随机选取一点作为滑动起点,并以`pos`为滑动终点进行一次智能模拟滑动 244 | 245 | **原型** 246 | 247 | ```python 248 | def find_pic_slide(target,pos) 249 | ``` 250 | **参数解释** 251 | 252 | `target`: 欲寻找的图片路径 253 | `pos`: 滑动终止位置,描述为一个二元组 (x, y) 254 | 255 | **返回值** 256 | 257 | 返回一个布尔值,如果成功寻找到满足要求的区块,则返回`True`,否则返回`False` 258 | 259 | **注意** 260 | 261 | 置信度阈值为settings配置中的`accuracy`,其余注意事项同[locate](#locate)及[slide](#slide) 262 | 263 |
264 | 265 | ## ImageProc-图片处理类 266 | 引入 267 | ```python 268 | import ImageProc 269 | ``` 270 | 271 |
272 | 273 | ### locate 274 | 从`source`图片中寻找`wanted`图片所在的位置,返回满足置信度大于`accuracy`的要求时,置信度最大的区块的左上角坐标 275 | 276 | **原型** 277 | 278 | ```python 279 | def locate(source, wanted, accuracy=0.90) 280 | ``` 281 | **参数解释** 282 | 283 | `source`: 原图片路径,被查找的图片 284 | 285 | `wanted`: 欲查找的图片路径 286 | 287 | `accuracy`: 置信度阈值,可空,默认为0.9;置信度阈值越大,匹配结果可信度越高 288 | 289 | **返回值** 290 | 291 | 返回一个点坐标 (x,y),当没有任何满足要求的结果时,返回None 292 | 293 | **注意** 294 | 295 | 此方法不会改变欲查找的图片的大小,而是直接去比对,因此如果存在被查找图片中找不到欲识别图片的情况,请先检查分辨率是否正确,然后再调节置信度阈值以达到效果 296 | 297 |
298 | 299 | ### locate_all 300 | 从`source`图片中寻找`wanted`图片所在的位置,返回满足置信度大于`accuracy`的要求的所有区块的左上角坐标,对识别到的邻近点自动去重 301 | 302 | **原型** 303 | 304 | ```python 305 | def locate_all(source, wanted, accuracy=0.90) 306 | ``` 307 | **参数解释** 308 | 309 | `source`: 原图片路径,被查找的图片 310 | 311 | `wanted`: 欲查找的图片路径 312 | 313 | `accuracy`: 置信度阈值,可空,默认为0.9;置信度阈值越大,匹配结果可信度越高 314 | 315 | **返回值** 316 | 317 | 返回一个点坐标数组 \[(x1,y1), (x2,y2), ...\],当没有任何满足要求的结果时,返回一个空数组 318 | 319 | **注意** 320 | 321 | 此方法不会改变欲查找的图片的大小,而是直接去比对,因此如果存在被查找图片中找不到欲识别图片的情况,请先检查分辨率是否正确,然后再调节置信度阈值以达到效果;针对某个被识别到的点坐标,如果下一个被识别到的点坐标在其为中心,半径为15个像素的范围内,将会被自动去重,不记录到结果中 322 | 323 |
324 | 325 | ### centerOfTouchArea 326 | 给定目标尺寸大小`wantedSize`和目标左上角顶点坐标`topLeftPos`,返回目标中心的坐标 327 | 328 | **原型** 329 | 330 | ```python 331 | def centerOfTouchArea(wantedSize, topLeftPos) 332 | ``` 333 | **参数解释** 334 | 335 | `wantedSize`: 目标尺寸大小,描述为一个三元组 (h_src, w_src, tongdao) 分别代表高,宽,通道数,通道数在本函数计算中不需要,可给任意值;一般这个参数传 img.shape 即可(shape方法为cv2计算图片尺寸的方法) 336 | 337 | `topLeftPos`: 目标左上角顶点坐标,描述为一个二元组 (x, y) 338 | 339 | **返回值** 340 | 341 | 返回一个点坐标 (x,y) 342 | 343 | **注意** 344 | 345 | 无 346 | 347 |
348 | 349 | ## ADBHelper-ADB助手类 350 | 351 | 引入 352 | ```python 353 | import ADBHelper 354 | ``` 355 | 356 | > 注意:首次调用 ADBHelper 中的方法时,由于ADB要启动守护进程,因此可能会慢一些 357 | 358 |
359 | 360 | ### getDevicesList 361 | 调用ADB命令,获取连接到当前计算机的安卓设备(支持模拟器)列表,返回一个数组,其中每一个项为一个安卓设备的deviceID 362 | 363 | **原型** 364 | 365 | ```python 366 | def getDevicesList() 367 | ``` 368 | **参数解释** 369 | 370 | 无入参 371 | 372 | **返回值** 373 | 374 | 返回一个文本数组 \["did1", "did2", ...\] 375 | 376 | **注意** 377 | 378 | 如果获取不到,请先检查设备有没有打开调试模式,并信任所连接的计算机;对于部分模拟器而言,获取到的did是一个`ip地址+端口`的形式 379 | 380 |
381 | 382 | ### killADBServer 383 | 调用ADB命令,杀掉当前ADB的守护进程,常用于ADB的重启 384 | 385 | **原型** 386 | 387 | ```python 388 | def killADBServer() 389 | ``` 390 | **参数解释** 391 | 392 | 无入参 393 | 394 | **返回值** 395 | 396 | 无返回 397 | 398 | **注意** 399 | 400 | 无 401 | 402 |
403 | 404 | ### screenCapture 405 | 给定设备ID`deviceID`和截图保存路径`capPath`,对指定设备进行一次屏幕截图 406 | 407 | **原型** 408 | 409 | ```python 410 | def screenCapture(deviceID, capPath) 411 | ``` 412 | **参数解释** 413 | 414 | `deviceID`: 设备ID,可以通过`getDevicesList()`方法获取 415 | 416 | `capPath`: 截图保存路径,截图完成后保存到本地的路径 417 | 418 | **返回值** 419 | 420 | 无返回 421 | 422 | **注意** 423 | 424 | 无 425 | 426 |
427 | 428 | ### touch 429 | 给定设备ID`deviceID`和点击位置`pos`,对指定设备的指定点击位置进行一次模拟点击的操作 430 | 431 | **原型** 432 | 433 | ```python 434 | def touch(deviceID, pos) 435 | ``` 436 | **参数解释** 437 | 438 | `deviceID`: 设备ID,可以通过`getDevicesList()`方法获取 439 | 440 | `pos`: 点击位置,描述为一个二元组 (x, y) 441 | 442 | **返回值** 443 | 444 | 无返回 445 | 446 | **注意** 447 | 448 | 无 449 | 450 |
451 | 452 | ### slide 453 | 给定设备ID`deviceID`、滑动起始位置`posStart`、滑动终止位置`posStop`和滑动所需时间`time`(单位为毫秒),对指定设备进行一次模拟滑动:花费`time`毫秒的时间从`posStart`滑动到`posStop` 454 | 455 | **原型** 456 | 457 | ```python 458 | def slide(deviceID, posStart, posStop, time) 459 | ``` 460 | **参数解释** 461 | 462 | `deviceID`: 设备ID,可以通过`getDevicesList()`方法获取 463 | 464 | `posStart`: 滑动起始位置,描述为一个二元组 (x, y) 465 | 466 | `posStop`: 滑动终止位置,描述为一个二元组 (x, y) 467 | 468 | `time`: 滑动所需时间,单位为毫秒 469 | 470 | **返回值** 471 | 472 | 无返回 473 | 474 | **注意** 475 | 476 | 无 477 | 478 |
479 | 480 | ### longTouch 481 | 给定设备ID`deviceID`、点击位置`pos`和长按时间`time`(单位为毫秒),对指定设备的指定点击位置进行一次模拟长按的操作 482 | 483 | **原型** 484 | 485 | ```python 486 | def longTouch(deviceID, pos, time) 487 | ``` 488 | **参数解释** 489 | 490 | `deviceID`: 设备ID,可以通过`getDevicesList()`方法获取 491 | 492 | `pos`: 长按位置,描述为一个二元组 (x, y) 493 | 494 | `time`: 长按时间,单位为毫秒 495 | 496 | **返回值** 497 | 498 | 无返回 499 | 500 | **注意** 501 | 502 | 无 503 | 504 |
505 | -------------------------------------------------------------------------------- /ImageProc.py: -------------------------------------------------------------------------------- 1 | import cv2, numpy 2 | 3 | # 从source图片中查找wanted图片所在的位置,当置信度大于accuracy时返回找到的最大置信度位置的左上角坐标 4 | def locate(source, wanted, accuracy=0.90): 5 | screen_cv2 = cv2.imread(source) 6 | wanted_cv2 = cv2.imread(wanted) 7 | 8 | result = cv2.matchTemplate(screen_cv2, wanted_cv2, cv2.TM_CCOEFF_NORMED) 9 | min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) 10 | 11 | if max_val >= accuracy: 12 | return max_loc 13 | else: 14 | return None 15 | 16 | # 从source图片中查找wanted图片所在的位置,当置信度大于accuracy时返回找到的所有位置的左上角坐标(自动去重) 17 | def locate_all(source, wanted, accuracy=0.90): 18 | loc_pos = [] 19 | screen_cv2 = cv2.imread(source) 20 | wanted_cv2 = cv2.imread(wanted) 21 | 22 | result = cv2.matchTemplate(screen_cv2, wanted_cv2, cv2.TM_CCOEFF_NORMED) 23 | location = numpy.where(result >= accuracy) 24 | 25 | ex, ey = 0, 0 26 | for pt in zip(*location[::-1]): 27 | x = pt[0] 28 | y = pt[1] 29 | 30 | if (x - ex) + (y - ey) < 15: # 去掉邻近重复的点 31 | continue 32 | ex, ey = x, y 33 | 34 | loc_pos.append([int(x), int(y)]) 35 | 36 | 37 | return loc_pos 38 | 39 | # 给定目标尺寸大小和目标左上角顶点坐标,即可给出目标中心的坐标 40 | def centerOfTouchArea(wantedSize, topLeftPos): 41 | tlx, tly = topLeftPos 42 | h_src, w_src, tongdao = wantedSize 43 | if tlx < 0 or tly < 0 or w_src <=0 or h_src <= 0: 44 | return None 45 | return (tlx + w_src/2, tly + h_src/2) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | RaphaelScriptHelper 拉斐尔脚本助手 3 |
4 |

5 | 6 |

7 | ~轻松编写更智能的游戏脚本~ 8 |
9 |
10 |

11 | 12 | > **警告:本项目仅作学习交流用途,禁止将本项目中的任何脚本、工具用于任何非法用途,如果使用本项目中的任何脚本、工具造成游戏被封号、处罚等一切损失需自行承担后果,本项目对此概不负责!** 13 | 14 | > 请注意:使用脚本会在一定程度上影响游戏体验,请根据自身需求使用。 15 | 16 | 17 | 本项目的命名取自轻小说[《关于我转生变成史莱姆这档事》](https://mzh.moegirl.org.cn/%E5%85%B3%E4%BA%8E%E6%88%91%E8%BD%AC%E7%94%9F%E5%8F%98%E6%88%90%E5%8F%B2%E8%8E%B1%E5%A7%86%E8%BF%99%E6%A1%A3%E4%BA%8B)中主角 利姆鲁·特恩佩斯特リムル・テンペスト 进化成魔王后持有的究极技能之一 智慧之王拉斐尔 ,拉斐尔具有接管主角意识并代打的能力,这很像使用本项目来帮助我们完成一些重复繁琐的流程。~~所以说本项目也可以帮助你成为萌王?(误)~~ 18 | 19 | [演示视频](https://www.bilibili.com/video/BV1RZ4y1f76m/) 20 | 21 | ## 简介 22 | RaphaelScriptHelper (拉斐尔脚本助手/拉斐尔模块) 是一个基于openCV图像处理、OCR、ADB等技术的Python框架,可以让脚本编写者在短时间内编写出一个实用的游戏脚本,适用于可以支持脚本运行的各种系统及模拟器,支持诸如识图、点击、按键、滑动、文字识别、随机操作等功能。一个实用的例子是几行代码编写一个简单的明日方舟日常刷图脚本(见演示视频)。 23 | 24 | ## 目前可以直接使用的脚本 25 | * [明日方舟集成战略模式(肉鸽)脚本](https://github.com/hanmin0822/RaphaelScriptHelper/blob/master/Arknights/ArknightsRoguelike.py) 26 | * [明日方舟日常刷图脚本](https://github.com/hanmin0822/RaphaelScriptHelper/blob/master/Arknights/Arknights_default.py) 27 | 28 | > 请注意:请仔细阅读各脚本最上方的注释说明再使用。 29 | 30 | 欢迎大家编写更多好用的脚本并提PR到本项目中来哦~ 31 | 32 | ## 环境配置 33 | 34 | Python 3.7 35 | 36 | 使用到的Python库 37 | 38 | ``` 39 | numpy=1.19.5 40 | opencv-python=4.5.1.48 41 | ``` 42 | 43 | ADB套件请在[这里](https://pan.baidu.com/s/15dpjviyIHezaT56knux2xQ?pwd=mr5p)下载并解压放置到脚本框架根目录 44 | 45 | ## 快速上手 46 | 使用一行代码来引入 RaphaelScriptHelper 47 | ```python 48 | import RaphaelScriptHelper as rsh 49 | ``` 50 | 使用一至两行代码来指定脚本运行的设备 51 | ```python 52 | rsh.deviceType = 1 #0为PC环境,1为安卓设备 53 | rsh.deviceID = "安卓设备ID" #如使用安卓设备,请填写 54 | ``` 55 | 使用一行代码来模拟点击屏幕,会自动在一定范围内随机偏移、随机时长(防作弊检测)来点击一次 56 | ```python 57 | rsh.touch(pos) 58 | ``` 59 | 使用一行代码来模拟点击屏幕上指定区域,先使用[选择工具](https://github.com/hanmin0822/RaphaelScriptHelper/blob/master/FunctionDoc.md#%E6%A0%87%E7%82%B9%E6%88%AA%E5%8F%96%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E)来指定区域并保存,方便后续识别,会自动在识别区域内任意点、随机时长(防作弊检测)来点击一次 60 | ```python 61 | rsh.find_pic_touch("./test.png") 62 | ``` 63 | ## 详细功能 64 | 请参阅[详细功能使用手册](https://github.com/hanmin0822/RaphaelScriptHelper/blob/master/FunctionDoc.md) 65 | 66 | ## 注意事项 67 | * 本项目仅作学习交流用途,禁止将本项目中的任何脚本、工具用于任何非法用途,如果使用本项目中的工具造成游戏被封号、处罚等概不负责 68 | * 针对识图功能,不会改变欲识别图片的大小,而是直接用这张图片去比对,因此如果存在被查找图片中找不到欲识别图片的情况,请先检查分辨率是否正确,然后再调节可信度阈值以达到效果 69 | 70 | ## TODO 71 | * 支持PC端截图、键盘鼠标智能模拟 72 | * 支持在线/离线OCR 73 | * 增加GUI界面支持可视化编写流程 74 | -------------------------------------------------------------------------------- /RaphaelScriptHelper.py: -------------------------------------------------------------------------------- 1 | import ImageProc, ADBHelper, random, time, cv2 2 | import settings as st 3 | 4 | deviceType = 1 5 | deviceID = "" 6 | 7 | def random_delay(): 8 | t = random.uniform(st.randomDelayMin, st.randomDelayMax) 9 | print("【随机延时】将随机延时 {0} 秒".format(t)) 10 | time.sleep(t) 11 | 12 | def delay(t): 13 | print("【主动延时】延时 {0} 秒".format(t)) 14 | time.sleep(t) 15 | 16 | def random_pos(pos): 17 | x, y = pos 18 | rand = random.randint(1, 10000) 19 | if rand % 2 == 0: 20 | x = x + random.randint(0, st.touchPosRange) 21 | else: 22 | x = x - random.randint(0, st.touchPosRange) 23 | 24 | rand = random.randint(1, 10000) 25 | if rand % 2 == 0: 26 | y = y + random.randint(0, st.touchPosRange) 27 | else: 28 | y = y - random.randint(0, st.touchPosRange) 29 | 30 | return (x, y) 31 | 32 | # 智能模拟点击某个点,将会随机点击以这个点为中心一定范围内的某个点,并随机按下时长 33 | def touch(pos): 34 | randTime = random.randint(0, st.touchDelayRange) 35 | _pos = random_pos(pos) 36 | print("【模拟点击】点击坐标 {0} {1} 毫秒".format(_pos, randTime)) 37 | if randTime < 10: 38 | ADBHelper.touch(deviceID, _pos) 39 | else: 40 | ADBHelper.longTouch(deviceID, _pos, randTime) 41 | 42 | # 智能模拟滑屏,给定起始点和终点的二元组,模拟一次随机智能滑屏 43 | def slide(vector): 44 | startPos, stopPos = vector 45 | _startPos = random_pos(startPos) 46 | _stopPos = random_pos(stopPos) 47 | randTime = random.randint(st.slideMinVer, st.slideMaxVer) 48 | print("【模拟滑屏】使用 {0} 毫秒从坐标 {1} 滑动到坐标 {2}".format(randTime, _startPos, _stopPos)) 49 | ADBHelper.slide(deviceID, _startPos, _stopPos, randTime) 50 | 51 | # 截屏,识图,返回坐标 52 | def find_pic(target, returnCenter = False): 53 | ADBHelper.screenCapture(deviceID, st.cache_path + "screenCap.png") 54 | time.sleep(0.1) 55 | if returnCenter == True: 56 | leftTopPos = ImageProc.locate(st.cache_path + "screenCap.png", target, st.accuracy) 57 | img = cv2.imread(target) 58 | centerPos = ImageProc.centerOfTouchArea(img.shape, leftTopPos) 59 | return centerPos 60 | else: 61 | leftTopPos = ImageProc.locate(st.cache_path + "screenCap.png", target, st.accuracy) 62 | return leftTopPos 63 | 64 | # 截屏,识图,返回所有坐标 65 | def find_pic_all(target): 66 | ADBHelper.screenCapture(deviceID, st.cache_path + "screenCap.png") 67 | time.sleep(0.1) 68 | leftTopPos = ImageProc.locate_all(st.cache_path + "screenCap.png", target, st.accuracy) 69 | return leftTopPos 70 | 71 | # 寻找目标区块并在其范围内随机点击 72 | def find_pic_touch(target): 73 | leftTopPos = find_pic(target) 74 | if leftTopPos is None: 75 | print("【识图】识别 {0} 失败".format(target)) 76 | return False 77 | print("【识图】识别 {0} 成功,图块左上角坐标 {1}".format(target, leftTopPos)) 78 | img = cv2.imread(target) 79 | tlx, tly = leftTopPos 80 | h_src, w_src, tongdao = img.shape 81 | x = random.randint(tlx, tlx + w_src) 82 | y = random.randint(tly, tly + h_src) 83 | touch((x, y)) 84 | return True 85 | 86 | # 寻找目标区块并将其拖动到某个位置 87 | def find_pic_slide(target,pos): 88 | leftTopPos = find_pic(target) 89 | if leftTopPos is None: 90 | print("【识图】识别 {0} 失败".format(target)) 91 | return False 92 | print("【识图】识别 {0} 成功,图块左上角坐标 {1}".format(target, leftTopPos)) 93 | img = cv2.imread(target) 94 | centerPos = ImageProc.centerOfTouchArea(img.shape,leftTopPos) 95 | slide((centerPos, pos)) 96 | return True -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | #图片匹配置信度,0-1之间,默认0.93,如果匹配出错误目标则提高置信度,如果要模糊匹配或高置信度无法匹配则降低置信度 2 | accuracy = 0.93 3 | 4 | #缓存文件存放地址,以/结尾 5 | cache_path = './cache/' 6 | 7 | #随机延时范围[randomDelayMin,randomDelayMax],单位秒 8 | randomDelayMin = 1 9 | randomDelayMax = 5 10 | 11 | #点击偏移范围,[0,x] 12 | touchPosRange = 8 13 | 14 | #点击延迟范围,[0,x] 15 | touchDelayRange = 30 16 | 17 | #滑屏所需时长范围[slideMinVer,slideMaxVer],单位毫秒 (滑屏操作不能太快,建议最小值设置在500ms以上) 18 | slideMinVer = 500 19 | slideMaxVer = 3000 20 | --------------------------------------------------------------------------------