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

ops/2025/2/12 17:18:33/

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

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

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

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

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

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

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

二. 技术思路与技术难点

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

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

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

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

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

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

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

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,用来和用户进行交互和各项任务的切换,管理,以及键盘事件,关闭事件的处理。

#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;// 发送信号给 MainWindowif (mainWindowInstance)QMetaObject::invokeMethod(mainWindowInstance, "handleGlobalKeyRelease", Qt::QueuedConnection, Q_ARG(int, keyCode));elsestd::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 实例指针qmainWindowInstance = 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()<<"已关闭御魂工作线程";}
}

然后是任务类

#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的类里加入了结界突破的任务入口,在特定情况下即突破卷满时进入。

#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);//将已经击败的对手区域置信度置为0for (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

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

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


http://www.ppmy.cn/ops/157818.html

相关文章

Redis的数据过期策略和数据淘汰策略

一、数据过期策略 Redis的key过期之后&#xff0c;会立即删除吗&#xff1f; 是否立即删除&#xff0c;这是根据Redis的数据过期策略来决定的 Redis对数据设值数据的过期时间&#xff0c;数据过期后&#xff0c;就需要将数据从内存中删除掉。可以按照不同的规则进行删除&…

使用Jenkins、K8S、Docker一键部署SpringCloud微服务

集成Jenkins、Kubernetes&#xff08;K8S&#xff09;和Docker&#xff0c;实现一键部署SpringCloud微服务。以下是具体的步骤&#xff1a; 步骤一&#xff1a;配置Jenkins 首先&#xff0c;我们需要安装并配置Jenkins。Jenkins是一个开源的持续集成/持续部署工具&#xff0c;…

智慧机房解决方案(文末联系,领取整套资料,可做论文)

智慧机房解决方案-软件部分 一、方案概述 本智慧机房解决方案旨在通过硬件设备与软件系统的深度整合&#xff0c;实现机房的智能化管理与服务&#xff0c;提升机房管理人员的工作效率&#xff0c;优化机房运营效率&#xff0c;确保机房设备的安全稳定运行。软件部分包括机房管…

kafka消费端之再均衡

文章目录 再均衡是指分区的所属权从一个消费者转移到另一消费者的行为&#xff0c;它为消费组具备高可用性和伸缩性提供保障&#xff0c;使我们可以既方便又安全地删除消费组内的消费者或往消费组内添加消费者。不过 在再均衡发生期间&#xff0c;消费组内的消费者是无法读取消…

C语言——排序(冒泡,选择,插入)

基本概念 排序是对数据进行处理的常见操作&#xff0c;即将数据按某字段规律排列。字段是数据节点的一个属性&#xff0c;比如学生信息中的学号、分数等&#xff0c;可针对这些字段进行排序。同时&#xff0c;排序算法有稳定性之分&#xff0c;若两个待排序字段一致的数据在排序…

除了try...catch,还有其他处理异步错误的方法吗?

在处理异步操作时,除了使用 try...catch 语句捕获错误,还有其他几种方法可以有效处理异步错误。以下是一些常用的方法: 1. Promise 的 .catch() 方法 如果你的异步操作返回一个 Promise,可以使用 .catch() 方法来捕获错误。这种方法适用于不使用 async/await 的情况。 f…

51c自动驾驶~合集49

我自己的原文哦~ https://blog.51cto.com/whaosoft/13164876 #Ultra-AV 轨迹预测新基准&#xff01;清华开源&#xff1a;统一自动驾驶纵向轨迹数据集 自动驾驶车辆在交通运输领域展现出巨大潜力&#xff0c;而理解其纵向驾驶行为是实现安全高效自动驾驶的关键。现有的开…

DeepSeek API 调用 - Spring Boot 实现

DeepSeek API 调用 - Spring Boot 实现 1. 项目依赖 在 pom.xml 中添加以下依赖&#xff1a; <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></depe…