思路
- 获取鼠标所在屏幕坐标(Vector2)
- 通过相机ScreenToWorldPoint(Vector3)转为世界坐标 (注意Vector3的z是距离相机的距离,相机需要正交)
- 通过SpriteRenderer访问边界Bounds
- 通过Bounds.Contain检测世界坐标是否在SpriteBounds内
- 通过比例计算来确定在Sprite内的UV坐标,并根据像素长宽确定像素坐标
float pixelX = (worldPositiopn.x - bounds.min.x) / bounds.size.x * bgSprite.texture.width;
float pixelY = (worldPositiopn.y - bounds.min.y) / bounds.size.y * bgSprite.texture.height;
部分编辑器依赖于OdinInspector,不用可以把红线都删了
实现的效果类似这样,根据边界贴图检测鼠标画线位置是否在范围内
using Sirenix.OdinInspector;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
namespace Yueh0607.ClothOperations
{
public class CutBehaviour : MonoBehaviour
{
[ShowIf("IsRuntime")]
[ReadOnly]
[SerializeField]
Sprite bgSprite, lineStyleSprite, edgeSprite;
[ShowIf("IsRuntime")]
[ReadOnly]
[SerializeField]
SpriteRenderer bgRenderer, lineStyleRenderer;
[SerializeField] Color edgeColor = Color.black;
bool initialized = false;
#if UNITY_EDITOR
bool IsRuntime() => Application.isPlaying;
#endif
/// <summary>
/// 初始化裁剪行为
/// </summary>
/// <param name="bgSprite">背景精灵图</param>
/// <param name="lineStyleSprite">裁剪线样式精灵图</param>
/// <param name="edgeSprite">边界范围精灵图(RBGA 0-1 使用(0,0,0,1)表示裁剪线范围)</param>
public void Initialize(Sprite bgSprite, Sprite lineStyleSprite, Sprite edgeSprite)
{
this.bgSprite = bgSprite;
this.edgeSprite = edgeSprite;
this.lineStyleSprite = lineStyleSprite;
bool sizeMatch = (lineStyleSprite.texture.width == bgSprite.texture.width && edgeSprite.texture.width == bgSprite.texture.width)
&& (lineStyleSprite.texture.height == bgSprite.texture.height && edgeSprite.texture.height == bgSprite.texture.height);
bool rectMatch = (lineStyleSprite.rect == bgSprite.rect && edgeSprite.rect == bgSprite.rect);
if (!sizeMatch) throw new System.Exception("贴图尺寸不合规");
if (!rectMatch) throw new System.Exception("精灵尺寸不合规");
DynamicInitialize();
initialized = true;
}
/// <summary>
/// 动态内容的初始化
/// </summary>
private void DynamicInitialize()
{
GameObject bg = new GameObject("CutBackgroundImage");
bg.transform.SetParent(transform);
bg.transform.position = Vector3.zero;
bgRenderer = bg.AddComponent<SpriteRenderer>();
bgRenderer.sprite = bgSprite;
GameObject lineStyle = new GameObject("LineStyle");
lineStyle.transform.SetParent(transform);
lineStyle.transform.position = Vector3.zero;
lineStyleRenderer = lineStyle.AddComponent<SpriteRenderer>();
lineStyleRenderer.sprite = lineStyleSprite;
}
/// <summary>
/// 检查裁剪行为初始化
/// </summary>
/// <exception cref="System.Exception"></exception>
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
private void ExceptionCheck()
{
if (!initialized) throw new System.Exception("未初始化裁剪行为!");
}
/// <summary>
/// 将世界坐标转换到像素坐标
/// </summary>
/// <param name="worldPositiopn"></param>
/// <param name="pixelPosition"></param>
/// <returns></returns>
public bool TransPoint(Vector2 worldPositiopn, out Vector2Int pixelPosition)
{
ExceptionCheck();
Bounds bounds= bgRenderer.bounds;
if (bounds.Contains(worldPositiopn))
{
//get pixelX and pixelY
float pixelX = (worldPositiopn.x - bounds.min.x) / bounds.size.x * bgSprite.texture.width;
float pixelY = (worldPositiopn.y - bounds.min.y) / bounds.size.y * bgSprite.texture.height;
//float pixelX = (worldPositiopn.x - bounds.) / bgSprite.rect.width * bgSprite.texture.width;
//float pixelY = (worldPositiopn.y - bgSprite.rect.y) / bgSprite.rect.height * bgSprite.texture.height;
pixelPosition = new Vector2Int(Mathf.RoundToInt(pixelX), Mathf.RoundToInt(pixelY));
return true;
}
pixelPosition = Vector2Int.zero;
return false;
}
/// <summary>
/// 判断某个世界坐标是否在裁剪线范围内
/// </summary>
/// <param name="worldPosition">Sprite所在的坐标系坐标</param>
/// <returns></returns>
public bool IsInEdgeRange(Vector2 worldPosition)
{
ExceptionCheck();
bool result = TransPoint(worldPosition, out Vector2Int pixelPosition);
if (!result) return false;
Color color = edgeSprite.texture.GetPixel(pixelPosition.x, pixelPosition.y);
if (color == edgeColor) return true;
return false;
}
}
}