使用Qt+opencv实现游戏辅助点击工具-以阴阳师为例

注:本文章技术交流使用,不侵犯任何著作权。

一. 阴阳师辅助软件需要实现哪些功能?

1.首先,对于肝绘卷拿角色而言,需要打困难28副本和结界突破循环刷绘卷碎片。这一功能让你每月免费悠闲地拿到最新角色,即使你是较新的玩家!

2.有人喜欢打阴阳寮突破,因为结界卡可以合成勾玉,另外寮突破后给的寮勋章可以维持寮正常运转。

3.御魂等副本,这款游戏的御魂是核心玩法。

而且这只是一个辅助工具,不修改游戏内存,用来解放双手的,不频繁点击,不会封你号的!

特别好用,真心希望每个人都能快快乐乐游戏,而不是觉得很快就放弃。

二. 技术思路与技术难点

技术思路比较简单,使用opencv自带的matchTemplate函数,将实时截图与预定好的模版图片进行比对,当比对到时,可以进行点击该模版图片的位置,从而实现随着游戏屏幕的变化我总能点击适当的地方。

难点1:需要与windows底层api进行交互,特别是键盘和鼠标事件,要确保频繁点击时使用键盘特定按键进行退出,这往往需要操作系统级别的编程,部分代码较为抽象。

难点2:来自业务方面的难点,结界突破一个界面共9个人,需要打8个人后第九个人失败4次再攻破他,这样可以降低刷新新一轮对手的强度。问题在于我要统计是否已经攻破了8个对手,从而开启第九个人的逻辑。

难点3:自动标记队友,让辅助把所有技能都给主c,往往是在开始时,屏幕闪烁很难把握时间匹配到,毕竟你不能每秒十几次的匹配吧,不够优雅。

难点4:意外和稳定性:别人发协作任务给你你得接,要不就会卡住,资源满了也要点确定,战斗失败也要考虑,野队的队友跑了你需要重新邀请新队友,确保全自动流程,另外网络波动也是需要考虑的因素,你不能做的太理想化,性能太极端的优化。

三. 代码简析,以肝绘卷部分代码为例

首先创建一个基础的opencv相关的类,这个类提供基础的函数,供其他各项任务使用。

cpp 复制代码
class COMMONLIB_EXPORT CommonLib
{
public:
    CommonLib();
    ~CommonLib();
    //这个函数根据句柄截图游戏屏幕并转换成Mat形式
    cv::Mat captureGameWindow(HWND& hwnd);
    //两个Mat是否能匹配到
    bool isFindTemplateImage(cv::Mat &imageTemplate,cv::Mat &cutImage, double threshold);
    //匹配两个Mat,匹配到就会点击并延时
    bool locateClickPos(cv::Mat &bigImage, cv::Mat &locateImage, double threshold, int ts);
    //点击指定像素位置
    void clickPos(int x, int y);

    void delay(int ts);
};

CommonLib::CommonLib() {}

CommonLib::~CommonLib() {}
cv::Mat CommonLib::captureGameWindow(HWND& hwnd)
{
    // SetForegroundWindow(hwnd);
    cv::Mat matColor;
    QScreen *screen = QGuiApplication::primaryScreen();
    if (!screen)
    {
        qDebug() << "无法获取主屏幕";
        return matColor;
    }
    QEventLoop loop;
    QTimer::singleShot(100,&loop, SLOT(quit()));
    QImage image = (screen->grabWindow(0)).toImage();

    switch (image.format()) {
    case QImage::Format_RGB32:
        matColor = cv::Mat(image.height(), image.width(), CV_8UC4, const_cast<uchar*>(image.bits()), image.bytesPerLine()).clone();
        cv::cvtColor(matColor, matColor, cv::COLOR_RGB2BGR); // 转换为BGR格式
        std::cout<<"Format_RGB32"<<std::endl;
        break;
    case QImage::Format_RGB888:
        matColor = cv::Mat(image.height(), image.width(), CV_8UC3, const_cast<uchar*>(image.bits()), image.bytesPerLine()).clone();
        cv::cvtColor(matColor, matColor, cv::COLOR_RGB2BGR); // 转换为BGR格式
        std::cout<<"Format_RGB888"<<std::endl;
        break;
    case QImage::Format_Indexed8:
        matColor = cv::Mat(image.height(), image.width(), CV_8UC1, const_cast<uchar*>(image.bits()), image.bytesPerLine()).clone();
        std::cout<<"Format_Indexed8"<<std::endl;
        break;
    default:
        qWarning() << "Unsupported QImage format";
    }
    return matColor;
}

bool CommonLib::isFindTemplateImage(cv::Mat &imageTemplate,cv::Mat &cutImage, double threshold)
{
    cv::Mat result;
    cv::matchTemplate(cutImage, imageTemplate, result, cv::TM_CCOEFF_NORMED);

    // 找到最大值
    double maxVal;
    cv::minMaxLoc(result, nullptr, &maxVal);

    // 判断是否匹配成功
    if (maxVal >= threshold)
    {
        std::cout << "Template matched successfully!" << std::endl;
        return true;

    } else
    {
        return false;
    }
}

bool CommonLib::locateClickPos(cv::Mat &bigImage, cv::Mat &locateImage, double threshold, int ts)
{
    // 确保目标图像和模板图像有相同的通道数
    if (bigImage.channels() != locateImage.channels()) {
        std::cout << "Error: The number of channels in the big image and locate image do not match." << std::endl;
        return false;
    }
    // 创建结果矩阵
    cv::Mat result;
    cv::matchTemplate(bigImage, locateImage, result, cv::TM_CCOEFF_NORMED);

    // 找到最大值和最小值的位置
    double minVal, maxVal;
    cv::Point minLoc, maxLoc;
    cv::minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc);

    // 判断是否匹配成功
    if (maxVal >= threshold)
    {
        std::cout << "Template matched successfully!" << std::endl;

        // 计算匹配位置的中心点
        int x = maxLoc.x + locateImage.cols / 2;
        int y = maxLoc.y + locateImage.rows / 2;

        // 创建 QPoint 对象表示匹配位置
        QPoint matchPosition(x, y);

        // 输出匹配位置
        std::cout << "Match position: (" << matchPosition.x() << ", " << matchPosition.y() << ")" << std::endl;
        // 如果需要,可以在这里添加模拟鼠标点击等操作
        clickPos(matchPosition.x(), matchPosition.y());
        delay(ts);
        return true;
    }

    return false;
}

void CommonLib::clickPos(int x, int y)
{
    INPUT inputdown = {0};
    inputdown.type = INPUT_MOUSE;
    inputdown.mi.dx = x * (65536 / GetSystemMetrics(SM_CXSCREEN));
    inputdown.mi.dy = y * (65536 / GetSystemMetrics(SM_CYSCREEN));
    inputdown.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_LEFTDOWN ;
    SendInput(1, &inputdown, sizeof(INPUT));

    int randomInt = QRandomGenerator::global()->bounded(100,250);
    delay(randomInt);

    INPUT inputup = {0};
    inputup.type = INPUT_MOUSE;
    inputup.mi.dx = x * (65536 / GetSystemMetrics(SM_CXSCREEN));
    inputup.mi.dy = y * (65536 / GetSystemMetrics(SM_CYSCREEN));
    inputup.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_LEFTUP;
    SendInput(1, &inputup, sizeof(INPUT));
}

void CommonLib::delay(int ts)
{
    QEventLoop loop;
    QTimer::singleShot(ts, &loop, SLOT(quit()));
    loop.exec();
}

接着我们设立一个MainWindow,用来和用户进行交互和各项任务的切换,管理,以及键盘事件,关闭事件的处理。

cpp 复制代码
#include "mainwindow.h"

// 全局变量,用于存储钩子句柄
HHOOK g_hHook = NULL;
// 静态指针,指向 MainWindow 实例
static MainWindow *mainWindowInstance = nullptr;
// 全局键盘钩子回调函数
LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode < 0) {
        return CallNextHookEx(g_hHook, nCode, wParam, lParam);
    }

    KBDLLHOOKSTRUCT *pKeyboard = (KBDLLHOOKSTRUCT *)lParam;
    int keyCode = pKeyboard->vkCode;

    if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP)
    {
        std::cout << "Global key released: " << keyCode << std::endl;
        // 发送信号给 MainWindow
        if (mainWindowInstance)
            QMetaObject::invokeMethod(mainWindowInstance, "handleGlobalKeyRelease", Qt::QueuedConnection, Q_ARG(int, keyCode));
        else
            std::cout << "MainWindow not found" << std::endl;
    }

    return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    centralWidget = nullptr;
    vLayout = nullptr;
    btnKun28Start = nullptr;
    m_kun28CVTask = nullptr;
    m_pkun28workerThread = nullptr;
    m_pliaotuCVTask = nullptr;
    m_pliaotuworkerThread = nullptr;
    m_yuhunCVTask = nullptr;
    m_yuhunworkerThread = nullptr;

    centralWidget = new QWidget(this);
    vLayout = new QVBoxLayout(centralWidget);
    btnKun28Start = new QPushButton(centralWidget);
    btnKun28Start->setText("绘卷/结界突破启动");
    btnKun28Start->setMinimumSize(100,40);
    btnKun28Start->setCheckable(true);

    btnliaotuStart = new QPushButton(centralWidget);
    btnliaotuStart->setText("寮突破启动");
    btnliaotuStart->setMinimumSize(100,40);
    btnliaotuStart->setCheckable(true);

    btnyuhunStart = new QPushButton(centralWidget);
    btnyuhunStart->setText("御魂等活动启动");
    btnyuhunStart->setMinimumSize(100,40);
    btnyuhunStart->setCheckable(true);



    m_textEdit = new QPlainTextEdit(centralWidget);
    m_textEdit->setPlainText("使用说明:\n将游戏窗口全屏,角色移动到困28那里\n点击按钮开始任务\nQ键退出\n本\
软件由副会长七夕制作,闲鱼号:河边护不湿郎,禁止其他人售卖,遇到请举报!正版享受永久更新");

    vLayout->addWidget(btnKun28Start);
    vLayout->setAlignment(btnKun28Start,Qt::AlignCenter);
    vLayout->addWidget(btnliaotuStart);
    vLayout->setAlignment(btnliaotuStart,Qt::AlignCenter);
    vLayout->addWidget(btnyuhunStart);
    vLayout->setAlignment(btnyuhunStart,Qt::AlignCenter);

    vLayout->addWidget(m_textEdit);
    vLayout->setAlignment(m_textEdit,Qt::AlignCenter);

    centralWidget->setLayout(vLayout);
    this->setCentralWidget(centralWidget);
    this->resize(800, 600);

    // 设置焦点策略
    setFocusPolicy(Qt::StrongFocus);
    setFocus();  // 设置焦点到主窗口

    // 激活窗口
    activateWindow();

    btnKun28Start->setEnabled(true);
    btnliaotuStart->setEnabled(true);
    btnyuhunStart->setEnabled(true);

    connect(btnKun28Start, &QPushButton::clicked, this, &MainWindow::startKun28);
    connect(btnliaotuStart, &QPushButton::clicked, this, &MainWindow::startLiaotu);
    connect(btnyuhunStart, &QPushButton::clicked, this, &MainWindow::startYuhun);

    // 安装全局键盘钩子
    g_hHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProc, nullptr, 0);
    if (g_hHook == nullptr) {
        std::cerr << "Failed to install global keyboard hook" << std::endl;
    }
    // 保存 MainWindow 实例指针q
    mainWindowInstance = this;
    setWindowIcon(QIcon("title.png"));
}

MainWindow::~MainWindow()
{
    // 卸载全局键盘钩子
    if (g_hHook != nullptr)
    {
        UnhookWindowsHookEx(g_hHook);
    }
}

void MainWindow::setThisFocus()
{
    setFocus();  // 设置焦点到主窗口
    // 激活窗口
    activateWindow();
    qDebug()<<"信号触发,设置窗口焦点";
}

void MainWindow::startKun28()
{
    btnKun28Start->setChecked(true);
    if (m_kun28CVTask !=nullptr)
    {
        m_kun28CVTask->setStopFlag(true);
        delete m_kun28CVTask;
        m_kun28CVTask = nullptr;

    }
    if (m_pkun28workerThread != nullptr)
    {
        delete m_pkun28workerThread;
        m_pkun28workerThread = nullptr;
    }
    m_kun28CVTask = new kun28opencvTask();
    m_pkun28workerThread = new QThread();
    m_kun28CVTask->moveToThread(m_pkun28workerThread);
    connect(m_pkun28workerThread, &QThread::started, m_kun28CVTask, &kun28opencvTask::runTask);
 //   connect(m_pkun28workerThread, &QThread::finished, m_kun28CVTask, &QObject::deleteLater);
    m_pkun28workerThread->start();

}

void MainWindow::startLiaotu()
{
    btnliaotuStart->setChecked(true);
    if (m_pliaotuCVTask !=nullptr)
    {
        m_pliaotuCVTask->setStopFlag(true);
        delete m_pliaotuCVTask;
        m_pliaotuCVTask = nullptr;

    }
    if (m_pliaotuworkerThread != nullptr)
    {
        delete m_pliaotuworkerThread;
        m_pliaotuworkerThread = nullptr;
    }
    m_pliaotuCVTask = new liaotupoopencvTask();
    m_pliaotuworkerThread = new QThread();
    m_pliaotuCVTask->moveToThread(m_pliaotuworkerThread);
    connect(m_pliaotuworkerThread, &QThread::started, m_pliaotuCVTask, &liaotupoopencvTask::runTask);
    m_pliaotuworkerThread->start();
}

void MainWindow::startYuhun()
{
    btnyuhunStart->setChecked(true);
    if (m_yuhunCVTask !=nullptr)
    {
        m_yuhunCVTask->setStopFlag(true);
        delete m_yuhunCVTask;
        m_yuhunCVTask = nullptr;

    }
    if (m_yuhunworkerThread != nullptr)
    {
        delete m_yuhunworkerThread;
        m_yuhunworkerThread = nullptr;
    }
    m_yuhunCVTask = new yuhunopencvTask();
    m_yuhunworkerThread = new QThread();
    m_yuhunCVTask->moveToThread(m_yuhunworkerThread);
    connect(m_yuhunworkerThread, &QThread::started, m_yuhunCVTask, &yuhunopencvTask::runTask);
    m_yuhunworkerThread->start();

}

void MainWindow::keyReleaseEvent(QKeyEvent* event)
{
    qDebug()<<"Q按下,正在执行关闭工作线程,请稍后......";
    if (event->key() == Qt::Key_Q)
    {



        btnKun28Start->setChecked(false);
        if (m_kun28CVTask !=nullptr)
        {
            personaltupocvtask::setStopFlag(true);
            m_kun28CVTask->setStopFlag(true);
        }

        if (m_pkun28workerThread != nullptr)
        {
            m_pkun28workerThread->quit();
            m_pkun28workerThread->wait();
            qDebug()<<"已关闭绘卷工作线程,关闭完成!";
        }
        btnliaotuStart->setChecked(false);
        if (m_pliaotuCVTask !=nullptr)
        {
            m_pliaotuCVTask->setStopFlag(true);
        }

        if (m_pliaotuworkerThread != nullptr)
        {
            m_pliaotuworkerThread->quit();
            m_pliaotuworkerThread->wait();
            qDebug()<<"已关闭寮突工作线程";
        }
        btnyuhunStart->setChecked(false);
        if (m_yuhunCVTask !=nullptr)
        {
            m_yuhunCVTask->setStopFlag(true);
        }
        if (m_yuhunworkerThread != nullptr)
        {
            m_yuhunworkerThread->quit();
            m_yuhunworkerThread->wait();
            qDebug()<<"已关闭御魂工作线程";
        }


    }
}

void MainWindow::closeEvent(QCloseEvent* event)
{

    if (m_kun28CVTask !=nullptr)
    {
        m_kun28CVTask->setStopFlag(true);
    }

    if (m_pkun28workerThread != nullptr)
    {
        connect(m_yuhunworkerThread, &QThread::finished, m_yuhunCVTask, &QObject::deleteLater);
        m_pkun28workerThread->exit();
        m_pkun28workerThread->wait();
        qDebug()<<"已关闭工作线程";
    }

    if (m_pliaotuCVTask !=nullptr)
    {
        m_pliaotuCVTask->setStopFlag(true);
    }

    if (m_pliaotuworkerThread != nullptr)
    {
        connect(m_pliaotuworkerThread, &QThread::finished, m_pliaotuCVTask, &QObject::deleteLater);
        m_pliaotuworkerThread->exit();
        m_pliaotuworkerThread->wait();
        qDebug()<<"已关闭工作线程";
    }

    if (m_yuhunCVTask !=nullptr)
    {
        m_yuhunCVTask->setStopFlag(true);
    }

    if (m_yuhunworkerThread != nullptr)
    {
        connect(m_yuhunworkerThread, &QThread::finished, m_yuhunCVTask, &QObject::deleteLater);
        m_yuhunworkerThread->exit();
        m_yuhunworkerThread->wait();
        qDebug()<<"已关闭御魂工作线程";
    }
}

然后是任务类

cpp 复制代码
#include "kun28opencvtask.h"
#include "personaltupocvtask.h"
kun28opencvTask::kun28opencvTask()
{
    //加载初始化模板图片
    initImage();
    m_isStop = false;
    m_nCount = 0;
    m_yCount = 200;
    m_cvLib = nullptr;
    m_cvLib = new CommonLib();
}

kun28opencvTask::~kun28opencvTask()
{
    if (m_cvLib)
    {
        delete m_cvLib;
        m_cvLib = nullptr;
    }
}
void kun28opencvTask::initImage()
{
    cv::Mat tempm_coperImage2 = cv::imread("imageFirstTemplate/coperImage2.png");
    cv::Mat tempm_sourceoverImage = cv::imread("imageFirstTemplate/sourceoverImage.png");
    cv::Mat tempm_beginImage = cv::imread("imageFirstTemplate/beginImage.png");
    cv::Mat tempsearchImage = cv::imread("imageFirstTemplate/searchImage.png");
    cv::Mat tempsearchImage2 = cv::imread("imageFirstTemplate/searchImage2.png");
    cv::Mat tempvictoryImage = cv::imread("imageFirstTemplate/victoryImage.png");
    cv::Mat tempm_bossImage = cv::imread("imageFirstTemplate/bossImage.png");
    cv::Mat tempm_endImage = cv::imread("imageFirstTemplate/endImage.png");
    cv::Mat tempm_boxImage = cv::imread("imageFirstTemplate/boxImage.png");
    cv::Mat tempm_boxImage2 = cv::imread("imageFirstTemplate/boxImage2.png");
    cv::Mat tempm_Image28 = cv::imread("imageFirstTemplate/Image28.png");
    cv::Mat tempm_closeImage = cv::imread("imageFirstTemplate/closeImage.png");
    //灰度化
    cv::cvtColor(tempm_coperImage2, m_coperImage, cv::COLOR_BGR2GRAY);
    cv::cvtColor(tempm_sourceoverImage, m_sourceoverImage, cv::COLOR_BGR2GRAY);
    cv::cvtColor(tempm_beginImage, m_beginImage, cv::COLOR_BGR2GRAY);
    cv::cvtColor(tempsearchImage, m_searchImage, cv::COLOR_BGR2GRAY);
    cv::cvtColor(tempsearchImage2, m_searchImage2, cv::COLOR_BGR2GRAY);
    cv::cvtColor(tempvictoryImage, m_victoryImage, cv::COLOR_BGR2GRAY);
    cv::cvtColor(tempm_endImage, m_endImage, cv::COLOR_BGR2GRAY);
    cv::cvtColor(tempm_boxImage, m_boxImage, cv::COLOR_BGR2GRAY);
    cv::cvtColor(tempm_boxImage2, m_boxImage2, cv::COLOR_BGR2GRAY);
    cv::cvtColor(tempm_Image28, m_Image28, cv::COLOR_BGR2GRAY);
    cv::cvtColor(tempm_bossImage, m_bossImage, cv::COLOR_BGR2GRAY);
    cv::cvtColor(tempm_closeImage, m_closeImage, cv::COLOR_BGR2GRAY);

}

void kun28opencvTask::setStopFlag(bool flag)
{
    m_isStop = flag;
}
void kun28opencvTask::runTask()
{
    QString windowTitle = "MuMu模拟器12";
    HWND h = FindWindow(nullptr, windowTitle.toStdWString().c_str());
    if (h == nullptr)
    {

        windowTitle = "MuMu模拟器13";
        h = FindWindow(nullptr, windowTitle.toStdWString().c_str());
        if (h == nullptr)
            qWarning("无法找到游戏窗口:%s", qPrintable(windowTitle));
    }
    this->m_hwnd = h;
    SetForegroundWindow(m_hwnd);

    // 获取屏幕中心位置
    int screenWidth = GetSystemMetrics(SM_CXSCREEN);
    int screenHeight = GetSystemMetrics(SM_CYSCREEN);
    int centerX = screenWidth / 2;
    int centerY = screenHeight / 2;
    m_cvLib->delay(1000);
    while(true)
    {
        if (m_isStop == true)
        {
            qDebug()<<"结束绘卷子线程循环";
            break;
        }

        cv::Mat colorCutImage = m_cvLib->captureGameWindow(m_hwnd);
        cv::cvtColor(colorCutImage,m_cutImage,cv::COLOR_BGR2GRAY);

        //开始每轮的匹配
        if (m_cvLib->locateClickPos(m_cutImage, m_coperImage, 0.8, 1000))
        {
            m_nCount = 0;
            continue;
        }
       if (m_yCount >= 100)
        {
            if (m_cvLib->locateClickPos(m_cutImage, m_closeImage, 0.8, 3000))
            {
                personaltupocvtask ptupo;
                ptupo.runTask();
                m_yCount = 0;
                continue;
            }
        }

        if (m_cvLib->locateClickPos(m_cutImage, m_bossImage, 0.8, 7800))
        {
            m_nCount = 0;
            continue;
        }
        if (m_cvLib->locateClickPos(m_cutImage, m_searchImage, 0.75, 7800) || \
            m_cvLib->locateClickPos(m_cutImage, m_searchImage2, 0.75, 7800))
        {
            m_nCount = 0;
            ++m_yCount;
            continue;
        }
        if (m_cvLib->locateClickPos(m_cutImage, m_victoryImage, 0.8, 2500))
        {
            m_nCount = 0;
            continue;
        }
        if (m_cvLib->locateClickPos(m_cutImage, m_endImage, 0.8, 700))
        {
            m_nCount = 0;
            continue;
        }
        if (m_cvLib->locateClickPos(m_cutImage, m_boxImage, 0.8, 200))
        {
            m_cvLib->clickPos(centerX, centerY+500);
            m_cvLib->delay(1000);
            m_nCount = 0;
            continue;
        }
        if (m_cvLib->locateClickPos(m_cutImage, m_boxImage2, 0.8, 1000))
        {
            m_nCount = 0;
            continue;
        }
        if (m_cvLib->locateClickPos(m_cutImage, m_sourceoverImage, 0.8, 1000))
        {
            m_nCount = 0;
            continue;
        }
        if (m_cvLib->locateClickPos(m_cutImage, m_beginImage, 0.87, 1000))
        {
            m_nCount = 0;
            continue;
        }
        if (m_cvLib->locateClickPos(m_cutImage, m_Image28, 0.8, 800))
        {
            m_nCount = 0;
            continue;
        }

        m_cvLib->delay(700);
        //拖拽往左滑界面

        // 移动鼠标到屏幕中心
        SetCursorPos(centerX, centerY);

        // 模拟鼠标左键按下
        mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);

        // 向左移动200像素
        int newX = centerX - 800;
        // 模拟鼠标移动到目标位置
        for (int x = centerX; x > newX; x -= 20) {  // 每次移动20像素
            SetCursorPos(x, centerY);
            Sleep(20);  // 短暂延迟,确保移动平滑
        }

        // 模拟鼠标左键释放
        mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
        //否则,我需要暂停几秒等待程序循环慢点
        m_cvLib->delay(100);
        ++m_nCount;
        std::cout<<"未匹配到任何目标次数"<<m_nCount;
        if (m_nCount >= 100)
            m_isStop = true;

    }
}

在困28的类里加入了结界突破的任务入口,在特定情况下即突破卷满时进入。

cpp 复制代码
#include "personaltupocvtask.h"
bool personaltupocvtask::m_ispersonalliaotuStop = false;
personaltupocvtask::personaltupocvtask()
{
    //加载初始化模板图片
    initImage();
    m_nCount = 0;
    m_cvLib = nullptr;
    m_cvLib = new CommonLib();
}
personaltupocvtask::~personaltupocvtask()
{
    if (m_cvLib)
    {
        delete m_cvLib;
        m_cvLib = nullptr;
    }
}

void personaltupocvtask::initImage()
{
    cv::Mat xunzhang1Image = cv::imread("imageThirdTemplate/tupoxunzhang1.png");
    cv::Mat xunzhang2Image = cv::imread("imageSecondTemplate/xunzhang2Image.png");
    cv::Mat attackImage = cv::imread("imageSecondTemplate/attackImage.png");
    cv::Mat charactorImage = cv::imread("imageSecondTemplate/charactorImage.png");
    cv::Mat charactorImage2 = cv::imread("imageSecondTemplate/charactorImage2.png");
    cv::Mat endImage = cv::imread("imageSecondTemplate/endImage.png");
    cv::Mat failureImage = cv::imread("imageSecondTemplate/failureImage.png");
    cv::Mat coperImage2 = cv::imread("imageSecondTemplate/coperImage2.png");
    cv::Mat poImage = cv::imread("imageThirdTemplate/po.png");
    cv::Mat returnImage = cv::imread("imageThirdTemplate/return.png");
    cv::Mat returnConformImage = cv::imread("imageThirdTemplate/returnConform.png");
    cv::Mat entryImage = cv::imread("imageThirdTemplate/entry.png");
    cv::Mat outImage = cv::imread("imageThirdTemplate/out.png");
    cv::Mat finalOutImage = cv::imread("imageFirstTemplate/closeImage.png");

    //灰度化
    cv::cvtColor(xunzhang1Image, m_xunzhang1Image, cv::COLOR_BGR2GRAY);
    cv::cvtColor(xunzhang2Image, m_xunzhang2Image, cv::COLOR_BGR2GRAY);
    cv::cvtColor(attackImage, m_attackImage, cv::COLOR_BGR2GRAY);
    cv::cvtColor(charactorImage, m_charactorImage, cv::COLOR_BGR2GRAY);
    cv::cvtColor(charactorImage2, m_charactorImage2, cv::COLOR_BGR2GRAY);
    cv::cvtColor(endImage, m_endImage, cv::COLOR_BGR2GRAY);
    cv::cvtColor(failureImage, m_failureImage, cv::COLOR_BGR2GRAY);
    cv::cvtColor(coperImage2, m_coperImage2, cv::COLOR_BGR2GRAY);
    cv::cvtColor(poImage, m_poImage, cv::COLOR_BGR2GRAY);
    cv::cvtColor(returnImage, m_returnImage, cv::COLOR_BGR2GRAY);
    cv::cvtColor(returnConformImage, m_returnConformImage, cv::COLOR_BGR2GRAY);
    cv::cvtColor(entryImage, m_entryImage, cv::COLOR_BGR2GRAY);
    cv::cvtColor(outImage, m_outImage, cv::COLOR_BGR2GRAY);
    cv::cvtColor(finalOutImage, m_finalOutImage, cv::COLOR_BGR2GRAY);
}

void personaltupocvtask::setStopFlag(bool flag)
{
    m_ispersonalliaotuStop = flag;
}

int personaltupocvtask::countDefeatNumber()
{
    // 创建结果矩阵
    cv::Mat result;
    int result_cols = m_cutImage.cols - m_poImage.cols + 1;
    int result_rows = m_cutImage.rows - m_poImage.rows + 1;
    result.create(result_rows, result_cols, CV_32FC1);

    // 执行模板匹配
    cv::matchTemplate(m_cutImage, m_poImage, result, cv::TM_CCOEFF_NORMED);

    // 设定阈值
    double threshold = 0.8;  // 根据实际情况调整此值

    // 查找大于阈值的位置
    cv::threshold(result, result, threshold, 1., cv::THRESH_BINARY);

    // 使用 minMaxLoc 方法查找所有局部最大值
    int size = m_defeatPos.size();
    for (int i = 0; i <size; ++i)
    {
        m_defeatPos.pop_back();
    }
        // 清空并释放内存
    while(true) {
        double minVal;
        double maxVal;
        cv::Point minLoc;
        cv::Point maxLoc;
        cv::minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc);

        if(maxVal >= threshold) {
            m_defeatPos.emplace_back(maxLoc);

            // 将找到的最大值区域设为非常低的值,以寻找下一个匹配
            cv::rectangle(result, cv::Point(maxLoc.x-4, maxLoc.y -4), cv::Point(maxLoc.x + m_poImage.cols+4, maxLoc.y + m_poImage.rows+4), cv::Scalar(0), cv::FILLED);
        } else {
            break;
        }
    }

    // 输出匹配次数
    int matchCount = m_defeatPos.size();
    std::cout<<"攻破次数是 "<<matchCount<<std::endl;
    return matchCount;
}

int personaltupocvtask::thisClassLocateClickPos(cv::Mat &bigImage, cv::Mat &locateImage, double threshold, int ts)
{

    // 确保目标图像和模板图像有相同的通道数
    if (bigImage.channels() != locateImage.channels()) {
        std::cout << "Error: The number of channels in the big image and locate image do not match." << std::endl;
        return false;
    }
    // 创建结果矩阵
    cv::Mat result;
    cv::matchTemplate(bigImage, locateImage, result, cv::TM_CCOEFF_NORMED);

    //将已经击败的对手区域置信度置为0
    for (auto& point : m_defeatPos)
    {
        // 将找到的击破点区域设为非常低的值,
        cv::rectangle(result, cv::Point(point.x - 4 * m_poImage.cols - 4, point.y -4 ), cv::Point(point.x + m_poImage.cols + 4 , point.y + 2 * m_poImage.rows + 4), cv::Scalar(0), cv::FILLED);
    }
    // 找到最大值和最小值的位置
    double minVal, maxVal;
    cv::Point minLoc, maxLoc;
    cv::minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc);

    // 判断是否匹配成功
    if (maxVal >= threshold)
    {
        std::cout << "Template matched successfully!" << std::endl;

        // 计算匹配位置的中心点
        int x = maxLoc.x + locateImage.cols / 2;
        int y = maxLoc.y + locateImage.rows / 2;

        // 创建 QPoint 对象表示匹配位置
        QPoint matchPosition(x, y);

        // 输出匹配位置
        std::cout << "Match position: (" << matchPosition.x() << ", " << matchPosition.y() << ")" << std::endl;
        // 如果需要,可以在这里添加模拟鼠标点击等操作
        m_cvLib->clickPos(matchPosition.x(), matchPosition.y());
        m_cvLib->delay(ts);
        return true;
    }

    return false;
}
void personaltupocvtask::runTask()
{
    QString windowTitle = "MuMu模拟器12";
    HWND h = FindWindow(nullptr, windowTitle.toStdWString().c_str());
    if (h == nullptr)
    {
        qWarning("无法找到游戏窗口:%s", qPrintable(windowTitle));
    }
    this->m_hwnd = h;
    SetForegroundWindow(m_hwnd);
    bool isNeedClickCharacter = false;
    while(true)
    {
        if (m_ispersonalliaotuStop == true)
        {
            qDebug()<<"结束结界突破子线程循环";
            break;
        }

        cv::Mat cutImage = m_cvLib->captureGameWindow(m_hwnd);
        cv::cvtColor(cutImage,this->m_cutImage,cv::COLOR_BGR2GRAY);

        //开始每轮的匹配
        if (m_cvLib->locateClickPos(m_cutImage, m_entryImage, 0.8, 1000))
        {
            m_nCount = 0;
            continue;
        }

        if (countDefeatNumber() >= 8)
        {
            int count = 0;
            while (count <= 4)
            {
                cv::Mat cutImage = m_cvLib->captureGameWindow(m_hwnd);
                cv::cvtColor(cutImage,this->m_cutImage,cv::COLOR_BGR2GRAY);
                if (m_cvLib->locateClickPos(m_cutImage, m_attackImage, 0.8, 300))
                {
                    //退出代码
                    cv::Mat cutImage = m_cvLib->captureGameWindow(m_hwnd);
                    cv::Mat cutGrayImage;
                    cv::cvtColor(cutImage,cutGrayImage,cv::COLOR_BGR2GRAY);
                    if (m_cvLib->locateClickPos(cutGrayImage, m_outImage, 0.8, 600))
                    {
                        //退出代码
                        if (m_cvLib->locateClickPos(cutGrayImage, m_finalOutImage, 0.8, 1000))
                            return;
                    }
                    m_cvLib->delay(1000);
                    continue;
                }
                if (thisClassLocateClickPos(m_cutImage, m_xunzhang2Image, 0.8, 1000)|| \
                    thisClassLocateClickPos(m_cutImage, m_xunzhang1Image, 0.8, 1000))
                {
                    ++count;
                    continue;
                }
                m_cvLib->locateClickPos(m_cutImage, m_returnConformImage, 0.8, 4000);
                m_cvLib->locateClickPos(m_cutImage, m_returnImage, 0.8, 800);                
                m_cvLib->locateClickPos(m_cutImage, m_failureImage, 0.8, 1000);
                m_cvLib->locateClickPos(m_cutImage, m_endImage, 0.8, 1000);
            }
            m_cvLib->delay(500);
            cv::Mat cutImage = m_cvLib->captureGameWindow(m_hwnd);
            cv::cvtColor(cutImage,this->m_cutImage,cv::COLOR_BGR2GRAY);
            if (m_cvLib->locateClickPos(m_cutImage, m_attackImage, 0.8, 1300))
            {
                isNeedClickCharacter = true;
                continue;
            }
        }
        if (m_cvLib->locateClickPos(m_cutImage, m_attackImage, 0.8, 500))
        {
            //退出代码
            cv::Mat cutImage = m_cvLib->captureGameWindow(m_hwnd);
            cv::cvtColor(cutImage,this->m_cutImage,cv::COLOR_BGR2GRAY);
            if (m_cvLib->locateClickPos(m_cutImage, m_outImage, 0.8, 600))
            {
                //退出代码
                if (m_cvLib->locateClickPos(m_cutImage, m_finalOutImage, 0.8, 1000))
                    return;
            }
           // m_cvLib->delay(3500);
            m_nCount = 0;
            isNeedClickCharacter = true;
            continue;
        }
        if (isNeedClickCharacter && m_cvLib->locateClickPos(m_cutImage, m_charactorImage, 0.8, 4000))
        {
            isNeedClickCharacter = false;
            m_nCount = 0;
            continue;
        }
        if (m_cvLib->locateClickPos(m_cutImage, m_endImage, 0.8, 1000))
        {
            m_nCount = 0;
            continue;
        }
        if (thisClassLocateClickPos(m_cutImage, m_xunzhang2Image, 0.7, 1000) || \
            thisClassLocateClickPos(m_cutImage, m_xunzhang1Image, 0.7, 1000))
        {
            m_nCount = 0;
            continue;
        }

        if (m_cvLib->locateClickPos(m_cutImage, m_failureImage, 0.8, 1500))
        {
            m_nCount = 0;
            continue;
        }

        //否则,我需要暂停几秒等待程序循环慢点
        m_cvLib->delay(100);
        ++m_nCount;
        qDebug()<<"未匹配到模板的次数是:"<<m_nCount;
        if (m_nCount >= 1000)
            m_ispersonalliaotuStop = true;
    }
}

在这个类里检测了每个界面的攻破次数,并将攻破的对手的置信度置为0,只留下那个未攻破的。

四.更多支持

我做完了这个工程后,已经上传至github上,欢迎来免费下载阅读完整版的代码,欢迎加颗星,这也是一个可运行的框架,你可以将该代码改改用在你自己的游戏上,甚至非游戏上,但是值得注意的是,代码中的延时时间是我精心调的,不建议点击延时时间过短,这将导致同一个界面频繁点击,过短的点击可能会被游戏检测到。

https://github.com/theblacktree/yangyangshuTool/tree/master

若是你只是一个用户,那你可以到咸鱼上找我要直接可运行的版本,只需要将模版图片换成自己的游戏截图即可,先试用几天,觉得好花点小钱支持我一下。咸鱼号:河边护不湿郎。

欢迎有问题进行提问交流。

相关推荐
Bluesonli2 小时前
第 9 天:UE5 物理系统 & 碰撞检测全解析!
开发语言·学习·游戏·ue5·虚幻·unreal engine
Bluesonli2 小时前
第 10 天:UE5 交互系统,拾取物品 & 触发机关!
学习·游戏·ue5·虚幻·unreal engine
阿运河5 小时前
UE5 如何通过命令行启动游戏工程
游戏·ue5
编织幻境的妖8 小时前
python2048游戏
开发语言·python·游戏
醉翁之意不在酒.max12 小时前
自制游戏——斗罗大陆
c++·游戏
Tom_wsc1 天前
题解:P1005 [NOIP 2007 提高组] 矩阵取数游戏
游戏·矩阵·动态规划
程序人生773 天前
游戏己停止运行:最新修复ntdll.dll的方法
游戏
微光守望者4 天前
Unity开发游戏使用XLua的基础
游戏·unity·lua
旭峰li4 天前
Block Blaster Online:免费解谜游戏的乐趣
游戏