从零开始用python实现部落冲突自动化脚本【第1篇】一些基础自动化操作的简要介绍

前篇文章中,我们介绍了构建一个自动化游戏脚本的基本思路。这一篇中,我们将简要介绍一些基础的自动化操作。在了解它们以后,读者就可以通过组织这些方法,自己构建一个简单的基本自动化逻辑了。


模拟点击

Python自动化操作当然离不开pyautogui库。我们先安装pyautogui。

pip install pyautogui

随后在程序中导入

import pyautogui

在脚本的开发中,我们主要用到的函数有pyautogui.click(),这是构建自动化脚本最基础的函数,可以用来点击需要的坐标点位。例如:

pyautogui.click(500,800)

我们让程序点击了x=500,y=800的位置。你可以分别传入x和y两个参数,也可以把(x, y)的元组作为一个参数传入click函数,这两者都是合法的。注意:显示器的坐标原点是显示器左上角顶点。水平向右为x轴正方向,竖直向下为y轴正方向。最大坐标取决于你显示器的分辨率,我们可以用pyautogui.size()来获得它(记住这个方法,后面我们会再次用到)。例如我的显示器是3840*2160分辨率,那么

print(pyautogui.size())

输出的信息为

Size(width=3840, height=2160)

好了,在掌握了click函数以后,如果想要实现一系列有且仅有固定位置、固定顺序的点击操作,你就已经可以使用click函数和time.sleep()来实现了——不过,用连点器也可以实现完全相同的效果。

pyautogui库除了click函数,也有很多功能相类似的函数,它们都是为了实现自动化而设计的。例如 moveTo() 控制鼠标移动,scroll() 实现滚轮滚动,dragRel() 实现相对拖拽,以及操控键盘按键的keyUp()keyDown()。在此不一一列举,实际开发时,可查阅pyautogui官方文档/源码,或向大语言模型AI询问,进而得到你所需要的功能。


坐标从何而来?

现在,我们会面临一个直接的问题:坐标从何而来?我们介绍了指定鼠标指针点击某点的click方法,我们又应该如何获得需要的某点的坐标?除了前文所述那种“仅包含固定位置、固定顺序的点击操作”,在不同分辨率的设备上,是否就意味着我们需要重新去测得每一个点的坐标?

首先回答第一个问题。

print(pyautogui.position())

pyautogui的position()函数可以获取鼠标指针当前所在位置的坐标,我们只需要再加上sleep函数,就能确保我们将指针移动到所需位置上后,得到该点的坐标。

例如,我们要获取打开部落战页面的坐标:

我们只需要在time.sleep的时间内,把鼠标指针移动到图标上,等待程序测算并输出,就可以了。

当然,这还不够。在不同分辨率的设备上,如果某些对象(例如特定的按钮)的位置是以近似等比例缩放来放置的,那么我们可以记录下这个目标对象的相对坐标,作为静态数据写入代码中,再在每次运行时计算它们的真实坐标——在大部分情况下,这种方法应该是表现良好的。计算相对坐标是个自然的想法,且在第0篇中亦有所介绍,有阅读过的读者一定不陌生。

  • P.S.虽然《部落冲突》游戏内大部分图标的相对位置都不受分辨率、DPI影响,但我还是建议你不要在极端情况下使用!

相对坐标

相对坐标=(真实坐标/分辨率)

我们分别将x坐标除以显示器的宽(pyautogui.size()返回的第0个参数),将y坐标除以显示器的高(pyautogui.size()返回的第1个参数),就能得到某一点的相对坐标。

# 计算某点的相对坐标
time.sleep(3)  # 确保鼠标在后续代码执行前移动到所需位置上
width, height = pyautogui.size()
x, y = pyautogui.position()
relative_x = x / width
relative_y = y / height
print(relative_x, relative_y)

当然,更进一步,我们可以直接一步到位写成:循环监听键盘,每按下一次空格键,就计算并输出当前坐标的相对值,按下esc则退出程序。同时将输出写成特定格式,以便后续直接填入存储所有程序运行需要的相对坐标的config.py文件中。代码如下:

# get_location.py
# 导入必要的模块
import keyboard  # 记得pip安装该模块
import pyautogui

x_size, y_size = pyautogui.size()


# 定义一个函数,当按下空格键时,打印当前鼠标指针的坐标
def print_mouse_position():
    # 获取鼠标指针的坐标
    x, y = pyautogui.position()
    x_relative, y_relative = x / x_size, y / y_size
    # 打印相对坐标
    print(f"(round(x * {x_relative}), round(y * {y_relative})),")  # 这里是便于后续填入config.py文件的存储坐标字典locations中,我们随后便可以看到


# 注册一个监听器,当按下空格键时,调用函数
keyboard.on_press_key("space", lambda _: print_mouse_position())

# 保持监听状态,直到按下esc键退出
keyboard.wait("esc")

存储程序运行所需的相对坐标如下:

# config.py
x, y = pyautogui.size()  # 获取屏幕分辨率,在后续运行时逐个计算乘上相对坐标后的实际坐标值

locations = \
    {
        "settings":  # 游戏设置相关的坐标
            {
                "open_settings_page": (round(x * 0.96015625), round(y * 0.724537037037037)),
                "more_settings": (round(x * 0.69921875), round(y * 0.899537037037037)),
                "snow_point": (round(x * 0.7140625), round(y * 0.775462962962963)),
            },
        "train_page":  # 改版后,coc已经取消了造兵时间,所有的部队训练都在一个页面下即时修改
            {
                "delete_1": (round(x * 0.9651041666666667), round(y * 0.2351851851851852)),
                "delete_2": (round(x * 0.7651041666666667), round(y * 0.48055555555555557)),
                "delete_3": (round(x * 0.965625), round(y * 0.4824074074074074)),
                "delete_4": (round(x * 0.7651041666666667), round(y * 0.7444444444444445)),
                "edit_troop": (round(x * 0.6911458333333333), round(y * 0.3402777777777778)),
                "edit_spell": (round(x * 0.6002604166666666), round(y * 0.5856481481481481)),
                "exit": (round(x * 0.96484375), round(y * 0.08935185185185185)),
            },
        "attack":  # 进攻页面的一些坐标
            {
                "attack_button": (round(x * 0.0625), round(y * 0.8796296296296296)),
                "find_a_match": (round(x * 0.7708333333333333), round(y * 0.6055555555555556)),
                "troop_1": (round(x * 0.1075520833333333), round(y * 0.9055555555555556)),
                "troop_2": (round(x * 0.1817708333333333), round(y * 0.9055555555555556)),
                "troop_3": (round(x * 0.2666666666666667), round(y * 0.9055555555555556)),
                "troop_4": (round(x * 0.3455729166666667), round(y * 0.9055555555555556)),
                "troop_5": (round(x * 0.4325520833333333), round(y * 0.9055555555555556)),
                "troop_6": (round(x * 0.5078125), round(y * 0.9055555555555556)),
                "troop_7": (round(x * 0.58203125), round(y * 0.9055555555555556)),
                "troop_8": (round(x * 0.6596354166666667), round(y * 0.9055555555555556)),
                "troop_9": (round(x * 0.73203125), round(y * 0.9055555555555556)),
                "troop_10": (round(x * 0.81015625), round(y * 0.9055555555555556)),

                "before_scroll":  # 在游戏进攻时,我们会先移动到左上,后续再往下滑动(利用pyautogui.scroll())因此这部分是scroll前的坐标
                    {
                        "village_up": (round(x * 0.5299479166666667), round(y * 0.0796296296296296)),
                        "village_left": (round(x * 0.1364583333333333), round(y * 0.6055555555555556)),
                        "village_right": (round(x * 0.9263020833333333), round(y * 0.6078703703703704))
                    },
                "after_scroll":  # scroll后的坐标,记录的是村庄的几个端点,方便下兵
                    {
                        "village_down_left": (round(x * 0.4869791666666667), round(y * 0.7967592592592593)),
                        "village_down_right": (round(x * 0.55390625), round(y * 0.8023148148148148)),
                        "village_left": (round(x * 0.12421875), round(y * 0.2856481481481481)),
                        "village_right": (round(x * 0.9481770833333333), round(y * 0.2662037037037037))
                    },
                "spell":  # 曾经记录的几个法术施放位置,当然后续已经荒废了,在此留作示例
                    {
                        "row_1_start": (round(x * 0.5479166666666667), round(y * 0.2625)),
                        "row_1_end": (round(x * 0.2755208333333333), round(y * 0.6273148148148148)),
                        "row_2_start": (round(x * 0.55625), round(y * 0.4027777777777778)),
                        "row_2_end":  (round(x * 0.37395833333333334), round(y * 0.6430555555555556)),
                        "row_3_start": (round(x * 0.6341145833333334), round(y * 0.47638888888888886)),
                        "row_3_end": (round(x * 0.42630208333333336), round(y * 0.7532407407407408)),
                    },
                "resource_region": (round(x * 0.05), round(y * 0.135),
                                    round(x * 0.1041666666666667), round(y * 0.1574074074074074)),
                "rgb_point": (round(x * 0.5), round(y * 0.82)),
                "next": (round(x * 0.9125), round(y * 0.7222222222222222)),
                "return": (round(x * 0.5), round(y * 0.8888888888888889)),
            },
        "clan":  # 部落页面的几个坐标
            {
                "open_chatting_page": (round(x * 0.01875), round(y * 0.4305555555555556)),
                "request_donation": (round(x * 0.05), round(y * 0.5185185185185185)),
                "edit_donation": (round(x * 0.3328125), round(y * 0.5185185185185185)),
            },
        "clan_war":  # 部落战的几个坐标,不过……已经没用了?
            {
                "start": (round(x * 0.35572916666666665), round(y * 0.3907407407407407)),
                "end": (round(x * 0.75), round(y * 0.9398148148148148)),
            },
        "donation":  # 捐兵相关的一些坐标
            {
                "troop_drag_left": (round(x * 0.4359375), round(y * 0.30277777777777776)),
                "troop_drag_right": (round(x * 0.8658854166666666), round(y * 0.30787037037037035)),
                "troop_area": (round(x * 0.615625), round(y * 0.39675925925925926)),
                "spell_area": (round(x * 0.6161458333333333), round(y * 0.6935185185185185)),
            },
        "reload": (round(x * 0.296875), round(y * 0.5828703703703704)),  # 长时间无操作,重新进入游戏的按钮坐标
    }
  • 注:实际上,在上述这么多的坐标数据中,有些已逐渐被更优的cv方法取代了,比如我们后面很快就要介绍的cv2.matchTemplate方法。

根据相对值×实际值的思路,以此类推,除了坐标可以使用一个相对的比例,有时需要在屏幕上拖动/滚动一定距离,也可以使用一个比例,运行时乘上真实的分辨率数据。这是一个很实用的技巧。

需要注意的是,上述方法仅用于游戏运行中那些我们能够提前知道出现时机、出现位置的固定坐标。对于会位置会变动、或出现时机不固定的对象,我们同样需要更复杂的cv方法,这将会在后续系列教程中讲到(点个关注,谢谢喵(୨୧• ᴗ •͈)◞︎ᶫᵒᵛᵉ ♡)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值