【Unity】Unity Transform缩放控制教程:实现3D模型缩放交互,支持按钮/鼠标/手势操作
引言
Transform 缩放控制是 3D 交互应用中的核心功能,广泛应用于模型查看器、建筑设计、VR/AR 交互等场景。本教程实现统一的缩放系统,支持多种输入方式:UI 按钮、鼠标滚轮/拖拽、VR 手势捏合。通过组件化设计和输入抽象层,确保跨平台兼容性(PC、移动、VR)。
核心技术包括 Unity 的 Input System(新输入系统)、XR Interaction Toolkit 和自定义手势识别。支持缩放限制、平滑插值、事件回调。适用于 Unity 2021.3+,推荐 Unity 2022.3 LTS。
基础实现:按钮控制缩放
1. 核心缩放控制器组件
using UnityEngine;
using UnityEngine.Events;
using System;
public class ScaleController : MonoBehaviour
{
[Header("缩放设置")]
[Range(0.1f, 10f)]
public float minScale = 0.1f;
[Range(0.1f, 10f)]
public float maxScale = 10f;
public float scaleSpeed = 1f;
public bool useSmoothScaling = true;
[Range(0.01f, 1f)]
public float smoothTime = 0.1f;
[Header("事件")]
public UnityEvent<float> onScaleChanged;
public UnityEvent onScaleMin;
public UnityEvent onScaleMax;
private Vector3 initialScale;
private Vector3 targetScale;
private Vector3 velocity;
private bool isScaling;
public float CurrentScale { get; private set; } = 1f;
public Action<float> OnScaleUpdate;
void Start()
{
initialScale = transform.localScale;
targetScale = initialScale;
CurrentScale = 1f;
}
void Update()
{
if (useSmoothScaling && isScaling)
{
transform.localScale = Vector3.SmoothDamp(transform.localScale, targetScale, ref velocity, smoothTime);
float newScale = transform.localScale.magnitude / initialScale.magnitude;
if (Vector3.Distance(transform.localScale, targetScale) < 0.001f)
{
isScaling = false;
transform.localScale = targetScale;
}
if (Mathf.Abs(CurrentScale - newScale) > 0.001f)
{
CurrentScale = newScale;
onScaleChanged?.Invoke(CurrentScale);
OnScaleUpdate?.Invoke(CurrentScale);
}
}
}
public void ScaleByFactor(float factor)
{
float newScale = CurrentScale * factor;
SetScale(Mathf.Clamp(newScale, minScale, maxScale));
}
public void SetScale(float scale)
{
scale = Mathf.Clamp(scale, minScale, maxScale);
targetScale = initialScale * scale;
isScaling = true;
if (scale <= minScale) onScaleMin?.Invoke();
if (scale >= maxScale) onScaleMax?.Invoke();
}
public void ResetScale()
{
SetScale(1f);
}
}
2. UI 按钮集成
创建 Canvas > Button,配置 OnClick 事件:
// ButtonZoomController.cs - 附加到 UI 管理器
using UnityEngine;
using UnityEngine.UI;
public class ButtonZoomController : MonoBehaviour
{
[Header("按钮引用")]
public Button zoomInButton;
public Button zoomOutButton;
public Button resetButton;
[Header("缩放参数")]
public ScaleController targetScaler;
public float zoomStep = 0.1f;
void Start()
{
zoomInButton.onClick.AddListener(() => targetScaler.ScaleByFactor(1f + zoomStep));
zoomOutButton.onClick.AddListener(() => targetScaler.ScaleByFactor(1f - zoomStep));
resetButton.onClick.AddListener(targetScaler.ResetScale);
// 按钮状态同步
targetScaler.onScaleChanged.AddListener(UpdateButtonStates);
UpdateButtonStates(targetScaler.CurrentScale);
}
void UpdateButtonStates(float scale)
{
zoomInButton.interactable = scale < targetScaler.maxScale;
zoomOutButton.interactable = scale > targetScaler.minScale;
}
}
鼠标交互实现
3. 鼠标滚轮与拖拽缩放
// MouseScaleInput.cs - 附加到 Camera 或管理器
using UnityEngine;
public class MouseScaleInput : MonoBehaviour
{
[Header("鼠标输入")]
public ScaleController targetScaler;
public float scrollSensitivity = 0.1f;
public float dragSensitivity = 0.5f;
[Header("拖拽设置")]
public bool enableDragZoom = true;
public float dragZoomThreshold = 50f; // 像素拖拽距离阈值
private Vector2 lastMousePos;
private bool isDragging;
private Camera cam;
void Start()
{
cam = Camera.main;
lastMousePos = Input.mousePosition;
}
void Update()
{
HandleScroll();
HandleDrag();
}
void HandleScroll()
{
float scroll = Input.GetAxis("Mouse ScrollWheel");
if (Mathf.Abs(scroll) > 0.01f)
{
targetScaler.ScaleByFactor(Mathf.Pow(1.1f, scroll * scrollSensitivity * 10f));
}
}
void HandleDrag()
{
if (!enableDragZoom) return;
Vector2 currentMousePos = Input.mousePosition;
Vector2 delta = currentMousePos - lastMousePos;
if (Input.GetMouseButtonDown(0))
{
isDragging = true;
lastMousePos = currentMousePos;
}
else if (Input.GetMouseButton(0))
{
if (isDragging && delta.magnitude > dragZoomThreshold)
{
// 垂直拖拽控制缩放
float zoomFactor = -delta.y * dragSensitivity * Time.deltaTime;
targetScaler.ScaleByFactor(1f + zoomFactor);
}
lastMousePos = currentMousePos;
}
else if (Input.GetMouseButtonUp(0))
{
isDragging = false;
}
}
}
VR/AR 手势交互
4. XR 手势捏合缩放(使用 XR Interaction Toolkit)
// XRScaleInput.cs - VR 手势缩放
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.InputSystem;
public class XRScaleInput : MonoBehaviour
{
[Header("XR 设置")]
public ScaleController targetScaler;
public XRController leftController;
public XRController rightController;
[Header("捏合检测")]
public float pinchThreshold = 0.7f;
public float pinchSensitivity = 2f;
private float lastPinchDistance;
private bool isPinching;
void Update()
{
DetectPinchGesture();
}
void DetectPinchGesture()
{
// 获取两手位置(或单手 + 物体)
Transform leftHand = leftController.transform;
Transform rightHand = rightController.transform;
float currentDistance = Vector3.Distance(leftHand.position, rightHand.position);
if (InputSystem.GetDevice<XRHMD>().isPresent)
{
// 检测捏合动作(使用 Action 或直接 Input)
bool leftPinch = leftController.selectInteractionState.active;
bool rightPinch = rightController.selectInteractionState.active;
if (leftPinch && rightPinch && !isPinching)
{
isPinching = true;
lastPinchDistance = currentDistance;
}
else if (leftPinch && rightPinch && isPinching)
{
float deltaDistance = currentDistance - lastPinchDistance;
float scaleFactor = 1f + (deltaDistance * pinchSensitivity * Time.deltaTime / lastPinchDistance);
targetScaler.ScaleByFactor(scaleFactor);
lastPinchDistance = currentDistance;
}
else if (!leftPinch || !rightPinch)
{
isPinching = false;
}
}
}
}
5. 统一输入管理器(推荐)
整合所有输入方式:
// UnifiedScaleManager.cs - 集中管理多种输入
using UnityEngine;
public class UnifiedScaleManager : MonoBehaviour
{
[System.Serializable]
public class InputConfig
{
public bool enableButtonInput = true;
public bool enableMouseInput = true;
public bool enableXRInput = true;
public ScaleController target;
}
public InputConfig config;
private ButtonZoomController buttonController;
private MouseScaleInput mouseInput;
private XRScaleInput xrInput;
void Start()
{
// 动态启用输入模块
if (config.enableButtonInput)
{
GameObject uiManager = GameObject.Find("UIManager");
if (uiManager) buttonController = uiManager.GetComponent<ButtonZoomController>();
}
if (config.enableMouseInput)
mouseInput = Camera.main.gameObject.AddComponent<MouseScaleInput>();
if (config.enableXRInput && XRSettings.isDeviceActive)
xrInput = gameObject.AddComponent<XRScaleInput>();
// 统一目标
if (mouseInput) mouseInput.targetScaler = config.target;
if (xrInput) xrInput.targetScaler = config.target;
}
}
高级功能与优化
6. 缩放限制与边界检测
public class ScaleBoundary : MonoBehaviour
{
public ScaleController scaler;
public Transform boundaryParent; // 父级边界容器
public float boundaryPadding = 0.1f;
void Update()
{
if (scaler == null) return;
Bounds objectBounds = GetRendererBounds();
Vector3 maxLocalScale = Vector3.one;
// 计算每个轴的最大允许缩放
if (boundaryParent != null)
{
foreach (Transform child in boundaryParent)
{
Bounds boundary = GetRendererBounds(child);
Vector3 direction = transform.InverseTransformDirection(child.position - transform.position);
// 根据边界计算最大缩放
maxLocalScale.x = Mathf.Min(maxLocalScale.x,
(boundary.size.x - boundaryPadding) / objectBounds.size.x);
}
}
// 动态更新缩放限制
scaler.minScale = Mathf.Min(scaler.minScale, maxLocalScale.magnitude * 0.1f);
scaler.maxScale = Mathf.Min(scaler.maxScale, maxLocalScale.magnitude);
}
Bounds GetRendererBounds(Transform target = null)
{
if (target == null) target = transform;
Renderer renderer = target.GetComponent<Renderer>();
return renderer != null ? renderer.bounds : new Bounds(target.position, Vector3.one);
}
}
7. 动画与视觉反馈
// ScaleAnimation.cs - 缩放动画增强
using UnityEngine;
using DG.Tweening; // DOTween(可选)
public class ScaleAnimation : MonoBehaviour
{
public ScaleController scaler;
public Ease scaleEase = Ease.OutQuad;
public float animationDuration = 0.3f;
void Start()
{
scaler.onScaleChanged.AddListener(AnimateScaleFeedback);
}
void AnimateScaleFeedback(float scale)
{
// 缩放脉冲反馈
transform.DOScale(transform.localScale * 1.05f, 0.1f)
.SetEase(Ease.OutBack)
.OnComplete(() => {
transform.DOScale(targetScale, animationDuration)
.SetEase(scaleEase);
});
}
}
使用示例与集成
完整工作流
- 创建模型:导入 3D 模型,添加 ScaleController 组件。
- UI 设置:创建 Canvas,添加缩放按钮,配置 ButtonZoomController。
- 输入绑定:附加 MouseScaleInput 到主相机。
- VR 支持:添加 XRScaleInput,配置控制器引用。
- 边界限制:添加 ScaleBoundary,设置父级容器。
- 测试:Play Mode 测试所有输入方式。
Inspector 配置示例
ScaleController:
├── Min Scale: 0.1
├── Max Scale: 5.0
├── Scale Speed: 1.0
├── Smooth Scaling: ✓
└── Events: OnScaleChanged → UI 更新
MouseScaleInput:
├── Scroll Sensitivity: 0.1
├── Drag Zoom: ✓
└── Target: [ScaleController]
跨平台兼容性
平台 | 输入方式 | 特殊配置 |
---|---|---|
PC | 鼠标滚轮/拖拽 | Input System 鼠标支持 |
移动 | 触屏捏合 | Touch Input Module |
VR | 控制器捏合 | XR Interaction Toolkit |
WebGL | 鼠标 + 触屏 | 浏览器输入限制 |
移动端触屏支持
// TouchScaleInput.cs
void HandleTouchPinch()
{
if (Input.touchCount == 2)
{
Touch touch1 = Input.GetTouch(0);
Touch touch2 = Input.GetTouch(1);
Vector2 touch1Prev = touch1.position - touch1.deltaPosition;
Vector2 touch2Prev = touch2.position - touch2.deltaPosition;
float prevDistance = Vector2.Distance(touch1Prev, touch2Prev);
float currentDistance = Vector2.Distance(touch1.position, touch2.position);
float pinchAmount = (currentDistance - prevDistance) / Time.deltaTime;
targetScaler.ScaleByFactor(1f + pinchAmount * touchSensitivity);
}
}
性能优化与最佳实践
- 组件解耦:使用事件系统避免强引用。
- 输入节流:限制 Update 频率,避免高频缩放。
- LOD 集成:缩放时动态切换模型细节级别。
- 内存管理:避免每帧创建新 Vector3,使用对象池。
资源与扩展
- 官方文档:Unity Manual – Input System、Transform
- XR 集成:XR Interaction Toolkit Samples
- 动画库:DOTween(平滑缩放动画)
- 社区示例:Unity Asset Store “Model Viewer” 模板
此实现提供完整的缩放交互系统,支持多种输入方式和平台。如需特定功能扩展(如旋转结合、多物体同步),请提供更多需求!