В связи с тем, что зона 3 имеет рандомный угол наклона, не получиться искать ее как изображение. Автокликер умеет искать только постоянные изображения без каких-либо трансформаций. Поэтому в данном случае необходимо использовать распознавание цвета пикселя. Искать конкретный цвет мы не можем, потому что, во-первых, цвет полоски индикатора градиентный, а, во-вторых, зона 3 принимает цвет, в зависимости от редкости рыбы. Единственное, что можно использовать, так это тот факт, что цвет зоны 3 значительно светлее основного фона индикатора 2. Автокликер понимает цвета только в целочисленном формате. Это сделано во избежание дополнительных конвертаций из строки HEX-формата. Кроме того, целые числа гораздо нагляднее выглядят во время их сравнения. Важно помнить только одно правило: чем цвет темнее, тем больше его значение. В скрипте вы можете использовать все 16777216 оттенков цветов, которые поддерживаются смартфонами. К слову, это число равно значению черного цвета. Белый цвет это 1.
За основу возьмем скрипт из предыдущей статьи, где мы рисовали часть окружности и изменим его под новую задачу.
Для начала, с помощью пипетки определим цвет рамки зоны 3 и запишем его в скрипт.
Код: Выделить всё
int frameColor = 13422296;Задаем центральную точку нашей окружности (читай первую и вторую часть).
Код: Выделить всё
Point cntr = Point.get(1640, 565);Код: Выделить всё
startScreenCapture(2);
sleep(2000);
Код: Выделить всё
int color;
color = getColor(x, y);
Код: Выделить всё
if(color < frameColor ){
setMark(x, y);
break;
}
В результате получился вот такой скрипт
Код: Выделить всё
int frameColor = 13422296;
Point cntr = Point.get(1640, 565);
int r = 127;
double pi = 3.1416;
double from = pi * 0.45;
double to = pi * 1.38;
startScreenCapture(2);
sleep(2000);
int x;
int y;
int color;
for (; from < to; from += 0.05)
{
x = (int)(cntr.x + Math.cos(from) * r);
y = (int)(cntr.y - Math.sin(from) * r);
color = getColor(x, y);
if (color < frameColor)
{
setMark(x, y);
break;
}
}

Немного не то, что мы ожидали, верно? В чем проблема не понятно. Вроде как условие корректно, ошибок нету. Но почему же тогда автокликер решил установить метку на пикселе, который ничем не отличается от остального фона и абсолютно в другом месте от зоны 3?
В скрипте метка может установиться только при условии, что цвет пикселя темнее, чем самый темный цвет рамки зоны 3. Хочу сразу посоветовать вам, не спорьте с автокликером. Если он видит что цвет в том месте светлее, значит так оно и есть. Человеческий глаз видит картину в целом, какие-то крошечные точки ему не видны, в то же время компьютер видит все в подробнейших деталях. Причина проблемного пикселя в том, что блики от воды могут накладываться на индикатор. Но как понять, какой именно цвет видит автокликер в том месте? Для этого необходимо использовать функцию логирования. Включите в настройках автокликера отображение кнопки журнала отладки, если у вас она скрыта. И добавьте в условное ветвление следующую строку перед оператором прерывания цикла.
Код: Выделить всё
log("" + color);Запускаем скрипт и получаем необходимый результат. Начало зоны определяется корректно.

Теперь можно переписать скрипт так, чтобы найденная точка не отмечалась меткой, но сохранялась в память. Ведь нам необходимо будет определить еще и конец зоны 3. А метка на экране может быть установлена только одна.
Сделать это крайне просто.
Объявим переменные, в которых будем хранить координаты начала и конца зоны 3.
Код: Выделить всё
Point startP = Point.get();
Point endP = Point.get();
Код: Выделить всё
if(color < frameColor){
startP.x = x;
startP.y = y;
}
Давайте рассмотрим внимательно индикатор. Начало зоны мы определяем во время перехода цвета от темного к светлому. Логично, что конец нужно искать на переходе от светлого к темному. Вот только есть проблема. Цвет зоны 3 может быть темнее, чем сама рамка, например, синий. То есть следующее условие не даст нужный нам результат.
Код: Выделить всё
if(color > frameColor){
endP.x = x;
endP.y = y;
}
Для начала объявим переменную-флаг.
Код: Выделить всё
boolean found;Теперь перепишем условное ветвление внутри цикла.
Код: Выделить всё
if (!found)
{
if (color < frameColor)
{
startP.x = x;
startP.y = y;
found = true;
}
}
else
{
}
Для теста можно вставить такой код в ветвление else
Код: Выделить всё
setMark(startP.x, startP.y);
break;
Теперь осталось придумать как найти конец зоны 3. Имеем следующие данные: полоска может быть темнее чем рамка по ее краям, но светлее чем цвет зоны 2. Я не придумал ничего лучше, чем запустить скрипт несколько раз в отладочном варианте и найти самый темный цвет для фона зоны 3. После чего начать сравнивать это значение с цветом фона зоны 2.
Для поиска самого темного значения цвета определим переменную и инициализируем ее цветом фона рамки, который получили в начале этой статьи. Позже это значение будет скорректировано.
Код: Выделить всё
int darkestBack = 13422296;Код: Выделить всё
if(color > darkestBack ){
log("" + color);
setMark(x, y);
break;
}
Дело в том, что индикатор прозрачный и в некоторых местах цвет становится темно синий, в то время когда зона 2 может быть светло серой. Из-за этого, метка устанавливалась далеко за пределами зоны 3.
Честно признаться, я был расстроен. Неужели придется оставить все как есть и пропускать ловлю синей рыбы… Забросив на какое-то время создание скрипта, я не мог выкинуть его с головы. И в какой-то момент меня осенило! Ведь можно же сначала найти переход от темного к светлому с одной стороны индикатора, после этого начать второй цикл, который начнет обход с противоположного края зоны 2 и найдет начало зоны 3 со своей стороны. При этом не нужно никаких флагов, дополнительных проверок цвета и тд.
Вот какой вид принял скрипт на данном этапе.
Код: Выделить всё
int frameColor = 11830383;
Point startP = Point.get();
Point endP = Point.get();
Point cntr = Point.get(1640, 565);
int r = 127;
double pi = 3.1416;
double from = pi * 0.45;
double to = pi * 1.38;
double step = 0.05;
double tFrom = from;
double tTo = to;
startScreenCapture(2);
sleep(2000);
int x;
int y;
int color;
while (tFrom < tTo){
x = (int)(cntr.x + Math.cos(tFrom) * r);
y = (int)(cntr.y - Math.sin(tFrom) * r);
color = getColor(x, y);
if (color < frameColor)
{
startP.x = x;
startP.y = y;
tFrom = to;
tTo = from;
break;
}
tFrom += step;
}
while (tFrom > tTo){
x = (int)(cntr.x + Math.cos(tFrom) * r);
y = (int)(cntr.y - Math.sin(tFrom) * r);
color = getColor(x, y);
if (color < frameColor)
{
endP.x = x;
endP.y = y;
break;
}
tFrom -= step;
}
setMark(startP);
sleep(1000);
setMark(endP);
sleep(1000);
removeMark();
После нахождения начала зоны 3, меняем местами начало и конец обхода.
Код: Выделить всё
tFrom = to;
tTo = from;
Код: Выделить всё
tFrom -= step;С логикой пока что закончили. Теперь немного геометрии. Нам необходимо нажать на кнопку вылова, когда рыбка будет находится, в идеале, напротив центра зоны 3. Расстояние, на котором движется рыба вокруг центра нашей окружности всегда постоянно. Осталось найти точку по которой проходит рыба, и в которой будем проверять изменение цвета. С теми данными, которыми мы уже располагаем, это можно выполнить с помощью формул.
Определим координаты белой стрелки под рыбкой 1 с помощью пипетки, после этого переходим в онлайн калькулятор для определения длины вектора. Вводим координаты центра индикатора, а также, полученной ранее точки, в соответствующие поля и, вуаля! Теперь у нас есть расстояние от центра индикатора до рыбки. Так как это расстояние постоянно, больше его вычислять не потребуется.
Добавим переменную длины вектора в скрипт.
Код: Выделить всё
int vectorLen = 165;Центральную точку получаем по формуле.
Код: Выделить всё
midP.x = (int)((startP.x + endP.x) * 0.5);
midP.y = (int)((startP.y + endP.y) * 0.5);
Дальше находим вектор с помощью максимально простой формулы
Код: Выделить всё
int vX = midP.x - cntr.x;
int vY = midP.y - cntr.y;
Код: Выделить всё
double norm = Math.sqrt(vX*vX + vY*vY);
double dX = vX / norm;
double dY = vY / norm;
Point fishP = Point.get();
fishP.x = (int)(cntr.x + dX * vectorLen);
fishP.y = (int)(cntr.y + dY * vectorLen);
Код: Выделить всё
int frameColor = 11830383;
Point startP = Point.get();
Point endP = Point.get();
Point midP = Point.get();
Point cntr = Point.get(1640, 565);
int r = 127;
int vectorLen = 165;
double pi = 3.1416;
double from = pi * 0.45;
double to = pi * 1.38;
double step = 0.05;
double tFrom = from;
double tTo = to;
startScreenCapture(2);
sleep(2000);
int x;
int y;
int color;
while (tFrom < tTo)
{
x = (int)(cntr.x + Math.cos(tFrom) * r);
y = (int)(cntr.y - Math.sin(tFrom) * r);
color = getColor(x, y);
if (color < frameColor)
{
startP.x = x;
startP.y = y;
tFrom = to;
tTo = from;
break;
}
tFrom += step;
}
while (tFrom > tTo)
{
x = (int)(cntr.x + Math.cos(tFrom) * r);
y = (int)(cntr.y - Math.sin(tFrom) * r);
color = getColor(x, y);
if (color < frameColor)
{
endP.x = x;
endP.y = y;
break;
}
tFrom -= step;
}
midP.x = (int)((startP.x + endP.x) * 0.5);
midP.y = (int)((startP.y + endP.y) * 0.5);
int vX = midP.x - cntr.x;
int vY = midP.y - cntr.y;
double norm = Math.sqrt(vX * vX + vY * vY);
double dX = vX / norm;
double dY = vY / norm;
Point fishP = Point.get();
fishP.x = (int)(cntr.x + dX * vectorLen);
fishP.y = (int)(cntr.y + dY * vectorLen);
setMark(startP);
sleep(1000);
setMark(endP);
sleep(1000);
setMark(midP);
sleep(1000);
setMark(fishP);
sleep(1000);
removeMark();
Смотрите видео выполнения скрипта на скриншоте из игры.
Самая сложная часть скрипта написана! Теперь можно добавить всю остальную обвязку для полной автоматизации процесса.
Добавим поиск изображения индикатора. Для этого нам нужно обрезать только часть индикатора, которая никогда не трансформируется. То есть не изменяет свой цвет и угол наклона.

Не страшно, если изображение получилось некрасивым. Для автокликера это не имеет никакого значения. Ему главное, чтоб фрагмент изображения был уникален и его легко можно было отличить от остальных объектов на экране.
Чтоб еще сильнее упростить жизнь автокликеру, зададим зону на экране, в которой следует искать данный фрагмент.
Загружаем вырезанный фрагмент индикатора из домашней папки автокликера в оперативную память устройства.
Код: Выделить всё
Image indicator = Image.load("fish");Зададим зону, в которой нужно искать индикатор
Код: Выделить всё
Point ltInd = Point.get(1479,460);
Point rbInd = Point.get(1798,682);
Укажем точку, куда нужно нажимать для перезапуска рыбалки.
Код: Выделить всё
Point pool = Point.get(1079,537);Код: Выделить всё
int fishArrowColor = 8019676;Код: Выделить всё
if(hasImg(indicator, ltInd, rbInd))Код: Выделить всё
while(!EXIT){
if(getColor(1762,599) < 16653488){
if(getColor(fishP) < fishArrowColor){
click(cntr);
sleep(3000);
click(pool);
break;
}
}else{
click(cntr);
sleep(3000);
click(pool);
break;
}
sleep(10);
}
Финальный скрипт имеет следующий вид:
Код: Выделить всё
Image indicator = Image.load("fish");
Point ltInd = Point.get(1479, 460);
Point rbInd = Point.get(1798, 682);
Point pool = Point.get(1079, 537);
int fishArrowColor = 8019676;
int frameColor = 11830383;
Point startP = Point.get();
Point endP = Point.get();
Point midP = Point.get();
Point cntr = Point.get(1640, 565);
int r = 127;
int vectorLen = 165;
double pi = 3.1416;
double from = pi * 0.45;
double to = pi * 1.38;
double step = 0.1;
double tFrom = from;
double tTo = to;
startScreenCapture(2);
sleep(1000);
int x;
int y;
int color;
while (!EXIT)
{
if (hasImg(indicator, ltInd, rbInd))
{
while (tFrom < tTo)
{
x = (int)(cntr.x + Math.cos(tFrom) * r);
y = (int)(cntr.y - Math.sin(tFrom) * r);
color = getColor(x, y);
if (color < frameColor)
{
startP.x = x;
startP.y = y;
tFrom = to;
tTo = from;
break;
}
tFrom += step;
}
while (tFrom > tTo)
{
x = (int)(cntr.x + Math.cos(tFrom) * r);
y = (int)(cntr.y - Math.sin(tFrom) * r);
color = getColor(x, y);
if (color < frameColor)
{
endP.x = x;
endP.y = y;
tFrom = from;
tTo = to;
break;
}
tFrom -= step;
}
midP.x = (int)((startP.x + endP.x) * 0.5);
midP.y = (int)((startP.y + endP.y) * 0.5);
int vX = midP.x - cntr.x;
int vY = midP.y - cntr.y;
double norm = Math.sqrt(vX * vX + vY * vY);
double dX = vX / norm;
double dY = vY / norm;
Point fishP = Point.get();
fishP.x = (int)(cntr.x + dX * vectorLen);
fishP.y = (int)(cntr.y + dY * vectorLen);
while (!EXIT)
{
if (getColor(1762, 599) < 16653488)
{
if (getColor(fishP) < fishArrowColor)
{
click(cntr);
sleep(3000);
click(pool);
break;
}
}
else
{
click(cntr);
sleep(3000);
click(pool);
break;
}
sleep(10);
}
}
sleep(200);
}
40 пойманных рыб суммарно, из которых 8 зеленого качества, 2 синего и 1 фиолетового.
Зум камеры и fov рекомендуется устанавливать таким образом, чтоб камера была максимально высоко над персонажем. Это поможет избавиться от лишних бликов в зоне индикатора рыбалки.
Из недостатков данного скрипта можно отметить следующее, иногда автокликер срабатывает на слишком светлые волны, считая, что это стрелка рыбы. Также периодически он не может выловить рыбу, потому что она может проплывать между проверками цвета в нужной точке. Но в целом, результат 40/49 я считаю очень успешным, при том, что после запуска скрипта и рыбалки один раз, больше вообще не нужно прикасаться к экрану.
На этом работу над скриптом можно считать завершенной. Поздравляю тех, у кого получилось настроить скрипт у себя в игре с помощью данной инструкции!
В качестве тренировки, попробуйте добавить поиск изображения индикатора лужи. Чтоб когда рыба в луже закончилась, автокликер подавал звуковое/вибро уведомление. Подсказка: целесообразнее всего проверять наличие индикатора лужи после перезапуска рыбалки. То есть, нажали по луже, подождали несколько секунд, проверили наличие индикатора. Если не нашли, включаем оповещение и прерываем скрипт путем установки переменной EXIT в состояние true.
Хочу напоследок сказать несколько слов о том, почему я показал пример скрипта, который не работает, как ожидалось. Во-первых, использование флага, иногда бывает незаменимо, поэтому о нем необходимо знать. Во-вторых, вы должны понимать, что даже разработчик автокликера не может сесть, и за 15 минут написать сразу рабочий скрипт, если этот скрипт должен проверять кучу состояний экрана и выполнять множество действий. Поэтому не нужно отчаиваться и забрасывать скриптинг, со словами: “Это не мое”, только потому, что у вас скрипт не заработал с первого раза. Например, на создание финальной версии скрипта, который был представлен выше в этой статье, было полностью потрачено два моих вечера. Так что задумайтесь, достаточно ли вы приложили усилий, перед тем как забрасывать скрипт. Если зашли в тупик во время скриптинга, отдохните, прежде чем продолжать, и попробуйте решить проблему, используя другой подход к решению задачи. Возможно даже полностью меняя логику скрипта, как это было в моем случае.
На этом у меня все. Желаю терпения и успехов в скриптинге!