鸟语天空
UGUI—拾色面板
post by:追风剑情 2019-11-8 17:16

一、工程截图

首先准备一张色带图

ColorCircle.pngPickCircle.pngPickCircle.png

4444.pngPickCircle是拖动用的小圆圈图片

3333.png

555.png

6666.png
所有UI组件都设置成居中
777.png
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;
    }
}

运行测试 (gif动图有点失真,将就看)
2222.gif

评论:
发表评论:
昵称

邮件地址 (选填)

个人主页 (选填)

内容