using Newtonsoft.Json; using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; /* 本文件用于控制手势的采集和比对 * 也包含导出和导入手势的功能 */ public class GestureManager { // Start is called once before the first execution of Update after the MonoBehaviour is created public List gesturePointOffsetList { set; get; } = new List(); public int sampleRate { set; get; } = 20; // 采样率 private Vector2 prePointLoc = new Vector2(0, 0); // 第一个点 private Vector2 curPointLoc = new Vector2(0, 0); // 第二个点 private float timer = 0f; // 配合interval使用 public bool isRecording; private float interval; // 采样间隔,使用采样率计算 private float lengthTorrence = 0.25f; // 允许的长度误差 private DateTime startTime; private DateTime endTime; private float duration; private int screenWidth; private int screenHeight; public GestureManager() { interval = 1f / sampleRate; screenWidth = Screen.width; screenHeight = Screen.height; } public string ExportToJson() { string json = string.Empty; json = JsonConvert.SerializeObject(this); return json; } public void ImportFromJson(string json) { isRecording = false; var data = JsonConvert.DeserializeObject>(json); sampleRate = int.Parse(data["sampleRate"].ToString()); gesturePointOffsetList.Clear(); GesturePointOffset[] pointOffsets = JsonConvert.DeserializeObject(data["gesturePointOffsetList"].ToString()); foreach (var point in pointOffsets) { gesturePointOffsetList.Add(point); } } // 添加一个运动轨迹 public void Record() { Vector2 pointLoc = Pointer.current.position.ReadValue(); if (isRecording) { // if (!IsMouseOnScreen()) // { // StopRecording(); // Debug.Log("Mouse not on screen"); // return; // } if (gesturePointOffsetList.Count == 0) { prePointLoc = pointLoc; curPointLoc = pointLoc; GesturePointOffset gesturePointMovement = new GesturePointOffset(0, 0); gesturePointOffsetList.Add(gesturePointMovement); return; } else { //Debug.Log("interval:" + interval.ToString()); timer += Time.deltaTime; if (timer >= interval) { timer = 0f; curPointLoc = pointLoc; Vector2 normalizedCurPoint = NormalizePoint(curPointLoc); Vector2 normalizedPrePoint = NormalizePoint(prePointLoc); float distance = Vector2.Distance(normalizedCurPoint, normalizedPrePoint); Vector2 direction = curPointLoc - prePointLoc; float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg; // float angle = Vector2.Angle(prePointLoc, curPointLoc); GesturePointOffset gesturePointMovement = new GesturePointOffset(distance, angle); if (distance * angle != 0) { gesturePointOffsetList.Add(gesturePointMovement); } prePointLoc = curPointLoc; } } } } public float CompareGesture(GestureManager anotherGesture) { float score = 0f; if (sampleRate != anotherGesture.sampleRate) { Debug.Log("Sample rate not match"); return -1f; } if (gesturePointOffsetList.Count == 0 || anotherGesture.gesturePointOffsetList.Count == 0) { Debug.Log("Gesture point count is 0"); return -1f; } int maxGesturePoints = Mathf.Max(gesturePointOffsetList.Count, anotherGesture.gesturePointOffsetList.Count); int minGesturePoints = Mathf.Min(gesturePointOffsetList.Count, anotherGesture.gesturePointOffsetList.Count); for (int i = 0; i < minGesturePoints; i++) { float distanceMin = Mathf.Min(gesturePointOffsetList[i].distance, anotherGesture.gesturePointOffsetList[i].distance); float distanceMax = Mathf.Max(gesturePointOffsetList[i].distance, anotherGesture.gesturePointOffsetList[i].distance); float angleMin = Mathf.Min(gesturePointOffsetList[i].angle, anotherGesture.gesturePointOffsetList[i].angle); float angleMax = Mathf.Max(gesturePointOffsetList[i].angle, anotherGesture.gesturePointOffsetList[i].angle); float distanceScore = 0f; if (distanceMin == 0) { distanceScore = 1f; } else { distanceScore = distanceMin / distanceMax; } float angleDitance1 = angleMax - angleMin; float angleDitance2 = 360 - angleDitance1; float angleScore = 1 - (Mathf.Min(angleDitance1, angleDitance2) / 180); score = score * (i / (i + 1f)) + (distanceScore + angleScore) / 2 * (1f / (i + 1)); } // 如果长度超过公差范围,就要考虑调整score if ((maxGesturePoints / minGesturePoints) > (1f + lengthTorrence)) { int deltaGesturePoints = Mathf.RoundToInt(maxGesturePoints - minGesturePoints * (1f + lengthTorrence)); score = score * ((float)minGesturePoints / (minGesturePoints + deltaGesturePoints)); } return score; } public void ResetGesture() { gesturePointOffsetList.Clear(); } public void StartRecording() { isRecording = true; gesturePointOffsetList.Clear(); timer = 0f; prePointLoc = Vector2.zero; curPointLoc = Vector2.zero; startTime = DateTime.Now; } public bool StopRecording() { isRecording = false; endTime = DateTime.Now; duration = (float)(endTime - startTime).TotalSeconds; Debug.Log("gesture duration:" + duration.ToString()); Debug.Log("gesture point count:" + gesturePointOffsetList.Count.ToString()); // 如果采样过少,就不记录这个手势 if (gesturePointOffsetList.Count <= sampleRate/2) { Debug.Log("gesture duration too short"); gesturePointOffsetList.Clear(); } if (gesturePointOffsetList.Count > 0) { return true; } else { return false; } } private bool IsMouseOnScreen() { Vector2 mousePos = Pointer.current.position.ReadValue(); return mousePos.x >= 0 && mousePos.x < Screen.width && mousePos.y >= 0 && mousePos.y < Screen.height; } private Vector2 NormalizePoint(Vector2 point) { float x = point.x / screenWidth; float y = point.y / screenHeight; return new Vector2(x, y); } private Vector2 DenormalizePoint(Vector2 point) { float x = point.x * screenWidth; float y = point.y * screenHeight; return new Vector2(x, y); } public void SetSampleRate(int rate) { sampleRate = rate; interval = 1f / sampleRate; } }