Unity中解锁图片像素点,动态闭合轨迹检测
Unity中解锁图片像素点,动态闭合轨迹检测
- 介绍
- 资源下载
- 搭建
- 总结
介绍
因为最近在研究Mane天蚕变的游戏完整逻辑,研究了两套方案做解锁图片的功能,这里我先讲一下我的这个图片像素点的方案解锁图片,这个逻辑其实很简单就是利用划线检测是否轨迹点闭合,然后闭合的点在映射到图片的像素中即可,当然这里需要制作一个shader就是主图层和遮罩层,因为遮罩层的图是动态生成的,所以这块你的像素越大则性能消耗越多也就会卡顿,所以根据实际情况去考量项目需求。
资源下载
项目资源
搭建
Canvas模式
相机模式
ImageMask为空物体
LineRenderer为对应组件的物体
GameObject为挂载我自定义脚本的对象
RawImage是我需要挂载Shader材质球的对象,同时也需要将图放上去
代码
UnlockImageWithShader运行脚本
using UnityEngine;
using UnityEngine.UI;
public class UnlockImageWithShader : MonoBehaviour
{
public RawImage imageToUnlock; // 需要解锁的图片(使用RawImage)
public LineRenderer lineRenderer; // 用于绘制曲线的LineRenderer
public Shader unlockShader; // 自定义Shader
private Texture2D maskTexture; // 遮罩纹理
private Material unlockMaterial; // 使用Shader的材质
void Start()
{
// 初始化遮罩纹理
maskTexture = new Texture2D(256, 256); // 根据需要设置分辨率
maskTexture.filterMode = FilterMode.Point;
maskTexture.wrapMode = TextureWrapMode.Clamp;
ClearMaskTexture(); // 初始化为全黑
unlockMaterial = new Material(unlockShader);
unlockMaterial.SetTexture("_MainTex", imageToUnlock.texture); // 设置主纹理
unlockMaterial.SetTexture("_MaskTex", maskTexture); // 设置遮罩纹理
imageToUnlock.material = unlockMaterial; // 应用材质到RawImage
// 初始化LineRenderer
lineRenderer.startWidth = 0.05f;
lineRenderer.endWidth = 0.05f;
lineRenderer.material = new Material(Shader.Find("Sprites/Default"));
lineRenderer.positionCount = 0;
}
void Update()
{
if (Input.GetMouseButton(0))
{
// 将鼠标屏幕坐标转换为UV坐标
TVector mousePos = GetMouseUVPosition();
points = AddPointUV(mousePos.m_uv);
linePoints = AddPointWP(mousePos.m_worldPos);
lineRenderer.positionCount = points.Length;
lineRenderer.SetPositions(System.Array.ConvertAll(linePoints, v => v));
}
if (Input.GetMouseButtonUp(0))
{
if (IsClosedCurve(points))
{
UnlockArea(points);
}
points = new Vector2[0]; // 清空点数组
linePoints = new Vector3[0];
lineRenderer.positionCount = 0; // 清空LineRenderer
}
}
private Vector2[] points = new Vector2[0]; // 存储玩家绘制的点
private Vector3[] linePoints = new Vector3[0]; // 存储玩家绘制的点
Vector2[] AddPointUV(Vector2 point)
{
Vector2[] newPoints = new Vector2[points.Length + 1];
points.CopyTo(newPoints, 0);
newPoints[points.Length] = point;
return newPoints;
}
Vector3[] AddPointWP(Vector3 point)
{
Vector3[] newPoints = new Vector3[points.Length + 1];
linePoints.CopyTo(newPoints, 0);
newPoints[points.Length] = point;
return newPoints;
}
bool IsClosedCurve(Vector2[] points)
{
if (points.Length < 3) return false; // 至少需要3个点才能形成闭合曲线
float distance = Vector2.Distance(points[0], points[points.Length - 1]);
return distance < 0.05f; // 判断起点和终点是否接近(阈值)
}
void UnlockArea(Vector2[] points)
{
// 将多边形区域填充为白色
for (int y = 0; y < maskTexture.height; y++)
{
for (int x = 0; x < maskTexture.width; x++)
{
Vector2 pixelUV = new Vector2((float)x / maskTexture.width, (float)y / maskTexture.height);
if (IsPointInPolygon(pixelUV, points))
{
Color c = new Color(Color.white.r, Color.white.g, Color.white.b, 1);
maskTexture.SetPixel(x, y, c); // 设置为白色(解锁)
}
}
}
maskTexture.Apply(); // 应用纹理更改
byte[] b = maskTexture.EncodeToPNG();
System.IO.File.WriteAllBytes(Application.dataPath + "/t1.png", b);
Debug.LogError("Mask texture updated."); // 调试日志
}
bool IsPointInPolygon(Vector2 point, Vector2[] polygon)
{
int intersections = 0;
for (int i = 0; i < polygon.Length; i++)
{
Vector2 p1 = polygon[i];
Vector2 p2 = polygon[(i + 1) % polygon.Length];
if (point.y > Mathf.Min(p1.y, p2.y))
{
if (point.y <= Mathf.Max(p1.y, p2.y))
{
if (point.x <= Mathf.Max(p1.x, p2.x))
{
float xIntersection = (point.y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) + p1.x;
if (p1.x == p2.x || point.x <= xIntersection)
{
intersections++;
}
}
}
}
}
return intersections % 2 != 0; // 奇数交点表示点在多边形内
}
public class TVector
{
public Vector2 m_uv;
public Vector3 m_worldPos;
public TVector(Vector2 uv,Vector3 wp)
{
m_uv = uv;
m_worldPos = wp;
}
}
TVector GetMouseUVPosition()
{
// 将鼠标屏幕坐标转换为UV坐标
Vector2 mousePos = Input.mousePosition;
// 确保传入的摄像机参数正确
bool success = RectTransformUtility.ScreenPointToLocalPointInRectangle(
imageToUnlock.rectTransform, mousePos, Camera.main, out Vector2 localPoint);
RectTransformUtility.ScreenPointToWorldPointInRectangle(
imageToUnlock.rectTransform, mousePos, Camera.main, out Vector3 worldPos);
// 将局部坐标转换为 UV 坐标
Vector2 uv = Rect.PointToNormalized(imageToUnlock.rectTransform.rect, localPoint);
return new TVector (uv, worldPos);
}
void ClearMaskTexture()
{
// 将遮罩纹理初始化为全黑
Color[] colors = new Color[maskTexture.width * maskTexture.height];
for (int i = 0; i < colors.Length; i++)
{
colors[i] = new Color(Color.black.r, Color.black.g, Color.black.b, 0);
}
maskTexture.SetPixels(colors);
maskTexture.Apply();
}
}
UnlockImageShader.Shader
Shader "Custom/UnlockImageShader"
{
Properties
{
_MainTex ("Main Texture", 2D) = "white" {} // 主纹理
_MaskTex ("Mask Texture", 2D) = "white" {} // 遮罩纹理
}
SubShader
{
Tags { "Queue"="Transparent" "RenderType"="Transparent" }
LOD 200
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
sampler2D _MaskTex;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 mainColor = tex2D(_MainTex, i.uv); // 主纹理颜色
fixed4 maskColor = tex2D(_MaskTex, i.uv); // 遮罩纹理颜色
// 如果遮罩纹理的 alpha 值大于 0.5,显示主纹理,否则显示黑色
return maskColor.a > 0.5 ? mainColor : fixed4(0, 0, 0, 1);
}
ENDCG
}
}
}
总结
需要的同学们可以去我上面资源下载的位置去下载。
感谢大家的支持!