MirzkisD1Ex0 7 months ago
parent
commit
0acb2b6d73

BIN
Materials/IOS对策/Snipaste_2025-05-07_11-31-26.png


+ 7 - 0
Materials/IOS对策/newtonsoftjson.txt

@@ -0,0 +1,7 @@
+  private void Awake()
+  {
+    // 设置IL2CPP环境变量 // 使Newtonsoft.Json进入AOT兼容模式
+    // Environment.SetEnvironmentVariable("UNITY_IL2CPP_PATH", Application.dataPath);
+  }
+
+PlayerSetting里设置.Net Framework (4.0)

+ 92 - 63
Materials/RemoveBG & BaiduBodySegment/BaiduBodySegmentManager.cs

@@ -5,54 +5,39 @@ using UnityEngine;
 using UnityEngine.Networking;
 using Newtonsoft.Json;
 using UnityEngine.Events;
+using ToneTuneToolkit.Common;
 
-public class BaiduBodySegmentManager : MonoBehaviour
+public class BaiduBodySegmentManager : SingletonMaster<BaiduBodySegmentManager>
 {
-  public static BaiduBodySegmentManager Instance;
+  public static UnityAction<Texture2D, int> OnSegmentFinished;
 
-  private const string CLIENTID = @"ltiCIE7Rq17Nt2MH77LX6Qmv";
-  private const string CLIENTSECRET = @"fjSdI4zFd9QjfFTWymf1sXKQrjzy0UjH";
+  #region Info
+  private const string CLIENTID = @"2fClRTA6uqf8WMMs3oYetrtN";
+  private const string CLIENTSECRET = @"9K1HQItadDrPdFizDJkRh5bzWwi1O1tJ";
   private const string TOKENURL = @"https://aip.baidubce.com/oauth/2.0/token";
   private const string BODYSEGURL = @"https://aip.baidubce.com/rest/2.0/image-classify/v1/body_seg?access_token=";
   private string token = @"25.0acc4e48d0f7450dd320126240dbaa7c.315360000.2037861152.282335-101570444"; // 后续会Get // 可以用一个月
+  #endregion
 
-  [SerializeField] private Texture2D texture2dOriginalPhoto;
-  [SerializeField] private Texture2D texture2dResultPhoto;
   private TokenJson tokenJson;
   private ResultJson resultJson;
 
-  public static event UnityAction<Texture2D> OnResultCallback;
-
   // ==================================================
+  #region 上传包
 
-  private void Awake() => Instance = this;
-
-  // private void Update()
-  // {
-  //   if (Input.GetKeyUp(KeyCode.U))
-  //   {
-  //     string testPath = @"D:\2024-06-08 00.33.12.1717777992216_myPic_0.jpg";
-  //     preuploadTexture = TextureProcessor.ReadTexture(testPath);
-  //     preuploadTexture = TextureProcessor.RotateTexture(preuploadTexture, false);
-  //     preuploadTexture = TextureProcessor.HorizontalFlipTexture(preuploadTexture);
-  //     preuploadTexture = TextureProcessor.ScaleTexture(preuploadTexture, preuploadTexture.width * .7f, preuploadTexture.height * .7f);
-  //     preuploadTexture.Apply();
-  //     UploadPhoto2Baidu(preuploadTexture);
-  //   }
-  // }
-
-  // ==================================================
+  [SerializeField] private Texture2D t2dOrigin;
+  [SerializeField] private Texture2D t2dResult;
+  [SerializeField] private int flag;
 
-  /// <summary>
-  /// 更新原图
-  /// </summary>
-  /// <param name="value"></param>
-  public void UpdateOriginalPhotoTexture2D(Texture2D value)
+  public void UpdatePackage(Texture2D value, int flagValue)
   {
-    texture2dOriginalPhoto = value;
+    t2dOrigin = value;
+    flag = flagValue;
     return;
   }
 
+  #endregion
+  // ==================================================
 
   /// <summary>
   /// 人像分割
@@ -62,68 +47,100 @@ public class BaiduBodySegmentManager : MonoBehaviour
   {
     #region GetToken // 获取Token
     string url = $"{TOKENURL}?client_id={CLIENTID}&client_secret={CLIENTSECRET}&grant_type=client_credentials";
-    using (UnityWebRequest request = UnityWebRequest.Post(url, ""))
+    using (UnityWebRequest uwr = UnityWebRequest.PostWwwForm(url, ""))
     {
-      request.SetRequestHeader("Content-Type", "application/json");
-      request.SetRequestHeader("Accept", "application/json");
-      yield return request.SendWebRequest();
+      uwr.SetRequestHeader("Content-Type", "application/json");
+      uwr.SetRequestHeader("Accept", "application/json");
+      yield return uwr.SendWebRequest();
 
-      if (request.result != UnityWebRequest.Result.Success)
+      if (uwr.result != UnityWebRequest.Result.Success)
       {
-        Debug.LogError("[BBSM] Error " + request.error);
+        Debug.LogError(@$"[BBSM] {uwr.error}");
         yield break;
       }
 
-      tokenJson = JsonConvert.DeserializeObject<TokenJson>(request.downloadHandler.text);
-      token = tokenJson.access_token;
+      try
+      {
+        tokenJson = JsonConvert.DeserializeObject<TokenJson>(uwr.downloadHandler.text);
+        token = tokenJson.access_token;
+      }
+      catch
+      {
+        Debug.Log("[BBSM] Token Analyze Failed.");
+        RetryBodySegment();
+      }
     }
     #endregion
 
 
     #region BodySegment // 人像分割
-    string base64 = Texture2Base64(texture2dOriginalPhoto);
-
     WWWForm form = new WWWForm();
-    form.AddField("image", base64);
+    form.AddField("image", Texture2Base64(t2dOrigin));
 
-    using (UnityWebRequest request = UnityWebRequest.Post(BODYSEGURL + token, form))
+    using (UnityWebRequest uwr = UnityWebRequest.Post(BODYSEGURL + token, form))
     {
-      request.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded");
-      yield return request.SendWebRequest();
+      uwr.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+      yield return uwr.SendWebRequest();
 
-      if (request.result != UnityWebRequest.Result.Success)
+      if (uwr.result != UnityWebRequest.Result.Success)
       {
-        Debug.LogError("[BBSM] Error " + request.error);
+        Debug.LogError(@$"[BBSM] {uwr.error}");
         yield break;
       }
 
-      // Debug.Log(request.downloadHandler.text);
-      resultJson = JsonConvert.DeserializeObject<ResultJson>(request.downloadHandler.text);
-      string foregroundBase64 = resultJson.foreground;
-
-      if (!string.IsNullOrEmpty(foregroundBase64)) // 判断是否有图
+      try
       {
-        texture2dResultPhoto = Base642Texture(foregroundBase64);
-        if (OnResultCallback != null)
+        resultJson = JsonConvert.DeserializeObject<ResultJson>(uwr.downloadHandler.text);
+        string foregroundBase64 = resultJson.foreground;
+
+        if (!string.IsNullOrEmpty(foregroundBase64)) // 判断是否有图
         {
-          OnResultCallback(texture2dResultPhoto);
+          Texture2D result = Base642Texture(foregroundBase64);
+          t2dResult = result;
+          // 保存至本地?
+          retryTime = 0;
+          if (OnSegmentFinished != null)
+          {
+            OnSegmentFinished(result, flag);
+          }
         }
-      }
-      else
-      {
-        // 重拍???
-        Debug.LogError("[BBSM] Error foreground image null");
-        texture2dResultPhoto = null;
-        if (OnResultCallback != null)
+        else
         {
-          OnResultCallback(null); // 没拍到 // 传空的回去
+          // 重拍???
+          Debug.LogError("[BBSM] Error foreground image null");
+          retryTime = 0;
+          if (OnSegmentFinished != null)
+          {
+            OnSegmentFinished(null, flag); // 没拍到 // 传空的回去
+          }
         }
       }
+      catch
+      {
+        Debug.Log("[BBSM] Image Analyze Failed.");
+        RetryBodySegment();
+      }
       #endregion
       yield break;
     }
   }
 
+  private int retryTime = 0;
+  private const int RETRYTIMELIMIT = 3;
+  private void RetryBodySegment() => StartCoroutine(nameof(RetryBodySegmentAction));
+  private IEnumerator RetryBodySegmentAction()
+  {
+    if (retryTime >= RETRYTIMELIMIT)
+    {
+      yield break;
+    }
+    yield return new WaitForSeconds(3f);
+    retryTime++;
+    Debug.Log($"[BBSM] Retry {retryTime} time(s).");
+    StartBodySegment();
+    yield break;
+  }
+
   // ==================================================
   // 工具类
 
@@ -163,6 +180,18 @@ public class BaiduBodySegmentManager : MonoBehaviour
     return texture2d;
   }
 
+  // /// <summary>
+  // /// 保存至本地
+  // /// </summary>
+  // private void Save2Local(Texture2D value)
+  // {
+  //   string path = @$"{Application.dataPath}/{DateTime.Now:yyyy-MM-dd-HH-mm-ss}-{new System.Random().Next(0, 100)}.png";
+  //   byte[] bytes = value.EncodeToPNG();
+  //   File.WriteAllBytes(path, bytes);
+  //   Debug.Log(path);
+  //   return;
+  // }
+
   // ==================================================
   // 数据类
 

+ 0 - 123
Materials/RemoveBG & BaiduBodySegment/RemoveBGManagerOld.cs

@@ -1,123 +0,0 @@
-using System.Collections;
-using System.Collections.Generic;
-using UnityEngine;
-using System;
-using System.IO;
-using BestHTTP;
-using BestHTTP.Forms;
-using UnityEngine.Events;
-using ToneTuneToolkit.Data;
-
-namespace LonginesYogaPhotoJoy
-{
-  public class RemoveBGManagerOld : MonoBehaviour
-  {
-    public static RemoveBGManagerOld Instance;
-
-    private UnityAction<Texture2D> onRemoveBGCompelete;
-
-    private const string removebgAPI = "https://api.remove.bg/v1.0/removebg";
-    private string key;
-    // 测试key U1j4pJeg9zT63Kfa8zDmiRkG
-    // 正式key 76YHaSA8WZYmbZXfqfBeYbqy // 20240606 剩余100
-    // live X859F9v3g4YpoPBRQe2n7h8T
-
-    [Header("DEBUG - Peek")]
-    [SerializeField] private Texture2D preuploadTexture;
-    [SerializeField] private Texture2D downloadTexture;
-
-    // ==================================================
-
-    private void Awake()
-    {
-      Instance = this;
-    }
-
-    private void Start()
-    {
-      Init();
-    }
-
-    // ==================================================
-
-    public void AddEventListener(UnityAction<Texture2D> unityAction)
-    {
-      onRemoveBGCompelete += unityAction;
-      return;
-    }
-
-    public void RemoveEventListener(UnityAction<Texture2D> unityAction)
-    {
-      onRemoveBGCompelete -= unityAction;
-      return;
-    }
-
-    // ==================================================
-
-    private void Init()
-    {
-      key = JsonManager.GetJson(Application.streamingAssetsPath + "/removebgkey.json", "Key");
-      return;
-    }
-
-    // ==================================================
-
-    /// <summary>
-    /// 上传照片至RemoveBG
-    /// </summary>
-    /// <param name="value"></param>
-    public void UploadPhoto2RemoveBG(Texture2D value)
-    {
-      preuploadTexture = value;
-      byte[] bytes = value.EncodeToPNG();
-
-      HTTPMultiPartForm form = new HTTPMultiPartForm();
-      form.AddBinaryData("image_file", bytes);
-      // form.AddField("size", "full");
-      form.AddField("size", "auto");
-      form.AddField("type", "person");
-
-      HTTPRequest request = new HTTPRequest(
-        new Uri(removebgAPI),
-        HTTPMethods.Post,
-        UploadPictureCallback);
-
-      request.SetHeader("X-Api-Key", key);
-      request.SetForm(form);
-      request.Send();
-      return;
-    }
-
-    private void UploadPictureCallback(HTTPRequest httpRequest, HTTPResponse httpResponse)
-    {
-      if (httpResponse == null)
-      {
-        Debug.Log("RemoveBG请求无响应...<color=red>[ER]</color>");
-        return;
-      }
-      if (httpResponse.StatusCode == 200)
-      {
-        Debug.Log(httpResponse.DataAsTexture2D.width + " / " + httpResponse.DataAsTexture2D.height);
-
-        string fullPath = $"{Application.streamingAssetsPath}/RemoveBG/{DateTime.Now:yyyy-MM-dd-HH-mm-ss}.png";
-        byte[] bytes = httpResponse.DataAsTexture2D.EncodeToPNG();
-        File.WriteAllBytes(fullPath, bytes);
-
-        downloadTexture = httpResponse.DataAsTexture2D; // 无必要
-
-        if (onRemoveBGCompelete != null)
-        {
-          onRemoveBGCompelete(httpResponse.DataAsTexture2D);
-        }
-        Debug.Log("RemoveBG返回成功...[OK]");
-      }
-      else
-      {
-        Debug.Log($"RemoveBG返回失败,{httpResponse.StatusCode}...<color=red>[ER]</color>");
-        Debug.Log(httpResponse.DataAsText);
-        // UploadPhoto2RemoveBG(preuoloadTexture);
-      }
-      return;
-    }
-  }
-}

+ 95 - 0
Materials/RemoveBG & BaiduBodySegment/RemoveBG纯净版/RemoveBGManager.cs

@@ -0,0 +1,95 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using System;
+using UnityEngine.Events;
+using UnityEngine.Networking;
+using ToneTuneToolkit.Common;
+
+public class RemoveBGManager : SingletonMaster<RemoveBGManager>
+{
+  public static UnityAction<Texture2D, int> OnRemoveBGFinished;
+
+  private const string REMOVEBGAPIURL = "https://api.remove.bg/v1.0/removebg";
+  private const string APIKEY = "76YHaSA8WZYmbZXfqfBeYbqy";
+
+  // ==================================================
+
+  [SerializeField] private Texture2D t2dOrigin;
+  [SerializeField] private Texture2D t2dResult;
+  [SerializeField] private int flag;
+
+  public void UpdatePackage(Texture2D value, int flagValue)
+  {
+    t2dOrigin = value;
+    flag = flagValue;
+    return;
+  }
+
+  // ==================================================
+
+  /// <summary>
+  /// 上传照片至RemoveBG
+  /// </summary>
+  public void StartRemoveBG() => StartCoroutine(nameof(RemoveBGAction));
+  private IEnumerator RemoveBGAction()
+  {
+    WWWForm wwwForm = new WWWForm();
+    wwwForm.AddBinaryData("image_file", t2dOrigin.EncodeToPNG());
+    wwwForm.AddField("size", "full");
+    wwwForm.AddField("type", "person");
+    wwwForm.AddField("format", "png");
+
+    using (UnityWebRequest uwr = UnityWebRequest.Post(REMOVEBGAPIURL, wwwForm))
+    {
+      uwr.SetRequestHeader("X-Api-Key", APIKEY);
+
+      yield return uwr.SendWebRequest();
+
+      if (uwr.result != UnityWebRequest.Result.Success)
+      {
+        Debug.LogError(@$"[RBGM] {uwr.error}");
+        Debug.LogError(@$"[RBGM] {uwr.downloadHandler.text}");
+        yield break;
+      }
+
+      try
+      {
+        Debug.Log(uwr.downloadHandler.data);
+        Debug.Log(uwr.downloadHandler.data.Length);
+
+        byte[] bytes = uwr.downloadHandler.data;
+        Texture2D t2d = new Texture2D(2, 2);
+        t2d.LoadImage(bytes);
+
+        t2dResult = t2d;
+        if (OnRemoveBGFinished != null)
+        {
+          OnRemoveBGFinished(t2d, flag);
+        }
+      }
+      catch { }
+    }
+    yield break;
+  }
+
+  // ==================================================
+  // 数据类
+
+  private ResultJson resultJson;
+
+  [Serializable]
+  public class ResultJson
+  {
+    public ResultDataJson data;
+  }
+  [Serializable]
+  public class ResultDataJson
+  {
+    public string result_b64;
+    public int foreground_top;
+    public int foreground_left;
+    public int foreground_width;
+    public int foreground_height;
+  }
+}

+ 48 - 48
ToneTuneToolkit/Assets/ToneTuneToolkit/Scripts/Media/ScreenshotMaster.cs

@@ -10,6 +10,7 @@ using System;
 using System.IO;
 using UnityEngine.Events;
 using ToneTuneToolkit.Common;
+using UnityEngine.UI;
 
 
 
@@ -20,31 +21,13 @@ namespace ToneTuneToolkit.Media
   /// </summary>
   public class ScreenshotMaster : SingletonMaster<ScreenshotMaster>
   {
-    public static UnityAction<Texture2D> OnScreenshotFinished;
+    public static UnityAction<Texture2D, int> OnScreenshotFinished;
 
     [Header("DEBUG - Peek")]
     [SerializeField] private Texture2D peekTexture;
 
     // ==================================================
 
-    // private void Update()
-    // {
-    //   if (Input.GetKeyDown(KeyCode.Q))
-    //   {
-    //     SaveTest();
-    //   }
-    // }
-
-    // public RectTransform Area;//用来取景的ui,设置为透明的
-
-    // public void SaveTest()
-    // {
-    //   string fullPath = $"{Application.streamingAssetsPath}/IMAGE/{SpawnTimeStamp()}.png";
-    //   TakeScreenshot(Area, fullPath, CanvasType.ScreenSpaceOverlay);
-    // }
-
-    // ==================================================
-
     /// <summary>
     /// 传入用于标定范围的Image
     /// 独立功能
@@ -52,19 +35,15 @@ namespace ToneTuneToolkit.Media
     /// <param name="screenshotArea">标定范围</param>
     /// <param name="fullFilePath">保存路径</param>
     /// <param name="canvasType">截图类型</param>
-    public void TakeScreenshot(RectTransform screenshotArea, string fullFilePath, CanvasType canvasType)
-    {
-      StartCoroutine(TakeScreenshotAction(screenshotArea, fullFilePath, canvasType));
-      return;
-    }
-    private IEnumerator TakeScreenshotAction(RectTransform screenshotArea, string fullFilePath, CanvasType canvasType)
+    public void TakeScreenshot(RectTransform screenshotArea, CanvasType canvasType, int flag = 0, string fullFilePath = null) => StartCoroutine(TakeScreenshotAction(screenshotArea, canvasType, flag, fullFilePath));
+    private IEnumerator TakeScreenshotAction(RectTransform screenshotArea, CanvasType canvasType, int flag = 0, string fullFilePath = null)
     {
       yield return new WaitForEndOfFrame(); // 等待渲染帧结束
 
       int width = (int)screenshotArea.rect.width;
       int height = (int)screenshotArea.rect.height;
 
-      Texture2D texture2D = new Texture2D(width, height, TextureFormat.RGBA64, false);
+      Texture2D texture2D = new Texture2D(width, height, TextureFormat.RGBA32, false);
 
       // 原点
       float leftBottomX = 0;
@@ -77,30 +56,29 @@ namespace ToneTuneToolkit.Media
           leftBottomX = screenshotArea.transform.position.x + screenshotArea.rect.xMin;
           leftBottomY = screenshotArea.transform.position.y + screenshotArea.rect.yMin;
           break;
-        case CanvasType.ScreenSpaceCamera: // 如果是camera需要额外加上偏移值
-
-          // leftBottomX = Screen.width / 2;
-          // leftBottomY = Screen.height / 2;
-          // 相机画幅如果是1920x1080,设置透视、Size540可让UI缩放为111
-
-          // Debug.Log(Screen.width / 2 + "/" + Screen.height / 2);
+        case CanvasType.ScreenSpaceCamera: // 如果是camera需要额外加上偏移值 // 相机画幅如果是1920x1080,设置透视、Size=540可让UI缩放为111
+          leftBottomX = screenshotArea.transform.position.x + (Screen.width / 2 + screenshotArea.rect.xMin);
+          leftBottomY = screenshotArea.transform.position.y + (Screen.height / 2 + screenshotArea.rect.yMin);
           break;
       }
 
       texture2D.ReadPixels(new Rect(leftBottomX, leftBottomY, width, height), 0, 0);
       texture2D.Apply();
 
-      // 保存至本地
-      byte[] bytes = texture2D.EncodeToPNG();
-      File.WriteAllBytes(fullFilePath, bytes);
-      Debug.Log($"[ScreenshotMasterLite] <color=green>{fullFilePath}</color>...[OK]");
-      // Destroy(texture2D);
+      if (fullFilePath != null)
+      {
+        // 保存至本地
+        byte[] bytes = texture2D.EncodeToPNG();
+        File.WriteAllBytes(fullFilePath, bytes);
+        Debug.Log($"[SM] <color=green>{fullFilePath}</color>");
+        // Destroy(texture2D);
+      }
 
       peekTexture = texture2D;
 
       if (OnScreenshotFinished != null)
       {
-        OnScreenshotFinished(texture2D);
+        OnScreenshotFinished(texture2D, flag);
       }
       yield break;
     }
@@ -115,7 +93,7 @@ namespace ToneTuneToolkit.Media
     /// </summary>
     /// <param name="screenshotCamera"></param>
     /// <param name="screenshotRT">新建的RT宽高色彩模式都要设置妥当 // RGBA8_SRGB</param>
-    public static Texture2D OffScreenshot(Camera screenshotCamera, RenderTexture screenshotRT, string fullFilePath)
+    public static Texture2D OffScreenshot(Camera screenshotCamera, RenderTexture screenshotRT, string fullFilePath = null)
     {
       screenshotCamera.clearFlags = CameraClearFlags.SolidColor;
       screenshotCamera.backgroundColor = Color.clear;
@@ -127,9 +105,12 @@ namespace ToneTuneToolkit.Media
       t2d.ReadPixels(new Rect(0, 0, screenshotRT.width, screenshotRT.height), 0, 0);
       t2d.Apply();
 
-      byte[] bytes = t2d.EncodeToPNG();
-      File.WriteAllBytes(fullFilePath, bytes);
-      Debug.Log(@$"[SM] <color=green>{fullFilePath}</color>");
+      if (fullFilePath != null)
+      {
+        byte[] bytes = t2d.EncodeToPNG();
+        File.WriteAllBytes(fullFilePath, bytes);
+        Debug.Log(@$"[SM] <color=green>{fullFilePath}</color>");
+      }
 
       RenderTexture.active = null;
       screenshotRT.Release();
@@ -139,7 +120,7 @@ namespace ToneTuneToolkit.Media
     #endregion
     // ==================================================
     #region 实验性功能
-    public Texture2D InstantTakeScreenshot(Camera renderCamera, string fullFilePath)
+    public Texture2D InstantTakeScreenshot(Camera renderCamera, string fullFilePath = null)
     {
       // 创建一个RenderTexture
       RenderTexture renderTexture = new RenderTexture(Screen.width, Screen.height, 24);
@@ -156,10 +137,13 @@ namespace ToneTuneToolkit.Media
       texture2D.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
       texture2D.Apply();
 
-      // 保存至本地
-      byte[] bytes = texture2D.EncodeToPNG();
-      File.WriteAllBytes(fullFilePath, bytes);
-      Debug.Log($"[ScreenshotMasterLite] <color=green>{fullFilePath}</color>...[OK]");
+      if (fullFilePath != null)
+      {
+        // 保存至本地
+        byte[] bytes = texture2D.EncodeToPNG();
+        File.WriteAllBytes(fullFilePath, bytes);
+        Debug.Log($"[SM] <color=green>{fullFilePath}</color>");
+      }
 
       peekTexture = texture2D;
 
@@ -171,6 +155,22 @@ namespace ToneTuneToolkit.Media
       return texture2D;
     }
 
+    public Texture2D TakeScreenshot2T2d(Camera screenshotCamera, RectTransform screenshotArea)
+    {
+      Vector2 size = Vector2.Scale(screenshotArea.rect.size, screenshotArea.lossyScale);
+      RenderTexture rt = new RenderTexture((int)size.x, (int)size.y, 24);
+
+      screenshotCamera.targetTexture = rt;
+      screenshotCamera.Render();
+
+      Texture2D t2d = new Texture2D(rt.width, rt.height, TextureFormat.RGBA32, false);
+      Graphics.CopyTexture(rt, t2d);
+
+      screenshotCamera.targetTexture = null;
+
+      return t2d;
+    }
+
     #endregion
     // ==================================================
     #region Tools

+ 169 - 0
ToneTuneToolkit/Assets/ToneTuneToolkit/Scripts/Media/WebCamManager.cs

@@ -0,0 +1,169 @@
+/// <summary>
+/// Copyright (c) 2025 MirzkisD1Ex0 All rights reserved.
+/// Code Version 1.4.20
+/// </summary>
+
+using System.Collections;
+using System.Collections.Generic;
+using ToneTuneToolkit.Common;
+using UnityEngine;
+using UnityEngine.UI;
+
+/// <summary>
+/// 相机管理器
+/// 并在相机初始化后返回贴图
+/// </summary>
+public class WebCamManager : SingletonMaster<WebCamManager>
+{
+  [SerializeField] private RawImage DEBUG_PreviewRawImage;
+
+  private string cameraName = "Logitech BRIO";
+  private int cameraWidth = 1500;
+  private int cameraHeight = 2000;
+  private int cameraFPS = 60;
+
+  private static WebCamTexture webCamTexture;
+
+  // ==================================================
+
+  private void Start() => Init();
+  private void OnDestroy() => UnInit();
+
+  // ==================================================
+
+  private void Init()
+  {
+    RequestCameraAuthorization();
+    return;
+  }
+
+  private void UnInit()
+  {
+    webCamTexture.Stop();
+    return;
+  }
+
+  // ==================================================
+  #region 获取摄像头使用权限
+
+  private void RequestCameraAuthorization() => StartCoroutine(nameof(RequestCameraAuthorizationAction));
+  private IEnumerator RequestCameraAuthorizationAction()
+  {
+    yield return Application.RequestUserAuthorization(UserAuthorization.WebCam);
+
+    if (Application.HasUserAuthorization(UserAuthorization.WebCam))
+    {
+      Debug.Log("[WCM] 已获取摄像头权限");
+      InitWebCamera();
+    }
+    else
+    {
+      Debug.Log("[WCM] 无法获取摄像头权限");
+      StartCoroutine("RequestCameraAuthorization");
+    }
+    yield break;
+  }
+
+  #endregion
+  // ==================================================
+  #region 创建摄像头
+
+  private static bool isWebCameraCreated = false;
+  public void InitWebCamera()
+  {
+    if (WebCamTexture.devices.Length <= 0)
+    {
+      Debug.Log("[WCM] 设备无可用摄像头");
+      return;
+    }
+
+
+    WebCamDevice[] devices = WebCamTexture.devices;
+    WebCamDevice device = devices[0];
+
+#if UNITY_EDITOR // 编辑器使用罗技 // 或笔记本前置
+    foreach (WebCamDevice item in devices)
+    {
+      Debug.Log($"[WCM] 找到摄像头:{item.name}");
+      if (item.name == cameraName)
+      {
+        device = item;
+        break;
+      }
+    }
+    webCamTexture = new WebCamTexture(device.name, cameraWidth, cameraHeight, cameraFPS)
+    {
+      wrapMode = TextureWrapMode.Clamp
+    };
+
+#else // IOS 0=后置相机 1=前置相机
+    device = devices[1];
+    webCamTexture = new WebCamTexture(device.name)
+    {
+      wrapMode = TextureWrapMode.Clamp
+    };
+    // webCamTexture = new WebCamTexture(device.name, cameraWidth, cameraHeight, cameraFPS);
+#endif
+    webCamTexture.Play();
+    isWebCameraCreated = true;
+    Debug.Log($"[WCM] 摄像头 Name :{device.name} / Width:{webCamTexture.width} / Height:{webCamTexture.height} / FPS:{webCamTexture.requestedFPS}");
+    Debug.Log("[WCM] 摄像头初始化完成");
+
+    if (DEBUG_PreviewRawImage) // Preview
+    {
+      DEBUG_PreviewRawImage.texture = webCamTexture;
+    }
+    return;
+  }
+
+  #endregion
+  // ==================================================
+
+  /// <summary>
+  /// 返回相机贴图
+  /// </summary>
+  /// <returns></returns>
+  public static Texture GetCamTexture()
+  {
+    if (isWebCameraCreated)
+    {
+      return webCamTexture;
+    }
+    else
+    {
+      return null;
+    }
+  }
+
+  // ==================================================
+  #region 状态控制
+
+  public void StartWebcam()
+  {
+    if (isWebCameraCreated)
+    {
+      webCamTexture.Play();
+    }
+    return;
+  }
+
+  public void PauseWebcam()
+  {
+    if (isWebCameraCreated)
+    {
+      webCamTexture.Pause();
+    }
+    return;
+  }
+
+  public void StopWebcam()
+  {
+    if (isWebCameraCreated)
+    {
+      webCamTexture.Stop();
+    }
+    return;
+  }
+
+  #endregion
+}

+ 47 - 12
ToneTuneToolkit/Assets/ToneTuneToolkit/Scripts/UI/ScrollViewHandler.cs

@@ -18,14 +18,19 @@ namespace ToneTuneToolkit.UI
     private ScrollRect sv;
     private CanvasGroup cg;
 
-    [SerializeField] private int currentContentIndex = 0;
+    [SerializeField] private CanvasGroup cgBlocker;
+
+    public int currentIndex = 0;
     private Vector2 scrollviewLocation;
 
-    private float stopThreshold = 50f; // 速度阈值,小于该值认为停止
+    private float stopThreshold = 100f; // 速度阈值,小于该值认为停止 // 惯性功能所迫
     private float checkInterval = 0.1f; // 检测间隔(秒)
     private bool isScrolling;
     private float lastCheckTime;
 
+    private float[] anchorPositions; // 锚点位置
+    private float cellDistance; // 单元距离
+
     private const float ANIMTIME = .33f;
 
     // ==================================================
@@ -39,12 +44,20 @@ namespace ToneTuneToolkit.UI
     {
       sv = GetComponent<ScrollRect>();
       cg = GetComponent<CanvasGroup>();
+
+      cellDistance = 1 / ((float)sv.content.childCount - 1);
+      anchorPositions = new float[sv.content.childCount];
+      for (int i = 0; i < anchorPositions.Length; i++)
+      {
+        anchorPositions[i] = cellDistance * i;
+      }
+
       return;
     }
 
     private void Reset()
     {
-      currentContentIndex = 0;
+      currentIndex = 0;
       sv.horizontalNormalizedPosition = 0f;
       return;
     }
@@ -69,6 +82,7 @@ namespace ToneTuneToolkit.UI
     public void GetVector2Location(Vector2 value)
     {
       scrollviewLocation = value;
+      // Debug.Log(scrollviewLocation.x);
       return;
     }
 
@@ -97,28 +111,40 @@ namespace ToneTuneToolkit.UI
       return;
     }
 
+
+
     /// <summary>
     /// 矫正视图位置
     /// </summary>
     public void AdjustView()
     {
-      int newContentIndex = (int)Math.Round(scrollviewLocation.x, 0);
+      int newIndex = 0;
+      float min = Mathf.Abs(scrollviewLocation.x - anchorPositions[0]);
+      for (int i = 1; i < anchorPositions.Length; i++)
+      {
+        float d = Mathf.Abs(scrollviewLocation.x - anchorPositions[i]);
+        if (d < min)
+        {
+          min = d;
+          newIndex = i;
+        }
+      }
 
       sv.horizontal = false;
-      sv.DOHorizontalNormalizedPos(newContentIndex, ANIMTIME).OnComplete(() =>
+      sv.DOHorizontalNormalizedPos(newIndex * cellDistance, ANIMTIME).OnComplete(() =>
         {
           sv.horizontal = true;
-
-          if (currentContentIndex == newContentIndex) // 无变化
+          if (cgBlocker)
           {
-            return;
+            cgBlocker.blocksRaycasts = false;
           }
-          else
+
+          if (currentIndex != newIndex) // 有变化
           {
-            currentContentIndex = newContentIndex;
+            currentIndex = newIndex;
             if (OnScrollViewStopped != null)
             {
-              OnScrollViewStopped(newContentIndex);
+              OnScrollViewStopped(newIndex);
             }
           }
         });
@@ -130,9 +156,18 @@ namespace ToneTuneToolkit.UI
     /// </summary>
     public void Scroll2HorizontalPosition(float normalizedPosition)
     {
+      if (cgBlocker)
+      {
+        cgBlocker.blocksRaycasts = true;
+      }
+
       sv.DOHorizontalNormalizedPos(normalizedPosition, ANIMTIME).OnComplete(() =>
       {
-        AdjustView(); // 视图矫正
+        // AdjustView(); // 视图矫正
+        if (cgBlocker)
+        {
+          cgBlocker.blocksRaycasts = false;
+        }
       });
       return;
     }