Files
Continentis/Assets/OtherPlugins/Kamgam/UGUIParticles/Scripts/ParticleImage.cs
SoulliesOfficial 9b1b5ca93f initial
2025-10-03 00:02:43 -04:00

907 lines
31 KiB
C#

#if UNITY_EDITOR
using System;
using UnityEditor;
#endif
using UnityEngine;
using UnityEngine.UI;
namespace Kamgam.UGUIParticles
{
public enum ParticlesLengthUnit
{
Pixels = 0,
Percent = 1
}
public enum ParticlesOrigin
{
Center = 9
, Top = 10
, Bottom = 11
, Left = 12
, Right = 13
, TopLeft = 14
, TopRight = 15
, BottomLeft = 16
, BottomRight = 17
}
public enum ParticlesEmitterShape
{
System = 0,
BoxFill = 1
}
[ExecuteAlways]
public partial class ParticleImage : MaskableGraphic, ILayoutElement, ICanvasRaycastFilter
{
/// <summary>
/// Triggered once the element is shown. It takes canvas group alpha and the transform hierarchy active state into account.
/// </summary>
public event System.Action<ParticleImage> OnShow;
/// <summary>
/// Triggered once the element is hidden. It takes canvas group alpha and the transform hierarchy active state into account.
/// </summary>
public event System.Action<ParticleImage> OnHide;
public bool PlayOnEnable {
get => ParticleSystemForImage.PlayOnEnable;
set => ParticleSystemForImage.PlayOnEnable = value;
}
public int PixelsPerUnit
{
get => ParticleSystemForImage.PixelsPerUnit;
set => ParticleSystemForImage.PixelsPerUnit = value;
}
public Texture Texture
{
get => ParticleSystemForImage.Texture;
set => ParticleSystemForImage.Texture = value;
}
public ParticlesOrigin Origin
{
get => ParticleSystemForImage.Origin;
set => ParticleSystemForImage.Origin = value;
}
public Transform OriginTransform
{
get => ParticleSystemForImage.OriginTransform;
set => ParticleSystemForImage.OriginTransform = value;
}
[System.NonSerialized]
protected RectTransform _originRectTransform;
[System.NonSerialized]
protected Transform _originTransform;
public ParticlesEmitterShape EmitterShape
{
get => ParticleSystemForImage.EmitterShape;
set => ParticleSystemForImage.EmitterShape = value;
}
public float PositionX
{
get => ParticleSystemForImage.PositionX;
set => ParticleSystemForImage.PositionX = value;
}
public ParticlesLengthUnit PositionXUnit
{
get => ParticleSystemForImage.PositionXUnit;
set => ParticleSystemForImage.PositionXUnit = value;
}
public float PositionY
{
get => ParticleSystemForImage.PositionY;
set => ParticleSystemForImage.PositionY = value;
}
public ParticlesLengthUnit PositionYUnit
{
get => ParticleSystemForImage.PositionYUnit;
set => ParticleSystemForImage.PositionYUnit = value;
}
protected RectTransform _rectTransform;
public RectTransform RectTransform
{
get
{
if (_rectTransform == null)
{
_rectTransform = transform as RectTransform;
}
return _rectTransform;
}
}
public float Width => RectTransform.rect.width;
public float Height => RectTransform.rect.height;
public bool IsPlaying => ParticleSystemForImage.IsPlaying;
/// <summary>
/// Use this in a context where auto-creation of the particle system may not be allowed.
/// </summary>
public bool HasParticleSystemForImage
{
get
{
if (_particleSystemForImage == null || _particleSystemForImage.gameObject == null)
_particleSystemForImage = this.GetComponentInChildren<ParticleSystemForImage>(includeInactive: true);
return _particleSystemForImage != null && _particleSystemForImage.gameObject != null;
}
}
protected ParticleSystemForImage _particleSystemForImage;
public ParticleSystemForImage ParticleSystemForImage
{
get
{
if (_particleSystemForImage == null || _particleSystemForImage.gameObject == null)
{
_particleSystemForImage = this.GetComponentInChildren<ParticleSystemForImage>(includeInactive: true);
if (_particleSystemForImage == null)
{
_particleSystemForImage = ParticleSystemForImage.CreateParticleSystemForImage(this);
}
}
return _particleSystemForImage;
}
}
public ParticleSystem ParticleSystem
{
get
{
if (ParticleSystemForImage == null || ParticleSystemForImage.gameObject == null)
return null;
return ParticleSystemForImage.ParticleSystem;
}
}
[System.NonSerialized]
protected ParticleSystem.Particle[] _particles;
protected Vector3[] _initialWorldCorners = null;
protected override void OnEnable()
{
base.OnEnable();
if (IsInPlayModeOrInBuild() && PlayOnEnable)
{
ParticleSystemForImage.Play();
}
// Update rect transforms positions
if (_initialWorldCorners == null)
_initialWorldCorners = new Vector3[4];
RectTransform.GetWorldCorners(_initialWorldCorners);
if (ParticleSystemForImage.UseAttractor)
{
ParticleSystemForImage.EnableAttractor();
}
MarkDirtyRepaint();
}
public float? _lastWidth = null;
public float? _lastHeight = null;
public void Update()
{
if (!_lastWidth.HasValue || _lastWidth.Value != Width)
{
_lastWidth = Width;
ParticleSystemForImage.UpdateEmitterShape(ParticleSystemForImage.EmitterShape, forceRefresh: true);
}
if (!_lastHeight.HasValue || _lastHeight.Value != Height)
{
_lastHeight = Height;
ParticleSystemForImage.UpdateEmitterShape(ParticleSystemForImage.EmitterShape, forceRefresh: true);
}
MarkDirtyRepaint();
}
public void MarkDirtyRepaint()
{
SetVerticesDirty();
}
public bool IsInPlayModeOrInBuild()
{
#if UNITY_EDITOR
return UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode;
#else
return true;
#endif
}
public RenderMode GetRenderMode()
{
var renderMode = RenderMode.ScreenSpaceOverlay;
if (canvas == null)
renderMode = RenderMode.ScreenSpaceOverlay;
else
renderMode = canvas.renderMode;
// Check if the camera matches the render mode and if not alter the render mode.
if (renderMode == RenderMode.ScreenSpaceCamera && canvas != null)
{
Camera cam = canvas.worldCamera;
// If no camera is specified then it will behave like screen scpae OVERLAY.
if (cam == null)
renderMode = RenderMode.ScreenSpaceOverlay;
}
return renderMode;
}
public bool IsInEditor()
{
#if UNITY_EDITOR
return true;
#else
return false;
#endif
}
public void Play()
{
ParticleSystemForImage.Play();
}
public void Pause(bool withChildren = true)
{
ParticleSystemForImage.Pause(withChildren);
}
public void Stop(bool withChildren = true, ParticleSystemStopBehavior stopBehaviour = ParticleSystemStopBehavior.StopEmitting)
{
ParticleSystemForImage.Stop(withChildren, stopBehaviour);
}
// Mesh Data caches
UIVertex[] _vertices;
int[] _triangles;
protected override void OnPopulateMesh(VertexHelper vh)
{
vh.Clear();
if (!IsActiveInHierarchyAndEnabled())
return;
if (!HasParticleSystemForImage)
return;
updateVisibilityEvents();
int maxParticles = ParticleSystem.main.maxParticles;
if (_particles == null || _particles.Length < maxParticles)
_particles = new ParticleSystem.Particle[maxParticles];
if (_vertices == null || _vertices.Length < maxParticles * 4)
{
_vertices = new UIVertex[maxParticles * 4];
_triangles = new int[maxParticles * 6];
}
// Fetch particles from ParticleSystem
int numParticlesAlive = ParticleSystem.GetParticles(_particles);
float width = RectTransform.rect.width;
float height = RectTransform.rect.height;
Vector3 origin = resolveOrigin(ParticleSystemForImage.Origin, width, height);
// Abort if num of active particles is zero
if (numParticlesAlive == 0)
return;
int vertex = 0;
int index = 0;
// Cache values needed for every particle
float startRotationConst = ParticleSystem.main.startRotation.constant * Mathf.Rad2Deg;
// Create one quad per particle
for (int i = 0; i < numParticlesAlive; i++)
{
var position = new Vector3(
(_particles[i].position.x - ParticleSystemForImage.DefaultPosition.x) * PixelsPerUnit,
(_particles[i].position.y - ParticleSystemForImage.DefaultPosition.y) * PixelsPerUnit,
_particles[i].position.z * PixelsPerUnit
);
// Compensate pivot position
position.x += -RectTransform.pivot.x * width;
position.y += -RectTransform.pivot.y * height;
var size = _particles[i].GetCurrentSize(ParticleSystem) * PixelsPerUnit;
var color = _particles[i].GetCurrentColor(ParticleSystem) * this.color;
var rotation = Quaternion.Euler(_particles[i].rotation3D);
// Interpret "alignToDirection" as always align to direction (not only at the start).
if (ParticleSystem.shape.alignToDirection)
{
var velocity2D = ((Vector2)_particles[i].velocity).normalized;
var angle = Mathf.Atan2(velocity2D.y, velocity2D.x) * Mathf.Rad2Deg;
rotation = Quaternion.Euler(0f, 0f, -angle + startRotationConst);
}
createQuad(ref vertex, _vertices, ref index, _triangles, origin + position, rotation, size, color);
}
// Move all the other particles off screen and make them invisible
for (int i = numParticlesAlive; i < maxParticles; i++)
{
var position = new Vector3(-300000, -300000, -300000);
var size = _particles[i].GetCurrentSize(ParticleSystem) * 0.001f;
var color = new Color(0, 0, 0, 0);
var rotation = Quaternion.identity;
createQuad(ref vertex, _vertices, ref index, _triangles, position, rotation, size, color);
}
writeMeshData(_vertices, _triangles, vh);
if (ParticleSystemForImage != null)
{
canvasRenderer.SetTexture(ParticleSystemForImage.Texture);
}
}
private void createQuad(ref int vertex, UIVertex[] vertices, ref int index, int[] triangles, Vector3 pos, Quaternion rotation, float size, Color tint)
{
// The coordinate system starts top left and clock-wise oriented tris are front facing.
vertices[vertex].position = new Vector3(pos.x - size * 0.5f, pos.y - size * 0.5f, 0f);
vertices[vertex].position = rotateAround(pos, vertices[vertex].position, rotation);
vertices[vertex].uv0 = new Vector2(0f, 0f);
vertices[vertex].color = tint;
vertex++;
vertices[vertex].position = new Vector3(pos.x + size * 0.5f, pos.y - size * 0.5f, 0f);
vertices[vertex].position = rotateAround(pos, vertices[vertex].position, rotation);
vertices[vertex].uv0 = new Vector2(1f, 0f);
vertices[vertex].color = tint;
vertex++;
vertices[vertex].position = new Vector3(pos.x + size * 0.5f, pos.y + size * 0.5f, 0f);
vertices[vertex].position = rotateAround(pos, vertices[vertex].position, rotation);
vertices[vertex].uv0 = new Vector2(1f, 1f);
vertices[vertex].color = tint;
vertex++;
vertices[vertex].position = new Vector3(pos.x - size * 0.5f, pos.y + size * 0.5f, 0f);
vertices[vertex].position = rotateAround(pos, vertices[vertex].position, rotation);
vertices[vertex].uv0 = new Vector2(0f, 1f);
vertices[vertex].color = tint;
vertex++;
triangles[index++] = (ushort)(vertex - 4);
triangles[index++] = (ushort)(vertex - 3);
triangles[index++] = (ushort)(vertex - 1);
triangles[index++] = (ushort)(vertex - 3);
triangles[index++] = (ushort)(vertex - 2);
triangles[index++] = (ushort)(vertex - 1);
}
private void writeMeshData(UIVertex[] vertices, int[] triangles, VertexHelper vh)
{
vh.Clear();
int existingVertexCount = vh.currentVertCount;
int vCount = vertices.Length;
int tCount = triangles.Length;
for (int i = 0; i < vCount; i++)
{
vh.AddVert(vertices[i]);
}
for (int i = 0; i < tCount; i += 3)
{
vh.AddTriangle(
existingVertexCount + triangles[i + 2],
existingVertexCount + triangles[i + 1],
existingVertexCount + triangles[i + 0]
);
}
}
/// <summary>
/// Calculates the particle system origin in the space of the ParticleImage rect.
/// The result is the position relative to the BOTTOM LEFT corner of the rect.<br />
/// Unless the simulation space is WORLD SPACE, then it will always return the center rect pos.
/// </summary>
/// <param name="originSetting"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <returns>The local position relative to the BOTTOM LEFT corner of the ParticleImage rect.</returns>
private Vector3 resolveOrigin(ParticlesOrigin originSetting, float width, float height)
{
var unitX = PositionXUnit;
var unitY = PositionYUnit;
if (OriginTransform != null)
{
_originRectTransform = OriginTransform as RectTransform;
if (_originRectTransform == null)
{
_originTransform = OriginTransform as Transform;
}
}
else
{
_originRectTransform = null;
_originTransform = null;
}
// How far the particle origin should be from the calculated origin.
Vector3 offset = new Vector3(
PositionXUnit == ParticlesLengthUnit.Pixels ? PositionX : width * PositionX / 100f,
PositionYUnit == ParticlesLengthUnit.Pixels ? PositionY : height * PositionY / 100f
);
// Reset the particle system position to the default
if (ParticleSystem.main.simulationSpace == ParticleSystemSimulationSpace.Local)
{
ParticleSystemForImage.ResetTransform();
}
Vector3 origin = new Vector3(0, 0, 0);
Vector3 originRelativeToBottomLeft = origin;
Vector2 deltaLocal = Vector2.zero;
if (_originRectTransform != null || _originTransform != null)
{
Vector2 localPos;
if (_originRectTransform != null)
localPos = WorldSpaceToUISpace(_originRectTransform.TransformPoint(_originRectTransform.rect.center), worldPosFromRect: true, RectTransform, GetRenderMode());
else
localPos = WorldSpaceToUISpace(OriginTransform, RectTransform, GetRenderMode());
originRelativeToBottomLeft = localPos + (Vector2) offset;
if (ParticleSystem.main.simulationSpace == ParticleSystemSimulationSpace.Local)
{
// Move the particles inside the ParticleImage element.
origin = localPos;
ParticleSystemForImage.ResetTransform();
}
else if (ParticleSystem.main.simulationSpace == ParticleSystemSimulationSpace.World)
{
localPos += (Vector2) offset;
// Here we do NOT move the particles inside the VisualElement.
// Instead we move the particle system in the WORLD. That way we can retain the
// properties set by the SimulationSpace setting (old particles will be left behind).
deltaLocal = localPos - new Vector2(width * 0.5f, height * 0.5f);
var deltaInWorldSpace = deltaLocal / (float)PixelsPerUnit;
// Debug.Log(deltaLocal + " > " + " > " + deltaInWorldSpace);
ParticleSystemForImage.ApplyPositionDelta(deltaInWorldSpace, GetRenderMode());
// Center pos is the default origin.
origin.x = width * 0.5f;
origin.y = height * 0.5f;
}
else
{
// Custom Space (not supported)
// Center pos is the default origin.
origin.x = width * 0.5f;
origin.y = height * 0.5f;
}
}
else
{
switch (originSetting)
{
case ParticlesOrigin.BottomRight:
origin.x = width;
origin.y = 0f;
break;
case ParticlesOrigin.Top:
origin.x = width * 0.5f;
origin.y = height;
break;
case ParticlesOrigin.Bottom:
origin.x = width * 0.5f;
origin.y = 0f;
break;
case ParticlesOrigin.Left:
origin.x = 0f;
origin.y = height * 0.5f;
break;
case ParticlesOrigin.Right:
origin.x = width;
origin.y = height * 0.5f;
break;
case ParticlesOrigin.TopRight:
origin.x = width;
origin.y = height;
break;
case ParticlesOrigin.TopLeft:
origin.y = height;
break;
case ParticlesOrigin.Center:
origin.x = width * 0.5f;
origin.y = height * 0.5f;
break;
case ParticlesOrigin.BottomLeft:
default:
origin.x = 0f;
origin.y = 0f;
break;
}
originRelativeToBottomLeft = origin;
applyWorldSimulationSpace(ref origin, ref deltaLocal, offset);
}
if (ParticleSystem.main.simulationSpace == ParticleSystemSimulationSpace.World)
{
ParticleSystemForImage.UpdateAttractorPosition(originRelativeToBottomLeft, width, height);
return new Vector3(origin.x, origin.y, origin.z);
}
else
{
var result = new Vector3(origin.x + offset.x, origin.y + offset.y, origin.z);
ParticleSystemForImage.UpdateAttractorPosition(result, width, height);
return result;
}
}
private void applyWorldSimulationSpace(ref Vector3 origin, ref Vector2 deltaLocal, Vector3 positionDeltaInPx)
{
if (ParticleSystem.main.simulationSpace == ParticleSystemSimulationSpace.World)
{
if (_initialWorldCorners != null)
{
var center = new Vector3(
_initialWorldCorners[0].x + (_initialWorldCorners[2].x - _initialWorldCorners[0].x) * 0.5f,
_initialWorldCorners[0].y + (_initialWorldCorners[2].y - _initialWorldCorners[0].y) * 0.5f,
_initialWorldCorners[0].z + (_initialWorldCorners[2].z - _initialWorldCorners[0].z) * 0.5f
);
deltaLocal = GetCenterPosLocalDelta(center, RectTransform);
// Here we do NOT move the particles inside the UI.
// Instead we move the particle system in the WORLD. That way we can retain the
// properties set by the SimulationSpace setting (old particles will be left behind).
var deltaInWorldSpace = (deltaLocal + (Vector2)positionDeltaInPx) / (float)PixelsPerUnit; // we also add the position delta here.
ParticleSystemForImage.ApplyPositionDelta(deltaInWorldSpace, GetRenderMode());
origin.x -= deltaLocal.x;
origin.y -= deltaLocal.y;
}
}
}
/// <summary>
/// Returns the distance vector between the source center and the target rect center in target local space.
/// </summary>
/// <param name="source"></param>
/// <param name="target"></param>
/// <returns></returns>
public Vector2 GetCenterPosLocalDelta(RectTransform source, RectTransform target)
{
var sourceWorldCenter = source.TransformPoint(source.rect.center);
return GetCenterPosLocalDelta(sourceWorldCenter, target);
}
/// <summary>
/// Returns the distance vector between the world position and the target rect center in target local space.
/// </summary>
/// <param name="sourceWorldCenter"></param>
/// <param name="target"></param>
/// <returns></returns>
public Vector2 GetCenterPosLocalDelta(Vector3 sourceWorldCenter, RectTransform target)
{
var sourcePosInTaget = target.InverseTransformPoint(sourceWorldCenter);
var deltaInTarget = new Vector2(
target.rect.center.x - sourcePosInTaget.x
, target.rect.center.y - sourcePosInTaget.y
);
return deltaInTarget;
}
public Vector2 WorldSpaceToUISpace(RectTransform source, RectTransform target, RenderMode renderMode)
{
return WorldSpaceToUISpace(source.position, worldPosFromRect: true, target, renderMode);
}
public Vector2 WorldSpaceToUISpace(Transform source, RectTransform target, RenderMode renderMode)
{
return WorldSpaceToUISpace(source.position, worldPosFromRect: false, target, renderMode);
}
/// <summary>
/// Transform the absolute world space position to the local UI position based on the camera and the rect of the given ui target.<br />
/// The result is a position is in the target's local space relative to the BOTTOM LEFT corner of the rect.
/// </summary>
/// <param name="worldSpacePos"></param>
/// <param name="worldPosFromRect"></param>
/// <param name="target"></param>
/// <param name="renderMode"></param>
/// <returns></returns>
public Vector2 WorldSpaceToUISpace(Vector3 worldSpacePos, bool worldPosFromRect, RectTransform target, RenderMode renderMode)
{
bool isScreenSpaceOverlay = false;
Camera worldCam = null;
Camera uiCam = null;
switch (renderMode)
{
case RenderMode.ScreenSpaceOverlay:
isScreenSpaceOverlay = true;
break;
case RenderMode.ScreenSpaceCamera:
if (canvas.worldCamera != null)
{
worldCam = canvas.worldCamera;
uiCam = canvas.worldCamera;
}
else
{
isScreenSpaceOverlay = true;
}
break;
case RenderMode.WorldSpace:
worldCam = canvas.worldCamera;
uiCam = canvas.worldCamera;
break;
default:
break;
}
if (isScreenSpaceOverlay)
{
if (worldPosFromRect)
{
worldCam = null;
uiCam = null;
}
else
{
worldCam = Camera.main != null ? Camera.main : Camera.current;
uiCam = null;
}
}
var screenPos = RectTransformUtility.WorldToScreenPoint(worldCam, worldSpacePos);
RectTransformUtility.ScreenPointToLocalPointInRectangle(target, screenPos, uiCam, out Vector2 localPos);
// Shift to lower left corner
localPos.x += target.rect.width * target.pivot.x;
localPos.y += target.rect.height * target.pivot.y;
return localPos;
}
private Vector3 rotateAround(Vector3 pivot, Vector3 point, float angle)
{
if (angle == 0f)
return point;
Quaternion rot = Quaternion.Euler(new Vector3(0f, 0f, angle));
var result = point - pivot;
result = rot * result;
result = pivot + result;
return result;
}
private Vector3 rotateAround(Vector3 pivot, Vector3 point, Quaternion rotation)
{
var result = point - pivot;
result = rotation * result;
result = pivot + result;
return result;
}
protected bool? _lastKnownVisibility = null;
protected void updateVisibilityEvents()
{
bool isShown = IsActiveInHierarchyAndEnabled();
bool changed = false;
if (_lastKnownVisibility.HasValue)
{
if (isShown != _lastKnownVisibility.Value)
changed = true;
}
else
{
changed = true;
}
_lastKnownVisibility = isShown;
if (changed)
{
if (isShown)
onShow();
else
onHide();
}
}
public bool IsActiveInHierarchyAndEnabled()
{
return gameObject.activeInHierarchy && enabled;
}
public void Show()
{
bool wasShown = IsActiveInHierarchyAndEnabled();
gameObject.SetActive(true);
if (!wasShown)
onShow();
}
public void Hide()
{
bool wasShown = IsActiveInHierarchyAndEnabled();
gameObject.SetActive(false);
if (wasShown)
onHide();
}
public void Toggle()
{
if (IsActiveInHierarchyAndEnabled())
{
Hide();
}
else
{
Show();
}
}
protected void onShow()
{
#if UNITY_EDITOR
if (!EditorApplication.isPlaying)
return;
#endif
if (PlayOnEnable)
ParticleSystemForImage.Play();
OnShow?.Invoke(this);
}
protected void onHide()
{
#if UNITY_EDITOR
if (!EditorApplication.isPlaying)
return;
#endif
OnHide?.Invoke(this);
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
MarkDirtyRepaint();
}
#endif
// I N T E R F A C E S
/// <summary>
/// See ILayoutElement.CalculateLayoutInputHorizontal.
/// </summary>
public virtual void CalculateLayoutInputHorizontal() { }
/// <summary>
/// See ILayoutElement.CalculateLayoutInputVertical.
/// </summary>
public virtual void CalculateLayoutInputVertical() { }
/// <summary>
/// See ILayoutElement.minWidth.
/// </summary>
public virtual float minWidth { get { return 0; } }
/// <summary>
/// If there is a sprite being rendered returns the size of that sprite.
/// In the case of a slided or tiled sprite will return the calculated minimum size possible
/// </summary>
public virtual float preferredWidth
{
get
{
return 0;
}
}
/// <summary>
/// See ILayoutElement.flexibleWidth.
/// </summary>
public virtual float flexibleWidth { get { return -1; } }
/// <summary>
/// See ILayoutElement.minHeight.
/// </summary>
public virtual float minHeight { get { return 0; } }
/// <summary>
/// If there is a sprite being rendered returns the size of that sprite.
/// In the case of a slided or tiled sprite will return the calculated minimum size possible
/// </summary>
public virtual float preferredHeight
{
get
{
return 0;
}
}
/// <summary>
/// See ILayoutElement.flexibleHeight.
/// </summary>
public virtual float flexibleHeight { get { return -1; } }
/// <summary>
/// See ILayoutElement.layoutPriority.
/// </summary>
public virtual int layoutPriority { get { return 0; } }
/// <summary>
/// Calculate if the ray location for this image is a valid hit location. Takes into account a Alpha test threshold.
/// </summary>
/// <param name="screenPoint">The screen point to check against</param>
/// <param name="eventCamera">The camera in which to use to calculate the coordinating position</param>
/// <returns>If the location is a valid hit or not.</returns>
/// <remarks> Also see See:ICanvasRaycastFilter.</remarks>
public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
{
return true;
}
}
}