[Из песочницы] Алгоритм жевания для тачскрина
Не так давно вышла моя первая личная игра для мобильных. Суть заключается в том, что врагов надо пережевывать пальцами. Алгоритм не уникальный, но встречается редко. На первый взгляд, нужно только прослушать зум-движение двух пальцев, вроде ничего сложного, однако в процессе разработки игры выявляются различные подводные камни.
- Вычисление времени на сжатие челюстей;
- Сочетание жевания с управлением персонажем;
- Изменение параметров по ходу тестов.
Весь код написан на языке с# для движка Unity3D, для 2Д игры. Перейдем непосредственно к коду. В методе Update вычисляем кол-во тачей, и производим соответствующие действия. Двигаем персонажа в случае одного прикосновения:
//Если одно прикосновение
if (Input.touchCount == 1) {
//Если челюсти не сжимаются или не разжимаются, персонаж двигается к месту прикосновения
if (!compressing && !decompressing) {
Touch singleTouch = Input.GetTouch(0);
Vector3 targetPoint = Camera.main.ScreenToWorldPoint (singleTouch.position);
targetPoint = new Vector3 (targetPoint.x, targetPoint.y, 0);
transform.position = Vector3.MoveTowards (transform.position, targetPoint, movementSpeed * Time.deltaTime);
}
}
Тут ничего сложного, можно двигаться дальше. Код обработки двух касаний. Если нет сжимания/разжимания челюстей, то персонаж перемещается между двух пальцев.
if (Input.touchCount > 1) {
//Работа с двумя первыми касаниями.
Touch touch1 = Input.GetTouch(0);
Touch touch2 = Input.GetTouch(1);
//Если челюсти не работают, передвигаем персонажа между пальцами
if (!compressing && !decompressing) {
Vector3 targetPoint = Camera.main.ScreenToWorldPoint ((touch1.position + touch2.position) / 2);
targetPoint = new Vector3 (targetPoint.x, targetPoint.y, 0);
transform.position = Vector3.MoveTowards (transform.position, targetPoint, movementSpeed * Time.deltaTime);
}
float currentDistance = Vector2.Distance(touch1.position, touch2.position);
if(pastFingersDistance == 0) {
//Обнуление прошлого расстояния, если первый раз засечено два тача
pastFingersDistance = currentDistance;
}else if(currentDistance < pastFingersDistance - fingersMunchDetectionMin) {
//Метод включения сжатия челюстей. Управление графикой, у каждого индивидуально.
SetCompression();
}else if(currentDistance > pastFingersDistance + fingersMunchDetectionMin) {
//Метод включения разжатия челюстей. Управление графикой, у каждого индивидуально.
SetDecompression();
}
}
//Обнуление переменной, для того чтоб вычислять необходимость жевания относительно новой позиции пальцев.
if(Input.touchCount < 2) pastFingersDistance = 0;
//Если касаний стало меньше двух, а челюсти сжаты - они автоматически разжимаются.
if(Input.touchCount < 2 && isCompressed) SetDecompression();
fingersMunchDetectionMin — переменная, определяющая какое расстояние достаточно для того, чтобы начать жевание. Достаточно долго настраивал с помощью нескольких друзей. У каждого оказалось разное восприятие, вывел нечто среднее. В ходе тестов также выяснилось, что постоянно жевать пальцами пользователю попросту неудобно. Возникла необходимость сделать сжимание челюстей по простому тапу и метод, изложенный выше, приобрел следующий вид:
if (Input.touchCount > 1) {
//Работа с двумя первыми касаниями.
Touch touch1 = Input.GetTouch(0);
Touch touch2 = Input.GetTouch(1);
//Проверка если челюсти не работают
if (!compressing && !decompressing) {
float touch1Time = 0;
float touch2Time = 0;
//Вычисляется сколько времени активен тач 1
if (tapsHash.Contains (touch1.fingerId)) {
float startTouch1Time = (float) tapsHash [touch1.fingerId];
touch1Time = Time.time - startTouch1Time;
}
//Вычисляется сколько времени активен тач 2
if (tapsHash.Contains (touch2.fingerId)) {
float startTouch2Time = (float) tapsHash [touch2.fingerId];
touch2Time = Time.time - startTouch2Time;
}
//Если время отведенное на тап уже превышено для двух пальцев, персонаж передвигается между пальцами.
if (touch1Time > SECONDS_FOR_TAP && touch2Time > SECONDS_FOR_TAP) {
Vector3 targetPoint = Camera.main.ScreenToWorldPoint ((touch1.position + touch2.position) / 2);
targetPoint = new Vector3 (targetPoint.x, targetPoint.y, 0);
transform.position = Vector3.MoveTowards (transform.position, targetPoint, movementSpeed * Time.deltaTime);
}
}
float currentDistance = Vector2.Distance(touch1.position, touch2.position);
if(pastFingersDistance == 0) {
//Обнуление прошлого расстояния, если первый раз засечено два тача
pastFingersDistance = currentDistance;
}else if(currentDistance < pastFingersDistance - fingersMunchDetectionMin) {
//Метод включения сжатия челюстей. Управление графикой, у каждого индивидуально.
SetCompression();
}else if(currentDistance > pastFingersDistance + fingersMunchDetectionMin) {
//Метод включения разжатия челюстей. Управление графикой, у каждого индивидуально.
SetDecompression();
}
}
//Обнуление переменной, для того чтоб вычислять необходимость жевания относительно новой позиции пальцев.
if(Input.touchCount < 2) pastFingersDistance = 0;
//Если касаний стало меньше двух, а челюсти сжаты - они автоматически разжимаются.
if(Input.touchCount < 2 && isCompressed) SetDecompression();
//Метод который отвечает за осуществление жевания по тапу.
SetTapAttackListener ();
Константа SECONDS_FOR_TAP — время, отведенное на тап, как и расстояние на жевание, достаточно долго тестировалась и настраивалась. Ну и собственно последние методы, которые осуществляют жевание по простому тапу:
void SetTapAttackListener() {
if (Input.touchCount > 0) {
foreach (Touch touch in Input.touches) {
//Обработка активного тача
DetectOneTouchTap (touch);
}
}
}
void DetectOneTouchTap(Touch touch) {
if (touch.phase == TouchPhase.Began) {
//В случае если тач только начался, он записывается в хэш-таблицу для обработки.
//Ключ - ид тача, значение - начало прикосновения.
tapsHash.Add (touch.fingerId, Time.time);
} else if(touch.phase == TouchPhase.Ended) {
float startTouchTime = (float) tapsHash [touch.fingerId];
float timeOfTouch = Time.time - startTouchTime;
//Осуществление сжатия и разжатия челюстей, если тач был тапом
if (timeOfTouch <= SECONDS_FOR_TAP) {
SetCompression();
SetDecompression();
}
tapsHash.Remove (touch.fingerId);
}
}
В начале пытался найти сей алгоритм на просторах интернета, не для копипаста, а для проверки своего хода мыслей. Однако ничего не нашел и решил выложить его в помощь коллегам. Сейчас очень хорошо вижу, что код несколько хаотичен, ну, а в остальном — жду комментариев.
Update 1:
Демонстрация работы алгоритма: