1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
| using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI;
[RequireComponent(typeof(RawImage))] [RequireComponent(typeof(RectTransform))] public class ScratchCard : MonoBehaviour, IBeginDragHandler, IDragHandler { private static readonly int OffsetPropertyId = Shader.PropertyToID("_Offset"); private static readonly int ScalePropertyId = Shader.PropertyToID("_Scale");
internal RawImage RawImage { get { if (rawImage == null) { rawImage = GetComponent<RawImage>(); } return rawImage; } }
internal RectTransform RectTransform { get { if (rectTransform == null) { rectTransform = GetComponent<RectTransform>(); } return rectTransform; } }
[Header("Brush")] public Material brushMaterial; public Vector2 brushScale = new Vector2(0.25f, 0.25f); public int interpolationMaxCount = 4; public int interpolationMinDistance = 8;
[Header("MaskTexture")] public float maskTextureScale = 10f; public float maskTextureMaxSize = 1024f;
private RawImage rawImage; private RectTransform rectTransform; private Texture baseTexture; private RenderTexture targetTexture; private Material brush; private Vector2 previousDrawPosition;
private void Awake() { SaveBaseTexture(); }
private void OnEnable() { Reload(); }
private void OnDisable() { DisposeTargetTexture(); DisposeBrush(); }
private void OnRectTransformDimensionsChange() { Reload(); }
public void OnBeginDrag(PointerEventData eventData) { previousDrawPosition = eventData.position; }
public void OnDrag(PointerEventData eventData) { if (targetTexture == null || brush == null) return;
Vector2 drawPositon = eventData.position; Vector2 delta = drawPositon - previousDrawPosition; float distance = delta.magnitude; int count = Mathf.Min(interpolationMaxCount, (int)distance / interpolationMinDistance);
for(int i = 1; i < count; i++) { DrawAt(previousDrawPosition + delta * ((float)i / count)); } DrawAt(drawPositon); previousDrawPosition = drawPositon; }
public void Restory() { ClearTargetTexture(); }
private void SaveBaseTexture() { if (RawImage.texture != null) { baseTexture = RawImage.texture; } }
private void Reload() { Rect rect = RectTransform.rect; if (rect.width <= 0 || rect.height <= 0) return;
AcquireTargetTexture(rect); CreateBrush(); }
private void AcquireTargetTexture(Rect rect) { float aspect = rect.height / rect.width; int width = (int)Mathf.Min(maskTextureMaxSize, rect.width * maskTextureScale); int height = (int)(width * aspect);
if (targetTexture != null && (targetTexture.width != width || targetTexture.height != height)) { DisposeTargetTexture(); }
targetTexture = RenderTexture.GetTemporary(width, height, -1, RenderTextureFormat.ARGB32); RawImage.texture = targetTexture; ClearTargetTexture(); }
private void DisposeTargetTexture() { if(targetTexture != null) { RenderTexture.ReleaseTemporary(targetTexture); targetTexture = null; } }
private void ClearTargetWithColor(Color color) { RenderTexture temp = RenderTexture.active; RenderTexture.active = targetTexture; GL.Clear(true, true, color); RenderTexture.active = temp; }
private void ClearTargetTexture() { if(baseTexture != null) { ClearTargetWithColor(Color.clear); Graphics.Blit(baseTexture, targetTexture, RawImage.material); } else { ClearTargetWithColor(Color.white); } }
private void CreateBrush() { if(brushMaterial == null) { brushMaterial = Resources.Load<Material>("Materials/ScratchCard_DefaultBrush"); }
DisposeBrush();
brush = new Material(brushMaterial); }
private void DisposeBrush() { if(brush != null) { Destroy(brush); brush = null; } }
private void DrawAt(Vector2 screenPoint) { Vector2 point = ScreenToGraphicLocalPoint(RawImage, screenPoint); Vector4 offsetScale = CaculateOffsetScale(RectTransform, point, brushScale); brush.SetVector(OffsetPropertyId, new Vector4(offsetScale.x, offsetScale.y)); brush.SetVector(ScalePropertyId, new Vector4(offsetScale.z, offsetScale.w)); Graphics.Blit(brush.mainTexture, targetTexture, brush); }
private Vector2 ScreenToGraphicLocalPoint(Graphic graphic, Vector2 screenPoint) { Canvas canvas = graphic.canvas.rootCanvas; Camera camera = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera; RectTransform rectTransform = graphic.rectTransform;
return RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, camera, out Vector2 localPoint) ? localPoint : default; }
private Vector4 CaculateOffsetScale(RectTransform rectTransform, Vector2 localPoint, Vector2 brushScale) { Rect rect = rectTransform.rect; localPoint += rectTransform.pivot * rect.size;
float aspect = rect.height / rect.width; float sx = brushScale.x * aspect; float sy = brushScale.y; float px = (localPoint.x / rect.width) - (0.5f * sx); float py = (localPoint.y / rect.height) - (0.5f * sy);
return new Vector4(px, py, 1.0f / sx, 1.0f / sy); } }
|