您的位置:首页 > 移动开发 > Unity3D

Unity3d:一个简单的画圈圈手势判断

2018-04-11 04:31 316 查看
因为最近制作了一款儿童教育游戏,所以涉及到不少操作手势,其中比较有意思的是这样一个手势如图:



在游戏中这个画圈的手势往往位于一个可操作物体之上,并绕着中心顺时针旋转.这个物体可能是一个转盘,也可能是一个打蛋用搅拌器等等,意思是提示用户手指按照这个轨迹操作就可以得到正确的反馈,比如顺时针转动转盘实现开关…

在参考了unity商店的LogViewer插件以后(插件的开启手势正巧是在屏幕上画圈),我写了一个测试脚本如下:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class TestGestureAround : MonoBehaviour {
public bool bIsClockWise;
public Camera camGestureTest;
public GameObject objRotateTarget;
public float fGestureRadius = 50;
public float fGestureRadiusFix = 10;
public float fRotateFactor = 500f;//旋转系数

private Vector3 _v3AroundCenterPoint;
private bool _bGesturing;
private Vector3 _v3LastStarPoint;
private float _fSampleDisThreshold;
private List<Vector3> _inputGesturePhases = new List<Vector3>();

private float _fTotalRotate;

System.Action OnRotateFinish;
// Use this for initialization
void Awake () {
_fSampleDisThreshold = fGestureRadius / 4f;//(2*PI/24),PI~=3
_v3AroundCenterPoint = camGestureTest.WorldToScreenPoint(objRotateTarget.transform.position);
_fTotalRotate = 0;
}

// Update is called once per frame
void Update () {
if (Input.GetMouseButtonDown(0))
{
_bGesturing = true;
_v3LastStarPoint = Input.mousePosition;
}
else if (Input.GetMouseButtonUp(0))
{
_bGesturing = false;
}

if (_bGesturing)
{
//支持一个大概的圆形区,空心处理的小一点,就用修正值了
if (Vector3.Distance(_v3AroundCenterPoint, Input.mousePosition) < fGestureRadius + fGestureRadiusFix &&
Vector3.Distance(_v3AroundCenterPoint, Input.mousePosition) > fGestureRadiusFix)
{
var deltaVec = Input.mousePosition - _v3LastStarPoint;
if (deltaVec.sqrMagnitude > _fSampleDisThreshold * _fSampleDisThreshold)//超过阈值,记录一下
{
_inputGesturePhases.Add(deltaVec);
if (_inputGesturePhases.Count > 1)
{
int curCount = _inputGesturePhases.Count;
float multiDot = Vector3.Dot(_inputGesturePhases[curCount - 1], _inputGesturePhases[curCount - 2]);
Vector3 multiCross = Vector3.Cross(_inputGesturePhases[curCount - 1], _inputGesturePhases[curCount - 2]);
if (multiDot <= 0)//画圆只能是锐角
{
_inputGesturePhases.Clear();
}
else if (multiCross.z == 0 || (multiCross.z > 0 && !bIsClockWise) || (multiCross.z < 0 && bIsClockWise))//叉积右手法则,顺时针后一条叉前一条,z应该是正,z是0表示平行
{
_inputGesturePhases.Clear();
}
else
{
//通过上面几个条件测试表示是正在画一个圆,可以转动物体了
float rotateZ = bIsClockWise ? -1 * fRotateFactor * Time.deltaTime : 1 * fRotateFactor * Time.deltaTime;
_fTotalRotate += rotateZ;
objRotateTarget.transform.Rotate(new Vector3(0, 0, rotateZ));
_v3LastStarPoint = Input.mousePosition;

if (Mathf.Abs(_fTotalRotate) > 360)
{
Debug.Log("Around callbakc here");
_fTotalRotate = 0;
}
}
}
}
}
else {
_inputGesturePhases.Clear();//乱画就清除路径,重新来过
}
}
}

}


想要看上面代码的效果,只需要新建一个带摄像机的空场景,建立一个cube,放在屏幕可见的位置,然后脚本挂在摄像机上并拖上参数(如图)运行:



运行以后试着在物体的周围逆时针画圈,物体就能开始转动啦.

因为是测试,所以代码中只有mouse的检测而没有涉及到Input.Touch的逻辑,如果要支持移动端操作,这方面我在正式代码中采用了LeanTouch插件,是免费而且开源的,有兴趣的话可以检索unity商店下载并自行替换.

现在来解释一下代码的大概思路:

1.当点击屏幕且未松开的情况下,只要手指移动一段距离,就把这个路径记录在list内.

2.当list中有两个元素以后,计算这两条路径的角度和角度的方向.

3.如果满足期望,就认为是在画圈的操作中,否则就认为用户没有按照期望来画圈,清空所有路径.

看了上面三条以后,可以知道代码的核心算法其实就是求向量的点积和叉积:

float multiDot = Vector3.Dot(_inputGesturePhases[curCount - 1], _inputGesturePhases[curCount - 2]);

Vector3 multiCross = Vector3.Cross(_inputGesturePhases[curCount - 1], _inputGesturePhases[curCount - 2]);


点积可以判断向量的角度,而叉积可以知道这个角度的方向,也就是说是顺时针方向的角还是逆时针方向的角.

当我们的操作通过了距离,角度,方向的种种判断之后,可以认为用户的操作是正确的,因此,代码中将目标物体旋转了一个小小的角度.当用户持续操作,我们创建的cube就可以旋转完整的360度了.

在实际的开发中,可能我们遇到的情况会比这个更复杂,比如无论正逆都可以接受,或者逻辑和表现剥离后加入各类回调,或者是范围的控制这些其实都好解决,只要在对应的if判断前后做出修改即可.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  unity3d C#
相关文章推荐