Алгоритм жевания для тачскрина

от автора

Не так давно вышла моя первая личная игра для мобильных. Суть заключается в том, что врагов надо пережевывать пальцами. Алгоритм не уникальный, но встречается редко. На первый взгляд, нужно только прослушать зум-движение двух пальцев, вроде ничего сложного, однако в процессе разработки игры выявляются различные подводные камни.

  • Вычисление времени на сжатие челюстей;
  • Сочетание жевания с управлением персонажем;
  • Изменение параметров по ходу тестов.

Весь код написан на языке с# для движка 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); 	} } 

В начале пытался найти сей алгоритм на просторах интернета, не для копипаста, а для проверки своего хода мыслей. Однако ничего не нашел и решил выложить его в помощь коллегам. Сейчас очень хорошо вижу, что код несколько хаотичен, ну а в остальном — жду комментариев.

ссылка на оригинал статьи https://habrahabr.ru/post/282752/