【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);
            });
    }
}

使用示例与集成

完整工作流

  1. 创建模型:导入 3D 模型,添加 ScaleController 组件。
  2. UI 设置:创建 Canvas,添加缩放按钮,配置 ButtonZoomController。
  3. 输入绑定:附加 MouseScaleInput 到主相机。
  4. VR 支持:添加 XRScaleInput,配置控制器引用。
  5. 边界限制:添加 ScaleBoundary,设置父级容器。
  6. 测试: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);
    }
}

性能优化与最佳实践

  1. 组件解耦:使用事件系统避免强引用。
  2. 输入节流:限制 Update 频率,避免高频缩放。
  3. LOD 集成:缩放时动态切换模型细节级别。
  4. 内存管理:避免每帧创建新 Vector3,使用对象池。

资源与扩展

  • 官方文档:Unity Manual – Input System、Transform
  • XR 集成:XR Interaction Toolkit Samples
  • 动画库:DOTween(平滑缩放动画)
  • 社区示例:Unity Asset Store “Model Viewer” 模板

此实现提供完整的缩放交互系统,支持多种输入方式和平台。如需特定功能扩展(如旋转结合、多物体同步),请提供更多需求!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注