killapp/Assets/Scripts/UI/AvatarSelection.cs
2026-03-30 16:25:00 +08:00

597 lines
20 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
using System.IO;
namespace Kill.UI
{
public class AvatarSelection : MonoBehaviour
{
[Header("UI Elements")]
public Button selectPhotoButton;
public RawImage avatarPreview;
public GameObject cropCanvas; // 用于裁剪的全屏Canvas
public RawImage cropImagePreview;
public RectTransform cropArea;
public Button confirmCropButton;
public Button cancelCropButton;
[Header("Avatar Settings")]
[Tooltip("生成头像的尺寸(宽高相等)")]
public int avatarSize = 100;
private Texture2D selectedImage;
private Vector2 startPoint;
private Vector2 endPoint;
private bool isSelecting = false;
private RectTransform cropImageRectTransform;
private CropAreaHandler cropAreaHandler;
void Start()
{
// 初始化按钮事件
if (selectPhotoButton != null)
selectPhotoButton.onClick.AddListener(SelectPhotoFromGallery);
if (confirmCropButton != null)
confirmCropButton.onClick.AddListener(ConfirmCrop);
if (cancelCropButton != null)
cancelCropButton.onClick.AddListener(CancelCrop);
// 隐藏裁剪界面
if (cropCanvas != null)
cropCanvas.SetActive(false);
// 获取裁剪图像的RectTransform
if (cropImagePreview != null)
cropImageRectTransform = cropImagePreview.GetComponent<RectTransform>();
// 为裁剪区域添加处理组件
if (cropArea != null)
{
cropAreaHandler = cropArea.gameObject.AddComponent<CropAreaHandler>();
cropAreaHandler.Initialize(this, cropImageRectTransform);
}
}
/// <summary>
/// 从相册选择照片
/// </summary>
public void SelectPhotoFromGallery()
{
#if UNITY_EDITOR
// 在编辑器中测试时,加载一个默认图片
string testImagePath = UnityEditor.EditorUtility.OpenFilePanel("Select Image", "", "jpg,png,jpeg");
if (!string.IsNullOrEmpty(testImagePath))
{
byte[] fileData = File.ReadAllBytes(testImagePath);
selectedImage = new Texture2D(2, 2);
selectedImage.LoadImage(fileData);
ShowCropInterface(selectedImage);
}
#else
// 使用NativeGallery插件从相册选择图片
bool permission = NativeGallery.CheckPermission(NativeGallery.PermissionType.Read, NativeGallery.MediaType.Image);
if (permission)
{
PickImage();
}
else
{
NativeGallery.RequestPermissionAsync((perm) =>
{
if (perm == NativeGallery.Permission.Granted)
{
PickImage();
}
else
{
Debug.LogWarning("没有获得相册访问权限");
}
}, NativeGallery.PermissionType.Read, NativeGallery.MediaType.Image);
}
#endif
}
private void PickImage()
{
NativeGallery.GetImageFromGallery((path) =>
{
if (path != null)
{
// 加载选中的图片,并确保纹理是可读的
selectedImage = NativeGallery.LoadImageAtPath(path, -1, false); // 设置 markTextureNonReadable 为 false
if (selectedImage != null)
{
ShowCropInterface(selectedImage);
}
else
{
Debug.LogError("无法加载选定的图片");
}
}
}, "选择一张图片");
}
/// <summary>
/// 显示裁剪界面
/// </summary>
/// <param name="image">要裁剪的图片</param>
private void ShowCropInterface(Texture2D image)
{
if (cropCanvas != null && cropImagePreview != null)
{
cropImagePreview.texture = image;
cropCanvas.SetActive(true);
// 调整图片显示大小以适应屏幕并保持原图比例
FitImageToScreen(image);
// 重置裁剪区域
if (cropAreaHandler != null)
{
cropAreaHandler.ResetCropArea();
}
}
}
/// <summary>
/// 调整图片大小以适应屏幕并保持原图比例同时确保不超过cropCanvas的大小
/// </summary>
/// <param name="image">要调整的图片</param>
private void FitImageToScreen(Texture2D image)
{
if (cropImageRectTransform == null) return;
// 获取cropCanvas的RectTransform
RectTransform canvasRect = cropCanvas.GetComponent<RectTransform>();
if (canvasRect == null) return;
// 计算可用空间(考虑到一些边距)
float maxWidth = canvasRect.rect.width * 0.9f;
float maxHeight = canvasRect.rect.height * 0.7f;
// 计算图片比例
float imageRatio = (float)image.width / image.height;
// 根据图片比例调整尺寸
float width, height;
if (imageRatio > 1) // 宽图
{
width = Mathf.Min(maxWidth, image.width);
height = width / imageRatio;
// 检查高度是否超出限制
if (height > maxHeight)
{
height = maxHeight;
width = height * imageRatio;
}
}
else // 高图或正方形
{
height = Mathf.Min(maxHeight, image.height);
width = height * imageRatio;
// 检查宽度是否超出限制
if (width > maxWidth)
{
width = maxWidth;
height = width / imageRatio;
}
}
// 设置尺寸
cropImageRectTransform.sizeDelta = new Vector2(width, height);
}
/// <summary>
/// 确认裁剪
/// </summary>
public void ConfirmCrop()
{
if (selectedImage == null || cropArea == null || cropImageRectTransform == null)
{
CancelCrop();
return;
}
// 计算在原图中的裁剪坐标
Rect cropRect = CalculateCropRect();
if (cropRect.width <= 0 || cropRect.height <= 0)
{
CancelCrop();
return;
}
// 创建新的指定尺寸的纹理
Texture2D croppedTexture = CropTexture(selectedImage, cropRect);
// 检查裁剪纹理是否小于目标尺寸,并决定使用哪种缩放算法
Texture2D resizedTexture;
if (croppedTexture.width < avatarSize || croppedTexture.height < avatarSize)
{
// 如果裁剪区域小于目标尺寸,使用高质量的双线性插值算法
resizedTexture = ResizeTextureWithBilinear(croppedTexture, avatarSize, avatarSize);
}
else
{
// 否则使用普通缩放
resizedTexture = ResizeTexture(croppedTexture, avatarSize, avatarSize);
}
// 显示结果
if (avatarPreview != null)
{
avatarPreview.texture = resizedTexture;
}
// 隐藏裁剪界面
if (cropCanvas != null)
{
cropCanvas.SetActive(false);
}
Debug.Log("头像裁剪完成,尺寸: " + avatarSize + "x" + avatarSize);
}
/// <summary>
/// 计算在原图中的裁剪区域
/// </summary>
/// <returns>裁剪区域</returns>
private Rect CalculateCropRect()
{
if (cropArea == null || cropImageRectTransform == null || selectedImage == null)
return new Rect(0, 0, 0, 0);
// 获取裁剪区域在图片中的相对位置
Rect cropAreaRect = cropAreaHandler.GetCropRect();
// 获取图片显示区域的世界角点
Vector3[] imageCorners = new Vector3[4];
cropImageRectTransform.GetWorldCorners(imageCorners);
// 获取裁剪区域的世界角点
Vector3[] cropAreaCorners = new Vector3[4];
cropArea.GetWorldCorners(cropAreaCorners);
// 计算裁剪区域在图片中的UV坐标
float uMin = Mathf.InverseLerp(imageCorners[0].x, imageCorners[2].x, cropAreaCorners[0].x);
float uMax = Mathf.InverseLerp(imageCorners[0].x, imageCorners[2].x, cropAreaCorners[2].x);
float vMin = Mathf.InverseLerp(imageCorners[0].y, imageCorners[2].y, cropAreaCorners[0].y);
float vMax = Mathf.InverseLerp(imageCorners[0].y, imageCorners[2].y, cropAreaCorners[2].y);
// 转换为像素坐标
int x = Mathf.RoundToInt(uMin * selectedImage.width);
int y = Mathf.RoundToInt(vMin * selectedImage.height);
int width = Mathf.RoundToInt((uMax - uMin) * selectedImage.width);
int height = Mathf.RoundToInt((vMax - vMin) * selectedImage.height);
// 确保不超过图片边界
x = Mathf.Clamp(x, 0, selectedImage.width);
y = Mathf.Clamp(y, 0, selectedImage.height);
width = Mathf.Clamp(width, 0, selectedImage.width - x);
height = Mathf.Clamp(height, 0, selectedImage.height - y);
return new Rect(x, y, width, height);
}
/// <summary>
/// 裁剪纹理
/// </summary>
/// <param name="texture">原纹理</param>
/// <param name="rect">裁剪区域</param>
/// <returns>裁剪后的纹理</returns>
private Texture2D CropTexture(Texture2D texture, Rect rect)
{
int x = Mathf.RoundToInt(rect.x);
int y = Mathf.RoundToInt(rect.y);
int width = Mathf.RoundToInt(rect.width);
int height = Mathf.RoundToInt(rect.height);
// 确保是正方形
int size = Mathf.Min(width, height);
Color[] pixels = texture.GetPixels(x, y, size, size);
Texture2D croppedTexture = new Texture2D(size, size, TextureFormat.RGBA32, false);
croppedTexture.SetPixels(pixels);
croppedTexture.Apply();
return croppedTexture;
}
/// <summary>
/// 调整纹理大小(普通质量)
/// </summary>
/// <param name="texture">原纹理</param>
/// <param name="targetWidth">目标宽度</param>
/// <param name="targetHeight">目标高度</param>
/// <returns>调整大小后的纹理</returns>
private Texture2D ResizeTexture(Texture2D texture, int targetWidth, int targetHeight)
{
// 创建渲染纹理
RenderTexture renderTexture = new RenderTexture(targetWidth, targetHeight, 24);
RenderTexture.active = renderTexture;
// 绘制原纹理到渲染纹理
Graphics.Blit(texture, renderTexture);
// 从渲染纹理创建新的纹理
Texture2D resizedTexture = new Texture2D(targetWidth, targetHeight, TextureFormat.RGBA32, false);
resizedTexture.ReadPixels(new Rect(0, 0, targetWidth, targetHeight), 0, 0);
resizedTexture.Apply();
// 清理
RenderTexture.active = null;
return resizedTexture;
}
/// <summary>
/// 使用双线性插值算法调整纹理大小(高质量)
/// 当源纹理小于目标尺寸时使用,以获得更好的图像质量
/// </summary>
/// <param name="texture">原纹理</param>
/// <param name="targetWidth">目标宽度</param>
/// <param name="targetHeight">目标高度</param>
/// <returns>调整大小后的纹理</returns>
private Texture2D ResizeTextureWithBilinear(Texture2D texture, int targetWidth, int targetHeight)
{
Color[] originalPixels = texture.GetPixels();
int originalWidth = texture.width;
int originalHeight = texture.height;
Color[] newPixels = new Color[targetWidth * targetHeight];
float xRatio = (float)originalWidth / targetWidth;
float yRatio = (float)originalHeight / targetHeight;
for (int y = 0; y < targetHeight; y++)
{
for (int x = 0; x < targetWidth; x++)
{
// 计算在原始图像中的位置
float px = x * xRatio;
float py = y * yRatio;
// 获取相邻的四个像素
int x1 = (int)Mathf.Floor(px);
int y1 = (int)Mathf.Floor(py);
int x2 = Mathf.Min(x1 + 1, originalWidth - 1);
int y2 = Mathf.Min(y1 + 1, originalHeight - 1);
// 计算权重
float fx = px - x1;
float fy = py - y1;
float w1 = (1 - fx) * (1 - fy);
float w2 = fx * (1 - fy);
float w3 = (1 - fx) * fy;
float w4 = fx * fy;
// 获取四个像素的颜色
Color c1 = originalPixels[y1 * originalWidth + x1];
Color c2 = originalPixels[y1 * originalWidth + x2];
Color c3 = originalPixels[y2 * originalWidth + x1];
Color c4 = originalPixels[y2 * originalWidth + x2];
// 双线性插值
Color finalColor = c1 * w1 + c2 * w2 + c3 * w3 + c4 * w4;
newPixels[y * targetWidth + x] = finalColor;
}
}
Texture2D resizedTexture = new Texture2D(targetWidth, targetHeight, TextureFormat.RGBA32, false);
resizedTexture.SetPixels(newPixels);
resizedTexture.Apply();
return resizedTexture;
}
/// <summary>
/// 取消裁剪
/// </summary>
public void CancelCrop()
{
if (cropCanvas != null)
{
cropCanvas.SetActive(false);
}
Debug.Log("取消裁剪");
}
public RectTransform GetCropImageRectTransform()
{
return cropImageRectTransform;
}
public RectTransform GetCropCanvasRectTransform()
{
if (cropCanvas != null)
{
return cropCanvas.GetComponent<RectTransform>();
}
return null;
}
}
/// <summary>
/// 裁剪区域处理组件
/// </summary>
public class CropAreaHandler : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IPointerDownHandler
{
private AvatarSelection avatarSelection;
private RectTransform cropArea;
private RectTransform cropImageRectTransform;
private Vector2 lastPointerPosition;
private Vector2 dragStartPosition;
private float MIN_CROP_SIZE = 50f;
private bool isScaling = false; // 添加标志位,用于在缩放时屏蔽移动操作
public void Initialize(AvatarSelection avatarSelection, RectTransform cropImageRectTransform)
{
this.avatarSelection = avatarSelection;
this.cropImageRectTransform = cropImageRectTransform;
cropArea = GetComponent<RectTransform>();
}
public void ResetCropArea()
{
if (cropArea == null || cropImageRectTransform == null) return;
// 设置默认裁剪区域为图片显示区域的正方形,并确保不小于图片尺寸的一半
float defaultSize = Mathf.Min(cropImageRectTransform.rect.width, cropImageRectTransform.rect.height);
MIN_CROP_SIZE =defaultSize/2.0f;
cropArea.sizeDelta = new Vector2(defaultSize, defaultSize);
cropArea.anchoredPosition = Vector2.zero;
}
public Rect GetCropRect()
{
if (cropArea == null) return new Rect(0, 0, 0, 0);
// 获取世界角点以获得准确的边界
Vector3[] corners = new Vector3[4];
cropArea.GetWorldCorners(corners);
// 返回基于屏幕坐标的矩形
return new Rect(
corners[0].x,
corners[0].y,
corners[2].x - corners[0].x,
corners[2].y - corners[0].y
);
}
public void OnPointerDown(PointerEventData eventData)
{
// 只有在非缩放状态下才允许移动
if (!isScaling)
{
lastPointerPosition = eventData.position;
dragStartPosition = cropArea.anchoredPosition;
}
}
public void OnBeginDrag(PointerEventData eventData)
{
// 只有在非缩放状态下才允许移动
if (!isScaling)
{
lastPointerPosition = eventData.position;
}
}
public void OnDrag(PointerEventData eventData)
{
// 只有在非缩放状态下才允许移动
if (cropArea == null || cropImageRectTransform == null || isScaling) return;
Vector2 currentPointerPosition = eventData.position;
Vector2 deltaPosition = currentPointerPosition - lastPointerPosition;
// 移动裁剪区域
cropArea.anchoredPosition += deltaPosition;
// 限制裁剪区域在图片范围内
ConstrainCropArea();
lastPointerPosition = currentPointerPosition;
}
public void OnEndDrag(PointerEventData eventData)
{
// 只有在非缩放状态下才处理拖拽结束
if (!isScaling)
{
// 拖拽结束时确保裁剪区域仍在有效范围内
ConstrainCropArea();
}
}
private void ConstrainCropArea()
{
if (cropArea == null || cropImageRectTransform == null) return;
// 获取图片显示区域的边界
Rect imageRect = cropImageRectTransform.rect;
Vector2 imageSize = new Vector2(imageRect.width, imageRect.height);
// 获取裁剪区域边界
Vector2 cropSize = cropArea.sizeDelta;
// 计算最大偏移量,确保裁剪区域不会超出图片边界
float maxXOffset = (imageSize.x - cropSize.x) / 2;
float maxYOffset = (imageSize.y - cropSize.y) / 2;
// 限制裁剪区域位置
Vector2 newPosition = cropArea.anchoredPosition;
newPosition.x = Mathf.Clamp(newPosition.x, -maxXOffset, maxXOffset);
newPosition.y = Mathf.Clamp(newPosition.y, -maxYOffset, maxYOffset);
cropArea.anchoredPosition = newPosition;
}
// 添加缩放功能
void Update()
{
if (cropArea == null) return;
// 处理双指缩放(移动端)
if (Input.touchCount == 2)
{
isScaling = true; // 设置缩放标志
Touch touchZero = Input.GetTouch(0);
Touch touchOne = Input.GetTouch(1);
Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition;
Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition;
float prevTouchDeltaMag = (touchZeroPrevPos - touchOnePrevPos).magnitude;
float touchDeltaMag = (touchZero.position - touchOne.position).magnitude;
float deltaMagnitudeDiff = touchDeltaMag - prevTouchDeltaMag;
ResizeCropArea(deltaMagnitudeDiff * 0.5f);
}
// 处理鼠标滚轮缩放PC端
else if (Input.mouseScrollDelta.y != 0)
{
isScaling = true; // 设置缩放标志
ResizeCropArea(Input.mouseScrollDelta.y * 10f);
}
else
{
// 没有缩放操作时,重置标志
isScaling = false;
}
}
private void ResizeCropArea(float deltaSize)
{
if (cropArea == null || cropImageRectTransform == null) return;
// 获取当前尺寸
Vector2 currentSize = cropArea.sizeDelta;
// 计算新尺寸
Vector2 newSize = new Vector2(
currentSize.x + deltaSize,
currentSize.y + deltaSize
);
// 保持正方形并确保在有效范围内
float maxSize = Mathf.Min(cropImageRectTransform.rect.width, cropImageRectTransform.rect.height);
float finalSize = Mathf.Clamp(newSize.x, MIN_CROP_SIZE, maxSize);
cropArea.sizeDelta = new Vector2(finalSize, finalSize);
// 调整位置以保持在图片范围内
ConstrainCropArea();
}
}
}