`

实现3d点在屏幕上、屏幕边缘的方向提示

 
阅读更多

1、首先需要解决两条线段相交的逻辑判定

2、按照方向指示类型的不同,进行不同的判定,以及显示处理

3、按不同类型处理

①需求1:在屏幕边缘,显示屏幕外进攻的敌人的来向

需要敌人点和己方点连线 与 屏幕边缘线段做相交点判定处理

②需求2:在屏幕边缘,提示玩家前进的方向

需要目标点和己方点连线 与 屏幕边缘线段做相交点判定处理

 

一、单个提示标记和线段相交逻辑文件如下(ScreenEdgeTips.cs)

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

public class Line2D
{
    //共线
    public const int COLINE = 0;
    //相交
    public const int CROSS = 1;
    //平行线
    public const int PARALLEL = 2;
    //无相交
    public const int NOT_CROSS = 3;


    private double EPS = 1e-4;

    public Vector3 point1;
    public Vector3 point2;

    private float A;
    private float B;
    private float C;

    public Line2D(Vector3 point1, Vector3 point2)
    {
        this.point1 = point1;
        this.point2 = point2;
        this.calcCoefficient();
    }

    private void calcCoefficient()
    {
        this.A = this.point2.y - this.point1.y;
        this.B = this.point1.x - this.point2.x;
        this.C = this.point2.x * this.point1.y - this.point1.x * this.point2.y;
    }

    private bool checkCross(Vector3 sp1, Vector3 ep1, Vector3 sp2, Vector3 ep2)
    {
    
        if (Math.Max(sp1.x, ep1.x) < Math.Min(sp2.x, ep2.x)){
            return false;
        }

        if (Math.Min(sp1.x, ep1.x) > Math.Max(sp2.x, ep2.x)){
            return false;
        }

        if (Math.Max(sp1.y, ep1.y) < Math.Min(sp2.y, ep2.y)){
            return false;
        }

        if (Math.Min(sp1.y, ep1.y) > Math.Max(sp2.y, ep2.y)){
            return false;
        }
    
        Vector3 vectorA = sp1 - sp2;
        Vector3 vectorB = ep2 - sp2;
        Vector3 vectorC = ep2 - sp2;
        Vector3 vectorD = ep1 - sp2;
        double temp1 = (vectorA.x * vectorB.y - vectorA.y * vectorB.x) * (vectorC.x * vectorD.y - vectorC.y * vectorD.x);

        vectorA = sp2 - sp1;
        vectorB = ep1 - sp1;
        vectorC = ep1 - sp1;
        vectorD = ep2 - sp1;
        double temp2 = (vectorA.x * vectorB.y - vectorA.y * vectorB.x) * (vectorC.x * vectorD.y - vectorC.y * vectorD.x);

        if ((temp1 >= 0) && (temp2 >= 0))
        {
            return true;
        }
 
        return false;
    }

    private bool isDoubleEqualZero(double data)
    {
        if (Math.Abs(data) <= EPS){
            return true;
        }else{
            return false;
        }
    }

    public int Intersection(Line2D otherLine, out Vector3 intersectantPoint)
    {
        intersectantPoint = Vector3.zero;
        if (!checkCross(this.point1, this.point2, otherLine.point1, otherLine.point2))
        {
            return Line2D.NOT_CROSS;
        }
        if (isDoubleEqualZero(this.A * otherLine.B - this.B * otherLine.A))
        {
            if (isDoubleEqualZero((this.A + this.B) * otherLine.C - (otherLine.A + otherLine.B) * this.C))
            {
                return Line2D.COLINE;
            }
            else
            {
                return Line2D.PARALLEL;
            }
        }
        else
        {
            intersectantPoint.x = (otherLine.B * this.C - this.B * otherLine.C) / (otherLine.A * this.B - this.A * otherLine.B);
            intersectantPoint.y = (this.A * otherLine.C - otherLine.A * this.C) / (otherLine.A * this.B - this.A * otherLine.B);
            intersectantPoint.z = 0;
            return Line2D.CROSS;
        }
    }
}


public class ScreenEdgeTips : MonoBehaviour
{
    //UI
    public UILabel distanceLabel;
    public GameObject directContainer;
    public float prefabWidth;

    //cameras
    private Camera mainCamera;
    private Camera uicamera;
    
    //param
    private bool isIdle = false;
    private int type = 0;
    private int fromActorId = 0;
    private int toActorId = 0;
    private Vector3 goalPoint;
    
    private List<Line2D> screenLines;

    public ScreenEdgeTips()
    {
        this.isIdle = true;
    }

    private void InitImp()
    {
        float offsetWidth = prefabWidth / 2;
        float originalPoint = 0 + offsetWidth;
        float correctionWidth = Screen.width - offsetWidth;
        float correctionHeight = Screen.height - offsetWidth;
        Vector3 point1 = new Vector3(offsetWidth, offsetWidth, 0);
        Vector3 point2 = new Vector3(offsetWidth, correctionHeight, 0);
        Vector3 point3 = new Vector3(correctionWidth, correctionHeight, 0);
        Vector3 point4 = new Vector3(correctionWidth, offsetWidth, 0);
        this.screenLines = new List<Line2D>();
        this.screenLines.Add(new Line2D(point1, point2));
        this.screenLines.Add(new Line2D(point2, point3));
        this.screenLines.Add(new Line2D(point3, point4));
        this.screenLines.Add(new Line2D(point4, point1));

        this.mainCamera = Camera.main;
        this.uicamera = NGUITools.FindCameraForLayer(LayersDefine.Ngui);

        this.isIdle = false;
        
    }

    public void Init(int type, int fromActorId, int toActorId)
    {
        this.InitImp();

        this.type = type;
        this.fromActorId = fromActorId;
        this.toActorId = toActorId;

        this.UpdateImp();
    }

    public void Init(int type, int fromActorId, Vector3 goalPoint)
    {
        this.InitImp();

        this.type = type;
        this.fromActorId = fromActorId;
        this.goalPoint = goalPoint;

        this.UpdateImp();
    }

    public bool GetIsIdle()
    {
        return this.isIdle;
    }

    private void SetIsIdle(bool isIdle)
    {
        this.isIdle = isIdle;
        if(isIdle)
        {
            this.type = 0;
            this.fromActorId = 0;
            gameObject.SetActive(false);
        }
    }

    public int GetType()
    {
        return this.type;
    }

    private bool PointIsInScreen(Vector3 pos)
    {
        if (pos.x <= this.screenLines[0].point1.x
            || pos.x >= this.screenLines[1].point2.x
            || pos.y <= this.screenLines[0].point1.y
            || pos.y >= this.screenLines[1].point2.y)
        {
            return false;
        }
        return true;
    }

    private Vector3 WorldToScreenPoint(Vector3 pos)
    {
        if (null != this.mainCamera) 
        {
            return mainCamera.WorldToScreenPoint (pos);
        }
        return Vector3.zero;
    }

    private Vector3 ScreenToUIPoint(Vector3 pos)
    {
        if (null != this.uicamera)
        {
            Vector3 uiWorldPosInUISpace = this.uicamera.ScreenToWorldPoint(pos);
            uiWorldPosInUISpace.z = 0f;
            return uiWorldPosInUISpace;
        }
        return Vector3.zero;
    }

    public void UpdateImp()
    {
        bool isIntersce = false;
        GameObject fromHero = ActorManager.Ins.GetActor(fromActorId);
        GameObject toHero = null;
        if (fromHero != null)
        {
            Vector3 intersecPos = new Vector3();
            Vector3 fromPos = this.WorldToScreenPoint(fromHero.transform.position);
            Vector3 toPos = Vector3.zero;

            if (this.toActorId == 0)
            {
                toPos = this.WorldToScreenPoint(this.goalPoint);
            }
            else
            {
                toHero = ActorManager.Ins.GetActor(toActorId);
                if (toHero != null)
                {
                    toPos = this.WorldToScreenPoint(toHero.transform.position);
                }
                else
                {
                    this.SetIsIdle(true);
                }
            }

            if (toPos != Vector3.zero && this.PointIsInScreen(fromPos))
            {
                Line2D line = new Line2D(fromPos, toPos);
                foreach (Line2D l in this.screenLines)
                {
                    if (line.Intersection(l, out intersecPos) == Line2D.CROSS)
                    {
                        isIntersce = true;
                        break;
                    }
                }

                if (isIntersce)
                {
                    transform.position = this.ScreenToUIPoint(intersecPos);

                    if (directContainer != null)
                    {
                        if (this.type == 1)
                        {
                            float angle = Vector3.Angle(toPos - fromPos, Vector3.right);
                            directContainer.transform.rotation = new Quaternion();
                            directContainer.transform.Rotate(Vector3.forward * angle);
                        }
                        else
                        {
                            float angle = Vector3.Angle(fromPos - toPos, Vector3.right);
                            directContainer.transform.rotation = new Quaternion();
                            directContainer.transform.Rotate(Vector3.back * angle);
                        }
                    }
                    if (this.distanceLabel != null && toHero != null)
                    {
                        this.distanceLabel.text = String.Format("{0}米", Math.Round((toHero.transform.position - fromHero.transform.position).magnitude));
                    }
                }
            }
        }
        else
        {
            this.SetIsIdle(true);
        }
        this.SetIsIdle(false);
        gameObject.SetActive(isIntersce);
    }
}

 二、多个提示的刷新、管理(增删)的逻辑处理文件(ScreenEdgeTipManager.cs

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

public class ScreenEdgeTipManager : MonoBehaviourX
{
    private Dictionary<int, string> prefabNameMap = new Dictionary<int, string>();
    private Dictionary<int, GameObject> prefabMap = new Dictionary<int, GameObject>();
    private ScreenEdgeTips ToGoalCtrl;

    private static ScreenEdgeTipManager instance = null;
    
    public static ScreenEdgeTipManager Ins
    { 
        get
        {
            return instance;  
        } 
    }
    
    ScreenEdgeTipManager()
    {
        instance = this;
    }

    void Start()
    {
        scope.Listen("BattleOver", OnBattleOver);
        scope.Listen("GameShutdown", OnBattleOver);

        prefabNameMap.Add(1, UIDefine.BATTLE_TIPS_GOAL);
        prefabNameMap.Add(2, UIDefine.BATTLE_TIPS_MONSTER);
        prefabNameMap.Add(3, UIDefine.BATTLE_TIPS_RAIDER);
    }

    private void OnBattleOver(object[] args)
    {
        ClearAll();
    }
    
    private void ClearAll()
    {
        gameObject.DestroyAllChildGameObject();
        prefabMap.Clear();

        NGUITools.SetActiveChildren(gameObject, false);
    }

    private GameObject GetPrefab(int type)
    {
        if (!prefabMap.ContainsKey(type))
        {
            GameObject tipsTemplate = null;
            tipsTemplate = (GameObject)ResourceMgr.LoadPrefab(prefabNameMap[type]);
            if (null != tipsTemplate)
            {
                prefabMap.Add(type, tipsTemplate);
            }
        }
        return prefabMap[type];
    }

    private ScreenEdgeTips GetNewTips(int type)
    {
        GameObject tipsTemplate = this.GetPrefab(type);
        GameObject go = GameObject.Instantiate(tipsTemplate) as GameObject;
        go.transform.parent = transform;
        go.layer = LayersDefine.Ngui;
        go.transform.localScale = Vector3.one;
        return go.GetComponent<ScreenEdgeTips>();
    }

    private ScreenEdgeTips GetTipsCtrl(int type)
    {
        if (type == 0)
        {
            return null;
        }
        //根据type复用 或 创建新的提示
        //尝试复用一个已经创建过的UI_BloodUp
        int count = transform.childCount;
        ScreenEdgeTips findTips = null;
        for (int i = 0; i < count; ++i)
        {
            GameObject obj = transform.GetChild(i).gameObject;
            if (!obj.activeSelf)
            {
                ScreenEdgeTips et = obj.GetComponent<ScreenEdgeTips>();
                if (et != null && et != this.ToGoalCtrl && et.GetType() == type && et.GetIsIdle())
                {
                    findTips = et;
                }
                break;
            }
        }
        //不存在时,创建新的tips模板
        return null != findTips ? findTips : this.GetNewTips(type);
    }

    private void _CreateTargetTips(int type, int fromActorId, int toActorId)
    {
        ScreenEdgeTips findTips = this.GetTipsCtrl(type);
        if (findTips != null)
        {
            findTips.Init(type, fromActorId, toActorId);
        }
    }

    private ScreenEdgeTips GetToGoalCtrl()
    {
        this.ToGoalCtrl = this.ToGoalCtrl != null ? this.ToGoalCtrl : this.GetNewTips(1);
        return this.ToGoalCtrl;
    }

    private void _CreatePointTips(int fromActorId, Vector3 goalPoint)
    {
        ScreenEdgeTips findTips = this.GetToGoalCtrl();
        if (findTips != null)
        {
            findTips.Init(1, fromActorId, goalPoint);
        }
    }


    void Update()
    {
        int count = transform.childCount;
        for (int i = 0; i < count; ++i)
        {
            GameObject obj = transform.GetChild(i).gameObject;
            ScreenEdgeTips et = obj.GetComponent<ScreenEdgeTips>();
            if (et != null)
            {
                et.UpdateImp();
            }
        }
    }

    public static void CreateTargetTips(int type, int fromActorId, int toActorId)
    {
        if (instance)
        {
            instance._CreateTargetTips(type, fromActorId, toActorId);
        }
    }

    public static void CreatePointTips(int fromActorId, Vector3 goalPoint)
    {
        if (instance)
        {
            instance._CreatePointTips(fromActorId, goalPoint);
        }
    }
}

 

************************************下面是用到到的基础类,不用也没关系*************************************

 

三、使用到的显示基类(MonoBehaviourX.cs

using System;
using UnityEngine;

public class MonoBehaviourX: MonoBehaviour
{
    protected EventScope scope = null;
    
    protected MonoBehaviourX()
    {
        scope = Eventer.Create();
    }
    
    protected void OnDestroy()
    {
        scope.Destroy();
        scope= null;
    }
}

 四、使用到的事件处理类(EventScope.cs

using System;
using System.Collections.Generic;

public class EventScope
{
    public Dictionary<string, DelegateObjList> eventTable = new Dictionary<string, DelegateObjList>();
    private EventScope parent = null;
    private List<EventScope> childer = new List<EventScope>();
    
    public EventScope(EventScope _parent)
    {
        parent = _parent;
    }
    
    public EventScope CreateChild()
    {
        EventScope scope = new EventScope(this);
        childer.Add(scope);
        return scope;
    }
    
    private void RemoveParentEvents(string name, Delegate deleObject)
    {
        if (parent == null)
        {
            return;
        }
        
        DelegateObjList list;
        if (parent.eventTable.TryGetValue(name, out list))
        {
            list.Remove(deleObject);
        }
        
        parent.RemoveParentEvents(name, deleObject);
    }
    
    public void ClearEvent()
    {
        foreach (var et in eventTable)
        {
            foreach (var _delegate in et.Value.events)
            {
                RemoveParentEvents(et.Key, _delegate);
            }
        }
        
        eventTable.Clear();
    }
    
    public void Destroy()
    {
        ClearEvent();
        
        if (parent != null)
        {
            parent.childer.Remove(this);
        }
    }
    
    public void Listen(string name, CALLBACK handler)
    {
        if (handler == null)
        {
            return;
        }
        DelegateObjList dol;
        if (!eventTable.TryGetValue(name, out dol))
        {
            dol = new DelegateObjList();
            eventTable [name] = dol;
        }
        
        dol.Add(handler);
        
        if (parent != null)
        {
            parent.Listen(name, handler);
        }
    }
}

 五、使用到的事件广播类(Eventer.cs

using System;
using System.Collections.Generic;

public class Eventer
{
    static public EventScope globe = new EventScope(null);
    
    static public EventScope Create()
    {
        return globe.CreateChild();
    }

    public static void Fire(string name, object[] args)
    {
        DelegateObjList dol;
        if (globe.eventTable.TryGetValue(name, out dol))
        {
            dol.Enter();
            int count = dol.events.Count;
            for (int i=0; i<count; ++i)
            {
                CALLBACK callback = dol.events [i] as CALLBACK;
                callback(args);
            }
            dol.Leave();
        }
    }

    public static void Fire(string name)
    {
        object[] args = new object[0] {};
        Fire(name, args);
    }

    //  ---------------- 以下接口提供给Lua调用 ----------------------
    public static void _Fire(string name)
    {
        object[] args = new object[0] {};
        Fire(name, args);
    }

    public static void _Fire(string name, object arg1)
    {
        object[] args = new object[1] { arg1 };
        Fire(name, args);
    }

    public static void _Fire(string name, object arg1, object arg2)
    {
        object[] args = new object[2] { arg1, arg2 };
        Fire(name, args);
    }
    
    public static void _Fire(string name, object arg1, object arg2, object arg3)
    {
        object[] args = new object[3] { arg1, arg2, arg3 };
        Fire(name, args);
    }

    public static void _Fire(string name, object arg1, object arg2, object arg3, object arg4) 
    {
        object[] args = new object[4] { arg1, arg2, arg3, arg4 };
        Fire(name, args);
    }

    public static void _Fire(string name, object arg1, object arg2, object arg3, object arg4, object arg5) 
    {
        object[] args = new object[5] { arg1, arg2, arg3, arg4, arg5 };
        Fire(name, args);
    }

    public static void _Fire(string name, object arg1, object arg2, object arg3, object arg4, object arg5, object arg6) 
    {
        object[] args = new object[6] { arg1, arg2, arg3, arg4, arg5, arg6 };
        Fire(name, args);
    }
}

 六、使用到的回调处理类(DelegateObjList

using System;
using System.Collections.Generic;

public delegate void CALLBACK(object[] args);

public class DelegateObjList
{
    public class DynamicDelegate
    {
        public Delegate callback;
        public bool append;
    }
    
    public List<Delegate> events = new List<Delegate>();
    public List<DynamicDelegate> delayProcesList = null;
    public bool accessEvent = false;
    
    private void AddDynamicDelegate(Delegate dele, bool append)
    {
        if (delayProcesList == null)
        {
            delayProcesList = new List<DynamicDelegate>();
        }
        
        DynamicDelegate dd = new DynamicDelegate();
        dd.append = append;
        dd.callback = dele;
        delayProcesList.Add(dd);
    }
    
    public void Add(Delegate c)
    {
        if (accessEvent)
        {
            AddDynamicDelegate(c, true);
        } else
        {
            events.Add(c);
        }
    }
    
    public void Remove(Delegate c)
    {
        if (accessEvent)
        {
            AddDynamicDelegate(c, false);
        } else
        {
            events.Remove(c);
        }
    }
    
    public void Enter()
    {
        accessEvent = true;
    }
    
    public void Leave()
    {
        accessEvent = false;
        
        if (delayProcesList == null)
        {
            return;
        }
        
        int count = delayProcesList.Count;
        if (count == 0)
        {
            return;
        }
        
        for (int i=0; i<count; ++i)
        {
            var dp = delayProcesList [i];
            if (dp.append)
            {
                events.Add(dp.callback);
            } else
            {
                events.Remove(dp.callback);
            }
        }
        
        delayProcesList.Clear();
    }
}

 

分享到:
评论

相关推荐

    Unity悬浮球自动靠边

    在本项目"Unity悬浮球自动靠边"中,开发者创建了一个可以在游戏界面中自由拖动,并在松开后自动贴合屏幕边缘的悬浮元素。这一功能在很多游戏和应用中都有实用价值,例如,作为控制面板或者信息提示窗口。 首先,让...

    多方向抽屉代码

    以上是对"多方向抽屉代码"的相关知识点的详细说明,这个项目不仅涵盖了前端开发的基本技术,还涉及到了用户体验和交互设计的考量,对于希望提高自己在移动端或网页开发中抽屉效果实现能力的人来说,是一份宝贵的资源...

    Windows7使用总结 - 77个小知识

    4. Snap功能:拖拽窗口至屏幕边缘,自动调整大小,便于并排查看两个程序。 5. Jumplists:在任务栏上右键点击程序,可访问最近打开的文件或常用任务,节省时间。 6. 自定义任务栏:可以自由调整任务栏上的图标顺序...

    飞出来的小demo

    - 消息通知:新的消息通知可以从屏幕边缘飞入,引起用户注意。 总的来说,"飞出来的小demo"是通过动画技术实现的一种视觉效果,它可以提升软件的交互性和趣味性,为用户带来独特的体验。通过学习和掌握相关的编程...

    MASTERCAM内部资料

    - **坐标显示**:屏幕右上角显示鼠标当前位置的(X,Y)坐标值。 - **视角设置**:屏幕左下角的坐标轴指示当前视角设置。 - **绘图单位**:屏幕下方显示当前使用的绘图单位(mm或inch)。 #### 三、CAD零件造型命令 - ...

    WIN8快捷键大全

    - 在Windows 8中,某些快捷键的使用方式与Windows 7有所不同,例如**Windows + Tab**键在Windows 8中不再显示Aero Flip 3D效果,而是提供了一种更加简洁的窗口切换方式。 - 需要注意的是,虽然Windows 7中的某些...

    CAD动态UCS面积三维及二维对齐[借鉴].pdf

    激活动态UCS后,用户不再需要通过复杂的UCS命令来重新定位坐标系,只需将光标移动到实体模型的边缘,光标就会自动更新,显示UCS的新方向。 在操作动态UCS时,可以使用以下步骤: 1. 开始一个支持动态UCS的绘图命令...

    17种基于mo.js制作的炫酷图标动画特效

    4. **3D翻转**:通过mo.js的`rotateZ`属性,可实现图标在二维平面上的3D翻转效果,增强立体感。 5. **淡入淡出**:利用mo.js的`opacity`属性,可以实现图标在显示和隐藏时的淡入淡出效果,使页面过渡更自然。 6. *...

    各种new的图标

    3. 响应性:在不同的设备和屏幕分辨率上,图标应当保持其可读性和美感。 4. 色彩搭配:颜色的选择应该与整体UI设计协调,同时突出新内容的重要性。 “各种new图标”可能包括了不同设计风格的PNG图标,如扁平化设计...

    这6种实验性的网页导航模式让网站设计吸引眼球.docx

    对于常规的上下滚动页面而言,常驻的侧边栏导航可能会改变整个页面的比例,因此需要设计师重新审视并调整布局,确保在不同尺寸和比例的屏幕上都能正常显示。 在设计侧边栏导航时,还需注意避免使用过长的文本,以免...

    ps快捷键文档

    - **全景相机工具(在“3D变化”滤镜中)**:`【E】` - 在使用“3D变化”滤镜时,切换到全景相机工具。 #### 六、视图操作 - **显示彩色通道**:`【Ctrl】+【~】` - 显示或隐藏彩色通道。 - **显示单色通道**:`...

    delphi控件属性和事件

    - **应用场景**:在实现上下文相关的操作时非常有用。 ##### 29. **ShowHint** - **描述**:ShowHint 属性用于控制组件上的提示信息是否显示。 - **应用场景**:有助于提高用户界面的易用性。 ##### 30. **...

    JAVA上百实例源码以及开源项目源代码

    Java数组倒置 简单 Java图片加水印,支持旋转和透明度设置 摘要:Java源码,文件操作,图片水印 util实现Java图片水印添加功能,有添加图片水印和文字水印,可以设置水印位置,透明度、设置对线段锯齿状边缘处理、水印...

Global site tag (gtag.js) - Google Analytics