一、工程截图
首先准备一张色带图
所有UI组件都设置成居中
CircleImage是小圆圈图片(用的是RawImage组件)
UIColorPicker.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
/// <summary>
/// 拾色器
/// </summary>
public class UIColorPicker : UIBehaviour, IPointerDownHandler, IBeginDragHandler, IDragHandler
{
public new Camera uicamera;
public RawImage circleImage;
public RawImage pickImage;
public GameObject rectPickImage;
[SerializeField]
private Color pickColor;
//红绿蓝三基色所在角度
private readonly float redAngle = 0;
private readonly float greenAngle = 120;
private readonly float blueAngle = 240;
private bool canDrag = false;
private UIRectPickImage m_RectPickImage;
[Serializable]
// 定义按钮OnChangeValue事件类
public class OnChangeValueEvent : UnityEvent { }
// 防止序列化变量重命名后丢失引用
[FormerlySerializedAs("onChangeValue")]
[SerializeField]
private OnChangeValueEvent m_OnChangeValue = new OnChangeValueEvent();
protected override void Awake()
{
base.Awake();
m_RectPickImage = rectPickImage.AddComponent<UIRectPickImage>();
m_RectPickImage.uicamera = uicamera;
SetColor(pickColor);
}
public void OnPointerDown(PointerEventData eventData)
{
Vector2 screenPoint = eventData.pressPosition;
Vector2 localPoint = ScreenPointToLocal(eventData.pressPosition);
// 鼠标是否击在色环上
if (InCircleRound(localPoint))
PickColor(screenPoint);
}
public void OnBeginDrag(PointerEventData eventData)
{
// 鼠标必须要点在色环上才能拖动
Vector2 localPoint = ScreenPointToLocal(eventData.pressPosition);
canDrag = InCircleRound(localPoint);
}
public void OnDrag(PointerEventData eventData)
{
if (!canDrag)
return;
Vector2 screenPoint = eventData.position;
PickColor(screenPoint);
}
// 判断坐标是否在色环上
private bool InCircleRound(Vector2 localPoint)
{
float length = localPoint.magnitude;
float radius_out = circleImage.rectTransform.sizeDelta.x / 2; //外半径
float radius_in = radius_out - pickImage.rectTransform.sizeDelta.x; //内半径
return length >= radius_in && length <= radius_out;
}
// 纠正坐标,使小圆圈绕圆形轨迹运动
private Vector2 AdjustPosition(Vector2 localPostion)
{
float R1 = circleImage.rectTransform.sizeDelta.x / 2;
float R2 = pickImage.rectTransform.sizeDelta.x / 2;
float length = R1 - R2;
Vector2 normal = localPostion.normalized;
localPostion = normal * length;
return localPostion;
}
// 获取小圆圈所在角度
public float GetAngle(Vector2 localPostion)
{
float angle = Vector2.Angle(Vector2.right, localPostion);
if (localPostion.y < 0)
angle = 360 - angle;
return angle;
}
// 设置小圆圈坐标
public void SetPickPosition(Vector2 localPostion)
{
pickImage.rectTransform.anchoredPosition = localPostion;
}
// 根据小圆圈所在坐标计算颜色
private Color ReadPickColor(Vector2 localPostion)
{
Color c = Color.white;
float t;
float angle = GetAngle(localPostion);
if (angle >= redAngle && angle <= greenAngle)
{
t = angle / 120;
c = Color.Lerp(Color.red, Color.green, t);
}
else if(angle > greenAngle && angle <= blueAngle)
{
t = (angle - 120) / 120;
c = Color.Lerp(Color.green, Color.blue, t);
}
else if (angle > blueAngle && angle <= 360)
{
t = (angle - 240) / 120;
c = Color.Lerp(Color.blue, Color.red, t);
}
//颜色提到最亮
float max = Mathf.Max(Mathf.Max(c.r, c.g), c.b);
c *= 1/max;
return c;
}
// 通过鼠标所在屏蔽坐标取色
private void PickColor(Vector2 screenPoint)
{
Vector2 localPoint = ScreenPointToLocal(screenPoint);
localPoint = AdjustPosition(localPoint);
SetPickPosition(localPoint);
pickColor = ReadPickColor(localPoint);
m_RectPickImage.SetPickColor(pickColor);
}
// 屏幕坐标转本地坐标
private Vector2 ScreenPointToLocal(Vector2 screenPoint)
{
Vector2 localPoint;
RectTransformUtility.ScreenPointToLocalPointInRectangle(circleImage.rectTransform, screenPoint, uicamera, out localPoint);
return localPoint;
}
// 计算色环内矩形区大小
public Rect CalculateInRect()
{
float radius_out = circleImage.rectTransform.sizeDelta.x / 2; //外半径
float radius_in = radius_out - pickImage.rectTransform.sizeDelta.x; //内半径
float half_width = Mathf.Sqrt(radius_in * radius_in / 2);//勾股定理计算内矩形半宽
Rect rect = new Rect();
rect.x = rect.y = 0;
rect.width = rect.height = half_width * 2;
return rect;
}
// 矩形拾色后回调
public void SetPickColor(Color pickColor)
{
this.pickColor = pickColor;
}
// 获取拾色器的当前颜色
public Color GetPickColor()
{
return m_RectPickImage.GetPickColor();
}
// 向拾取器设置颜色
public void SetColor(Color color)
{
this.pickColor = color;
Color c = color;
// 找出最小分量
float min = Mathf.Min(Mathf.Min(c.r, c.g), c.b);
//颜色提到最亮
float max = Mathf.Max(Mathf.Max(c.r, c.g), c.b);
c *= 1 / max;
float angle = 0;
// 蓝色 (介于红-绿之间)
if (Mathf.Approximately(color.b, min))
{
//0-60度之间 g=[0, 1]; 60-120度之间 r=[1, 0]
if (c.r >= 1)
angle = Mathf.Lerp(0, 60, c.g);
else
angle = Mathf.Lerp(60, 120, 1 - c.r);
}
// 绿色 (介于蓝-红之间)
else if (Mathf.Approximately(color.g, min))
{
//240-300度之间 r=[0, 1]; 300-360度之间 b=[1, 0]
if (c.b >= 1)
angle = Mathf.Lerp(240, 300, c.r);
else
angle = Mathf.Lerp(300, 360, 1 - c.b);
}
// 红色 (介于绿-蓝之间)
else if (Mathf.Approximately(color.r, min))
{
//120-180度之间 b=[0, 1]; 180-240度之间 g=[1, 0]
if (c.g >= 1)
angle = Mathf.Lerp(120, 180, c.b);
else
angle = Mathf.Lerp(180, 240, 1 - c.g);
}
float rad = angle * Mathf.Deg2Rad;
float x = Mathf.Acos(rad);
float y = Mathf.Asin(rad);
Vector2 localPoint = new Vector2(x, y);
localPoint = AdjustPosition(localPoint);
SetPickPosition(localPoint);
pickColor = ReadPickColor(localPoint);
m_RectPickImage.SetColor(color);
}
}
// 中间矩形取色区
public class UIRectPickImage : RawImage, IDragHandler, IPointerDownHandler
{
public new Camera uicamera;
private UIColorPicker picker;
private Color pickColor = Color.red;
private RawImage circleImage;
protected override void Awake()
{
base.Awake();
picker = transform.parent.GetComponent<UIColorPicker>();
Rect rect = picker.CalculateInRect();
rectTransform.sizeDelta = new Vector2(rect.width - 40, rect.height - 40);
Transform tran = transform.Find("CircleImage");
if (tran != null)
{
circleImage = tran.GetComponent<RawImage>();
}
}
public void OnPointerDown(PointerEventData eventData)
{
Vector2 screenPoint = eventData.pressPosition;
PickColor(screenPoint);
}
public void OnDrag(PointerEventData eventData)
{
Vector2 screenPoint = eventData.position;
PickColor(screenPoint);
}
protected override void OnPopulateMesh(VertexHelper vh)
{
Texture tex = mainTexture;
vh.Clear();
if (tex != null)
{
var r = GetPixelAdjustedRect();
var v = new Vector4(r.x, r.y, r.x + r.width, r.y + r.height);
var scaleX = tex.width * tex.texelSize.x;
var scaleY = tex.height * tex.texelSize.y;
{
var color32 = color;
vh.AddVert(new Vector3(v.x, v.y), Color.black, new Vector2(uvRect.xMin * scaleX, uvRect.yMin * scaleY));
vh.AddVert(new Vector3(v.x, v.w), Color.white, new Vector2(uvRect.xMin * scaleX, uvRect.yMax * scaleY));
vh.AddVert(new Vector3(v.z, v.w), this.pickColor, new Vector2(uvRect.xMax * scaleX, uvRect.yMax * scaleY));
vh.AddVert(new Vector3(v.z, v.y), Color.black, new Vector2(uvRect.xMax * scaleX, uvRect.yMin * scaleY));
vh.AddTriangle(0, 1, 2);
vh.AddTriangle(2, 3, 0);
}
}
}
// 色环拾色后调用
public void SetPickColor(Color pickColor)
{
this.pickColor = pickColor;
this.SetVerticesDirty();
}
// 获取拾色器的当前颜色
public Color GetPickColor()
{
return ReadPickColor(circleImage.rectTransform.anchoredPosition);
}
// 外部设值时调用
public void SetColor(Color color)
{
SetPickColor(color);
Color c = color;
float max = Mathf.Max(Mathf.Max(c.r, c.g), c.b);
float min = Mathf.Min(Mathf.Min(c.r, c.g), c.b);
//x控制亮度值
float x = (1 - min / max) * rectTransform.sizeDelta.x - rectTransform.sizeDelta.x / 2;
//y控制暗度值
float y = max * rectTransform.sizeDelta.y - rectTransform.sizeDelta.y / 2;
Vector2 localPoint = new Vector2(x, y);
SetPickPosition(localPoint);
}
// 根据小圆圈所在坐标计算颜色
private Color ReadPickColor(Vector2 localPostion)
{
float w = rectTransform.sizeDelta.x;
float h = rectTransform.sizeDelta.y;
// 将坐标原点移到左下角
float x = localPostion.x + w / 2;
float y = localPostion.y + h / 2;
// 对坐标归一化
x = x / w;
y = y / h;
Color c = Color.Lerp(Color.white, this.pickColor, x);
// 颜色提到最亮
float max = Mathf.Max(Mathf.Max(c.r, c.g), c.b);
c *= 1 / max;
// 根据y坐标计算亮度值
c *= y;
c.a = 1;
return c;
}
// 通过鼠标所在屏蔽坐标取色
private void PickColor(Vector2 screenPoint)
{
Vector2 localPoint = ScreenPointToLocal(screenPoint);
localPoint = AdjustPosition(localPoint);
SetPickPosition(localPoint);
Color c = ReadPickColor(localPoint);
picker.SetPickColor(c);
}
// 屏幕坐标转本地坐标
private Vector2 ScreenPointToLocal(Vector2 screenPoint)
{
Vector2 localPoint;
RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, uicamera, out localPoint);
return localPoint;
}
// 校正坐标,确保坐标在矩形区内
private Vector2 AdjustPosition(Vector2 localPostion)
{
float R = rectTransform.sizeDelta.x / 2;
localPostion.x = Mathf.Clamp(localPostion.x, -R, R);
localPostion.y = Mathf.Clamp(localPostion.y, -R, R);
return localPostion;
}
// 设置小圆圈坐标
public void SetPickPosition(Vector2 localPostion)
{
if (circleImage == null)
return;
circleImage.rectTransform.anchoredPosition = localPostion;
}
}