LMQT/Assets/TouchScript/Scripts/Layers/TouchLayer.cs
2024-12-10 09:03:45 +08:00

304 lines
10 KiB
C#

/*
* @author Valentin Simonov / http://va.lent.in/
*/
using System;
using TouchScript.Hit;
using TouchScript.Utils;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using TouchScript.Pointers;
namespace TouchScript.Layers
{
/// <summary>
/// Base class for all pointer layers. Used to check if some object is hit by a pointer.
/// <seealso cref="ITouchManager"/>
/// <seealso cref="HitData"/>
/// <seealso cref="Pointer"/>
/// </summary>
/// <remarks>
/// <para>In <b>TouchScript</b> it's a layer's job to determine if a pointer on the screen hits anything in Unity's 3d/2d world.</para>
/// <para><see cref="ILayerManager"/> keeps a sorted list of all layers in <see cref="ILayerManager.Layers"/> which it queries when a new pointer appears. It's a layer's job to return <see cref="HitResult.Hit"/> if this pointer hits an object. Layers can even be used to "hit" objects outside of Unity's 3d world, for example <b>Scaleform</b> integration is implemented this way.</para>
/// <para>Layers can be configured in a scene using <see cref="TouchManager"/> or from code using <see cref="ITouchManager"/> API.</para>
/// <para>If you want to route pointers and manually control which objects they should "pointer" it's better to create a new layer extending <see cref="TouchLayer"/>.</para>
/// </remarks>
[ExecuteInEditMode]
public abstract class TouchLayer : MonoBehaviour
{
#region Events
/// <summary>
/// Occurs when layer determines that a pointer has hit something.
/// </summary>
public event EventHandler<TouchLayerEventArgs> PointerBegan
{
add { pointerPressInvoker += value; }
remove { pointerPressInvoker -= value; }
}
// Needed to overcome iOS AOT limitations
private EventHandler<TouchLayerEventArgs> pointerPressInvoker;
#endregion
#region Public properties
/// <summary>
/// Pointer layer's name.
/// </summary>
public string Name;
/// <summary>
/// Layers screen to world projection normal.
/// </summary>
public virtual Vector3 WorldProjectionNormal
{
get { return transform.forward; }
}
/// <summary>
/// Gets or sets an object implementing <see cref="ILayerDelegate"/> to be asked for layer specific actions.
/// </summary>
/// <value> The delegate. </value>
public ILayerDelegate Delegate { get; set; }
#endregion
#region Private variables
/// <summary>
/// The layer projection parameters.
/// </summary>
protected ProjectionParams layerProjectionParams;
/// <summary>
/// Layer manager.
/// </summary>
protected ILayerManager manager;
#endregion
#region Temporary variables
private List<HitTest> tmpHitTestList = new List<HitTest>(10);
#endregion
#region Public methods
/// <summary>
/// Gets the projection parameters of this layer which might depend on a specific pointer data.
/// </summary>
/// <param name="pointer"> Pointer to retrieve projection parameters for. </param>
/// <returns></returns>
public virtual ProjectionParams GetProjectionParams(Pointer pointer)
{
return layerProjectionParams;
}
/// <summary>
/// Checks if a point in screen coordinates hits something in this layer.
/// </summary>
/// <param name="pointer">Pointer.</param>
/// <param name="hit">Hit result.</param>
/// <returns><c>true</c>, if an object is hit, <see cref="HitResult.Miss"/>; <c>false</c> otherwise.</returns>
public virtual HitResult Hit(IPointer pointer, out HitData hit)
{
hit = default(HitData);
if (enabled == false || gameObject.activeInHierarchy == false) return HitResult.Miss;
if (Delegate != null)
{
if (Delegate.ShouldReceivePointer(this, pointer)) return HitResult.Hit;
return HitResult.Miss;
}
return HitResult.Hit;
}
#endregion
#region Unity methods
/// <summary>
/// Unity Awake callback.
/// </summary>
protected virtual void Awake()
{
setName();
if (!Application.isPlaying) return;
manager = LayerManager.Instance;
layerProjectionParams = createProjectionParams();
StartCoroutine(lateAwake());
}
private IEnumerator lateAwake()
{
yield return null;
// Add ourselves after TouchManager finished adding layers in order
manager.AddLayer(this, -1, false);
}
// To be able to turn layers off
private void Start() {}
/// <summary>
/// Unity OnDestroy callback.
/// </summary>
protected virtual void OnDestroy()
{
if (!Application.isPlaying || TouchManager.Instance == null) return;
StopAllCoroutines();
manager.RemoveLayer(this);
}
#endregion
#region Internal methods
internal void INTERNAL_AddPointer(Pointer pointer)
{
addPointer(pointer);
}
internal void INTERNAL_UpdatePointer(Pointer pointer)
{
updatePointer(pointer);
}
internal bool INTERNAL_PressPointer(Pointer pointer)
{
pressPointer(pointer);
if (pointerPressInvoker != null) pointerPressInvoker.InvokeHandleExceptions(this, new TouchLayerEventArgs(pointer));
return true;
}
internal void INTERNAL_ReleasePointer(Pointer pointer)
{
releasePointer(pointer);
}
internal void INTERNAL_RemovePointer(Pointer pointer)
{
removePointer(pointer);
}
internal void INTERNAL_CancelPointer(Pointer pointer)
{
cancelPointer(pointer);
}
#endregion
#region Protected functions
/// <summary>
/// Checks the hit filters.
/// </summary>
/// <param name="pointer">The pointer.</param>
/// <param name="hit">HitData for the pointer.</param>
/// <returns></returns>
protected HitResult checkHitFilters(IPointer pointer, HitData hit)
{
hit.Target.GetComponents(tmpHitTestList);
var count = tmpHitTestList.Count;
if (count == 0) return HitResult.Hit;
var hitResult = HitResult.Hit;
for (var i = 0; i < count; i++)
{
var test = tmpHitTestList[i];
if (!test.enabled) continue;
hitResult = test.IsHit(pointer, hit);
if (hitResult != HitResult.Hit) break;
}
return hitResult;
}
/// <summary>
/// Updates pointer layers's name.
/// </summary>
protected virtual void setName()
{
if (string.IsNullOrEmpty(Name)) Name = "Layer";
}
/// <summary>
/// Called when a pointer is added.
/// </summary>
/// <param name="pointer">Pointer.</param>
/// <remarks>This method may also be used to update some internal state or resend this event somewhere.</remarks>
protected virtual void addPointer(Pointer pointer) {}
/// <summary>
/// Called when a layer is pressed over an object detected by this layer.
/// </summary>
/// <param name="pointer">Pointer.</param>
/// <remarks>This method may also be used to update some internal state or resend this event somewhere.</remarks>
protected virtual void pressPointer(Pointer pointer) {}
/// <summary>
/// Called when a pointer is moved.
/// </summary>
/// <param name="pointer">Pointer.</param>
/// <remarks>This method may also be used to update some internal state or resend this event somewhere.</remarks>
protected virtual void updatePointer(Pointer pointer) {}
/// <summary>
/// Called when a pointer is released.
/// </summary>
/// <param name="pointer">Pointer.</param>
/// <remarks>This method may also be used to update some internal state or resend this event somewhere.</remarks>
protected virtual void releasePointer(Pointer pointer) {}
/// <summary>
/// Called when a pointer is removed.
/// </summary>
/// <param name="pointer">Pointer.</param>
/// <remarks>This method may also be used to update some internal state or resend this event somewhere.</remarks>
protected virtual void removePointer(Pointer pointer) {}
/// <summary>
/// Called when a pointer is cancelled.
/// </summary>
/// <param name="pointer">Pointer.</param>
/// <remarks>This method may also be used to update some internal state or resend this event somewhere.</remarks>
protected virtual void cancelPointer(Pointer pointer) {}
/// <summary>
/// Creates projection parameters.
/// </summary>
/// <returns> Created <see cref="ProjectionParams"/> instance.</returns>
protected virtual ProjectionParams createProjectionParams()
{
return new ProjectionParams();
}
#endregion
}
/// <summary>
/// Arguments used with <see cref="TouchLayer"/> events.
/// </summary>
public class TouchLayerEventArgs : EventArgs
{
/// <summary>
/// Gets the pointer associated with the event.
/// </summary>
public Pointer Pointer { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="TouchLayerEventArgs"/> class.
/// </summary>
/// <param name="pointer">The pointer associated with the event.</param>
public TouchLayerEventArgs(Pointer pointer)
: base()
{
Pointer = pointer;
}
}
}