killapp/Assets/Scripts/UI/AvatarSelection.cs

597 lines
20 KiB
C#
Raw Normal View History

2026-03-30 16:25:00 +08:00
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();
}
}
}