在 Unity 3D 中使用触摸输入在地形上移动相机

Posted

技术标签:

【中文标题】在 Unity 3D 中使用触摸输入在地形上移动相机【英文标题】:Move Camera Over Terrain Using Touch Input in Unity 3D 【发布时间】:2012-07-16 01:20:40 【问题描述】:

我是 Unity 的新手,我正在尝试弄清楚如何使用触摸输入在地图/地形上移动相机。相机将以 (90,0,0) 的旋转方向俯视地形。地形在第 8 层。我用键盘移动它没有问题,现在我尝试移动到触摸,如果你想在 ios 上保持预期的使用,这是非常不同的。

我能想到的关于内置 iOS 应用程序的最佳示例是地图,其中用户会触摸屏幕,只要手指停留在屏幕上,地图上的那个点就会停留在手指下方。因此,当用户移动他们的手指时,地图似乎也随着手指移动。我无法找到展示如何以这种方式进行操作的示例。我见过很多用鼠标移动相机或角色的例子,但它们似乎不能很好地转化为这种风格。

也发布在 Unity3D 上的答案:

http://answers.unity3d.com/questions/283159/move-camera-over-terrain-using-touch-input.html

【问题讨论】:

澄清一下,您是想模仿“地图”应用程序的行为,还是只需要一个脚本来处理基于触摸的移动? 基本上是的,如果用户将手指放在屏幕上,则手指下方的地形/地图区域在移动时应保持在手指下方。 【参考方案1】:

以下应该是您需要的。请注意,在使用透视相机时,很难在手指/光标和地形之间获得 1 对 1 的对应关系。如果您将相机更改为正交,下面的脚本应该会为您提供手指/光标位置和地图移动之间的完美地图。通过透视,您会注意到轻微的偏移。

你也可以用光线追踪来做到这一点,但我发现这条路线很草率而且不够直观。

用于测试的相机设置(值是从检查器中提取的,因此在此处应用它们):

    位置:0,20,0 方向:90,0,0 投影:透视/正交

using UnityEngine;
using System.Collections;



public class ViewDrag : MonoBehaviour 
    Vector3 hit_position = Vector3.zero;
    Vector3 current_position = Vector3.zero;
    Vector3 camera_position = Vector3.zero;
    float z = 0.0f;
    
    // Use this for initialization
    void Start () 
        
    
    
    void Update()
        if(Input.GetMouseButtonDown(0))
            hit_position = Input.mousePosition;
            camera_position = transform.position;
            
        
        if(Input.GetMouseButton(0))
            current_position = Input.mousePosition;
            LeftMouseDrag();        
        
    
    
    void LeftMouseDrag()
        // From the Unity3D docs: "The z position is in world units from the camera."  In my case I'm using the y-axis as height
        // with my camera facing back down the y-axis.  You can ignore this when the camera is orthograhic.
        current_position.z = hit_position.z = camera_position.y;
        
        // Get direction of movement.  (Note: Don't normalize, the magnitude of change is going to be Vector3.Distance(current_position-hit_position)
        // anyways.  
        Vector3 direction = Camera.main.ScreenToWorldPoint(current_position) - Camera.main.ScreenToWorldPoint(hit_position);
        
        // Invert direction to that terrain appears to move with the mouse.
        direction = direction * -1;
        
        Vector3 position = camera_position + direction;
        
        transform.position = position;
    

【讨论】:

效果相当接近。向左或向右移动的时间越长,手指/鼠标下方的地面在拖动方向上漂移的越多。它引人注目,是一个很好的起点。谢谢! @bagusflyer 是的。 (Input.GetMouseButtonDown(0)) 等同于 (Input.touchCount>0 && Input.GetTouch(0).phase == TouchPhase.Began),或者在 Unity 3.5 中。 4.x版没试过【参考方案2】:

我想出了这个脚本(我已经将它附加到相机上):

private Vector2 worldStartPoint;

void Update () 

    // only work with one touch
    if (Input.touchCount == 1) 
        Touch currentTouch = Input.GetTouch(0);

        if (currentTouch.phase == TouchPhase.Began) 
            this.worldStartPoint = this.getWorldPoint(currentTouch.position);
        

        if (currentTouch.phase == TouchPhase.Moved) 
            Vector2 worldDelta = this.getWorldPoint(currentTouch.position) - this.worldStartPoint;

            Camera.main.transform.Translate(
                -worldDelta.x,
                -worldDelta.y,
                0
            );
        
    


// convert screen point to world point
private Vector2 getWorldPoint (Vector2 screenPoint) 
    RaycastHit hit;
    Physics.Raycast(Camera.main.ScreenPointToRay(screenPoint), out hit);
    return hit.point;

【讨论】:

这对 e 有好处。 G。在你的“光标”/触摸下保持一架飞机。【参考方案3】:

Pavel 的回答对我帮助很大,因此想与社区分享我的解决方案,以防它帮助其他人。我的场景是一个带有正交相机的 3D 世界。我正在研究的自上而下风格的 RTS。我希望平移和缩放像 Google 地图一样工作,当您平移和缩放时,鼠标始终停留在地图上的同一位置。这个脚本为我实现了这一点,并希望它足够强大,可以满足其他人的需求。我还没有对它进行大量测试,但我对它进行了评论,供初学者学习。

using UnityEngine;

// I usually attach this to my main camera, but in theory you can attach it to any object in scene, since it uses Camera.main instead of "this".
public class CameraMovement : MonoBehaviour

    private Vector3 MouseDownPosition;

    void Update()
    
        // If mouse wheel scrolled vertically, apply zoom...
        // TODO: Add pinch to zoom support (touch input)
        if (Input.mouseScrollDelta.y != 0)
        
            // Save location of mouse prior to zoom
            var preZoomPosition = getWorldPoint(Input.mousePosition);

            // Apply zoom (might want to multiply Input.mouseScrollDelta.y by some speed factor if you want faster/slower zooming
            Camera.main.orthographicSize = Mathf.Clamp(Camera.main.orthographicSize + Input.mouseScrollDelta.y, 5, 80);

            // How much did mouse move when we zoomed?
            var delta = getWorldPoint(Input.mousePosition) - preZoomPosition;

            // Rotate camera to top-down (right angle = 90) before applying adjustment (otherwise we get "slide" in direction of camera angle).
            // TODO: If we allow camera to rotate on other axis we probably need to adjust that also.  At any rate, you want camera pointing "straight down" for this part to work.
            var rot = Camera.main.transform.localEulerAngles;
            Camera.main.transform.localEulerAngles = new Vector3(90, rot.y, rot.z);

            // Move the camera by the amount mouse moved, so that mouse is back in same position now.
            Camera.main.transform.Translate(delta.x, delta.z, 0);

            // Restore camera rotation
            Camera.main.transform.localEulerAngles = rot;
        

        // When mouse is first pressed, just save location of mouse/finger.
        if (Input.GetMouseButtonDown(0))
        
            MouseDownPosition = getWorldPoint(Input.mousePosition);
        

        // While mouse button/finger is down...
        if (Input.GetMouseButton(0))
        
            // Total distance finger/mouse has moved while button is down
            var delta = getWorldPoint(Input.mousePosition) - MouseDownPosition;

            // Adjust camera by distance moved, so mouse/finger stays at exact location (in world, since we are using getWorldPoint for everything).
            Camera.main.transform.Translate(delta.x, delta.z, 0);
        
    

    // This works by casting a ray.  For this to work well, this ray should always hit your "ground".  Setup ignore layers if you need to ignore other colliders.
    // Only tested this with a simple box collider as ground (just one flat ground).
    private Vector3 getWorldPoint(Vector2 screenPoint)
    
        RaycastHit hit;
        Physics.Raycast(Camera.main.ScreenPointToRay(screenPoint), out hit);
        return hit.point;
    

【讨论】:

在更新函数内部使用 Camera.main 效率低下,因为每次调用它都会调用 FindObjectWithTag()。如果您在 start 函数中缓存一个引用成员变量,您将获得更好的性能。【参考方案4】:

根据 Pavel 的回答,我简化了脚本并删除了当用一个以上的手指触摸并释放第二个手指时不可爱的“跳跃”:

private bool moreThenOneTouch = false;
private Vector3 worldStartPoint;

void Update() 

    Touch currentTouch;
    // only work with one touch
    if (Input.touchCount == 1 && !moreThenOneTouch) 
        currentTouch = Input.GetTouch(0);

        if (currentTouch.phase == TouchPhase.Began) 
            this.worldStartPoint = Camera.main.ScreenToWorldPoint(currentTouch.position);
        

        if (currentTouch.phase == TouchPhase.Moved) 
            Vector3 worldDelta = Camera.main.ScreenToWorldPoint(currentTouch.position) - this.worldStartPoint;
            
            Camera.main.transform.Translate(
                -worldDelta.x,
                -worldDelta.y,
                0
            );
        
    
    

    if (Input.touchCount > 1) 
        moreThenOneTouch = true;
     else 
        moreThenOneTouch = false;
        if(Input.touchCount == 1)
            this.worldStartPoint = Camera.main.ScreenToWorldPoint(Input.GetTouch(0).position);
    

【讨论】:

【参考方案5】:
using UnityEngine;

// I usually attach this to my main camera, but in theory you can attach it to any object in scene, since it uses Camera.main instead of "this".
public class CameraMovement : MonoBehaviour

    private Vector3 MouseDownPosition;

    void Update()
    
        // If mouse wheel scrolled vertically, apply zoom...
        // TODO: Add pinch to zoom support (touch input)
        if (Input.mouseScrollDelta.y != 0)
        
            // Save location of mouse prior to zoom
            var preZoomPosition = getWorldPoint(Input.mousePosition);

            // Apply zoom (might want to multiply Input.mouseScrollDelta.y by some speed factor if you want faster/slower zooming
            Camera.main.orthographicSize = Mathf.Clamp(Camera.main.orthographicSize + Input.mouseScrollDelta.y, 5, 80);

            // How much did mouse move when we zoomed?
            var delta = getWorldPoint(Input.mousePosition) - preZoomPosition;

            // Rotate camera to top-down (right angle = 90) before applying adjustment (otherwise we get "slide" in direction of camera angle).
            // TODO: If we allow camera to rotate on other axis we probably need to adjust that also.  At any rate, you want camera pointing "straight down" for this part to work.
            var rot = Camera.main.transform.localEulerAngles;
            Camera.main.transform.localEulerAngles = new Vector3(90, rot.y, rot.z);

            // Move the camera by the amount mouse moved, so that mouse is back in same position now.
            Camera.main.transform.Translate(delta.x, delta.z, 0);

            // Restore camera rotation
            Camera.main.transform.localEulerAngles = rot;
        

        // When mouse is first pressed, just save location of mouse/finger.
        if (Input.GetMouseButtonDown(0))
        
            MouseDownPosition = getWorldPoint(Input.mousePosition);
        

        // While mouse button/finger is down...
        if (Input.GetMouseButton(0))
        
            // Total distance finger/mouse has moved while button is down
            var delta = getWorldPoint(Input.mousePosition) - MouseDownPosition;

           // Adjust camera by distance moved, so mouse/finger stays at exact location (in world, since we are using getWorldPoint for everything).
           Camera.main.transform.Translate(delta.x, delta.z, 0);
        
    

    // This works by casting a ray.  For this to work well, this ray should always hit your "ground".  Setup ignore layers if you need to ignore other colliders.
    // Only tested this with a simple box collider as ground (just one flat ground).
    private Vector3 getWorldPoint(Vector2 screenPoint)
    
        RaycastHit hit;
        Physics.Raycast(Camera.main.ScreenPointToRay(screenPoint), out hit);
        return hit.point;
    

【讨论】:

投了反对票,因为这只是对@eselk 发布的脚本的重新发布。 @eselk 证明它

以上是关于在 Unity 3D 中使用触摸输入在地形上移动相机的主要内容,如果未能解决你的问题,请参考以下文章

需要帮助在 Unity 3D 中实现移动输入

Unity 3D 使用高度图创建地形|| Unity 3D 使用笔刷绘制地形

在unity3D中,点击=触摸?

如何使 Unity 3D 输入字段支持的 Windows 10 触摸键盘滑动和预测?

Unity3D中的地形转成模型

Unity 3D 地形系统概述|| Unity 3D 创建地形