大家好,又见面了,我是你们的朋友全栈君。
需求:点击UI,在场景中生成3D对象,对象跟随鼠标移动,放置后可再次拖拽对象,改变其位置。做了一个小Demo,如下图所示:
实现大致思路:
- 射线碰撞检测
- 对象空间坐标变换(世界坐标->屏幕坐标、屏幕坐标->世界坐标)
首先为要生成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地址扫码后当前文章末尾获取。
更多内容,欢迎关注:
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/149720.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...