Unity2D 图片支持拖拽和以鼠标中心缩放

news/2025/11/8 18:28:36/文章来源:https://www.cnblogs.com/youxiamou/p/19202856
  • 引言
    作为一个Unity初学者,遇到了需要实现以鼠标为中心缩放的功能且需要支持拖拽,秉着复用主义的原则,在网上查找了不少博客,要么免费但不能直接拿来使用,要么需要VIP充值获取项目代码,此外,原理且讲解甚少。为此,笔者花了半天时间研究清楚底层原理后,实现了这个功能。核心代码量其实就那么十几行,懂得原理才是核心。

在讲解下面基础原理前,默认读者已经会实现以图片轴心(缩放的默认中心)缩放的功能。(这个功能很简单,相信网上有很多可靠的资料。)

  • 以鼠标为中心缩放的基础介绍:
    在图片缩放中,一般做法是对rectTransform.localScale赋值缩放倍率,这个缩放是以图片轴心(默认是图片几何中心)缩放的,相对以鼠标位置缩放存在偏移。
    直观上就是当我们鼠标滚轮滚动时,以图片轴心为中心的缩放,在缩放后,鼠标的位置相对图片发生偏移了,也就是说--鼠标对应的屏幕位置没有动,但是该屏幕上显示的内容不是缩放前的内容,
    而很多功能需要具有局部放大的功能,即需要以鼠标为中心进行缩放。

  • 以鼠标为中心缩放的基础原理:
    既然我们明白了这两者缩放存在一个偏移,那么实现以鼠标为中心缩放,可以考虑先使用以图片为中心的缩放,再在缩放后,进行偏移的修正,具体做法是计算出鼠标对应的图片中的位置在缩放前后的偏移量,然后把图片整体朝相反方向进行移动,从而保证鼠标位置处的像素在屏幕上没有动。
    这里清楚的几个坐标是:鼠标位置坐标(屏幕坐标系),鼠标位置坐标转成的图片局部坐标(以图片轴心为原点,右上是正方向),Unity有提供鼠标位置坐标转成图片局部坐标的API函数(具体见下面代码)
    具体偏移修正是这样的:假设图片上像素点A,它的局部坐标是localPos,在放大x倍后,localPos不会变化,但是像素点A在屏幕上的位置会朝着localPos的方向被放大x倍,因此需要将图片整体朝localPos相反的方向移动x倍,即:imgRectTransform.anchoredPosition-=localPos*x;

  • 鼠标拖拽的基础原理:
    其实鼠标拖拽十分基础,并不是我这篇帖子的重点,但有一个点容易被忽视(AI也忽视了)。在鼠标拖拽中有一步从屏幕坐标转局部坐标时会以图片位置为参照,因此在修改图片的位置后,在记录上一次的鼠标局部坐标时,不能直接将图片位置修改前的局部坐标拿来赋值,而应该拿屏幕坐标再转换一次来赋值。

好了,原理和一些细节我讲完了,下面是最重要的环节--可复用的代码,毕竟可复用的代码才是硬道理(代码中有些逻辑本文没有提到,但也是交互的重点,如:拖拽鼠标倍率问题)。对于那些只差临门一脚的同行们,上面的原理他们想必了然于胸,只是某些细节被忽视了,或者对于像我这样的初学者,可运行的代码才是最好的学习资料。

PS:由于这篇博客是笔者第一次做的博客分享,带着对初次写博客的新奇,可能略显得行文啰嗦,但是其中表达了我对博客内容的一些态度,后续博客会减少这部分的比例,用实际内容输出。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;using UnityEngine.EventSystems;[RequireComponent(typeof(RectTransform))]
public class RawImageDragZoom : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IScrollHandler
{[Header("缩放参数")]public float zoomSpeed = 0.2f;   // 滚轮灵敏度public float minScale = 0.2f;   // 最小缩放public float maxScale = 5f;     // 最大缩放[Header("拖动参数")]public float moveSpeed = 1f;[Header("边界缓冲")]public Vector2 padding = Vector2.zero; // 距离边缘留白private RectTransform rt;          // 自己的 RectTransformprivate Vector2 lastMouseLocal;    // 上次鼠标局部坐标private Vector2 originalSize;      // 初始尺寸(用于限制边界)private Canvas canvas;             // 所在 Canvasvoid Awake(){rt = transform as RectTransform;canvas = GetComponentInParent<Canvas>();originalSize = rt.rect.size;}/* -------------------- 拖拽 -------------------- */public void OnBeginDrag(PointerEventData eventData){RectTransformUtility.ScreenPointToLocalPointInRectangle(rt, eventData.position, canvas.worldCamera, out lastMouseLocal);}public void OnDrag(PointerEventData eventData){Vector2 mouseLocal;RectTransformUtility.ScreenPointToLocalPointInRectangle(rt, eventData.position, canvas.worldCamera, out mouseLocal);Vector2 delta = mouseLocal - lastMouseLocal;rt.anchoredPosition += delta * moveSpeed * rt.transform.localScale.x;           //拖拽放大后会导致屏幕坐标映射到rt上的局部坐标以对应倍数缩小lastMouseLocal = mouseLocal;// rt.anchoredPosition变了,ScreenPointToLocalPointInRectangle输出的位置也会变化,需要重新算一遍新的mouseLocal来赋值给lastMouseLocalRectTransformUtility.ScreenPointToLocalPointInRectangle(rt, eventData.position, canvas.worldCamera, out lastMouseLocal);ClampPosition();   // 拖完立刻限制边界}public void OnEndDrag(PointerEventData eventData) { }/* -------------------- 滚轮缩放 -------------------- */public void OnScroll(PointerEventData eventData){float delta = eventData.scrollDelta.y * zoomSpeed;float _newScale = Mathf.Clamp(rt.localScale.x + delta, minScale, maxScale);float newScale = _newScale / rt.localScale.x;Debug.Log($"rt.localScale: {rt.localScale}");Vector3 scale = rt.localScale * newScale;// 以鼠标为中心缩放:先计算鼠标在 Rect 内的归一化坐标Vector2 mouseLocal;Debug.Log($"eventData: {eventData}");RectTransformUtility.ScreenPointToLocalPointInRectangle(rt, eventData.position, canvas.worldCamera, out mouseLocal);delta = _newScale - rt.localScale.x;                        //缩放倍速被限制情况,不移动rt.localScale = scale;rt.anchoredPosition -= mouseLocal * delta;         //ClampPosition();}/* -------------------- 边界限制 -------------------- */private void ClampPosition(){Vector2 scale = rt.localScale;Vector2 size = originalSize * scale;Vector2 min = -size * rt.pivot + padding;Vector2 max = (Vector2.one - rt.pivot) * size - padding;Vector2 pos = rt.anchoredPosition;pos.x = Mathf.Clamp(pos.x, min.x, max.x);pos.y = Mathf.Clamp(pos.y, min.y, max.y);rt.anchoredPosition = pos;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/959904.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

轻松可视化信息的利器——JSON Crack

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

详细介绍:C++微基础备战蓝桥杯string篇10.5

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

[ jupyter conda 环境]

在激活的环境中,可以通过以下命令安装所需的库(包括 NumPy、Pandas、JupyterLab),以及配置自动补全功能,步骤如下: 1. 安装核心库(NumPy、Pandas、JupyterLab) 在激活 myenv 环境后,直接用 conda install 安装…

深入解析:仿mudou——Connection模块(连接管理)

深入解析:仿mudou——Connection模块(连接管理)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas"…

Linux中查看个人磁盘容量

001、 lfs quota -u s20223040682 /public/home/s20223040682

以太坊私有链搭建与智能合约部署指南 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

【学弟向】图的存储与遍历,最短路,连通性 tarjan,树状数组

树状数组 单点修改,前缀查询。 int lbd(int x) {return (x&(-x));} void gai(int x,int c) {for(int i=x;i<=n;i+=lbd(i)) a[i]+=c;} int cha(int x) {int da=0;for(int i=x;i;i-=lbd(i)) da+=a[i];return da;…

2025年11月中国伸缩门制造企业技术实力排行榜TOP5

摘要 随着智慧城市建设的深入推进,2025年中国伸缩门行业迎来技术升级浪潮。智能防夹技术、物联网集成系统和节能驱动技术成为行业新标准,市场需求同比增长23.6%。本文基于企业研发投入、专利数量、客户案例等权威数据…

完整教程:Redis-Zest

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

(数学)好玩但没啥用的解题方法(1)

\[a\geq 0,b\geq 0,a+b=1,求\sqrt{a}+\sqrt{b}的最大与最小值 \]设 \(x=\sqrt{a},y=\sqrt{b}\) ,则\(a+b=1<=>x^2+y^2=1\),是一个单位圆,而 \(\sqrt{a}+\sqrt{b}=x+y\) 。 容易注意到 \(x+y\) 为 \((0,0)\…

2025年11月中国伸缩门厂家综合实力排行榜TOP5

摘要 随着智慧城市建设的深入推进,伸缩门行业在2025年迎来技术升级与市场扩容的双重机遇。本文基于产品性能、技术创新、服务网络及客户案例等维度,对国内主流伸缩门厂家进行综合评估,为政府采购、企业采购提供权威…

我目前所理解的“生成式认知主体”

# 我目前所理解的“生成式认知主体”>前言:[写作能力提升的结构化路径](https://chat.deepseek.com/share/zq67x3d1xpamytl5qp)>在我有打算写关于这个主题的内容前实际上经过了很长时间的 整理思想、理清方向、…

P10627 中暑

题目大意: 有 \(n\) 个盒子,每个盒子有个容量 \(a_{i}\),接下来有 \(m\) 次投球操作。 每次给定一个 \(x\),表示你可以将当前这个球放到第 \(x\) 或者第 \(x + 1\) 个盒子里(前提是他没满),如果两个盒子都满了,…

C语言“变量”与Python“Name”:跨语言核心概念及内存模型辨析

C语言“变量”与Python“Name”:跨语言核心概念及内存模型辨析 摘要:本文针对C语言转Python学习者的认知障碍问题,系统剖析C语言“变量即容器”与Python“名字即指针”的内存模型本质差异。通过理论溯源、源码分析、…

*题解:P14364 [CSP-S 2025] 员工招聘 / employ

原题链接 参考文献 我这篇写的烂,建议看参考文献。 解析 设有 \(x\) 个人未被录用。显然,\(x\) 单调不减,当 \(x\) 变为 \(x + 1\) 时, \(c_i = x + 1\) 的所有人就必定无法被录用了,如果在此时才统计已经面试了的…

MarkDown Day1

MarkDown学习 标题 (空格)标题名字 一级标题 (空格)标题名字 二级标题 (空格)标题名字 三级标题 井号最多到六级 字体 hello,word! 粗体:** 文字 ** hello,word! 斜体:* 文字 * hello,…

逆向基础--C++介绍与环境 (01)

逆向基础--C++介绍与环境 (01)一.介绍1.1 C++ 是一种高级语言,它是由 Bjarne Stroustrup 于 1979 年在贝尔实验室开始设计开发的。C++ 进一步扩充和完善了 C 语言,是一种面向对象的程序设计语言。C++ 可运行于多种平…

【技术术语】静默失效

【技术术语】静默失效“静默失效”(Silent Failure)是指系统或程序在出现错误或故障时,没有给出任何明显的错误提示或警告,而是继续运行,导致问题被掩盖,最终可能引发更严重的后果。这种情况在软件开发、硬件设计…

深入解析:Git Commit Message 最佳实践:从一次指针Bug说起

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025年文化节活动篷房订做厂家权威推荐榜单:航空机库篷房/体育篷房/宴会篷房源头厂家精选

在各类文化节庆活动日益丰富的市场环境下,活动篷房以其灵活的空间解决方案,成为保障活动顺利实施的关键设施。行业数据显示,2024年国内篷房市场规模增长率稳定在15%左右,其中针对文化节、展览展示等临时性活动的定…