Fat*_*tie 16 c# touch unity-game-engine
在现代Unity3D中,我们使用IPointerDownHandler系列调用.
关于IPointerDownHandler电话的家庭,
public class FingerMove:MonoBehaviour, IPointerDownHandler...
{
public void OnPointerDown (PointerEventData data)
{
Run Code Online (Sandbox Code Playgroud)
当然,他们太棒了
但是你如何以严肃的方式处理多种接触?
你可以"自己动手"跟踪自己的触摸,但看起来令人难以置信的Unity会希望你做那些绝对基本的事情.(我的意思是 - 它是一个游戏引擎.当然,我也可以编写我自己的渲染和物理!)
这是一个基本上"牛仔编程"的例子,只是手工完成而没有软件工程.什么是真正的解决方案?
//
// example of programming a pinch (as well as swipes) using modern Unity
//
// here we are forced to track "by hand" in your own code
// how many fingers are down and which
// fingers belong to you etc etc:
//
// pedagogic example code:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using UnityEngine.EventSystems;
public class FingerMove:MonoBehaviour,
IPointerDownHandler, IDragHandler, IPointerUpHandler
{
// these three for the ordinary one-finger-only drag
private Vector2 prevPoint;
private Vector2 newPoint;
private Vector2 screenTravel;
// and this one is the ordinary one-finger-only drag
private int currentMainFinger = -1;
// and this for the (strictly second finger only) drag...
private int currentSecondFinger = -1;
private Vector2 posA;
private Vector2 posB;
private float previousDistance = -1f;
private float distance;
private float pinchDelta = 0f;
public void OnPointerDown (PointerEventData data)
{
if (currentMainFinger == -1)
{
// this is the NEW currentMainFinger
currentMainFinger = data.pointerId;
prevPoint = data.position;
// and for the drag (if it becomes used)...
posA = data.position;
return;
}
if (currentSecondFinger == -1)
{
// this is the NEW currentSecondFinger
currentSecondFinger = data.pointerId;
posB = data.position;
figureDelta();
previousDistance = distance;
return;
}
Debug.Log("third+ finger! (ignore)");
}
public void OnDrag (PointerEventData data)
{
// handle single-finger moves (swipes, drawing etc) only:
if ( currentMainFinger == data.pointerId )
{
newPoint = data.position;
screenTravel = newPoint - prevPoint;
prevPoint = newPoint;
if (currentSecondFinger == -1)
{
Debug.Log("NO 2f");
_processSwipe(); // handle it your way
}
else
{
}
// and for two-finger if it becomes used next frame
// or is already being used...
posA = data.position;
}
if (currentSecondFinger == -1) return;
// handle two-finger (eg, pinch, rotate etc)...
if ( currentMainFinger == data.pointerId ) posA = data.position;
if ( currentSecondFinger == data.pointerId ) posB = data.position;
figureDelta();
pinchDelta = distance - previousDistance;
previousDistance = distance;
_processPinch(); // handle it your way
}
private void figureDelta()
{
// when/if two touches, keep track of the distance between them
distance = Vector2.Distance(posA, posB);
}
public void OnPointerUp (PointerEventData data)
{
if ( currentMainFinger == data.pointerId )
{
currentMainFinger = -1;
}
if ( currentSecondFinger == data.pointerId )
{
currentSecondFinger = -1;
}
}
private float sensitivity = 0.3f;
// in this example, the swipes/pinch affects these three calls:
public Changer orbitLR;
public Changer orbitUD;
public Changer distanceZ;
// initial values of those...
private float LR = -20f;
private float UD = 20f;
private float distanceCam = 5f;
private void _processSwipe()
{
// in this example, just left-right or up-down swipes
LR = LR + sensitivity * screenTravel.x;
UD = UD - sensitivity * screenTravel.y;
LR = Mathf.Clamp(LR, -150f, 30f);
UD = Mathf.Clamp(UD, 5f, 50f);
orbitLR.RotationY = LR;
orbitUD.RotationX = UD;
}
private void _processPinch()
{
// in this example, pinch to zoom
distanceCam = distanceCam - pinchDelta * 0.0125f;
distanceCam = Mathf.Clamp(distanceCam, 3f, 8f);
distanceZ.DistanceZ = distanceCam;
}
}
Run Code Online (Sandbox Code Playgroud)
(注意,请不要回答关于遗留的"Touches"系统,这是不可用的.这是关于正常的现代Unity开发.)
我不想回答我自己的问题,但经过大量调查和专家意见后,以下是唯一的方法.
让我们总结一下:
您可能会认为"如果不添加守护程序,它就不会自动运行".但是 - 事实上,使用Unity自己,你必须添加一个守护进程,即"TouchInput"系列.(他们有时会自动添加,有时他们会忘记,你必须这样做.)
很简单,自动化的"追逐"是愚蠢的,忘了它.你必须添加一个守护进程.
很简单,沿着StandAloneInputModule子类的路径走向不好的工程,因为Unity搞砸了.您只需在新守护进程中使用IPointerDownHandler/etc.关于此的更多讨论.
下面我举例说明"单触"和"捏".这些都是生产就绪的.您可以编写自己的其他情况,例如四触式等.因此,使用pinch-daemon(字面意思只是将其放在有问题的对象上),然后它非常容易处理pinches:
public void OnPinchZoom (float delta)
{
_processPinch(delta);
}
Run Code Online (Sandbox Code Playgroud)
很难看出它更容易.
所以这样做,直到Unity记得它的产品"在手机上使用"并且他们添加了捏等的电话.
制作一个立方体,将自己的脚本放在上面FingerMove.
制作剧本,比如移动相机LR,UD.(或者其他 - 只需调试.记录更改.)
粘贴在此处理程序脚本中...
/*
ISingleFingerHandler - handles strict single-finger down-up-drag
Put this daemon ON TO the game object, with a consumer of the service.
(Note - there are many, many philosophical decisions to make when
implementing touch concepts; just some issues include what happens
when other fingers touch, can you "swap out" etc. Note that, for
example, Apple vs. Android have slightly different takes on this.
If you wanted to implement slightly different "philosophy" you'd
do that in this script.)
*/
public interface ISingleFingerHandler
{
void OnSingleFingerDown (Vector2 position);
void OnSingleFingerUp (Vector2 position);
void OnSingleFingerDrag (Vector2 delta);
}
/* note, Unity chooses to have "one interface for each action"
however here we are dealing with a consistent paradigm ("dragging")
which has three parts; I feel it's better to have one interface
forcing the consumer to have the three calls (no problem if empty) */
using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
public class SingleFingerInputModule:MonoBehaviour,
IPointerDownHandler,IPointerUpHandler,IDragHandler
{
private ISingleFingerHandler needsUs = null;
// of course that would be a List,
// just one shown for simplicity in this example code
private int currentSingleFinger = -1;
private int kountFingersDown = 0;
void Awake()
{
needsUs = GetComponent(typeof(ISingleFingerHandler)) as ISingleFingerHandler;
// of course, you may prefer this to search the whole scene,
// just this gameobject shown here for simplicity
// alternately it's a very good approach to have consumers register
// for it. to do so just add a register function to the interface.
}
public void OnPointerDown(PointerEventData data)
{
kountFingersDown = kountFingersDown + 1;
if (currentSingleFinger == -1 && kountFingersDown == 1)
{
currentSingleFinger = data.pointerId;
if (needsUs != null) needsUs.OnSingleFingerDown(data.position);
}
}
public void OnPointerUp (PointerEventData data)
{
kountFingersDown = kountFingersDown - 1;
if ( currentSingleFinger == data.pointerId )
{
currentSingleFinger = -1;
if (needsUs != null) needsUs.OnSingleFingerUp(data.position);
}
}
public void OnDrag (PointerEventData data)
{
if ( currentSingleFinger == data.pointerId && kountFingersDown == 1 )
{
if (needsUs != null) needsUs.OnSingleFingerDrag(data.delta);
}
}
}
Run Code Online (Sandbox Code Playgroud)
将该守护进程与您的消费者一起放在游戏对象上FingerMove,然后忘掉它.就是现在
处理拖动:
public class FingerMove:MonoBehaviour, ISingleFingerHandler
{
public void OnSingleFingerDown(Vector2 position) {}
public void OnSingleFingerUp (Vector2 position) {}
public void OnSingleFingerDrag (Vector2 delta)
{
_processSwipe(delta);
}
private void _processSwipe(Vector2 screenTravel)
{
.. move the camera or whatever ..
}
}
Run Code Online (Sandbox Code Playgroud)
就像我说的,
现在让我们考虑两个手指的情况,一个缩放到缩放/解缩.
/*
IPinchHandler - strict two sequential finger pinch Handling
Put this daemon ON TO the game object, with a consumer of the service.
(Note, as always, the "philosophy" of a glass gesture is up to you.
There are many, many subtle questions; eg should extra fingers block,
can you 'swap primary' etc etc etc - program it as you wish.)
*/
public interface IPinchHandler
{
void OnPinchStart ();
void OnPinchEnd ();
void OnPinchZoom (float gapDelta);
}
/* note, Unity chooses to have "one interface for each action"
however here we are dealing with a consistent paradigm ("pinching")
which has three parts; I feel it's better to have one interface
forcing the consumer to have the three calls (no problem if empty) */
using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
public class PinchInputModule:MonoBehaviour,
IPointerDownHandler,IPointerUpHandler,IDragHandler
{
private IPinchHandler needsUs = null;
// of course that would be a List,
// just one shown for simplicity in this example code
private int currentFirstFinger = -1;
private int currentSecondFinger = -1;
private int kountFingersDown = 0;
private bool pinching = false;
private Vector2 positionFirst = Vector2.zero;
private Vector2 positionSecond = Vector2.zero;
private float previousDistance = 0f;
private float delta = 0f;
void Awake()
{
needsUs = GetComponent(typeof(IPinchHandler)) as IPinchHandler;
// of course, this could search the whole scene,
// just this gameobject shown here for simplicity
}
public void OnPointerDown(PointerEventData data)
{
kountFingersDown = kountFingersDown + 1;
if (currentFirstFinger == -1 && kountFingersDown == 1)
{
// first finger must be a pure first finger and that's that
currentFirstFinger = data.pointerId;
positionFirst = data.position;
return;
}
if (currentFirstFinger != -1 && currentSecondFinger == -1 && kountFingersDown == 2)
{
// second finger must be a pure second finger and that's that
currentSecondFinger = data.pointerId;
positionSecond = data.position;
FigureDelta();
pinching = true;
if (needsUs != null) needsUs.OnPinchStart();
return;
}
}
public void OnPointerUp (PointerEventData data)
{
kountFingersDown = kountFingersDown - 1;
if ( currentFirstFinger == data.pointerId )
{
currentFirstFinger = -1;
if (pinching)
{
pinching = false;
if (needsUs != null) needsUs.OnPinchEnd();
}
}
if ( currentSecondFinger == data.pointerId )
{
currentSecondFinger = -1;
if (pinching)
{
pinching = false;
if (needsUs != null) needsUs.OnPinchEnd();
}
}
}
public void OnDrag (PointerEventData data)
{
if ( currentFirstFinger == data.pointerId )
{
positionFirst = data.position;
FigureDelta();
}
if ( currentSecondFinger == data.pointerId )
{
positionSecond = data.position;
FigureDelta();
}
if (pinching)
{
if ( data.pointerId == currentFirstFinger || data.pointerId == currentSecondFinger )
{
if (kountFingersDown==2)
{
if (needsUs != null) needsUs.OnPinchZoom(delta);
}
return;
}
}
}
private void FigureDelta()
{
float newDistance = Vector2.Distance(positionFirst, positionSecond);
delta = newDistance - previousDistance;
previousDistance = newDistance;
}
}
Run Code Online (Sandbox Code Playgroud)
将该守护进程置于游戏对象上,您拥有该服务的使用者.请注意,"混合和匹配"绝对没有问题.在这个例子中,让我们同时拥有拖拽和捏合手势.就是现在
处理捏:
public class FingerMove:MonoBehaviour, ISingleFingerHandler, IPinchHandler
{
public void OnSingleFingerDown(Vector2 position) {}
public void OnSingleFingerUp (Vector2 position) {}
public void OnSingleFingerDrag (Vector2 delta)
{
_processSwipe(delta);
}
public void OnPinchStart () {}
public void OnPinchEnd () {}
public void OnPinchZoom (float delta)
{
_processPinch(delta);
}
private void _processSwipe(Vector2 screenTravel)
{
.. handle drag (perhaps move LR/UD)
}
private void _processPinch(float delta)
{
.. handle zooming (perhaps move camera in-and-out)
}
}
Run Code Online (Sandbox Code Playgroud)
就像我说的,
为了看看这有多优雅,可以考虑这样的问题:当捏时,你想要"暂停"拖动,还是让两者都发生?令人惊奇的是,你只需在SingleFingerInputModule.cs中编程即可.在特定示例中,我希望它"保持"拖动,而/如果用户正在缩放,那么上面的SingleFingerInputModule.cs就是这样编程的.您可以轻松地修改它以进行持续拖动,更改为质心,取消拖动或任何您想要的内容.令人惊奇的是,FingerMove.cs完全没有受到影响!令人难以置信的有用的抽象!
请注意,对于Gökhan上面的优秀四角示例,我会这样写:
public class FingerStretch:MonoBehaviour, IFourCornerHandler
{
public void OnFourCornerChange (Vector2 a, b, c, d)
{
... amazingly elegant solution
... Gökhan does all the work in FourCornerInputModule.cs
... here I just subscribe to it. amazingly simple
}
Run Code Online (Sandbox Code Playgroud)
这只是一种令人难以置信的简单方法.
Gökhan会在FourCornerInputModule.cs中封装手指的所有逻辑,这些逻辑将具有IFourCornerHandler接口.请注意,FourCornerInputModule会明智地做出所有的哲学决策(例如,你必须把所有四个手指都放下,如果你有一个额外的,等等).
以下是出现的问题:
在所谓的"独立输入模块"中查看您的Unity项目,该模块是一个带有a EventSystem和a 的游戏对象StandAloneInputModule
实际上,您可以"从头开始编写"类似SingleFingerInputModule.cs或PinchInputModule.cs的内容,这样它就可以像Unity的StandAloneInputModule一样工作.
但是有一个具体的,可拆卸的问题:荒谬的是,你不能使用OO原则:在我上面的代码中SingleFingerInputModule.cs,我们非常明智地 - 当然 - 使用Unity已经完成的现有的惊人的IPointerDownHandler等功能和我们(基本上)"子类那个",增加了一点逻辑.这正是你应该,真的必须做的.相反:如果你决定"制作像StandAloneInputModule一样的东西",那就是惨败 - 你必须重新开始,可能会复制并粘贴Unity的源代码(对于IPointerDownHandler等),并按照你的方式修改它,当然是您不应该从事软件工程的一个确切示例.
请注意,如果你去"制作像StandAloneInputModule一样的东西"的路线,事实上,你仍然必须这样做!这有点奇怪; 没有优势.
如果你去"制作像StandAloneInputModule一样工作的东西"的路线,Unity就会有一个"执行......"调用......就这样做.它仅仅是一个用于"呼叫订阅者"的宏(我们每天都在每个脚本中进行除了最微不足道的事情之外); 没有优势.
事实上:我个人认为订阅电话实际上要好得多,正如Everts在这里建议的那样,只需将其作为接口调用之一.我只是认为这比试图"变得像Unity"的魔术召唤系统(它真的根本不起作用)更好的工程 - 你必须"记住附加"StandAloneInputModule无论如何).
我开始相信了
无可争议的是,开始重写代码以"制作像StandAloneInputModule一样的东西"是一个坏主意.
如果你试图"制作像StandAloneInputModule一样的东西"......那么无论如何你必须"记住添加它",为了善良.
如果你试图"制作像StandAloneInputModule一样的东西",那么Unity给你的"执行......"调用几乎不存在优势,这是一行代码而不是你的代码(更短,更清晰,更快)代码"呼叫用户".再简单地进行订阅调用就更加明显和明确,你可以说任何非Unity程序员都会这样做.
所以对我来说,今天Unity中最好的方法就是编写模块/接口,例如SingleFingerInputModule.cs,PinchInputModule.cs,FourCornerInputModule.cs,将它放在游戏对象上,你想拥有那些消费者 - 你就是完成."就这么简单."
public class Zoom:MonoBehaviour, ISingleFingerHandler, IPinchHandler
{
public void OnPinchZoom (float delta)
{
...
Run Code Online (Sandbox Code Playgroud)
在 Unity 中没有预定义的方法来执行此操作。您所能做的就是再次采用面向对象的方法定制解决方案。最好的办法是将事件检测和事件处理分开。
要问的主要问题是,如何以 OOP 方式表示手指、触摸、手势等。我选择这样做:
f1, f2, f3,则创建的手指组合为:f3, f1f3, f2f3, f1f2f3。这在使用多个手指时提供了最大的灵活性。你可以做这样的手势。例如,如果你想做锚点手势,你只需要f2f3但手势f1也必须存在。f1在这种情况下你可以简单地忽略。另外,您通常需要从多点触控事件中获得以下内容:
前面的长代码:
using UnityEngine;
using UnityEngine.EventSystems;
using System.Linq;
using System.Collections.Generic;
public class MultitouchHandler : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler {
public List<Finger> Fingers = new List<Finger>();
public List<FingerCombination> FingerCombinations = new List<FingerCombination>();
public FingerCombination GetFingerCombination(params int[] fingerIndices) {
var fc = FingerCombinations.Find(x => x.IDs.Count == fingerIndices.Length && fingerIndices.All(y => x.IDs.Contains(Fingers[y].ID)));
if (fc != null) return fc;
fc = new FingerCombination() {
Fingers = fingerIndices.Select(x => Fingers[x]).ToList()
};
fc.IDs = fc.Fingers.Select(x => x.ID).ToList();
fc.Data = Fingers.Select(x => x.Data).ToList();
fc.PreviousData = Fingers.Select(x => x.Data).ToList();
FingerCombinations.Add(fc);
return fc;
}
public delegate void MultitouchEventHandler(int touchCount, MultitouchHandler sender);
public event MultitouchEventHandler OnFingerAdded;
public event MultitouchEventHandler OnFingerRemoved;
public void OnDrag(PointerEventData eventData) {
var finger = Fingers.Find(x => x.ID == eventData.pointerId);
var fcs = FingerCombinations.Where(x => x.IDs.Contains(eventData.pointerId));
finger.PreviousData = finger.Data;
finger.Data = eventData;
foreach (var fc in fcs) {
fc.PreviousData = fc.Data;
fc.Data = fc.Fingers.Select(x => x.Data).ToList();
fc.PreviousGesture = fc.Gesture;
fc.Gesture = new Gesture() {
Center = fc.Center,
Size = fc.Size,
Angle = fc.Angle,
SizeDelta = 1
};
if (fc.PreviousGesture != null) {
fc.Gesture.CenterDelta = fc.Center - fc.PreviousGesture.Center;
fc.Gesture.SizeDelta = fc.Size / fc.PreviousGesture.Size;
fc.Gesture.AngleDelta = fc.Angle - fc.PreviousGesture.Angle;
}
fc.Changed();
}
}
public void OnPointerDown(PointerEventData eventData) {
var finger = new Finger() { ID = eventData.pointerId, Data = eventData };
Fingers.Add(finger);
if (OnFingerAdded != null)
OnFingerAdded(Fingers.Count, this);
}
public void OnPointerUp(PointerEventData eventData) {
Fingers.RemoveAll(x => x.ID == eventData.pointerId);
if (OnFingerRemoved != null)
OnFingerRemoved(Fingers.Count, this);
var fcs = FingerCombinations.Where(x => x.IDs.Contains(eventData.pointerId));
foreach (var fc in fcs) {
fc.Finished();
}
FingerCombinations.RemoveAll(x => x.IDs.Contains(eventData.pointerId));
}
public class Finger {
public int ID;
public PointerEventData Data;
public PointerEventData PreviousData;
}
public class FingerCombination {
public List<int> IDs = new List<int>();
public List<Finger> Fingers;
public List<PointerEventData> PreviousData;
public List<PointerEventData> Data;
public delegate void GestureEventHandler(Gesture gesture, FingerCombination sender);
public event GestureEventHandler OnChange;
public delegate void GestureEndHandler(FingerCombination sender);
public event GestureEndHandler OnFinish;
public Gesture Gesture;
public Gesture PreviousGesture;
public Vector2 Center
{
get { return Data.Aggregate(Vector2.zero, (x, y) => x + y.position) / Data.Count; }
}
public float Size
{
get
{
if (Data.Count == 1) return 0;
var magnitudeSum = 0f;
for (int i = 1; i < Data.Count; i++) {
var dif = (Data[i].position - Data[0].position);
magnitudeSum += dif.magnitude;
}
return magnitudeSum / (Data.Count - 1);
}
}
public float Angle
{
get
{
if (Data.Count == 1) return 0;
var angleSum = 0f;
for (int i = 1; i < Data.Count; i++) {
var dif = (Data[i].position - Data[0].position);
angleSum += Mathf.Atan2(dif.y, dif.x) * Mathf.Rad2Deg;
}
return angleSum / (Data.Count - 1);
}
}
internal void Changed() {
if (OnChange != null)
OnChange.Invoke(Gesture, this);
}
internal void Finished() {
if (OnFinish != null)
OnFinish.Invoke(this);
}
}
public class Gesture {
public Vector2 Center;
public float Size;
public float Angle;
public Vector2 CenterDelta;
public float SizeDelta;
public float AngleDelta;
}
}
Run Code Online (Sandbox Code Playgroud)
下面是一个示例,展示了如何使用 4 个手指来使用它。
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
public class MultiTouchTest : MonoBehaviour {
public Vector2 rectSize = Vector2.one * 2;
public Vector2 skewedRectSize = Vector2.one;
public Vector2 rectPos = Vector2.zero;
public List<Vector3> Fingers = new List<Vector3>();
void Start() {
var h = GetComponent<MultitouchHandler>();
h.OnFingerAdded += OnGestureStart;
}
private void OnGestureStart(int touchCount, MultitouchHandler sender) {
if (touchCount != 4) return;
var fc = sender.GetFingerCombination(0, 1, 2, 3);
fc.OnChange += OnGesture;
}
private void OnGesture(MultitouchHandler.Gesture gesture, MultitouchHandler.FingerCombination sender) {
rectSize *= gesture.SizeDelta;
Fingers = sender.Fingers.Select(x => Camera.main.ScreenToWorldPoint(x.Data.position)).ToList();
var tan = Mathf.Tan(gesture.Angle * Mathf.Deg2Rad);
skewedRectSize = new Vector2(rectSize.x / tan, rectSize.y * tan);
rectPos += gesture.CenterDelta / 50;
}
public void OnDrawGizmos() {
Gizmos.color = Color.red;
Gizmos.DrawCube(rectPos, skewedRectSize);
Gizmos.color = Color.blue;
foreach (var finger in Fingers) Gizmos.DrawSphere(finger + Vector3.forward, 0.5f);
}
}
Run Code Online (Sandbox Code Playgroud)
结果如下:
但这只是一个简单的例子。一个好的答案对于 SO 的格式来说太长了。