Unity城市虚拟搭建的时候,【房子】或【桥廊】跟随鼠标在【地面】或者【水面】移动

请添加图片描述

一、需求

常见虚拟搭建的步骤:
  • 1、在模型库中点击对应的模型图片
  • 2、光标变成扳手
  • 3、光标第一次进入地面后,对应3D物体出现在地面上,并跟随光标在地面上移动
  • 4、鼠标单击物体的时候,物体停止移动并固定下来
  • 5、完成搭建

二、关键点

  • 1、光标变成扳手状
  • 2、光标拖动3D物体在某3D物体平面上移动

三、光标变成扳手状——扳手图标在屏幕上跟随光标移动

光标变成扳手状,代表即将有建筑物需要安装

1、代码

using UnityEngine;
using System.Linq;
using static txlib;

/// <summary>
/// 图片在屏幕上的鼠标跟随
/// </summary>
public class DragTools : MonoBehaviour
{
    /// <summary>
    /// 扳手图片:需要鼠标跟随
    /// 多处有图片,所以用数组
    /// </summary>
    [Header("扳手图片")]
    [SerializeField]
    public GameObject[] images;

    /// <summary>
    /// 图片跟随鼠标——开关
    ///     true:跟随
    ///     false:不跟随
    /// </summary>
    public static bool dragOn;

    // Update is called once per frame
    void Update()
    {
        if (dragOn)
        {
            images.ToList().ForEach(img => Drag2dObjectViaMouse(img));
        }
    }

#if UNITY_EDITOR
    [ContextMenu("设置拖动开关的值")]
#endif
    void Test5()
    {
        dragOn = !dragOn;
    }
}

2、脚本配置

在这里插入图片描述

四、光标拖动3D物体在某3D物体平面上移动——虚拟搭建

1、常见虚拟搭建的步骤

(1)在模型库中点击对应的模型图片
(2)光标变成扳手
(3)光标第一次进入地面后,对应3D物体出现,并跟随光标在地面上移动
(4)鼠标单击物体的时候,物体停止移动并固定下来
(5)完成搭建

请添加图片描述

2、如何把【光标】在【地面上】的当前位置获取

从当前相机发射一条射线,穿过鼠标位置,与场景中的物体交汇,获取交汇的点,这是一个世界坐标,然后把物体摆放到该点。

核心代码:

 Ray camRay = mainCamera.ScreenPointToRay(Input.mousePosition);
 RaycastHit hitInfo;            
  bool isHit = Physics.Raycast(camRay, out hitInfo);         
  if (isHit)
  {
        Debug.Log($"交汇的物体:{hitInfo.transform.gameObject.name}");
        Debug.Log($"交汇的坐标点:{hitInfo.point}");
  }

示例代码:

 // Update is called once per frame
    void Update()
    {
        if ((isOn == true) && (obj != null))
        { 
            Ray camRay = mainCamera.ScreenPointToRay(Input.mousePosition);
            RaycastHit hitInfo;            
            bool isHit = Physics.Raycast(camRay, out hitInfo);         
            if (isHit)
            {
                colliders = placeType switch 
                {
                    1=> defaultColliders,  //默认地面搭建
                    2 => m_grounds,        //指定平台搭建
                    _ => throw new Exception("placeType:错误的参数类型"),
                };

                if (colliders.Contains(hitInfo.transform.gameObject.name))
                {
                    Debug.Log($"交汇的物体:{hitInfo.transform.gameObject.name}");
                    Debug.Log($"交汇的坐标点:{hitInfo.point}");                   
                    obj.transform.position = new Vector3(hitInfo.point.x, obj.transform.position.y, hitInfo.point.z);//在xz平面上移动
                    obj.SetActive(true);
                    obj.GetComponentsInChildren<Transform>(true).ToList().ForEach(x => x.gameObject.SetActive(true)); //子物体也显示
                    hadIntersected = true;
                }
            }
        }
    }

3、一次搭建的逻辑

一次虚拟搭建的逻辑:
    【1】打开[3D物体随光标在地面移动的开关],参数初始化(拖拽的物体设置,update打开...)
    【2】光标移动
    【3】等待光标第一次碰到地面的时候,建筑物出现,然后跟随光标在地面移动
    【4】等待鼠标左键单击时,建筑安装到位,停止不动 TODO:单击指定地点才安装,比如【桥】要装在【水面】上,【房子】要装在【地面】上
    【5】关闭脚本,参数更新

定义一个供外部调用的委托变量

    /// <summary>
    /// 放置物体【鼠标巡游放置】
    /// 参数:
    ///     1、要放置的物体
    ///     2、要显示的物体(物体自己和物体的root)
    ///     3、搭建在那个平台上(桥在【水面】,商铺在【铺装地面】)
    /// 外部调用的入口,在Start()里面初始化  
    /// </summary>
    public static Func<GameObject, GameObject[],string[], UniTask> PutObjAsync2;

委托的绑定——虚拟搭建的异步实现

//【随意位置安装】事件绑定
        PutObjAsync2 = async (myObj, objs,grounds) =>
        {
            //【1】打开脚本
            obj = myObj;
            isOn = true;
            placeType = 2;

            hadIntersected = false;
            m_grounds = grounds.ToList();

            //【2】鼠标腾挪物体...ing
            await UniTask.WaitUntil(() => hadIntersected == true);
            objs.ToList().ForEach(x => x.SetActive(true));

            //【3】等待鼠标左键单击事件
            await UniTask.WaitUntil(() => Input.GetMouseButtonDown(0));//鼠标左键按下,代表摆放该物体。TODO:合理的逻辑应该是,鼠标点击地面才能摆放建筑

            //【4】关闭脚本
            isOn = false;
            obj = null;
            hadIntersected = false;
            //Debug.Log($" {myObj.name}  PutObjAsync()安装完成!");
        };

4、虚拟搭建的代码

using Cysharp.Threading.Tasks;
using System;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using HighlightingSystem;
using UnityEngine.EventSystems;
using static txlib;

/// <summary>
/// 功能:获取鼠标与【物体】相交会的位置,然后在该位置摆放一些模型。
///       【物体】说明——物体特指能用来摆放其它模型的地方,比如建筑物的地面广场,工厂的工作台......
/// 原理:从当前相机发射一条射线,穿过鼠标位置,与场景中的物体交汇,获取交汇的点,这是一个世界坐标,然后把物体摆放到该点。
/// 备注:摆放的时候,Y值保持不变,只在xz平面上移动
/// TODO:如果物体与【指定的物体】发生碰撞,则不能摆放,不碰撞时,才能够摆放。与【指定的物体】发生碰撞的意思——比如已经有一个房子在那里了,你却又摆了另一个房子上去,视为不合适的行为。
/// </summary>
public class MouseIntersectObject : MonoBehaviour
{
    /********************************************
    常见虚拟搭建的步骤:
    1、在模型库中点击对应的模型图片
    2、光标变成扳手
    3、光标第一次进入地面后,对应3D物体出现,并跟随光标在地面上移动
    4、鼠标单击物体的时候,物体停止移动并固定下来
    5、完成搭建

    关键点:
    1、光标变成扳手
    2、光标拖动3D物体在某3D物体平面上移动

    一次虚拟搭建的逻辑:
    【1】打开[3D物体随光标在地面移动的开关],参数初始化(拖拽的物体设置,update打开...)
    【2】光标移动
    【3】等待光标第一次碰到地面的时候,建筑物出现,然后跟随光标在地面移动
    【4】等待鼠标左键单击时,建筑安装到位,停止不动 TODO:单击指定地点才安装,比如【桥】要装在【水面】上,【房子】要装在【地面】上
    【5】关闭脚本,参数更新
     ********************************************/

    /// <summary>
    /// 要移动的物体
    /// </summary>
    [Header("要移动的物体")]
    public GameObject obj;

    /// <summary>
    /// 主相机[射线发射的相机]
    /// </summary>
    [Header("主相机[射线发射的相机]")]
    public Camera mainCamera;

    /// <summary>
    /// 脚本的功能开关,控制Update的逻辑,防止空刷
    /// </summary>
    public bool isOn;

    /// <summary>
    /// 光标是否与可摆放的位置发生碰撞
    /// 建筑摆放的时候,不能让它提前显示出来,而是应该等到光标和摆放平台第一次Intersected时,才把建筑显示出来
    /// </summary>
    public static bool hadIntersected;

    /// <summary>
    /// 放置物体【鼠标巡游放置】
    /// 参数:
    ///     1、要放置的物体
    ///     2、要显示的物体(物体自己和物体的root)
    /// 外部调用的入口,在Start()里面初始化  
    /// </summary>
    public static Func<GameObject,GameObject[], UniTask> PutObjAsync;

    /// <summary>
    /// 放置物体【鼠标巡游放置】
    /// 参数:
    ///     1、要放置的物体
    ///     2、要显示的物体(物体自己和物体的root)
    ///     3、搭建在那个平台上(桥在【水面】,商铺在【铺装地面】)
    /// 外部调用的入口,在Start()里面初始化  
    /// </summary>
    public static Func<GameObject, GameObject[],string[], UniTask> PutObjAsync2;

    /// <summary>
    /// 放置物体【不需要巡游】
    /// 参数:
    ///     1、要放置的物体
    ///     2、要显示的物体(物体自己和物体的root)
    /// 外部调用的入口,在Start()里面初始化  
    /// </summary>
    public static Func<GameObject, GameObject[], UniTask> PutObjWithOutMouseNavegateAsync;

    /// <summary>
    /// 默认的搭建平台
    /// </summary>
    [Header("默认的搭建平台")]
    public List<string> defaultColliders = new List<string>();

    /// <summary>
    /// 哪些是地面的碰撞体
    /// </summary>
    private List<string> colliders = new List<string>();

    /// <summary>
    /// 单例脚本,本脚本只能挂一处
    /// </summary>
    private static bool attached;

    /// <summary>
    /// 被点击的物体
    /// </summary>
    private static string clickedObj;

    /// <summary>
    /// 可以摆放建筑物的平台
    /// </summary>
    private List<string> m_grounds;

    /// <summary>
    /// 搭建的种类:对应1 = PutObjAsync(),2 = PutObjAsync2()
    /// </summary>
    private int placeType;

    private void Awake()
    {
        attached = true;
        if (attached)
        {
            Debug.Log($"脚本MouseIntersectObject只能挂载一次!! from {this.gameObject.name}");
        }
    }

    private void Start()
    {
        #region      ============事件绑定============begin

        //【随意位置安装】事件绑定
        PutObjAsync = async (myObj,objs) => 
        {
            //【1】打开脚本
            obj = myObj;
            isOn = true;
            placeType = 1;

            hadIntersected = false;

            //【2】鼠标腾挪物体...ing
            await UniTask.WaitUntil(() =>  hadIntersected == true);
            objs.ToList().ForEach(x=>x.SetActive(true));

            //【3】等待鼠标左键单击事件
            await UniTask.WaitUntil(()=>Input.GetMouseButtonDown(0));//鼠标左键按下,代表摆放该物体。TODO:合理的逻辑应该是,鼠标点击地面才能摆放建筑

            //【4】关闭脚本
            isOn = false;
            obj = null;
            hadIntersected = false;
            //Debug.Log($" {myObj.name}  PutObjAsync()安装完成!");
        };

        //【随意位置安装】事件绑定
        PutObjAsync2 = async (myObj, objs,grounds) =>
        {
            //【1】打开脚本
            obj = myObj;
            isOn = true;
            placeType = 2;

            hadIntersected = false;
            m_grounds = grounds.ToList();

            //【2】鼠标腾挪物体...ing
            await UniTask.WaitUntil(() => hadIntersected == true);
            objs.ToList().ForEach(x => x.SetActive(true));

            //【3】等待鼠标左键单击事件
            await UniTask.WaitUntil(() => Input.GetMouseButtonDown(0));//鼠标左键按下,代表摆放该物体。TODO:合理的逻辑应该是,鼠标点击地面才能摆放建筑

            //【4】关闭脚本
            isOn = false;
            obj = null;
            hadIntersected = false;
            //Debug.Log($" {myObj.name}  PutObjAsync()安装完成!");
        };

        //【固定位置安装】事件绑定
        PutObjWithOutMouseNavegateAsync = async (myObj, objs) => 
        {
            await UniTask.DelayFrame(1);

            //物体显示
            objs.ToList().ForEach(x =>
            {
                x.SetActive(true);
            });

            //物体高亮开
            objs.ToList().ForEach(async (x) =>
            {
                if (x.GetComponent<Highlighter>() == null) 
                {
                    x.AddComponent<Highlighter>();
                    //await UniTask.DelayFrame(1);  //等待脚本初始化
                }

                Debug.Log($"点亮物体{x.name}");
                x.GetComponent<Highlighter>().tween = true;
                x.GetComponent<Highlighter>().overlay = true;
            });

            //等待click该物体
            var clicked = false;
            if (myObj.GetComponent<EventTrigger>() == null)  //组件添加——防止为空
            {
                myObj.AddComponent<EventTrigger>();
                //await UniTask.DelayFrame(1);  //等待脚本初始化
            }            
            
            myObj.GetComponent<EventTrigger>().AddListener(EventTriggerType.PointerDown, (PointerEventData eventData) =>
            {                
                clicked = true;
            });

            await UniTask.WaitUntil(() => clicked == true);
            //物体高亮关
            objs.ToList().ForEach(x =>
            {
                x.GetComponent<Highlighter>().tween = false;
            });

            Debug.Log($"物体 {myObj.name} 摆放结束");

        };
        #endregion   ============事件绑定============end
    }

    // Update is called once per frame
    void Update()
    {
        if ((isOn == true) && (obj != null))
        { 
            Ray camRay = mainCamera.ScreenPointToRay(Input.mousePosition);
            RaycastHit hitInfo;            
            bool isHit = Physics.Raycast(camRay, out hitInfo);         
            if (isHit)
            {
                colliders = placeType switch 
                {
                    1=> defaultColliders,  //默认地面搭建
                    2 => m_grounds,        //指定平台搭建
                    _ => throw new Exception("placeType:错误的参数类型"),
                };

                if (colliders.Contains(hitInfo.transform.gameObject.name))
                {
                    Debug.Log($"交汇的物体:{hitInfo.transform.gameObject.name}");
                    Debug.Log($"交汇的坐标点:{hitInfo.point}");                   
                    obj.transform.position = new Vector3(hitInfo.point.x, obj.transform.position.y, hitInfo.point.z);//在xz平面上移动
                    obj.SetActive(true);
                    obj.GetComponentsInChildren<Transform>(true).ToList().ForEach(x => x.gameObject.SetActive(true)); //子物体也显示
                    hadIntersected = true;
                }
            }
        }
    }

    private void OnDestroy()
    {
        /*
         * 场景销毁时(e.g. 切换场景),置false
         */
        attached = false;
    }
}
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值