注:本文章技术交流使用,不侵犯任何著作权。
一. 阴阳师辅助软件需要实现哪些功能?
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;
// 发送信号给 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()<<"已关闭御魂工作线程";
}
}
然后是任务类
#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);
//将已经击败的对手区域置信度置为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
若是你只是一个用户,那你可以到咸鱼上找我要直接可运行的版本,只需要将模版图片换成自己的游戏截图即可,先试用几天,觉得好花点小钱支持我一下。咸鱼号:河边护不湿郎。
欢迎有问题进行提问交流。