Unity 从UI中拖拽对象放置并拖动[通俗易懂]

Unity 从UI中拖拽对象放置并拖动[通俗易懂]需求:点击UI,在场景中生成3D对象,对象跟随鼠标移动,放置后可再次拖拽对象,改变其位置。做了一个小Demo,如下图所示:实现大致思路:射线碰撞检测对象空间坐标变换(世界坐标->屏幕坐标、屏幕坐标->世界坐标)首先为要生成3D对象的UI添加一个鼠标监听事件,脚本如下:SelectImage.csusingSystem.Collections;using…

大家好,又见面了,我是你们的朋友全栈君。

需求:点击UI,在场景中生成3D对象,对象跟随鼠标移动,放置后可再次拖拽对象,改变其位置。做了一个小Demo,如下图所示:

image

实现大致思路:

  • 射线碰撞检测
  • 对象空间坐标变换(世界坐标->屏幕坐标、屏幕坐标->世界坐标)

首先为要生成3D对象的UI添加一个鼠标监听事件,脚本如下:

SelectImage.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class SelectImage : MonoBehaviour,IPointerDownHandler{
    //需要被实例化的预制
    public GameObject inistatePrefab;
    //实例化后的对象
    private GameObject inistateObj;

    // Use this for initialization
    void Start () {
        if (inistatePrefab==null)return;
        //实例化预制
        inistateObj=Instantiate(inistatePrefab) as GameObject;
        inistateObj.SetActive(false);
    }
    //实现鼠标按下的接口
    public void OnPointerDown(PointerEventData eventData)
    {
        inistateObj.SetActive(true);

        //将当前需要被实例化的对象传递到管理器中
        SelectObjManager.Instance.AttachNewObject(inistateObj);
    }
}

将脚本挂载到UI对象上。

创建一个对象放置管理器,用于处理拖动的放置的逻辑:

SelectObjManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SelectObjManager : MonoBehaviour {

    private static SelectObjManager _instance;
    public static SelectObjManager Instance {
        get { return _instance; }
    }
    //物体z轴距摄像机的长度
    public float _zDistance = 50f;
    //对象的缩放系数
    public float _scaleFactor=1.2f;
    //地面层级
    public LayerMask _groundLayerMask;
    int touchID;
    bool isDragging = false;
    bool isTouchInput = false;
    //是否是有效的放置(如果放置在地面上返回True,否则为False)
    bool isPlaceSuccess = false;
    //当前要被放置的对象
    public GameObject currentPlaceObj = null;
    //坐标在Y轴上的偏移量
    public float _YOffset=0.5F;

    void Awake () {
        _instance = this;
    }
    void Update () {
        if (currentPlaceObj == null) return;

        if (CheckUserInput()){
            MoveCurrentPlaceObj();
        }else if (isDragging){
            CheckIfPlaceSuccess();
        }
    }
    /// <summary>
    ///检测用户当前输入
    /// </summary>
    /// <returns></returns>
    bool CheckUserInput () {
        #if !UNITY_EDITOR&&(UNITY_ANDROID||UNITY_IOS)
        if (Input.touches.Length > 0) {
            if (!isTouchInput) {
                isTouchInput = true;
                touchID = Input.touches[0].fingerId;
                return true;
            } else if (Input.GetTouch (touchID).phase == TouchPhase.Ended) {
                isTouchInput = false;
                return false;
            } else {
                return true;
            }
        }
        return false;
        #else
        return Input.GetMouseButton (0);
        #endif
    }
    /// <summary>
    ///让当前对象跟随鼠标移动
    /// </summary>
    void MoveCurrentPlaceObj () {
        isDragging = true;
        Vector3 point;
        Vector3 screenPosition;
        #if !UNITY_EDITOR&&(UNITY_ANDROID||UNITY_IOS)
        Touch touch = Input.GetTouch (touchID);
        screenPosition = new Vector3 (touch.position.x, touch.position.y, 0);
        #else
        screenPosition = Input.mousePosition;
        #endif
        Ray ray = Camera.main.ScreenPointToRay (screenPosition);
        RaycastHit hitInfo;
        if (Physics.Raycast (ray, out hitInfo, 1000, _groundLayerMask)) {
            point = hitInfo.point;
            isPlaceSuccess = true;
        } else {
            point = ray.GetPoint (_zDistance);
            isPlaceSuccess = false;
        }
        currentPlaceObj.transform.position = point+new Vector3(0,_YOffset,0);
        currentPlaceObj.transform.localEulerAngles = new Vector3 (0, 60, 0);
    }
    /// <summary>
    ///在指定位置化一个对象
    /// </summary>
    void CreatePlaceObj(){
        GameObject obj=Instantiate(currentPlaceObj) as GameObject;

        obj.transform.position=currentPlaceObj.transform.position;
        obj.transform.localEulerAngles=currentPlaceObj.transform.localEulerAngles;
        obj.transform.localScale*=_scaleFactor;
        //改变这个对象的Layer为Drag,以便后续拖动检测
        obj.layer=LayerMask.NameToLayer("Drag");
    }
    /// <summary>
    ///检测是否放置成功
    /// </summary>
    void CheckIfPlaceSuccess(){
        if (isPlaceSuccess){
            CreatePlaceObj();
        }
        isDragging=false;
        currentPlaceObj.SetActive(false);
        currentPlaceObj=null;
    }
    /// <summary>
    /// 将要创建的对象传递给当前对象管理器
    /// </summary>
    /// <param name="newObject"></param>
    public void AttachNewObject(GameObject newObject){
        if (currentPlaceObj){
            currentPlaceObj.SetActive(false);
        }
        currentPlaceObj=newObject;
    }
}

脚本中都有详细注释,我就不多解释了。

创建一个脚本,用于处理放置成功后,再次改变位置的逻辑:

DragObject.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DragObject : MonoBehaviour {
    //只针对指定的层级进行拖动
    public LayerMask _dragLayerMask;
    //指定当前要拖动的对象
    public Transform currentTransform;
    //是否可以拖动当前对象
    public bool isDrag = false;
    //用于存储当前需要拖动的对象在屏幕空间中的坐标
    Vector3 screenPos = Vector3.zero;
    //当前需要拖动对象的坐标相对于鼠标在世界空间坐标中的偏移量
    Vector3 offset = Vector3.zero;
    void Update () {

        if (Input.GetMouseButtonDown (0)) {
            //将鼠标输入点转化为一条射线
            Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
            RaycastHit hitinfo;
            //如果当前对象与指定的层级发生碰撞,表示当前对象可以被拖动
            if (Physics.Raycast (ray, out hitinfo, 1000f, _dragLayerMask)) {
                isDrag = true;
                //将当前需要拖动的对象赋值为射线碰撞到的对象
                currentTransform = hitinfo.transform;
                //将当前对象的世界坐标转化为屏幕坐标
                screenPos = Camera.main.WorldToScreenPoint (currentTransform.position);
                //将鼠标的屏幕坐标转换为世界空间坐标,再与当前要拖动的对象计算两者的偏移量
                offset = currentTransform.position - Camera.main.ScreenToWorldPoint (new Vector3 (Input.mousePosition.x, Input.mousePosition.y, screenPos.z));
            } else {
                isDrag = false;
            }
        }
        if (Input.GetMouseButton (0)) {
            if (isDrag == true) {

                var currentScreenPos = new Vector3 (Input.mousePosition.x, Input.mousePosition.y, screenPos.z);
                //鼠标的屏幕空间坐标转化为世界坐标,并加上偏移量
                var currentPos = Camera.main.ScreenToWorldPoint (currentScreenPos) + offset;
                currentTransform.position = currentPos;
            }
        }
        if (Input.GetMouseButtonUp (0)) {
            isDrag = false;
            currentTransform = null;
        }
    }
}

主要是一些坐标空间的变换和计算。

多余的我就不说了,脚本中都有很详细的注释,Demo地址扫码后当前文章末尾获取。

更多内容,欢迎关注:

码码小虫

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/149720.html原文链接:https://javaforall.net

(1)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 【原创】ERROR 1142 (42000): command denied to user 引发的权限不足问题[亲测有效][通俗易懂]

    【原创】ERROR 1142 (42000): command denied to user 引发的权限不足问题[亲测有效][通俗易懂]mysqlgrants引发的权限不足问题[42000]基于mysql5.7.x1、先退出mysql,找到mysql的配置文件我的文件在这里./etc.my.cnf2、然后重新启动mysql,3、进入mysql,切换到mysql数据库,找到user表,查看user表的权限:4、修改权限,基于mysql5.7.x正常创建数据库查看权限>>>showgrants;…

    2022年9月1日
    3
  • Node.js REPL模块「建议收藏」

    Node.js REPL模块「建议收藏」repl模块提供了一个”读取-求值-输出-循环”(REPL交互式解释器)的实现,它可以作为一个单独的程序,或者包含在其他程序内部。

    2025年7月24日
    4
  • 回溯算法之N皇后问题[通俗易懂]

    回溯算法之N皇后问题[通俗易懂]问题描述什么是皇后问题八皇后问题(英文:Eightqueens),是由国际西洋棋棋手马克斯·贝瑟尔于1848年提出的问题,是回溯算法的典型案例。问题表述为:在8×8格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。如果经过±90度、±180度旋转,和对角线对称变换的摆法看成一类,共有42类。计算机发明后,有多种计算机语

    2022年9月30日
    4
  • 最好用的mysql 管理工具_汽车行业质量管理五大工具

    最好用的mysql 管理工具_汽车行业质量管理五大工具对于数据库运维人员来说,想要保证数据库在高效平稳的运行就有点像杂技演员在转盘子,需要灵活、专注、能快速做出反应、并且拥有冷静的头脑。数据库几乎是所有能够成功运行系统的核心。而数据库运维人员对组织的数据… 对于数据库运维人员来说,想要保证数据库在高效平稳的运行就有点像杂技演员在转盘子,需要灵活、专注、能快速做出反应、并且拥有冷静的头脑。数据库几乎是所有能够成功运行系统的核心。而数据库运维人员对组织的数据负责,能找到可依靠的工具来更加高效的管理数据库,并且轻松的维护日常的工作就变得格外重要。数据库运维人

    2022年8月22日
    6
  • MATLAB—-输入和输出

    MATLAB—-输入和输出文章目录 1 输入语句 1 1 输入数值或矩阵 1 2 输入字符串 2 输出语句 2 1 输出单个字段 2 2 输出多个字段 1 输入语句 1 1 输入数值或矩阵 value1 input 请输入一个数值 value2 input 请输入一个矩阵 1 2 输入字符串输入字符串 需要加第二个参数 s string input 请输入一个字符串 s 2 输出语句使用 disp 函数可以输出 输出多个字段时 需要将多个字段转

    2025年7月2日
    3
  • Java案例:实现九九乘法表「建议收藏」

    Java案例:实现九九乘法表「建议收藏」Java案例:实现九九乘法表前言本篇文章主要讲述并实现Java实现九九乘法表。一、九九乘法表?九九乘法表就是咱们小学时期最开始接触乘法运算时,数字10以内,以及结果100以内的乘法口诀。二、解题思路因为涉及到行与列,而且均有1~9这样的循环出现,因此首先想到的就是for循环,而且要出现两个其次,因为九九乘法表每一行的等式左边的因数为等式的列,右边的因数为等式的行,同一行,右边的因数不变,所以for循环有嵌套关系又因为左边的因数永远小于等于右边的因数,所以嵌套的for循环条件一定为

    2022年7月15日
    27

发表回复

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

关注全栈程序员社区公众号