【小松教你手游开发】【unity实用技能】射线触发按钮

最近在做在一个Scrollview下每个Item要实现长按出现其他效果。

在NGUI上可以正常的这么做。

但是在2D Toolkit上却有问题。

在NGUI上滑动Scrollview其实是通过拖动每一个Item实现拖动效果。

而2D Toolkit上是在Scrollview上有一个一大块碰撞体。通过触发这个碰撞体实现拖动。

这里的区别就导致了当你想在2D Toolkit上实现长按Item时,被前面的Scrollview的碰撞体遮挡。

这时候我就用射线来触发长按功能。(其实原本按钮就是通过射线来触发)

射线触发有个问题就是层次。可能在你想要触发的按钮前面有很多个碰撞体,所以我们需要解决的是如何在很多碰撞体的情况下找到你想要的。

这里我们先说整个逻辑然后看代码。

首先射线是需要每帧射,所以我们在Update函数里写射线逻辑。

射线函数Physics.Raycast(newRay, out hitInfo, 1000, _layerMask)会返回射线中第一个碰到的碰撞体。

所以我们要做的是,首先通过屏幕射出第一个射线,当射线碰到碰撞体后判断这个是否我们需要的。

如果不是的话,我们可以通过这个函数输出的hitInfo中获得碰撞的点和碰撞体的宽

这个点加上这个碰撞体的Collider.size.z获得这个碰撞体后面的点。

我们在这个新点上在向屏幕内发出射线检测下一个碰撞

直到找到我们想要的点为止。

这里我们可以用递归来找。

触发的时候根据点击的点获取这个Vector3,并保存他的x,y以便之后根据这个坐标继续找到自己想要找的Gameobject

当找到之后就发出点击的效果了,所以这里用的是Action,ClickHandler像事件一样通知监听的脚本

可以看到下面开了一个协程。

这个协程的作用是通过时间间隙LongPressIntervalTime来判断这个按钮是否一致按着。

如果在这段时间内没有松开按钮,就会触发长按事件。

而现在还需加入松开按钮的逻辑。也是靠射线来判断

这里是根据之前保留下来的点和目标GameObject来判断是否一直点击

发现松开触发松开的函数

这就完成了一个按钮的点击。松开。和长按功能。

中间还有各种逻辑限制和判断。具体代码看脚本。

using UnityEngine;
using System.Collections;
using System;
public class RaycastGameObject : MonoBehaviour {

public Action ClickHandler;
    public Action ReleaseHandler;
    public Action LongPressHandler;

public GameObject TargetGameObject = null;
    public float LongPressIntervalTime = 1.5f;
    public string LayerName = "";

bool _itemPressing = false;
    bool _checkingPressing = false;
    Vector3 _raycaseOriginVector3 = new Vector3();
    LayerMask _layerMask;
    F_Panel _panel;

void Awake()
    {
        if (_panel == null)
            GetPanelInParent(transform);
        if (TargetGameObject == null)
            TargetGameObject = gameObject;

if (LayerName == "")
        {
            _layerMask = 1 << gameObject.layer;
            LayerName = LayerMask.LayerToName(gameObject.layer);
        }
           
        else
            _layerMask = 1 << LayerMask.NameToLayer(LayerName);
    }

// Update is called once per frame
    void Update()
    {
        if (_panel == null)
        {
            GetPanelInParent(transform);
        }
        else
        {
            if (!_checkingPressing && Input.GetMouseButton(0) && PopUpManager.Get.CurrentPopup.name == _panel.name)
            {
                _checkingPressing = true;
                _raycaseOriginVector3 = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0);
                RaycastGameObjectClick(Camera.main.ScreenPointToRay(Input.mousePosition), TargetGameObject);

}
            else if (_itemPressing)
            {
                RaycastGameObjectRelease(new Ray(_raycaseOriginVector3, new Vector3(0, 0, 1)), TargetGameObject);
            }
        }
    }

void RaycastGameObjectClick(Ray ray, GameObject targetObject)
    {

Ray newRay = ray;
        RaycastHit hitInfo;
        if (Physics.Raycast(newRay, out hitInfo, 1000, _layerMask))
        {
            Debug.DrawLine(newRay.origin, hitInfo.point);
            if (hitInfo.collider.gameObject == null)
            {
                _checkingPressing = false;
                return;
            }
            else if (hitInfo.collider.gameObject != targetObject)
            {
                _raycaseOriginVector3 = hitInfo.point;
                Vector3 newPoint = new Vector3(hitInfo.point.x, hitInfo.point.y, hitInfo.point.z + hitInfo.transform.GetComponent<BoxCollider>().size.z);
                RaycastGameObjectClick(new Ray(newPoint, new Vector3(0, 0, 1)), targetObject);
            }
            else
            {
                OnItemPressedEnter();
                return;
            }

}
        else
        {
            _checkingPressing = false;
        }
    }

void RaycastGameObjectRelease(Ray ray, GameObject targetObject)
    {
        if (Input.GetMouseButton(0))
        {
            Ray newRay = ray;
            RaycastHit hitInfo;
            if (Physics.Raycast(newRay, out hitInfo, _layerMask))
            {
                Debug.DrawLine(newRay.origin, hitInfo.point);
                GameObject gameobj = hitInfo.collider.gameObject;
                if (gameobj != targetObject)
                    OnItemPressedEnd();
            }

}
        else
        {
            OnItemPressedEnd();
        }
    }

void OnItemPressedEnter()
    {
        _itemPressing = true;

if (ClickHandler != null)
            ClickHandler();

StartCoroutine(WaitAndCheckPress(LongPressIntervalTime));

}

void OnItemPressedEnd()
    {
        _checkingPressing = false;
        _itemPressing = false;

if (ReleaseHandler != null)
            ReleaseHandler();
    }

IEnumerator WaitAndCheckPress(float seconds)
    {
        yield return new WaitForSeconds(seconds);
        if (_itemPressing)
        {
            if (PopUpManager.Get.CurrentPopup.name == _panel.name)
                LongPressHandler();
        }
    }

void GetPanelInParent(Transform _t)
    {
        if (_t.parent != null)
        {
            F_Panel getPanel = _t.parent.GetComponent<F_Panel>();
            if (getPanel == null)
            {
                GetPanelInParent(_t.parent);
            }
            else
            {
                _panel = getPanel;
            }
        }
    }

}

时间: 2024-10-28 21:08:51

【小松教你手游开发】【unity实用技能】射线触发按钮的相关文章

【小松教你手游开发】【unity实用技能】角色头部跟随镜头旋转

这个在端游上比较场景,在角色展示的时候,当摄像头在角色身边上下左右旋转时,角色头部跟随镜头旋转.如天涯明月刀等. 这个在手游上比较少见,不过实现也没什么区别. 首先一般情况下,找到模型的头部节点,直接用lookAt指向camera就可以了,不过一般需求不会这么简单. 比如说,超过头部扭动极限,头部需要插值回到原始点:当镜头从外部回到极限内,需要插值回来.这时候lookat就没法使用. 更有情况,头部本身坐标系不在世界坐标轴上, 可能旋转了90多或者输出的prefab就是歪的等等,这些情况都没办法

【小松教你手游开发】【unity实用技能】根据上一个GameObject坐标生成的tips界面

开发游戏,特别是mmo手游的时候经常需要开发的一个需求是,点击某个装备,在它附近的位置生成一个tips界面,介绍装备功能和各种信息. 像上面红色框框里的这个. 这个主要的问题是 根据点击的GameObject对应生成这个详情界面时,详情界面位置需要合理摆放(不能显示不到,不能遮挡等) 基本的思路是, 首先找到GameObject的position, 把手机屏幕大概分成四个象限,知道这个GameObject大概在这个屏幕的哪个象限(左上,左下,右上,右下) 根据象限来判断详情界面应该在GameOb

【小松教你手游开发】【unity系统模块开发】热更

现在的手游项目如果没个热更新迭代根本跟不上, 特别是像我们项目做mmo的更是需要经常改动代码. 而现在的项目一般会选择用lua的方式实现热更新 不过我们项目由于历史原因没有使用,用的是另外一种方案 在项目里的所有GameObject都不挂脚本(NGUI脚本就通过代码的方式挂上),自己写的脚本都不继承Mono并打成dll,然后通过一个启动脚本去打开这些dll. 不过这样就有个问题,ios不能热更... 不管怎么样,先来讲讲这种方案要怎么做. 首先有两部分,一部分是打包,一部分是解包. 而包又分为资

【小松教你手游开发】【unity实用技能】InvalidOperationException: out of sync

在unity开发中出现这个bug. 在网上查了下是在迭代器中直接修改引起的.c#是不允许你在迭代器中直接修改. 改了一下确实解决. 原本是这样 [csharp] view plain copy public void Run() { foreach (var item in timerDict) { if (null != item.Value) { item.Value.Run(); } } } 改成这样: [csharp] view plain copy public void Run()

【小松教你手游开发】【unity实用技能】Unity内存申请和释放(转自tnqiang)

这里先声明转自http://www.jianshu.com/p/b37ee8cea04c 1.资源类型 GameObject, Transform, Mesh, Texture, Material, Shader, Script和各种其他Assets. 2.资源创建方式 静态引用,在脚本中加一个public GameObject变量,在Inspector面板中拖一个prefab到该变量上,然后在需要引用的地方Instantiate: Resource.Load,资源需要放在Assets/Reso

【小松教你手游开发】【unity实用技能】给每个GameObject的打开关闭加上一个渐变

在游戏开发中,经常会因为直接将GameObject,setActive的方式打开关闭,这种方式效果太过生硬而给它加上一个Tween 可能是AlphaTween或者ScaleTween. 再加上一个PlayTween来做控制. 这样子需要在每个GameObject上加上这几个Component不说,还很不好用 所以结合之前用的一个拓展函数的方法,想到一个非常非常方便的方法 (之前的拓展函数文章:http://blog.csdn.net/chrisfxs/article/details/512218

【小松教你手游开发】【unity实用技能】计算目标物体是否在自己的扇形视野范围

在做游戏开发中经常会需要到计算扇形的视野或者是受击范围的时候. 其实这个分为两部分, 第一部分是在扇形距离范围内(也就是不考虑角度,其实是圆形范围内) 第二部分是扇形角度范围内 第一部分很简单,Vector3.Distance(a, b);计算距离 下面讲讲第二部分,扇形角度范围内. 计算怪物是否在你的视野范围内其实可以这么看 Avatar的正方向向量与Avatar到Enemy之间向量的夹角大小是否小于于视线大小的一半. 这样就能判断是否在视线范围内. 所以现在的问题就是这么计算这个夹角 计算的

【小松教你手游开发】【unity实用技能】unity发包优化(android一键发包)

unity本身的发包其实还是挺方便的,国外的游戏基本都用unity本身的发包. 但在国内的游戏有这么多渠道,这个迭代的速度的情况下,就需要一套更高效的发包方式. 接下来讲具体步骤,如果你们项目有热更新会更麻烦一点. 发包优化的目标是做到一键发包,一般发包机会是一台独立的机子,所以 第一步,更新svn 第二步,配置打包信息.根据不同渠道接入不同sdk 第三步,build apk. 因为我们项目暂时还是测试,所以还没做根据不同渠道接入不同sdk.具体思路是写个xml,在上面填写各种配置信息,版本号,

【小松教你手游开发】【unity实用技能】线性差值计算实现

其实这个unity本身就有的函数Mathf.Lerp(),为什么还要自己实现呢. 有一个原因就是这个函数返回的是float型,float型如果数字非常大,转出int时会有精度丢失,也就是转出来的值不对. 而且非常简单. 看下公式 public int Lerp(int a,int b,int v) { return (int)(a - (0 - v) / (0 - 1) * (a - b)): } 原文地址:http://blog.51cto.com/13638120/2084965

【小松教你手游开发】【unity实用技能】unity ios快捷打包

ios打包是比较麻烦的,配通一次流程后需要做个笔记把各种插件各种配置在每次打包的时候重新配置,作为一个程序员当然不能接受这么笨的事情,写个脚本让代码去实现这些. 首先,介绍一个标记 [PostProcessBuild] 在函数前写上这个标记,unity在打完包后便会调用这个函数. 所以我们也将用这个标记让unity在包打成xcode项目以后,去改动xcode里面的配置. 而unity也有一个默认的函数给我们使用 static void OnPostprocessBuild(BuildTarget