unity3d 拾取

2017-07-25 22:03:00 weixin_30772261 阅读数 43

3D拾取

3D游戏实际上看到的是2D画面,我们在屏幕上点击,想要找到哪个3D物体,我们实际上是在一个2维平面内做3D拾取。

3D拾取实际上是,当玩家点击屏幕的时候,会从显示屏幕的摄像头发射一条射线,射到它所照射的平面上,射线第一次碰撞到的物体就是玩家所选的物体。

1: 游戏中需要用户触摸/点击 操作3D世界里面的3D物体,那么需要判断用户点击的是3D中的哪个物体;
2: 3D拾取的原理: 从摄像机到屏幕空间的触摸点发出一条射线,这条射线第一个撞到哪个3D物体就会认为哪个3D物体被用户选择;
3: 代码编写
  (1) 发射一条射线: Ray ray = Camera.main.ScreenPointToRay(Touch.position);
  (2) 检测撞到那个物体: Raycast hit; bool Physics.Raycast(ray, out hit);
  (3) hit.transform, 获得物体的transform组件, name可以获得被碰撞的物体的名字;
  (4) Camera.main获取当前我们的主Camera
  (5) 如果要拾取,需要有一个碰撞器

 

 

3D拾取实例

1.创建Unity工程目录

2.创建一个立方体Cube,自带Box Collider(其他物体想要参与3D拾取的必须有碰撞器组件才可以)

3.创建一个脚本ray_test挂载到Cube节点下

4.打开ray_test

using UnityEngine;
using System.Collections;

public class ray_test : MonoBehaviour {

    // Use this for initialization
    void Start () {
        
    }
    
    // Update is called once per frame
    void Update () {
        if (Input.GetMouseButtonDown(0)) {//判断鼠标是否按下
            // 从摄像机开始,到屏幕触摸点,发出一条射线
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            // 撞击到了哪个3D物体
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit)) {
                Debug.Log(hit.transform.name);//打印出碰撞到的节点的名字
            }
        }
    }
}

5.点击Game视图中的Cube,控制台打印出Cube的名字Cube

 

 

注意:

获取主摄像机方法,main是数据成员,使用的时候注意Main Camera节点的Tag必须是MainCamera

Camera.main

 

转载于:https://www.cnblogs.com/HangZhe/p/7236801.html

2019-05-06 17:10:08 qq_38229886 阅读数 1179

 

写在前面

3D拾取功能在实际项目开发中是一种非常常用的功能,尤其是在3D游戏中,拾取技术是必不可少的基础操作,拾取技术极大的增强了游戏的交互性。

3D拾取原理

简单的来说,3D拾取是通过屏幕上的一个点与摄像机的位置(摄像机控件原点)构成一条射线,然后与场景中物体进行相交,判断是否相交,进而进行拾取操作,这个相交判断的实际是射线与三角形的相交判断算法。在实际开发中,更多的使用包围盒等算法。想要深入了解3D拾取原理的同学,请点击。深入了解3D拾取原理

Unity中简单实现3D拾取功能

1: 代码编写

  (1) 发射一条射线: Ray ray = Camera.main.ScreenPointToRay(Touch.position);

  (2) 检测撞到那个物体: Raycast hit; bool Physics.Raycast(ray, out hit);

  (3) hit.transform, 获得物体的transform组件, name可以获得被碰撞的物体的名字;

  (4) Camera.main获取当前我们的主Camera

  (5) 如果要拾取,需要有一个碰撞器

2.源代码

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

public class CatchScript : MonoBehaviour
{
    private GameObject gameobj;
    private bool CubeFlag = false;
    private bool SphereFlag = false;
    private bool CapsuleFlag = false;
    public Texture2D texture;
    void Start()
    {

    }
    void Update()
    {
        //鼠标监听 是否点击
        if (Input.GetMouseButtonDown(0))
        {
            //创建射线 Camera.main 只是代表tag标签为main camera 的摄像机 其可以替换为任何摄像机
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            //创建碰撞体对象
            RaycastHit hit;
            //判断是否碰撞
            if (Physics.Raycast(ray,out hit))
            {
                //打印拾取物体的名称
                Debug.Log(hit.transform.name);
                SetObj(hit.transform.name);        
            }
            else
            {
                CubeFlag = false;
                CapsuleFlag = false;
                SphereFlag = false;
            }
        }

        if (CubeFlag)
        {
            gameobj.GetComponent<Renderer>().material.mainTexture = texture;
            gameobj.transform.Rotate(0, 10, 0);
        }

        if (SphereFlag)
        {
            gameobj.transform.Translate(0, 0.02f, 0);
        }
        if (CapsuleFlag)
        {
            gameobj.transform.Rotate(0, 10, 0);
        }

    }
    void SetObj(string hitname)
    {
        switch(hitname)
        {
            case "Cube":

                gameobj = GameObject.Find("Cube");
                CubeFlag = true;
                CapsuleFlag = false;
                SphereFlag = false;
                break;
            case "Sphere":
                gameobj = GameObject.Find("Sphere");
                SphereFlag = true;
                CubeFlag = false;
                CapsuleFlag = false;
                break;
            case "Capsule":
                gameobj = GameObject.Find("Capsule");
                CapsuleFlag = true;
                CubeFlag = false;
                SphereFlag = false;
                break;
            default:
                CubeFlag = false;
                CapsuleFlag = false;
                SphereFlag = false;
                break;
        }
    }
}

3.将该脚本挂在对应摄像机上。

这样我们就在Unity中简单实现了3D拾取功能。

2017-04-06 16:23:21 SYSUJackJiao 阅读数 13041

一、鼠标拾取物体的原理

Unity3D当中,想要在观察面(Aspect)中拾取物体(有碰撞属性)的方法一般如下:

1、声明一个观察的摄像机、一个从摄像机原点出发的射线Ray以及一个用于检测碰撞的RaycastHit;

2、将射线Ray定义为从摄像机原点出发并且指向当前鼠标所在的坐标(屏幕坐标);

3、定义碰撞RaycastHit为射线Ray与有碰撞属性的物体的碰撞点。

具体代码实现如下(C#代码):

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

public class camera : MonoBehaviour
{
    public Camera ca;
    private Ray ra;
    private RaycastHit hit;

    // Use this for initialization
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
            ra = ca.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ra, out hit))
            {  
                     
            }
    }
}
应用一:当鼠标按住不动时,移动被选定物体随鼠标一起移动
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class camera : MonoBehaviour
{
    public Camera ca;
    private Ray ra;
    private RaycastHit hit;

    // Use this for initialization
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButton(0))
        {
            ra = ca.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ra, out hit))
            {
                hit.collider.gameObject.transform.position = ca.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, hit.collider.gameObject.transform.position.z));
            }
        }
    }
}
应用二:当鼠标点击物体时,物体随鼠标一起移动;当鼠标再次点击时,放下物体。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class camera : MonoBehaviour {
    public Camera ca;
    private Ray ra;
    private RaycastHit hit;
    private int flag = 0;

	// Use this for initialization
	void Start () {
		
	}
	
	// Update is called once per frame
	void Update () {
        if (Input.GetMouseButtonDown(0))
        {
            ra = ca.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ra, out hit))
            {
               if (flag == 0)
                {
                    flag = 1;
                } else
                {
                    flag = 0;
                }
            }
        }
        if (flag == 1)
        {
            hit.collider.gameObject.transform.position = ca.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, hit.collider.gameObject.transform.position.z));
        }

	}
}


2018-05-31 15:19:48 WUYIDUER 阅读数 1018

用鼠标拾取模型的顶点,  那么首先要做的是获取鼠标发出的射线与模型的碰撞点.
代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ll : MonoBehaviour {
    private object spheretransform;
    // Use this for initialization
    void Start () {
  
 }
 
 // Update is called once per frame
 void Update () {
        RaycastHit hit;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        if (Physics.Raycast(ray, out hit, 100))
        {
            Debug.Log(hit.collider.gameObject.name);
            Debug.DrawLine(ray.origin, hit.point, Color.red);
    }
}

 那么要注意几个问题:

         1.场景中的摄像机必须是主摄像机,就是摄像机的tag是MainCamera

 2. Ray ray = Camera.mainCamera.ScreenPointToRay(Input.mousePosition);

         Camera.ScreenPointToRay()方法其实就是“通过2D屏幕坐标在

3D空间中拾取”,我们的3D场景经过摄像机的投影,变成了2D图像,显示在了屏幕上,那么通过逆运算,就可以换算出鼠标点的位置的3D位置,从而可以进一步选取3D场景中的物体!当然这个摄像机是离不开的,是那个摄像机渲染的当前场景,就要用哪个摄像机进行ScreenPointToRay()来进行运算!

 先说一下射线,射线是什么东西呢?

         Ray,Ray有两个字段,origin和direction,其中origin就是摄像的起始点,

         direction是射线的方向!

         Physics.Raycast(Ray ray,out RaycastHit,float distance)

         第一个参数就是一条射线,就是判断射线与其它物体的碰撞!

          碰撞之后会返回RaycastHit信息,distance是射线发出的距离。

  通过Physics.Raycast()可以out出一个参数(形参),RaycastHit,这个可以获取到碰撞点RaycastHit.point;那么为了明显的看到它,我用了一个红色材质的球体,球体要小一些,不断设置球体的位置为碰撞点的位置,大家就很容易看到结果对不对!

          在调试的过程中,我发现红色的球不断的向屏幕移动过来,不解到底是什么原因,后来分析是因为,射线不断的红球发生了碰撞,碰撞点在球的表面,所以球就不断向屏幕外移动,所以要加一句

          if (hit.collider.gameObject.name == "Sphere") return;
  拾取原理   
                   拾取主要用来表示能过鼠标在屏幕上单击来选中某个3D模型,然后就可以获取这个模型信息,同时也可以对这个模型进行编辑。   
            拾取算法的主要思想是:得到鼠标点击处的屏幕坐标,通过投影矩阵和观察矩阵把该坐标转换为通过视点和鼠标点击点的一条射入场景的光线,该光线如果与场景模型的三角形相交,则获取该相交三角形的信息。   
            拾取的具体过程如下:   
                   1.使用获取鼠标当前状态。   
                   2.把屏幕坐标转换为屏幕空间坐标。   屏幕中心是(0, 0),屏幕右下角是 (1*(屏幕宽度/屏幕高度), 1)。屏幕空间的x坐标这样计算: ((鼠标x坐标) / (屏幕宽度/2))-1.0f) *(屏幕宽度/屏幕高度)   屏幕空间的y坐标这样计算: (1.0f ? ((鼠标y坐标) / (屏幕高度/ 2 ) )   
                   3.计算摄像机视图中的宽度和高度的截距比。如下计算:   view ratio = tangent(camera field of view / 2 )   通常,你只需要一次,然后保存这个结果供以后使用。在摄像机视野改变的时候,你需要重新计算。   
                   4.把屏幕空间坐标转换成摄像机空间坐标系近景裁剪平面中的一个点。   Near point = ((屏幕空间x坐标)*(近景裁剪平面的Z值)*(view ratio ),   (屏幕空间y坐标)*(近景裁剪平面的Z值)*(view ratio ),   (-近景裁剪平面的Z值) )   
                   5.把屏幕空间坐标转换成摄像机空间坐标系远景裁剪平面中的一个点。   Far point = ((屏幕空间x坐标)* (远景裁剪平面的Z值)*(view ratio ),   (屏幕空间y坐标)*(远景裁剪平面的Z值 )*(view ratio),   (-远景裁剪平面的Z值) )   
                   6.使用Invert 获得摄像机视图矩阵的一个Invert的拷贝。使用摄像机视图矩阵(Matrix)来把世界坐标转化成摄像机坐标,使用这个反转的矩阵可以把摄像机坐标转化成世界坐标。   Matrix invView = Matrix.Invert(view);   
                   7.使用反转的视图矩阵(view Matrix ) 和Transform方法把远景点和近景点转换成世界空间坐标。   Vector3 worldSpaceNear = Vector3.Transform(cameraSpaceNear, invView);   Vector3 worldSpaceFar = Vector3.Transform(cameraSpaceFar, invView);   
                   8.创建一个射线(Ray)类的对象,起点是近景点,指向远景点。   Ray pickRay = new Ray(worldSpaceNear, worldSpaceFar - worldSpaceNear);   
                   9.对世界空间中的所有物体循环调用 Intersects 方法来检测 Ray 是否与其相交。如果相交,则检测是不是目前为止距玩家最近的物体,如果是,记录下这个物体以及距离值,替换掉之前找到的最近的物体的记录。当完成对所有物体的检测后,最后一次记录的那个物体就是玩家用鼠标点击的距离玩家最近的物体。


实现效果:
2017-03-10 03:01:42 baidu_24650743 阅读数 14372

Unity 3D 实现拾取物品功能(一)


刚刚接触Unity不久,为了记录自己学习的过程。我决定写写博客,哈哈哈。

在网上看了一些拾取物品的实现原理之后,自己摸索了一下,写了个小Demo,下面来介绍一下基本功能。

1)人物靠近物体,且视角朝向物体,改变物体颜色。
2)按E键拾取物品到背包,并销毁游戏对象。
3)按B键在背包中显示已有物品。

一般背包设定分为格子上限和负重上限两种,在这个Demo里我们使用格子上限的模式。且物品分为可叠加和不可叠加两种。

首先,来预览一下成果。
在游戏场景中建立这么一些游戏对象,来模拟游戏里的真实物品。
游戏场景

然后我们操作角色,移动到物体前。然后镜头指向游戏想,把默认颜色修改为红色。
物品颜色变更

一般游戏都使用E键拾取物品,我也使用E键拾取。拾取成功后,我们就使用Destroy()函数,销毁游戏对象。从而达到物品被加入背包的感觉。
我们把ObjectItem挂到物体上,然后设置属性。
方块1数量为685,可叠加,最大叠加数量为1000。
方块2数量为888,可叠加,最大叠加数量为1000。
相同物品的判断使用ObjId字段,所以同类物品就设置ObjId一致就可以了。
这里写图片描述

拾取以上两个方块后,按下B键,调用显示物品的函数,暂时显示到Debug控制台中。在后续的博客中再继续完善。
这里写图片描述
我们就可以看到,物品栏就有了两条记录。

然后设置两个圆柱个数都为1,且不可叠加。拾取后按B键,查看控制台
这里写图片描述
就可以看到柱子的数量分别存在的单独的格子。

把背包和物品属性抽象,形成两个类。Pack.cs和ObjectItem.cs

Pack.cs代码如下:

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

public class Pack : MonoBehaviour {
    //保存物品的List数组
    public List<ItemEntity> items = null;
    //背包最大容量
    public int maxItem = 32;

    // Use this for initialization
    void Start () {
        items = new List<ItemEntity>();
    }

    //拾取物品
    public ObjectItem getItem(ObjectItem item)
    {
        //使用中间过渡的实体类
        ItemEntity itemEntity = ItemEntity.FillData(item);

        //如果物品不可合并
        if (!itemEntity.IsCanAdd)
        {   
            //背包有空余
            if(items.Count < maxItem)
            {   
                //获得物品
                items.Add(itemEntity);
                item.count = 0;
            } else
            {   
                //拾取失败
            }
        } else
        {
            //空背包时,直接添加
            if (items.Count < 1)
            {
                items.Add(itemEntity);
                item.count = 0;
            } else
            {
                //判断当前背包合并物品
                foreach (ItemEntity currItem in items)
                {
                    //相同物品,且可叠加,且分组未满
                    if (currItem.ObjId.Equals(itemEntity.ObjId) && currItem.Count < currItem.MaxAdd)
                    {
                        //数量叠加
                        currItem.Count = currItem.Count + itemEntity.Count;
                        //如果超出叠加范围
                        if (currItem.Count - currItem.MaxAdd > 0)
                        {
                            //拾取物品的剩余数量变更
                            item.count = currItem.Count - currItem.MaxAdd;
                            itemEntity.Count = item.count;
                            //背包里的数量为最大值
                            currItem.Count = currItem.MaxAdd;
                        } else
                        {
                            // 未超出叠加最大值,拾取成功
                            item.count = 0;
                        }
                    } else
                    {
                        //如果物品不是同类,或分组是满的,遍历下一个格子
                        continue;
                    }
                }
                //物品超出分组,有剩余格子则添加。否则剩余部分无法拾取
                if(item.count > 0 && items.Count < maxItem)
                {
                    items.Add(itemEntity);
                    item.count = 0;
                }
            } 
        }
        return item;      
    }

    //显示物品
    public void showPack()
    {
        string show = "物品:\n";
        int i = 0;
        foreach (ItemEntity currItem in items)
        {  
            show += ++i + " [" + currItem.ObjName + "], 数量: " + currItem.Count + "\n";
        }
        Debug.Log(show);
    }
}

ObjectItem代码如下

using UnityEngine;
using System.Collections;

public class ObjectItem : MonoBehaviour {

    public string objId;
    public string objName;
    public int count;
    public string note;
    public int level;
    public bool isCanAdd;
    public int maxAdd;

    public bool isChecked;

    // Use this for initialization
    void Start () {
        isChecked = false;
    }

// Update is called once per frame
void Update () {
        if (isChecked)
        {
            GetComponent<MeshRenderer>().material.color = Color.red;
        } else
        {
            GetComponent<MeshRenderer>().material.color = Color.white;
        }
        isChecked = false;
    }
}

在使用过程中,使用List保存物品时,调用Destroy()函数后,List里面的对象也会被删除掉。
所以,我们在代码中使用一个中间类ItemEntity.cs,代码如下:

public class ItemEntity {

    private string objId;
    private string objName;
    private int count;
    private string note;
    private int level;
    private bool isCanAdd;
    private int maxAdd;
    private bool isChecked;


    public static ItemEntity FillData(ObjectItem item)
    {
        ItemEntity itemEntity = new ItemEntity();
        itemEntity.ObjId = item.objId;
        itemEntity.ObjName = item.objName;
        itemEntity.Count = item.count;
        itemEntity.Note = item.note;
        itemEntity.Level = item.level;
        itemEntity.IsCanAdd = item.isCanAdd;
        itemEntity.MaxAdd = item.maxAdd;
        itemEntity.IsChecked = item.isChecked;
        return itemEntity;
    }

    public string ObjId
    {
        get
        {
            return objId;
        }

        set
        {
            objId = value;
        }
    }

    public string ObjName
    {
        get
        {
            return objName;
        }

        set
        {
            objName = value;
        }
    }

    public int Count
    {
        get
        {
            return count;
        }

        set
        {
            count = value;
        }
    }

    public string Note
    {
        get
        {
            return note;
        }

        set
        {
            note = value;
        }
    }

    public int Level
    {
        get
        {
            return level;
        }

        set
        {
            level = value;
        }
    }

    public bool IsCanAdd
    {
        get
        {
            return isCanAdd;
        }

        set
        {
            isCanAdd = value;
        }
    }

    public int MaxAdd
    {
        get
        {
            return maxAdd;
        }

        set
        {
            maxAdd = value;
        }
    }

    public bool IsChecked
    {
        get
        {
            return isChecked;
        }

        set
        {
            isChecked = value;
        }
    }
}

最后,我们在玩家控制的角色上使用代码:

using UnityEngine;
using System.Collections;

public class PlayerController : MonoBehaviour {

    private Transform tr;
    //背包类
    private Pack pack;

    // Use this for initialization
    void Start () {
        tr = GetComponent<Transform>();
        pack = GetComponent<Pack>();
    }

    // Update is called once per frame
    void Update () {

        if (Input.GetKeyDown(KeyCode.B))
        {
            pack.showPack();
        }

        Debug.DrawRay(tr.position, tr.forward * 2.0f, Color.green);
        RaycastHit hit;
        if (Physics.Raycast (tr.position, tr.forward, out hit, 2.0f)) {
            //Debug.Log ("射线击中:" + hit.collider.gameObject.name + "\n tag:" + hit.collider.tag);
            GameObject gameObj = hit.collider.gameObject;
            ObjectItem obj = (ObjectItem)gameObj.GetComponent<ObjectItem>();
            if (obj != null)
            {
                obj.isChecked = true;
                //Debug.Log(obj.objName);
                if (Input.GetKeyDown(KeyCode.E))
                {
                    obj = pack.getItem(obj);
                    if(obj.count == 0)
                    {
                        //gameObj.SetActive(false);
                        Destroy(gameObj);
                    }
                }
            }
        }
    }
}

这样,我们就可以实现了拾取物品的基本功能。
当拾取超过了背包容量时,可以重叠的物品会叠加至最大数量,然后剩余数量依然存在于场景中。相当于只拾取了一部分。

代码还有很多没有使用到的属性,也是临时的一些想法,方便后续拓展。
代码变量的命名规范及代码流程还可以有更多的优化。有大牛可以给我指点指点 >.<

以上就是我摸索出来的拾取物品的一个大概模型。不知道你们是怎么实现的呢!