using System.Collections; using System.Collections.Generic; using Ichni; using Ichni.Editor; using Ichni.RhythmGame; using UnityEngine; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.UI; public partial class HsvDrawer { [Header("Color Wheel Settings")] public int textureSize = 256; // 纹理尺寸 public float saturation = 1f; // 最大饱和度 public float value = 1f; // 明度 [Header("Color Picker Settings")] public RectTransform pickerIndicator; // 取色器指示器 public Color currentColor = Color.white; // 当前选中的颜色 public Slider valueSlider; // 亮度拖动条 public Slider alphaSlider; // 透明度拖动条 public RawImage previewImage; // 颜色预览窗 private Texture2D _texture; public RawImage _rawImage; private RectTransform _rectTransform; public bool isEmission = false; public GameObject emissionObj; public InputField emissionInput; public DynamicUIBaseColorPicker baseColorPicker; void Start() { _rectTransform = _rawImage.GetComponent(); // 初始化 CreateColorWheelTexture(); ApplyTextureToRawImage(); if (valueSlider != null) { valueSlider.minValue = 0f; valueSlider.maxValue = 1f; valueSlider.value = value; valueSlider.onValueChanged.AddListener(OnValueSliderChanged); } if (alphaSlider != null) { alphaSlider.minValue = 0f; alphaSlider.maxValue = 1f; alphaSlider.onValueChanged.AddListener(OnAlphaSliderChanged); } if (previewImage != null) { previewImage.color = currentColor; } } private Coroutine valueSliderCoroutine; private float pendingValue = -1f; // 新增:高低分辨率参数 public int highResTextureSize = 256; public int lowResTextureSize = 64; private bool isLowRes = false; void OnValueSliderChanged(float newValue) { value = newValue; pendingValue = newValue; // 切换到低分辨率 if (!isLowRes) { isLowRes = true; CreateColorWheelTexture(true); ApplyTextureToRawImage(); } if (valueSliderCoroutine != null) StopCoroutine(valueSliderCoroutine); valueSliderCoroutine = StartCoroutine(DelayedRedrawColorWheel()); // 刷新当前颜色 if (pickerIndicator != null) UpdateCurrentColor(pickerIndicator.localPosition); } IEnumerator DelayedRedrawColorWheel() { float waitTime = 0.01f; float timer = 0f; while (timer < waitTime) { yield return null; timer += Time.unscaledDeltaTime; } // 恢复高分辨率 if (isLowRes) { isLowRes = false; CreateColorWheelTexture(false); ApplyTextureToRawImage(); } else { CreateColorWheelTexture(false); ApplyTextureToRawImage(); } valueSliderCoroutine = null; } void OnAlphaSliderChanged(float newAlpha) { // 只更新透明度 currentColor.a = newAlpha; if (previewImage != null) previewImage.color = currentColor; } void Update() { // 鼠标点击色轮区域即可拖动 if (Mouse.current.leftButton.wasPressedThisFrame && RectTransformUtility.RectangleContainsScreenPoint(_rectTransform, Mouse.current.position.ReadValue())) { StartCoroutine(DraggingColorPicker()); } } IEnumerator DraggingColorPicker() { while (Mouse.current.leftButton.isPressed) { Vector2 localPosition; RectTransformUtility.ScreenPointToLocalPointInRectangle(_rectTransform, Mouse.current.position.ReadValue(), null, out localPosition); // 计算中心和最大半径 Vector2 center = _rectTransform.rect.center; float maxRadius = _rectTransform.rect.width / 2f; Vector2 offset = localPosition - center; float distance = offset.magnitude; // 如果超出圆形区域,则贴边 if (distance > maxRadius) { offset = offset.normalized * maxRadius; localPosition = center + offset; } if (pickerIndicator != null) pickerIndicator.localPosition = localPosition; UpdateCurrentColor(localPosition); yield return null; // 等待下一帧 } } // 整合分辨率逻辑到此方法 void CreateColorWheelTexture(bool useLowRes = false) { int targetSize = useLowRes ? lowResTextureSize : highResTextureSize; textureSize = targetSize; // 1. 优化:仅在尺寸变化时重新分配纹理 if (_texture == null || _texture.width != textureSize || _texture.height != textureSize) { _texture = new Texture2D(textureSize, textureSize, TextureFormat.RGBA32, false); _texture.filterMode = FilterMode.Bilinear; // 确保平滑 _texture.wrapMode = TextureWrapMode.Clamp; } // 2. 核心优化:使用 Color32 数组代替 Color 数组 // Color32 占用的内存更小,SetPixels32 的执行效率远高于 SetPixels Color32[] pixels = new Color32[textureSize * textureSize]; float halfSize = textureSize * 0.5f; float invMaxRadius = 1.0f / halfSize; // 预计算半径倒数,将除法转为乘法 Color32 clearColor = new Color32(0, 0, 0, 0); // 3. 循环优化 for (int y = 0; y < textureSize; y++) { // 预计算当前行的偏移量 Y 轴部分 float offY = (y + 0.5f) - halfSize; float offYSq = offY * offY; int rowOffset = y * textureSize; for (int x = 0; x < textureSize; x++) { float offX = (x + 0.5f) - halfSize; float distSq = offX * offX + offYSq; // 使用平方比较,避免在外部判断时使用 magnitude (开方) int idx = rowOffset + x; if (distSq <= halfSize * halfSize) { float distance = Mathf.Sqrt(distSq); // 计算色相 (Hue) float angle = Mathf.Atan2(offY, offX) * Mathf.Rad2Deg; if (angle < 0) angle += 360f; // 计算饱和度 (Saturation),受外部变量 saturation 影响 float sat = (distance * invMaxRadius) * saturation; // 转换 HSV 到 RGB 并存入 Color32 // 注意:Color.HSVToRGB 返回 Color,赋值给 Color32 会自动隐式转换 pixels[idx] = Color.HSVToRGB(angle / 360f, Mathf.Clamp01(sat), value); } else { pixels[idx] = clearColor; } } } // 4. 应用像素数据 _texture.SetPixels32(pixels); _texture.Apply(); } void ApplyTextureToRawImage() { if (_rawImage == null) _rawImage = GetComponent(); _rawImage.texture = _texture; _rawImage.color = Color.white; // 确保显示原始颜色 } void UpdateCurrentColor(Vector2 localPosition) { Vector2 center = _rectTransform.rect.center; Vector2 offset = localPosition - center; float maxRadius = _rectTransform.rect.width / 2f; // 计算半径比例(饱和度) float distance = offset.magnitude; float sat = Mathf.Clamp01(distance / maxRadius) * saturation; // 计算角度(色调) float angle = Mathf.Atan2(offset.y, offset.x) * Mathf.Rad2Deg; if (angle < 0) angle += 360; // 计算颜色 float alpha = alphaSlider != null ? alphaSlider.value : 1f; currentColor = Color.HSVToRGB(angle / 360f, sat, value); currentColor.a = alpha; // 更新取色器颜色(使其与背景有对比度) if (pickerIndicator != null) { var img = pickerIndicator.GetComponent(); if (img != null) img.color = (value > 0.5f) ? Color.black : Color.white; } // 若有亮度滑块,保持同步 if (valueSlider != null && valueSlider.value != value) { valueSlider.SetValueWithoutNotify(value); } // 若有透明度滑块,保持同步 if (alphaSlider != null && alphaSlider.value != alpha) { alphaSlider.SetValueWithoutNotify(alpha); } // 更新预览窗 if (previewImage != null) { previewImage.color = currentColor; } if (baseColorPicker != null) { // 同步到BaseColorPicker baseColorPicker.SliderChangeWithoutNotification(currentColor); } ApplyParameters(); } } public partial class HsvDrawer : DynamicUIElement { private void ApplyParameters() { Color newValue = currentColor; connectedBaseElement.GetType().GetField(parameterName).SetValue(connectedBaseElement, newValue); connectedBaseElement.Refresh(); } // 新增:同步connectedBaseElement的color到色盘 public void SyncFromBaseElement() { if (connectedBaseElement == null || string.IsNullOrEmpty(parameterName)) return; Color color = (Color)connectedBaseElement.GetType().GetField(parameterName).GetValue(connectedBaseElement); currentColor = color; // 解析HSV Color.RGBToHSV(color, out float h, out float s, out float v); value = v; if (valueSlider != null) valueSlider.SetValueWithoutNotify(v); if (alphaSlider != null) alphaSlider.SetValueWithoutNotify(color.a); // 计算picker位置 if (_rectTransform == null) _rectTransform = _rawImage.GetComponent(); Vector2 center = _rectTransform.rect.center; float maxRadius = _rectTransform.rect.width / 2f; float angle = h * 360f * Mathf.Deg2Rad; float radius = s * maxRadius; Vector2 offset = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * radius; Vector2 pickerPos = center + offset; if (pickerIndicator != null) pickerIndicator.localPosition = pickerPos; // 更新色盘和预览 CreateColorWheelTexture(); ApplyTextureToRawImage(); if (previewImage != null) previewImage.color = currentColor; } public override void Initialize(IBaseElement baseElement, string title, string parameterName) { base.Initialize(baseElement, title, parameterName); SyncFromBaseElement(); } public override DynamicUIElement AddListenerFunction(UnityAction action) { valueSlider.onValueChanged.AddListener(_ => action()); alphaSlider.onValueChanged.AddListener(_ => action()); valueSlider.onValueChanged.AddListener(_ => UpdateCurrentColor(pickerIndicator.localPosition)); alphaSlider.onValueChanged.AddListener(_ => UpdateCurrentColor(pickerIndicator.localPosition)); // 拖动色盘时也触发 // 这里通过在 UpdateCurrentColor 里调用 action // 但由于没有事件,这里可以考虑用委托或事件,但为兼容性直接调用 // 可以在 UpdateCurrentColor 末尾加一行(如果需要多次触发): // action?.Invoke(); // 但一般只需响应滑块即可 return this; } }