python-游戏自动化(三)(实战-豆腐女孩)

news2024/11/25 12:52:54

前提准备

特别注意:
        本节教程所演示的模拟器分辨率设置为 720x1080(手机版),电脑分辨率设置大720x1080并且没有设置放大。

        今天的课程开始之前我们来回顾一下昨天所学的知识内容,因为今天要学的内容和昨天内容有着紧密的联系。昨天的课程主要讲解了计算机的图像相关基础知识,图像坐标系,图像灰度化,图像二值化,使用OpenCV进行图像匹配、图像切割,还尝试了对图像进行轮廓处理。

        在前面的学习中,我们已经掌握了使用OpenCV进行图片处理的相关知识,之所以做了那么多的铺垫,目的就是为这节课服务的。
        写出这个游戏的自动化脚本中,图像的处理占了超过一半的工作量,这也是模拟类自动化脚本的常见情况。
        图像处理的目的是什么? 是把图像识别成简单的数据类型或数据结构,以便于使用算法来解决问题。
        大家来想一想,在这个 豆腐女孩 的游戏中,规则很简单,开始后,只要不断识别游戏画面,看到有豆腐过来,就判断豆腐离女孩的距离,如果距离在某个数值范围内,点击鼠标起跳就可以了。

        通过观察游戏界面,豆腐出现的区域是不变的,所以我们只需要关注中间那一块区域就了,通过坐标把这一块区域裁剪出来,一方面可以减少图像处理的计算量,另一方面可以减少不必要的干扰,简化处理的逻辑。

        接下来对这块目标区域的图像进行处理,先装成灰度图,再找出一个合适的阈值,变成二值化图像:

        对于这样背景和前景分明的图像,就可以非常轻松的找出轮廓了,但不是所有的轮廓都是符合条件的,我们只需要找到面积最大的轮廓的边框,就可以确认豆腐的位置。

        上图中的绿色线条方框就是找出来的豆腐的位置,而小女孩是居中显示的,所以得到豆腐的边,再计算两个物体的距离,然后根据豆腐的移动速度,就可以算出起跳前的等待时间。

        通过OpenCV这个强大的图像处理库,就可以真正从目标识别的角度去编写逻辑,跟那些基于找图找色的按键精灵、易语言写的那些脚本不一样,这是基于图像识别的方法,使用分层思想来设计模型,帮助大家真正提高从底层逻辑去建立起解决问题的思维,这在大家以后不光是学编程,写python,做脚本时非常有用,对于生活中、学习中、工作中的复杂问题的解决,同样可以有很大的帮助。
        人跟人的本质区别,在于思维的层次差别。

        有了上面整理出来的思路,接下来老师就带领同学们来一步步来实现我们的思路。 

        抽离出一个模拟器的模块,负责对窗口的基本操作,比如激活、点击、截图等功能,到时作为模块导入,可以很方便的调用这里面的功能,从而简化逻辑,并且达到高内聚,低耦合的设计目标。

模拟器模块

import time
import win32api
import win32con
import win32gui
import win32ui
import numpy as np
import cv2 as cv

def activate_window():
    hwnd = win32gui.FindWindow(None, '雷电模拟器')  # 注意引号使用英文
    if hwnd == 0:
        print('未找到游戏窗口')
        return 0
    tup = win32gui.GetWindowPlacement(hwnd)  # 获取窗口布局
    if tup[1] != win32con.SW_SHOWNORMAL:  # 非正常尺寸(已最小化或最大化)
        win32gui.SendMessage(hwnd, win32con.WM_SYSCOMMAND, win32con.SC_RESTORE, 0)
        # 恢复窗口大小
        time.sleep(0.1)
    if win32gui.GetForegroundWindow() != hwnd:
        win32gui.SetForegroundWindow(hwnd)  # 窗口前置
        time.sleep(0.3)
    return hwnd

def click(window_coords):
    #:param window_coords: 元组(x, y) x为横坐标,y为纵坐标
    #:return: 返回点击执行结果

    window_handle = activate_window() #得到窗口句柄
    if window_handle == 0:
        print('模拟器窗口不存在,点击失败')
        return False
    desktop_coords = win32gui.ClientToScreen(window_handle, window_coords)  # 窗口坐标换算成桌面坐标
    win32api.SetCursorPos(desktop_coords)  # 设置鼠标位置
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)  # 鼠标按下
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)  # 鼠标弹起
    return True

def _capture_window(hwnd):
    desktop = win32gui.GetDesktopWindow()
    dc = win32gui.GetWindowDC(desktop)
    mfc_dc = win32ui.CreateDCFromHandle(dc)
    save_dc = mfc_dc.CreateCompatibleDC()
    save_bit_map = win32ui.CreateBitmap()
    left, top, right, bottom = win32gui.GetWindowRect(hwnd)
    w, h = right - left, bottom - top
    save_bit_map.CreateCompatibleBitmap(mfc_dc, w, h)
    save_dc.SelectObject(save_bit_map)
    save_dc.BitBlt((0, 0), (w, h), mfc_dc, (left, top), win32con.SRCCOPY)
    signed_ints_array = save_bit_map.GetBitmapBits(True)
    im_opencv = np.frombuffer(signed_ints_array, dtype='uint8')
    im_opencv.shape = (h, w, 4)
    save_dc.DeleteDC()
    win32gui.DeleteObject(save_bit_map.GetHandle())
    win32gui.ReleaseDC(hwnd, dc)  # 修正此行
    return im_opencv

def screenshot():
    window_handle = activate_window()
    if window_handle == 0:
        print('模拟器窗口不存在,截图失败')
        return None
    return _capture_window(window_handle)

if __name__ == '__main__':
    activate_window()
    time.sleep(0.5)
    start = time.time()
    img = screenshot()
    end = time.time()
    print('花费', end - start, '秒')
    if img is not None:
        cv.imwrite(r'E:\home.bmp', img)
        h, w = img.shape[:2]
        detection_result = '检测通过!' if w == 762 and h == 1316 else '检测未通过!请修改模拟器分辨率'
        print(f'期望截图的分辨率为:762*1316 实际分辨率为{h}*{w}')
        click((300, 650))
        time.sleep(1.2)
        tofu = screenshot()
        if tofu is not None:
            cv.imwrite(r'E:\tofu.bmp', tofu)

游戏控制模块

在这个游戏控制模块中,主要完成图像的处理和目标的检测,并通过简单的算法计算出操作间隔,然后调用 模拟器模块 中的相应函数来实现操作。
来看看源码:

import time
import cv2 as cv
import numpy as np
import 模拟器

开始按钮图片 = cv.imread(r"E:\bbbb.bmp", 0)  # 开始按钮


def 是彩色图片(图片):
    if 图片 is None:
        print('是彩色图片() 参数错误:', '图片为空')
        return False
    return len(图片.shape) > 2


def 查找图片(大图, 小图, 相似度=0.8):
    if 是彩色图片(大图):
        大图 = cv.cvtColor(大图, cv.COLOR_BGR2GRAY)
    if 是彩色图片(小图):
        小图 = cv.cvtColor(小图, cv.COLOR_BGR2GRAY)

    res = cv.matchTemplate(大图, 小图, cv.TM_CCOEFF_NORMED)  # 模板匹配
    loc = np.where(res >= 相似度)  # 过滤结果

    for pt in zip(*loc[::-1]):
        left = int(pt[0])
        top = int(pt[1])
        right = int(pt[0] + 小图.shape[1])
        bottom = int(pt[1] + 小图.shape[0])
        area = ((left, top), (right, bottom))
        return area  # 实际操作区域


def find_cube(灰色截图):
    sw = 720
    关键区域 = 灰色截图[600:720, 1:sw]  # 裁剪出感兴趣的区域
    _, 黑白图像 = cv.threshold(关键区域, 248, 255, cv.THRESH_BINARY)  # 图像二值化
    contours, hierarchy = cv.findContours(黑白图像, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)  # 查找轮廓

    max_area, max_cnt = 0, None  # 设置最大面积和最大轮廓的默认值
    for cnt in contours:
        area = cv.contourArea(cnt)
        if area > 5000 and area > max_area:  # 筛选出轮廓
            max_area = area
            max_cnt = cnt

    if max_cnt is None:
        # print('没有发现豆腐')
        return 0

    x, y, w, h = cv.boundingRect(max_cnt)  # 计算出边框
    center_x = sw / 2  # 中线位置
    cube_x = x + w if x + w < center_x else x  # 取离中线最近的豆腐边线x(豆腐不确定从哪边来)
    dist = abs(center_x - cube_x)  # 豆腐边缘到中线的距离
    return dist

def 跳跃检测():
    for i in range(100):  # 每次跳跃前循环检测画面
        截图 = 模拟器.screenshot()
        截图_灰色 = cv.cvtColor(截图, cv.COLOR_BGR2GRAY)  # 转成灰度图片
        if 查找图片(截图_灰色, 开始按钮图片):
            print('游戏已结束')
            return True

        dist = find_cube(截图_灰色)  # 算出豆腐离中线距离
        if 150< dist < 200:
            print(f'---跳---')
            jump()  # 点击鼠标(跳跃)
            time.sleep(0.1)
            break
    return False

def jump():
    坐标 = (300, 800)
    模拟器.click(坐标)


if __name__ == '__main__':
    截图 = 模拟器.screenshot()



    if 查找图片(截图, 开始按钮图片):
        print('找到开始按钮')
        jump()  # 点击开始
        time.sleep(0.5)
    else:
        print('游戏不在开始画面')
        exit(0)

   # time.sleep(0.6)
    #截图 = 模拟器.screenshot()
   # 截图_灰色 = cv.cvtColor(截图,cv.COLOR_BGR2GRAY)
    #find_cube(截图_灰色)
    #cv.waitKey()
    for i in range(100):  # 最大预计跳跃次数
        if 跳跃检测():
            break

课程总结

虽然这个游戏的玩法比较简单,但是要拿到特别高的分数还是不容易的。在本教程中,霸夫老师也只是以启发大家为目标,教大家去分析如何写出一个自动化游戏教程,程序功能虽然比较简单,但其中涉及的思路分析,图像处理,数据计算、算法优化、设计模式等是很值得大家去细细学习体会的。

课后习题

1.(编程题)在教程的示例代码中继续改进,示例中jump()函数中点击的是固定坐标,将坐标的取值改为查找到的start.bmp图片的中心点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2127303.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CogView-3-Plus:深度解锁智谱AI的图像生成新力量

一、引言&#xff1a;AI助力创意与效率的全面提升 在如今这个瞬息万变的科技时代&#xff0c;AI大模型早就不是实验室里的“神秘武器”&#xff0c;它们已经实实在在地融入到我们的日常工作中了&#xff0c;尤其是在图像生成和内容创作这块儿&#xff0c;简直是效率神器。只要几…

Leetcode3270. 求出数字答案

Every day a Leetcode 题目来源&#xff1a;3270. 求出数字答案 解法1&#xff1a;模拟 按题意模拟。 代码&#xff1a; /** lc appleetcode.cn id3270 langcpp** [3270] 求出数字答案*/// lc codestart class Solution { public:int generateKey(int num1, int num2, int…

基于python+django+vue鲜花商城系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于pythondjangovueMySQL的线…

反射(java)

一、junit单元测试框架 1、单元测试 就是针对最小的功能单元&#xff08;方法&#xff09;&#xff0c;编写测试代码对其进行正确性测试。 之前的是如何进行单元测试的&#xff1f; 有啥问题&#xff1f; 1、只能在ma方法编写测试代码&#xff0c;去调用其他方法进行测试。…

CCS811二氧化碳传感器详解(STM32)

目录 一、介绍 二、传感器原理 1.原理图 2.引脚描述 3.工作原理介绍 三、程序设计 main.c文件 ccs811.h文件 ccs811.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 CCS811模块是一种气体传感器&#xff0c;可以测量环境中TVOC(总挥发性有机物质)浓度和eCO2…

OPPO 全家桶:Find X8/Pro、Pad3 Pro与Enco X3即将10月登场

随着科技的不断发展&#xff0c;智能手机、平板电脑和耳机等电子产品已经成为我们日常生活中不可或缺的一部分。 作为全球知名的科技企业&#xff0c;OPPO一直致力于为消费者提供优质的产品和服务。 近日&#xff0c;有关OPPO全家桶将在10月份“凑齐”的消息引起了广泛关注。…

Elemnt-UI + 递归组件实现后台管理系统左侧菜单

Elemnt-UI 递归组件实现后台管理系统左侧菜单 在 Vue.js 中&#xff0c;允许你编写一个组件来表示一个节点&#xff0c;而这个节点可以包含多个子节点&#xff0c;每个子节点又可以是同样的组件。这种方式使得组件能够处理无限层级的嵌套结构。 应用场景 递归组件非常适合处…

2013年

B D B C D 分支结点是非叶结点 B 47 C A C C D D C A C

2010-2022年各省乡村振兴新质生产力相关变量数据(40+指标)

2010-2022年各省乡村振兴新质生产力相关变量数据&#xff08;40指标&#xff09; 1、时间&#xff1a;2010-2022年 2、来源&#xff1a;统计年鉴、能源统计年鉴、农村统计年鉴、人口和就业统计年鉴、城乡建设统计年鉴以及各省份统计年鉴 3、指标&#xff1a;省份、年份、分地…

信号量(二值信号量和计数信号量)和互斥量

信号量 信号量&#xff08;Semaphore&#xff09; 是一种实现任务间通信的机制&#xff0c; 可以实现任务之间同步或临界资源的互斥访问&#xff0c; 常用于协助一组相互竞争的任务来访问临界资源。 在多任务系统中&#xff0c; 各任务之间需要同步或互斥实现临界资源的保护&a…

图神经网络介绍3

1. 图同构网络&#xff1a;Weisfeiler-Lehman 测试与图神经网络的表达力 本节介绍一个关于图神经网络表达力的经典工作&#xff0c;以及随之产生的另一个重要的模型——图同构网络。图同构问题指的是验证两个图在拓扑结构上是否相同。Weisfeiler-Lehman 测试是一种有效的检验两…

第二期: 第二节 , 逻辑编程 , gpio

1 首先就是 看原理图&#xff1a; 这里有两个 &#xff2c;&#xff25;&#xff24; 核心板的原理图。 可以看到 是这个脚。 &#xff12; 然后就是 查看数据手册。 从 数据手册可以看出 &#xff0c;一共有这么多的 gpio 组&#xff0c; 但是这些 组 是有复用的&#xf…

多文件编程实现链表创建,插入,输出(上)

linklist.c #include "linklist.h" //创建空的链表&#xff0c;为头结点在堆区分配空间 linklist_t *creat_empty_linklist() {linklist_t *head NULL;head (linklist_t *) malloc(sizeof(linknode_t));if(NULL head){printf("malloc is fail!\n");ret…

网格参数的应用和数学基础

引言 对于任意两个拓扑结构相似的表面&#xff0c;可以计算它们之间的一一对应映射。如果其中一个表面由三角形网格表示&#xff0c;那么计算这种映射的问题被称为网格参数化。映射到的表面通常被称为参数域。表面网格与各种域之间的参数化在计算机图形学和几何处理中有广泛的应…

移动WEB开发(第二天)_flex布局

移动WEB开发&#xff08;第二天&#xff09;_flex布局 移动web开发——flex布局1.0传统布局和flex布局对比1.1传统布局1.2 flex布局1.3 建议 2.0 flex布局原理3.0 父项常见属性3.1 flex-direction设置主轴的方向3.2 justify-content 设置主轴上的子元素排列方式3.3 flex-wrap设…

9月美联储决策前哨战——美国CPI数据来袭

随着本周关键CPI数据的即将发布&#xff0c;市场正翘首以待&#xff0c;这将是美联储在9月17日至18日议息会议前获取的最后一块重要经济拼图。鉴于美联储官员已进入传统的政策静默期&#xff0c;8月份的CPI报告无疑将成为交易员们评估未来货币政策走向的重要标尺。 欧洲央行降…

python列表判断是否为空的三种方式

#列表是否为空判断 a[]一&#xff1a; if a:print(not null) else:print(null)二&#xff1a; b len(a) if b 0:print(null) else:print(not null)三&#xff1a; if not a:print(null) else:print(not null)运行结果&#xff1a;

Day9 | Java框架 | SpringBoot

Day9 | Java框架 | SpringBoot SpringBoot简介入门程序概述起步依赖 基础配置配置文件格式&#xff1a;3种yaml语法规则yaml数据读取三种格式 多环境启动配置文件参数命令行参数多环境开发控制&#xff1a;Maven & SpringBoot 多环境兼容 配置文件分类&#xff1a;4种 整合…

Qt+FFmpeg开发视频播放器笔记(三):音视频流解析封装

音频解析 音频解码是指将压缩的音频数据转换为可以再生的PCM(脉冲编码调制)数据的过程。 FFmpeg音频解码的基本步骤如下: 初始化FFmpeg解码器(4.0版本后可省略): 调用av_register_all()初始化编解码器。 调用avcodec_register_all()注册所有编解码器。 打开输入的音频流:…

k8s以及prometheus

#生成控制器文件并建立控制器 [rootk8s-master ~]# kubectl create deployment bwmis --image timinglee/myapp:v1 --replicas 2 --dry-runclient -o yaml > bwmis.yaml [rootk8s-master ~]# kubectl expose deployment bwmis --port 80 --target-port 80 --dry-runclient…