端午三天假,刚过完端午就被老板拉过去加班去了,端午三天假加了两天班,好了不吐槽了。记录一下Unity通过TouchScript插件中TUIO协议的使用以及代码的简单分析。
先说一下项目的大致情况,对方通过TUIO协议发送Blob格式的消息,发送的Blob消息中的面积(Area)是一个识别的重要信息,但TouchScript中返回的是Pointer类,但这个类中并没有我需要的消息。后来分析了一下代码的流向最终拿到了需要的信息。
首先分析一下Tuio的输入,先看一下Unity函数:
///
protected override void OnEnable()
{
base.OnEnable();
screenWidth = Screen.width;
screenHeight = Screen.height;
cursorProcessor = new CursorProcessor();
cursorProcessor.CursorAdded += OnCursorAdded;
cursorProcessor.CursorUpdated += OnCursorUpdated;
cursorProcessor.CursorRemoved += OnCursorRemoved;
blobProcessor = new BlobProcessor();
blobProcessor.BlobAdded += OnBlobAdded;
blobProcessor.BlobUpdated += OnBlobUpdated;
blobProcessor.BlobRemoved += OnBlobRemoved;
objectProcessor = new ObjectProcessor();
objectProcessor.ObjectAdded += OnObjectAdded;
objectProcessor.ObjectUpdated += OnObjectUpdated;
objectProcessor.ObjectRemoved += OnObjectRemoved;
connect();
}
///
protected override void OnDisable()
{
disconnect();
base.OnDisable();
}
在OnEnable函数中,首先记录了一下屏幕的宽高,其次new了三个处理类,分别处理鼠标、Blob和物体的,并且分别注册了处理类的回调,当添加时、当更新时、当移除时,最后调用了connect连接函数。按下F12追踪一下connect函数;
private void connect()
{
if (!Application.isPlaying) return;
if (server != null) disconnect();
server = new TuioServer(TuioPort);
server.Connect();
updateInputs();
}
可以看到此处新建一个TuioServer类,并调用器自身的Connect函数,最后调用updateInputs函数。按下F12看一下TuioServer;
namespace TUIOsharp
{
public class TuioServer
{
public TuioServer();
public TuioServer(int port);
public int Port { get; }
public event EventHandler ErrorOccured;
public void AddDataProcessor(IDataProcessor processor);
public void Connect();
public void Disconnect();
public void RemoveAllDataProcessors();
public void RemoveDataProcessor(IDataProcessor processor);
}
}
这里可以看到TuioServer类中有一个添加处理器的函数AddDataProcessor和移除处理器的函数RemoveDataProcessor,这个下边会说到。我们再看一下updateInputs函数;
private void updateInputs()
{
if (server == null) return;
if ((supportedInputs & InputType.Cursors) != 0) server.AddDataProcessor(cursorProcessor);
else server.RemoveDataProcessor(cursorProcessor);
if ((supportedInputs & InputType.Blobs) != 0) server.AddDataProcessor(blobProcessor);
else server.RemoveDataProcessor(blobProcessor);
if ((supportedInputs & InputType.Objects) != 0) server.AddDataProcessor(objectProcessor);
else server.RemoveDataProcessor(objectProcessor);
}
可以看到再updateInputs函数内部,把再OnEnable函数中新建的三个处理类添加进TuioServer中。好的,我们再看一下TuioInput注册三个处理类的回调函数(由于对方是发送Blob消息的,这里只看一下Blob的回调函数,其他类似);
private void OnBlobAdded(object sender, TuioBlobEventArgs e)
{
var entity = e.Blob;
lock (this)
{
var x = entity.X * screenWidth;
var y = (1 - entity.Y) * screenHeight;
var touch = internalAddObject(new Vector2(x, y));
updateBlobProperties(touch, entity);
blobToInternalId.Add(entity, touch);
}
}
首先看一下参数TuioBlobEventArgs;
namespace TUIOsharp.DataProcessors
{
public class TuioBlobEventArgs : EventArgs
{
public TuioBlob Blob;
public TuioBlobEventArgs(TuioBlob blob);
}
}
在追踪一下TuioBlob类;
namespace TUIOsharp.Entities
{
public class TuioBlob : TuioEntity
{
public TuioBlob(int id);
public TuioBlob(int id, float x, float y, float angle, float width, float height, float area, float velocityX, float velocityY, float rotationVelocity, float acceleration, float rotationAcceleration);
public float Angle { get; }
public float Width { get; }
public float Height { get; }
public float Area { get; }
public float RotationVelocity { get; }
public float RotationAcceleration { get; }
public void Update(float x, float y, float angle, float width, float height, float area, float velocityX, float velocityY, float rotationVelocity, float acceleration, float rotationAcceleration);
}
}
好的,在这个类中可以看到有很多信息,坐标、角度、宽度、高度等一些列信息,我需要的面积也在其中,下一步就是怎么取出数据了,由于没怎么用过TUIO,也没研究过TouchScript关于这块的内容走了很多岔子,这就不提了。关于这个类的一些参考可以看一下TUIO官网的说明,链接在这:http://www.tuio.org/?specification
接着看OnBlobAdded函数,在函数内部可以看到x值乘以了缓存的屏幕宽度、y值乘以了缓存的屏幕高度,可以断定传过来的xy是归一化后的数字(即介于0-1之间),同时y轴翻转;紧接着调用internalAddObject函数,并传参xy,看一下internalAddObject函数;
private ObjectPointer internalAddObject(Vector2 position)
{
var pointer = objectPool.Get();
pointer.Position = remapCoordinates(position);
pointer.Buttons |= Pointer.PointerButtonState.FirstButtonDown | Pointer.PointerButtonState.FirstButtonPressed;
addPointer(pointer);
pressPointer(pointer);
return pointer;
}
在这里更新了一下位置,随后调用addPointer和pressPointer两个函数,这里看一下addPointer函数;
///
/// Adds the pointer to the system.
///
/// The pointer to add.
protected virtual void addPointer(Pointer pointer)
{
manager.INTERNAL_AddPointer(pointer);
}
这里的函数实际调用了父类InputSource的函数,TuioInput类继承InputSource类,函数内部又调用了manager.INTERNAL_AddPointer函数,这里的manager是TouchManagerInstance类,一个比较核心的类。看一下INTERNAL_AddPointer函数;
internal void INTERNAL_AddPointer(Pointer pointer)
{
lock (pointerLock)
{
pointer.INTERNAL_Init(nextPointerId);
pointersAdded.Add(pointer);
#if TOUCHSCRIPT_DEBUG
pLogger.Log(pointer, PointerEvent.IdAllocated);
#endif
nextPointerId++;
}
}
在这里调用了Pointer类自身的INTERNAL_Init函数,这里就不看了,INTERNAL_Init函数内部更新了一下Id并 记录了一下位置,;在调用Pointer类自身的INTERNAL_Init函数后,将其添加至pointersAdded这个list中,并自动更新下一个Id,关于pointersAdded这里先不深究,等一下再说;接着看一下pressPointer函数;
///
/// Mark the pointer as touching the surface.
///
/// The pointer.
protected virtual void pressPointer(Pointer pointer)
{
if (pointer == null) return;
manager.INTERNAL_PressPointer(pointer.Id);
}
和上边相同调用了manager的函数,看一下INTERNAL_PressPointer函数;
internal void INTERNAL_PressPointer(int id)
{
lock (pointerLock)
{
Pointer pointer;
if (!idToPointer.TryGetValue(id, out pointer))
{
// This pointer was added this frame
if (!wasPointerAddedThisFrame(id, out pointer))
{
// No pointer with such id
#if TOUCHSCRIPT_DEBUG
if (DebugMode)
Debug.LogWarning("TouchScript > Pointer with id [" + id +
"] is requested to PRESS but no pointer with such id found.");
#endif
return;
}
}
#if TOUCHSCRIPT_DEBUG
if (!pointersPressed.Add(id))
if (DebugMode)
Debug.LogWarning("TouchScript > Pointer with id [" + id +
"] is requested to PRESS more than once this frame.");
#else
pointersPressed.Add(id);
#endif
}
}
在这里可以看到函数首先会从idToPointer这个字典中尝试获取Pointer,由于取反,当字典中存在时将会执行
pointersPressed.Add(id);
这条语句,将id添加至pointersPressed中;当字典不存在时,会调用wasPointerAddedThisFrame进行一次判断(应该是判断是否是新添加的Pointer),同样取反,true->return,false->添加至pointersPressed。wasPointerAddedThisFrame函数内部如下:
private bool wasPointerAddedThisFrame(int id, out Pointer pointer)
{
pointer = null;
foreach (var p in pointersAdded)
{
if (p.Id == id)
{
pointer = p;
return true;
}
}
return false;
}
通过foreach进行判断,有意思的是遍历是pointersAdded,上边分析INTERNAL_AddPointer函数时,最终的Pointer被添加的就是pointersAdded。
好的,经过上边一串有点长的函数调用分析,终于把OnBlobAdded函数内部中的internalAddObject函数分析完毕,请滚动一下鼠标重新看一下OnBlobAdded函数,接下来要继续分析下边的执行语句;调用updateBlobProperties函数,并把一些数据添加到blobToInternalId字典中;
private void updateBlobProperties(ObjectPointer obj, TuioBlob target)
{
obj.Width = target.Width;
obj.Height = target.Height;
obj.Angle = target.Angle;
}
updateBlobProperties函数内部更新了一些属性,如上代码所见,我们是不是可以把Area(面积)更新一下???这样我们就可以拿到自己想要的数据了,事实证明这样是可以的,我最终也是这样解决的,理论上这篇博客已经给出了开始问题的解决方案,只需要订阅TouchManager的相应事件即可,比如TouchManager.Instance.PointersAdded、TouchManager.Instance.PointersUpdated、TouchManager.Instance.PointersRemoved……在回调时把相应的Pointer转换为ObjectPointer即可拿到area(ObjectPointer时是Pointer的子类)。但最终我继续分析了其他代码,把Pointer在TouchScript内部流通给搞明白了。所以,我们继续分析;
OnBlobUpdated函数:
private void OnBlobUpdated(object sender, TuioBlobEventArgs e)
{
var entity = e.Blob;
lock (this)
{
ObjectPointer touch;
if (!blobToInternalId.TryGetValue(entity, out touch)) return;
var x = entity.X * screenWidth;
var y = (1 - entity.Y) * screenHeight;
touch.Position = remapCoordinates(new Vector2(x, y));
updateBlobProperties(touch, entity);
updatePointer(touch);
}
}
在这里会首先对blobToInternalId字典尝试获取ObjectPointer(在OnBlobAdded函数最后把OnBlobAdded添加到了blobToInternalId字典中),如果获取成功,会计算位置,并通过remapCoordinates最终调用函数把位置重新映射一下(这里就补贴其他代码了,如果有兴趣请自行查看,下同),调用updateBlobProperties(上边分析过了)更新属性,最终调用updatePointer函数,updatePointer函数内部调用manager.INTERNAL_UpdatePointer函数,该函数如下:
internal void INTERNAL_UpdatePointer(int id)
{
lock (pointerLock)
{
Pointer pointer;
if (!idToPointer.TryGetValue(id, out pointer))
{
// This pointer was added this frame
if (!wasPointerAddedThisFrame(id, out pointer))
{
// No pointer with such id
#if TOUCHSCRIPT_DEBUG
if (DebugMode) Debug.LogWarning("TouchScript > Pointer with id [" + id + "] is requested to MOVE to but no pointer with such id found.");
#endif
return;
}
}
pointersUpdated.Add(id);
}
}
和INTERNAL_PressPointer函数类似,只不过最后添加的是pointersUpdated而不是pointersPressed。
OnBlobRemoved函数:
private void OnBlobRemoved(object sender, TuioBlobEventArgs e)
{
var entity = e.Blob;
lock (this)
{
ObjectPointer touch;
if (!blobToInternalId.TryGetValue(entity, out touch)) return;
blobToInternalId.Remove(entity);
releasePointer(touch);
removePointer(touch);
}
}
同样的先在blobToInternalId中尝试获取ObjectPointer,如果获取成功,则从blobToInternalId移除,且调用releasePointer函数和removePointer,这两个函数最终调用了TouchManagerInstance的INTERNAL_ReleasePointer函数和INTERNAL_RemovePointer函数;其内部实现如下:
///
internal void INTERNAL_ReleasePointer(int id)
{
lock (pointerLock)
{
Pointer pointer;
if (!idToPointer.TryGetValue(id, out pointer))
{
// This pointer was added this frame
if (!wasPointerAddedThisFrame(id, out pointer))
{
// No pointer with such id
#if TOUCHSCRIPT_DEBUG
if (DebugMode)
Debug.LogWarning("TouchScript > Pointer with id [" + id +
"] is requested to END but no pointer with such id found.");
#endif
return;
}
}
#if TOUCHSCRIPT_DEBUG
if (!pointersReleased.Add(id))
if (DebugMode)
Debug.LogWarning("TouchScript > Pointer with id [" + id +
"] is requested to END more than once this frame.");
#else
pointersReleased.Add(id);
#endif
}
}
///
internal void INTERNAL_RemovePointer(int id)
{
lock (pointerLock)
{
Pointer pointer;
if (!idToPointer.TryGetValue(id, out pointer))
{
// This pointer was added this frame
if (!wasPointerAddedThisFrame(id, out pointer))
{
// No pointer with such id
#if TOUCHSCRIPT_DEBUG
if (DebugMode)
Debug.LogWarning("TouchScript > Pointer with id [" + id +
"] is requested to REMOVE but no pointer with such id found.");
#endif
return;
}
}
#if TOUCHSCRIPT_DEBUG
if (!pointersRemoved.Add(pointer.Id))
if (DebugMode)
Debug.LogWarning("TouchScript > Pointer with id [" + id +
"] is requested to REMOVE more than once this frame.");
#else
pointersRemoved.Add(pointer.Id);
#endif
}
}
这两个函数内部极为相似,不同的是最后是添加的不是同一个HashSet。
先看一下几个比较眼熟的字段:
private List pointers = new List(30);
private HashSet pressedPointers = new HashSet();
private Dictionary idToPointer = new Dictionary(30);
// Upcoming changes
private List pointersAdded = new List(10);
private HashSet pointersUpdated = new HashSet();
private HashSet pointersPressed = new HashSet();
private HashSet pointersReleased = new HashSet();
private HashSet pointersRemoved = new HashSet();
private HashSet pointersCancelled = new HashSet();
我们看一下它的Update函数:
private void Update()
{
sendFrameStartedToPointers();
updateInputs();
updatePointers();
}
这里只看一下updatePointers函数:
private void updatePointers()
{
IsInsidePointerFrame = true;
if (frameStartedInvoker != null) frameStartedInvoker.InvokeHandleExceptions(this, EventArgs.Empty);
// need to copy buffers since they might get updated during execution
List addedList = null;
List updatedList = null;
List pressedList = null;
List releasedList = null;
List removedList = null;
List cancelledList = null;
lock (pointerLock)
{
if (pointersAdded.Count > 0)
{
addedList = pointerListPool.Get();
addedList.AddRange(pointersAdded);
pointersAdded.Clear();
}
if (pointersUpdated.Count > 0)
{
updatedList = intListPool.Get();
updatedList.AddRange(pointersUpdated);
pointersUpdated.Clear();
}
if (pointersPressed.Count > 0)
{
pressedList = intListPool.Get();
pressedList.AddRange(pointersPressed);
pointersPressed.Clear();
}
if (pointersReleased.Count > 0)
{
releasedList = intListPool.Get();
releasedList.AddRange(pointersReleased);
pointersReleased.Clear();
}
if (pointersRemoved.Count > 0)
{
removedList = intListPool.Get();
removedList.AddRange(pointersRemoved);
pointersRemoved.Clear();
}
if (pointersCancelled.Count > 0)
{
cancelledList = intListPool.Get();
cancelledList.AddRange(pointersCancelled);
pointersCancelled.Clear();
}
}
var count = pointers.Count;
for (var i = 0; i < count; i++)
{
pointers[i].INTERNAL_UpdatePosition();
}
if (addedList != null)
{
updateAdded(addedList);
pointerListPool.Release(addedList);
}
if (updatedList != null)
{
updateUpdated(updatedList);
intListPool.Release(updatedList);
}
if (pressedList != null)
{
updatePressed(pressedList);
intListPool.Release(pressedList);
}
if (releasedList != null)
{
updateReleased(releasedList);
intListPool.Release(releasedList);
}
if (removedList != null)
{
updateRemoved(removedList);
intListPool.Release(removedList);
}
if (cancelledList != null)
{
updateCancelled(cancelledList);
intListPool.Release(cancelledList);
}
if (frameFinishedInvoker != null) frameFinishedInvoker.InvokeHandleExceptions(this, EventArgs.Empty);
IsInsidePointerFrame = false;
}
首先看一下pointersAdded,在这里把pointersAdded装进addedList中;
if (pointersAdded.Count > 0)
{
addedList = pointerListPool.Get();
addedList.AddRange(pointersAdded);
pointersAdded.Clear();
}
最后调用updateAdded函数:
if (addedList != null)
{
updateAdded(addedList);
pointerListPool.Release(addedList);
}
看一下updateAdded函数:
private void updateAdded(List pointers)
{
samplerUpdateAdded.Begin();
var addedCount = pointers.Count;
var list = pointerListPool.Get();
for (var i = 0; i < addedCount; i++)
{
var pointer = pointers[i];
list.Add(pointer);
this.pointers.Add(pointer);
idToPointer.Add(pointer.Id, pointer);
#if TOUCHSCRIPT_DEBUG
pLogger.Log(pointer, PointerEvent.Added);
#endif
tmpPointer = pointer;
layerManager.ForEach(_layerAddPointer);
tmpPointer = null;
#if TOUCHSCRIPT_DEBUG
if (DebugMode) addDebugFigureForPointer(pointer);
#endif
}
if (pointersAddedInvoker != null)
pointersAddedInvoker.InvokeHandleExceptions(this, PointerEventArgs.GetCachedEventArgs(list));
pointerListPool.Release(list);
samplerUpdateAdded.End();
}
可以看到最终这些Pointer被添加进idToPointer和pointers中,在函数最后几行进行了回调,完成了一次触摸点从接收到回调的一个完整流程。
在看一下pointersUpdated:
if (pointersUpdated.Count > 0)
{
updatedList = intListPool.Get();
updatedList.AddRange(pointersUpdated);
pointersUpdated.Clear();
}
被添加进updatedList中,接着往下看:
if (updatedList != null)
{
updateUpdated(updatedList);
intListPool.Release(updatedList);
}
最终调用了updateUpdated函数:
private void updateUpdated(List pointers)
{
samplerUpdateUpdated.Begin();
var updatedCount = pointers.Count;
var list = pointerListPool.Get();
for (var i = 0; i < updatedCount; i++)
{
var id = pointers[i];
Pointer pointer;
if (!idToPointer.TryGetValue(id, out pointer))
{
#if TOUCHSCRIPT_DEBUG
if (DebugMode)
Debug.LogWarning("TouchScript > Id [" + id +
"] was in UPDATED list but no pointer with such id found.");
#endif
continue;
}
list.Add(pointer);
#if TOUCHSCRIPT_DEBUG
pLogger.Log(pointer, PointerEvent.Updated);
#endif
var layer = pointer.GetPressData().Layer;
if (layer != null) layer.INTERNAL_UpdatePointer(pointer);
else
{
tmpPointer = pointer;
layerManager.ForEach(_layerUpdatePointer);
tmpPointer = null;
}
#if TOUCHSCRIPT_DEBUG
if (DebugMode) addDebugFigureForPointer(pointer);
#endif
}
if (pointersUpdatedInvoker != null)
pointersUpdatedInvoker.InvokeHandleExceptions(this, PointerEventArgs.GetCachedEventArgs(list));
pointerListPool.Release(list);
samplerUpdateUpdated.End();
}
这里和updateAdded函数类似,不过它是在idToPointer中尝试获取(idToPointer承载了大部分的工作),最后回调一次PointersUpdated。
最后分析的是pointersRemoved:
if (pointersRemoved.Count > 0)
{
removedList = intListPool.Get();
removedList.AddRange(pointersRemoved);
pointersRemoved.Clear();
}
添加至removedList:
if (removedList != null)
{
updateRemoved(removedList);
intListPool.Release(removedList);
}
调用updateRemoved函数:
private void updateRemoved(List pointers)
{
samplerUpdateRemoved.Begin();
var removedCount = pointers.Count;
var list = pointerListPool.Get();
for (var i = 0; i < removedCount; i++)
{
var id = pointers[i];
Pointer pointer;
if (!idToPointer.TryGetValue(id, out pointer))
{
#if TOUCHSCRIPT_DEBUG
if (DebugMode) Debug.LogWarning("TouchScript > Id [" + id + "] was in REMOVED list but no pointer with such id found.");
#endif
continue;
}
idToPointer.Remove(id);
this.pointers.Remove(pointer);
pressedPointers.Remove(pointer);
list.Add(pointer);
#if TOUCHSCRIPT_DEBUG
pLogger.Log(pointer, PointerEvent.Removed);
#endif
tmpPointer = pointer;
layerManager.ForEach(_layerRemovePointer);
tmpPointer = null;
#if TOUCHSCRIPT_DEBUG
if (DebugMode) removeDebugFigureForPointer(pointer);
#endif
}
if (pointersRemovedInvoker != null)
pointersRemovedInvoker.InvokeHandleExceptions(this, PointerEventArgs.GetCachedEventArgs(list));
removedCount = list.Count;
for (var i = 0; i < removedCount; i++)
{
var pointer = list[i];
pointer.InputSource.INTERNAL_DiscardPointer(pointer);
}
pointerListPool.Release(list);
samplerUpdateRemoved.End();
}
同样在idToPointer中尝试获取,最后完成回调。
到这里这篇博客就基本结束了,也不总结什么了,想研究的自己去看代码琢磨琢磨就清楚了。最后说一下事件的大致调用顺序
add-press-update-released-remove
另外Cancell没找到。
就这样,本人水平有限,如果有错误,欢迎大佬指正,谢谢!