【Vic的小课堂】Unity实现游戏功能(1)—矩形框选

·略带吐槽的序言

Unity是一款功能强大且运用广泛的引擎,但它也存在着一些颇受诟病的缺点。

对于想要快速做出可玩作品的开发者而言,Unity整个引擎的功能体系较为“白板”:它看上去很像是复杂化的代码编辑器,一切内容等待你的书写,而你很难认为,它针对某一类游戏的典型需求进行过优化。或者说,Unity不易直接实现任何一种令人惊喜或熟悉的游戏功能。(相比之下,虚幻引擎有着广受喜爱的蓝图机制,可以为开发者实现常见游戏功能提供很大便利)

简单解释一下前面是什么意思。

作为游戏开发者,每当构思并试图创作自己的游戏时,我们并不会首先从int整数、class...这样的程序基本概念来考虑如何开发它。从玩家的角度,我们非常容易去设想,自己的游戏应该具有哪些基本功能单元,这些功能单元在Unity中能否实现,是否容易实现。以下列举了一些功能单元的例子。

·创建有数个装备格子的背包,可以装入、取出、丢弃或使用武器、护甲、药水等物品;(背包系统)

·展示一个炫酷的技能栏,玩家可以通过点击各个按钮,使用英雄角色的各种法术;(技能系统)

·使用WASD键或屏幕轮盘来操控人物的前后左右移动;(FPS游戏的操控系统)

·使用鼠标或触控来改变观察方向,开火时向屏幕中心(准星)指向的位置发射子弹;(FPS游戏的射击系统)

·使用左键点选或框选士兵,右键移动它们到指定位置或攻击敌人;(RTS游戏的操控系统)

......

以上这些,是在各种类型的游戏中极为常见且通用的【基本功能单元】;但可惜的是,Unity基本没有提供过针对此类功能单元的标准解决方案。而当我们试图自己去实现这些功能模块时,时常会遇到一些出乎意料的难点,踩上很多暗藏的大坑。因此,我们要想熟练而稳定地实现某些较为"定型"的游戏功能,就需要有一些【套路级】的方法积累。

在这个系列,Victor想要分享的就是若干【成套】的解决方案——它们可以在你试着开发自己的游戏时,成为至关重要的功能起步模块。

让我们从一个大家都很熟悉的例子——矩形框选开始吧。

1.矩形框

1.1 游戏中的框选操作

画矩形框,或者说【框选】,是一个随处可见的PC游戏操作逻辑:玩家用鼠标拖移(Drag)的方式,在屏幕上画出一个透明或半透明方框,选中方框内所有可以交互的物体,忽略方框外的物体。框选最常见的用法是在RTS(即时战略)类游戏中,玩家通过画框的方式来选中若干个属于己方的作战单位,然后对它们下达集体指令。当然,这种画方框的操作也可以用在其它类型游戏中,例如在塔防游戏中选中一群防御塔、或者在建造类游戏中铺设地板和墙纸。

图1:《帝国时代2》中的框选效果

 图2:《魔兽争霸3》中的框选效果

图3:《星际争霸2》中的框选效果

此外还有一个奇特的事实:在Unity本身的Scene场景视图内,也可以通过框选来批量选中物体,可见Unity内其实是有这个功能的代码的。(然而这个框选功能竟然没有被做成一个用户可调用的API,实在是令人费解......)

图4:Unity场景视图中的框选效果

【Vic的小课堂】Unity实现游戏功能(1)—矩形框选_第1张图片

1.2 描述功能逻辑

我们首先用通俗的自然语言,把框选的详细逻辑陈述一遍。这不是一件难事,大家也可以自行陈述,然后与Victor的陈述比对,看意思是否相同。

Step 1: 玩家按下鼠标左键时,以此时鼠标指针所在的点为【画框起点】;

Step 2: 在玩家未放开左键,并不断移动鼠标指针的过程中,屏幕上会动态绘制以【画框起点】和【当前鼠标指针所在点】的连线为对角线的矩形框;

Step 3: 当玩家放开鼠标左键时,以此时鼠标指针所在的点为【画框终点】;此时就正式画出了一个以【画框起点】和【画框终点】的连线为对角线的【框】,游戏会执行一次框选逻辑,例如选定所有位于框内的士兵。

这个流程和你想的内容一样吗?是不是很简单?

现在,我们可以将这个功能单元划分成三个部分,然后编写代码来实现它。

第一部分:在玩家进行按下左键、拖移鼠标和放开左键操作时,确定玩家的鼠标指针指在哪里,并根据按下和放开的时机,确定画框的起点和终点;

第二部分:在玩家按下左键之后的拖移过程中,动态绘制玩家正在试图画出的矩形框;

第三部分:在玩家放开鼠标左键后,执行游戏的画框逻辑。(开放性内容,因为不同游戏画框后发生什么是不确定的)

现在,我们在Unity中建立新场景,准备编写代码来实现框选功能。

这是Victor写作本文时使用的测试场景:(风景应该还算不错)

使用这个场景当然是为了美观;你在根据本文自行测试时,只要场景中有一个主摄像机(Main Camera)即可,其余没有要求。

1.3 实现第一部分:确定框的起止点

第一部分非常简单,它是一项Unity的基础知识。

在Unity中,玩家鼠标指针在屏幕上的位置是这样获取的:

Input.mousePosition

这是一个Vector3(三维坐标)变量。以屏幕的左下角为原点,该坐标的x值和y值,分别表示鼠标指针位于屏幕上的横向第几个像素纵向第几个像素;(和初中学过的平面直角坐标系没有区别)该坐标的z值始终为0,没有实际意义。

 

在项目内新建一个空的C# Script,命名为RectRender.cs,将其挂载到主摄像机(Main Camera)上。我们编写代码来进行一个最简单的测试,观察Input.mousePosition的含义,代码如下。

using UnityEngine;

public class RectRender : MonoBehaviour
{
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Mouse0))//单击鼠标左键时
        {
            Vector3 mousePoint = Input.mousePosition;//获取鼠标指针在屏幕上的坐标
            Debug.LogFormat("当前鼠标指针位置:{0}", mousePoint);//输出上述坐标
        }
    }
}

保存,运行游戏。将鼠标指针移动到屏幕的不同位置并单击左键,观察控制台的输出。

【Vic的小课堂】Unity实现游戏功能(1)—矩形框选_第2张图片

可以看到,在单击左键时,我们的代码正确识别了鼠标指针在屏幕上的位置,并将其打印了出来。

在这个测试完成后,框选功能的第一部分已经触手可及:我们只要检测玩家按下/放开鼠标左键的操作,然后在对应的时机调取Input.mousePosition,即可轻松获取玩家“画框”的的起止点。

我们来修改并扩充RectRender.cs代码文件。只需补充一点逻辑,即可让它监控玩家画框的动作,并打印“框”的起止点。

using UnityEngine;

public class RectRender : MonoBehaviour
{
    private bool onDrawingRect;//是否正在画框(即鼠标左键处于按住的状态)

    private Vector3 startPoint;//框的起始点,即按下鼠标左键时指针的位置
    private Vector3 currentPoint;//在拖移过程中,玩家鼠标指针所在的实时位置
    private Vector3 endPoint;//框的终止点,即放开鼠标左键时指针的位置

    void Update()
    {
        //玩家按下鼠标左键,此时进入画框状态,并确定框的起始点
        if (Input.GetKeyDown(KeyCode.Mouse0))
        {
            onDrawingRect = true;
            startPoint = Input.mousePosition;
            Debug.LogFormat("开始画框,起点:{0}", startPoint);
        }

        //在鼠标左键未放开时,实时记录鼠标指针的位置
        if (onDrawingRect)
        {
            currentPoint = Input.mousePosition;
        }

        //玩家放开鼠标左键,说明框画完,确定框的终止点,退出画框状态
        if (Input.GetKeyUp(KeyCode.Mouse0))
        {
            endPoint = Input.mousePosition;
            onDrawingRect = false;
            Debug.LogFormat("画框结束,终点:{0}", endPoint);
        }
    }
}

再次运行游戏,然后你可以像在真正的游戏中一样,执行一次画框动作——在屏幕上的一个点按下鼠标左键,拖移到另一个点,然后放开。如下图所示。

观察控制台的输出,结果大致会是这样:

【Vic的小课堂】Unity实现游戏功能(1)—矩形框选_第3张图片

注意,我们尚未编写任何【绘制】方框的代码,因此执行这个动作的全过程不会看到任何反应。但通过控制台的输出,我们可以确信——现在我们已经能够识别到玩家的画框操作,并正确获取框的起始点终止点

1.4 实现第二部分:绘制方框

俗话说得好,”光说不练假把式”既然捕捉到了玩家所画的框,如果不能把它漂漂亮亮地显示到屏幕上,那显然也没有什么本领可言。如何根据玩家的鼠标动作,实现前面列举过的各款游戏的效果——在屏幕上绘出矩形选定框呢?

Q:对于这个我们想要画出的方框,我们能够知道它的哪些信息?

A:当玩家通过鼠标指针拖移进行画框时,我们可以知道两个屏幕坐标,即框的起始点当前鼠标指针所在点

(注意:框需要在玩家拖移过程中的每一帧都画出,并跟着玩家的指针不断变形,而不是仅在鼠标左键放开时绘制一次;因此当前鼠标指针所在点,也就是上面那段代码中的currentPoint,在画框时应被认为是框的终止点。这里的终止点指的是图形上的终止点,而不是框选动作生效时,框在逻辑上的终止点。)

所以——我们画框的目标就是下图的样子。

对吗?

Victor上高中的时候,英语考试有一道题目“短文改错”,要求我们在短文中找到用错的单词,并用斜杠" \ "将其划掉。我们的英语老师,Miss Ma,经常提醒我们:【划斜杠的时候一定要“从左上到右下”】,即划成" \ "形,而不要划另一种方向" / ",否则在阅卷时可能不被识别。

哈哈哈~就是这个问题!

玩家在拖移鼠标指针来画框时,画框的方向有四种可能:从左上到右下,从右下到左上,从右上到左下、从左下到右上鉴于玩家才是大爷,我们当然要让这四种不同的画框方向都能够正确地画出框来,而不是只识别“从左上到右下”这一种。

“画框有四种方向”是一个极易被忽略的“坑”,但在发现这个“坑”之后,处理起来的难度只不过是Hello World级别而已。通过初一的数学知识,就可以轻松想到确定这个框的四角坐标的方法:对于玩家给出的起始点和终止点,比较两个点x值的大小,记为Xmin和Xmax;再比较两个点y值的大小,记为Ymin和Ymax,如下图。由图可见,无论起止点具体属于哪种情况,我们都可以简单明了地获知矩形方框四个角的坐标。

【Vic的小课堂】Unity实现游戏功能(1)—矩形框选_第4张图片

到这里,我们已经能够计算所需矩形框四个角的坐标,可以着手绘制了;对于初步熟悉Unity功能的同学来说,可能最先想到的,就是使用Unity自带的UI系统,即OnGUI方法。

1.4.1 通过OnGUI绘制矩形框

OnGUI方法其实藏着一个大坑(这与Unity UI系统的历史沿革有关)——在GUI相关方法内,对屏幕坐标系的规定与Unity其它地方的规定不一样。GUI指令中的屏幕坐标系是以屏幕左上角为原点,x轴正方向指向右方,y轴正方向指向下方的。

Unity在一般情况下的屏幕坐标系规定

【Vic的小课堂】Unity实现游戏功能(1)—矩形框选_第5张图片

Unity在UGUI系统中的屏幕坐标系规定

【Vic的小课堂】Unity实现游戏功能(1)—矩形框选_第6张图片

因此,在后面将要展示的OnGUI()相关代码内,某些坐标值已经经过了必要的换算。如果你一时觉得有疑问,可以自行探究验证。

通过GUI.Box方法我们可以在画面上呈现一个矩形的"Box",这或许可以作为我们想要的“框”。

现在我们继续扩充RectRender.cs代码,在Update方法之后添加OnGUI方法,来实现对框的绘制。

    public GUIStyle rectStyle;

    void OnGUI()
    {
        if (onDrawingRect)
        {
            //获取确定矩形框各角坐标所需的各个数值
            float Xmin = Mathf.Min(startPoint.x, currentPoint.x);
            float Xmax = Mathf.Max(startPoint.x, currentPoint.x);
            float Ymin = Mathf.Min(startPoint.y, currentPoint.y);
            float Ymax = Mathf.Max(startPoint.y, currentPoint.y);

            //确定方框的定位点(左上角点)的横坐标、纵坐标,以及方框的横向宽度和纵向高度
            Rect rect = new Rect(Xmin, Screen.height - Ymax, Xmax - Xmin, Ymax - Ymin);

            //画框
            GUI.Box(rect, "", rectStyle);
        }
    }

这段代码中的GUI.Box用到了三个参数。

第一个参数rect是对屏幕上矩形的定义(由左上角点的横坐标、左上角点的纵坐标、矩形横向宽度、矩形纵向高度四个值来定义,其中左上角点的纵坐标已经进行了UGUI坐标系下的换算);

第二个参数是一个字符串,表示在Box方框内显示什么文本,这里我们不显示任何文本;

第三个参数是GUIStyle变量,表示该方框使用什么样的UI风格设定。GUIStyle有许多设定项,不过后面我们只需要设置一个选项——方框的背景图片,就可以啦。

在Inspector中编译修改后的RectRender组件,为rectStyle-Normal-BackGround设定项赋予一张图片,作为我们将要画出的框的背景样式。这里Victor采用了一张半透明的绿色纯色图片,i如下图所示;当然你也可以采用自己喜欢的图片。

【Vic的小课堂】Unity实现游戏功能(1)—矩形框选_第7张图片

再次运行游戏!拿起鼠标,在屏幕上拖移画框,这次可以看到预期的效果啦。

1.4.2 通过Unity的GL库绘制矩形框(推荐)

通过GUI方法,我们对于画框的需求得到了初步的解决;但需要承认的是,GUI.Box本身并不是为了满足“画框”这样的游戏功能性需求而设计的。它的原本目的,只是用来在屏幕上显示弹窗、提示文字等UI内容,因此上面的办法显得有些“投机取巧”。说白了,它是一种“障眼法”,只是把一张半透明的图片按照算出的位置贴到了屏幕上。

Q:为什么说GUI.Box是投机取巧的方法?

A:因为这个方法没有“一般性”。如果我们要绘制复杂一点的几何图样(而不是纯色矩形)——例如为矩形加上描边,或者将矩形的四个点两两相连,再或者只显示矩形的半边三角形那么使用上述方法很快就会露馅。你只能试图贴上去一张包含预期图样的图片,而该图片会在变形时出现缩放和拉伸问题,从而损失正常的比例和清晰度;以矩形描边为例,当你改变矩形框的大小和长宽比时,边框线条的粗细也会不停地改变。

从“授人以渔”的角度来说,如果我们要让这个功能变得更加“一般化”,即在屏幕上画出指定的几何图形,那么最好使用一套更加“专业对口”的工具。

Unity为我们提供了一套官方GL库,我们可以用它来绘制图形;使用Unity GL库的指令与OpenGL的代码风格非常类似(不过你在使用它的时候并不需要了解OpenGL),我们只要在OnPostRender()函数中写入GL指令,然后保证代码组件挂在主摄像机上即可。

关于GL库的详情,可以参阅官方文档,或看这篇文章:https://blog.csdn.net/Htlas/article/details/79748512

现在我们尝试新方案。注释掉原RectRender.cs代码中的OnGUI()部分,写入新的内容。修改后的RectRender.cs全文如下:

using UnityEngine;

public class RectRender : MonoBehaviour
{
    private bool onDrawingRect;//是否正在画框(即鼠标左键处于按住的状态)

    private Vector3 startPoint;//框的起始点,即按下鼠标左键时指针的位置
    private Vector3 currentPoint;//在拖移过程中,玩家鼠标指针所在的实时位置
    private Vector3 endPoint;//框的终止点,即放开鼠标左键时指针的位置

    void Update()
    {
        //玩家按下鼠标左键,此时进入画框状态,并确定框的起始点
        if (Input.GetKeyDown(KeyCode.Mouse0))
        {
            onDrawingRect = true;
            startPoint = Input.mousePosition;
            Debug.LogFormat("开始画框,起点:{0}", startPoint);
        }

        //在鼠标左键未放开时,实时记录鼠标指针的位置
        if (onDrawingRect)
        {
            currentPoint = Input.mousePosition;
        }

        //玩家放开鼠标左键,说明框画完,确定框的终止点,退出画框状态
        if (Input.GetKeyUp(KeyCode.Mouse0))
        {
            endPoint = Input.mousePosition;
            onDrawingRect = false;
            Debug.LogFormat("画框结束,终点:{0}", endPoint);
        }
    }

    public Material GLRectMat;//绘图的材质,在Inspector中设置
    public Color GLRectColor;//矩形的内部颜色,在Inspector中设置
    public Color GLRectEdgeColor;//矩形的边框颜色,在Inspector中设置

    void OnPostRender()
    {
        if (onDrawingRect)
        {
            //准备工作:获取确定矩形框各角坐标所需的各个数值
            float Xmin = Mathf.Min(startPoint.x, currentPoint.x);
            float Xmax = Mathf.Max(startPoint.x, currentPoint.x);
            float Ymin = Mathf.Min(startPoint.y, currentPoint.y);
            float Ymax = Mathf.Max(startPoint.y, currentPoint.y);

            GL.PushMatrix();//GL入栈
                            //在这里,只需要知道GL.PushMatrix()和GL.PopMatrix()分别是画图的开始和结束信号,画图指令写在它们中间
            if (!GLRectMat)
                return;

            GLRectMat.SetPass(0);//启用线框材质rectMat

            GL.LoadPixelMatrix();//设置用屏幕坐标绘图


            /*------第一步,绘制矩形------*/
            GL.Begin(GL.QUADS);//开始绘制矩形,这一段画的是框中间的半透明部分,不包括边界线

            GL.Color(GLRectColor);//设置矩形的颜色,注意GLRectColor务必设置为半透明

            //陈述矩形的四个顶点
            GL.Vertex3(Xmin, Ymin, 0);//陈述第一个点,即框的左下角点,记为点1
            GL.Vertex3(Xmin, Ymax, 0);//陈述第二个点,即框的左上角点,记为点2
            GL.Vertex3(Xmax, Ymax, 0);//陈述第三个点,即框的右上角点,记为点3
            GL.Vertex3(Xmax, Ymin, 0);//陈述第四个点,即框的右下角点,记为点4

            GL.End();//告一段落,此时画好了一个无边框的矩形


            /*------第二步,绘制矩形的边框------*/
            GL.Begin(GL.LINES);//开始绘制线,用来描出矩形的边框

            GL.Color(GLRectEdgeColor);//设置方框的边框颜色,建议设置为不透明的

            //描第一条边
            GL.Vertex3(Xmin, Ymin, 0);//起始于点1
            GL.Vertex3(Xmin, Ymax, 0);//终止于点2

            //描第二条边
            GL.Vertex3(Xmin, Ymax, 0);//起始于点2
            GL.Vertex3(Xmax, Ymax, 0);//终止于点3

            //描第三条边
            GL.Vertex3(Xmax, Ymax, 0);//起始于点3
            GL.Vertex3(Xmax, Ymin, 0);//终止于点4

            //描第四条边
            GL.Vertex3(Xmax, Ymin, 0);//起始于点4
            GL.Vertex3(Xmin, Ymin, 0);//返回到点1

            GL.End();//画好啦!

            GL.PopMatrix();//GL出栈
        }
    }
}

新加入的OnPostRender()方法内,我们使用GL库提供的GL.QUADS方法绘制了所需的矩形,然后用GL.LINES方法依次描出了矩形的四条边框线。我们只需在Inspector内设定绘图材质(使用Unity附送的Sprites-Default材质十分合适)、矩形内部颜色矩形边框颜色,即可完整地实现矩形框的绘制。

保存编译,在Inspector内设置如下:

(喜欢什么颜色就设置什么颜色,只要内部颜色是半透明,边框颜色不透明即可)

【Vic的小课堂】Unity实现游戏功能(1)—矩形框选_第8张图片

运行游戏,这次可以画出十分标准的矩形框了,完美地复刻了《星际争霸2》级别的效果;

1.5 实现第三部分:依据选定框,捕获游戏物体

到这里,我们实现了矩形框的绘制,但这个画框的动作在游戏中还没有实际的功能。回到我们一开始举的游戏效果示例:

在图中我们可以看到矩形选定框的功能。通过矩形选定框,游戏应能识别出哪些可交互物体在画面上位于框内,并将它们拾取出来;同时排除不在框内的物体。

如何将框中的物体拾取出来?要实现这一点,我们需要在框选事件发生时,将可能被选中的物体的坐标转换为屏幕坐标,然后判断物体的屏幕坐标是否位于框内

在Unity中,对于一个游戏物体GameObject A,要将一个游戏内的普通3D坐标,转换为主摄像机视图的屏幕坐标,方法如下:

Vector3 ScreenPos = Camera.main.WorldToScreenPoint(A.transform.position)

是不是很简单?

有了这一方法后,我们只需来讨论一下,一个屏幕坐标在什么情况下“位于框内”。

回到先前用来讲解”框“的那张图:

【Vic的小课堂】Unity实现游戏功能(1)—矩形框选_第9张图片

通过中学的线性规划知识,我们不难知道,一个点(x0,y0)位于由图中四个蓝点围成的框内部的条件是

    x0 > Xmin;

    x0 < Xmax;

    y0 > Ymin;

    y0 < Ymax.

 归纳前面的分析可知,要用矩形选定框捕获游戏物体,流程应该是以下这样:

第1步.确定框选的范围

当玩家放开鼠标左键来结束框选时,创建一个选定框对象,并依次算出该选定框的左边界Xmin、右边界Ymin、下边界Ymin、上边界Ymax;

第2步.执行框选事件

对于所有可能被选中的物体(通常是具备某一特定Tag或者Layer的物体),依次判定它们的屏幕坐标是否位于框内;

第3步.执行游戏的选定功能

如果位于框内,则执行相应的游戏功能事件(例如选中该物体,将其高亮显示,等等)。

为了实现并演示这一部分,在场景中加入三个人物模型,分别名为ChanYunaRin。将它们的Tag设置为“Unit”

【Vic的小课堂】Unity实现游戏功能(1)—矩形框选_第10张图片

下面,按照刚刚总结过的三步流程,继续扩充RectRender.cs,形成完整的功能演示代码。扩充后的代码包含CheckSelection方法,该方法会在每次画框完成后,找出所有具备Unit标签且位于框内的物体,并在控制台打印出所有这些物体的名字。

这个版本的代码可以作为完整的框选功能模块了。你可以拷贝它,继续扩充以加入你自己想要的个性化功能。

using UnityEngine;

public class RectRender : MonoBehaviour
{
    /// 
    /// 这是一个自定义类,用来表示一个生效的选定框;创建它时会自动算出包含四角坐标的四项数据
    /// 
    public class Selector
    {
        public float Xmin;
        public float Xmax;
        public float Ymin;
        public float Ymax;

        //构造函数,在创建选定框时自动计算Xmin/Xmax/Ymin/Ymax
        public Selector(Vector3 start, Vector3 end)
        {
            Xmin = Mathf.Min(start.x, end.x);
            Xmax = Mathf.Max(start.x, end.x);
            Ymin = Mathf.Min(start.y, end.y);
            Ymax = Mathf.Max(start.y, end.y);
        }
    }

    private bool onDrawingRect;//是否正在画框(即鼠标左键处于按住的状态)

    private Vector3 startPoint;//框的起始点,即按下鼠标左键时指针的位置
    private Vector3 currentPoint;//在拖移过程中,玩家鼠标指针所在的实时位置
    private Vector3 endPoint;//框的终止点,即放开鼠标左键时指针的位置

    private Selector selector;

    void Update()
    {
        //玩家按下鼠标左键,此时进入画框状态,并确定框的起始点
        if (Input.GetKeyDown(KeyCode.Mouse0))
        {
            onDrawingRect = true;
            startPoint = Input.mousePosition;
            Debug.LogFormat("开始画框,起点:{0}", startPoint);
        }

        //在鼠标左键未放开时,实时记录鼠标指针的位置
        if (onDrawingRect)
        {
            currentPoint = Input.mousePosition;
        }

        //玩家放开鼠标左键,说明框画完,确定框的终止点,退出画框状态
        if (Input.GetKeyUp(KeyCode.Mouse0))
        {
            endPoint = Input.mousePosition;
            onDrawingRect = false;
            Debug.LogFormat("画框结束,终点:{0}", endPoint);
            //当框画完时,创建一个生效的选定框selector
            selector = new Selector(startPoint, endPoint);
            //执行框选事件
            CheckSelection(selector, "Unit");
        }
    }

    //框选事件
    //按照选定框的范围,捕获标签为tag的所有物体,并打印这些物体的名字
    void CheckSelection(Selector selector, string tag)
    {
        GameObject[] Units = GameObject.FindGameObjectsWithTag(tag);
        foreach(GameObject Unit in Units)
        {
            Vector3 screenPos = Camera.main.WorldToScreenPoint(Unit.transform.position);
            if (screenPos.x > selector.Xmin && screenPos.x < selector.Xmax && screenPos.y > selector.Ymin && screenPos.y < selector.Ymax)
            {
                Debug.LogFormat("已选中目标:{0}", Unit.name);
            }
        }
    }

    public Material GLRectMat;//绘图的材质,在Inspector中设置
    public Color GLRectColor;//矩形的内部颜色,在Inspector中设置
    public Color GLRectEdgeColor;//矩形的边框颜色,在Inspector中设置

    void OnPostRender()
    {
        if (onDrawingRect)
        {
            //准备工作:获取确定矩形框各角坐标所需的各个数值
            float Xmin = Mathf.Min(startPoint.x, currentPoint.x);
            float Xmax = Mathf.Max(startPoint.x, currentPoint.x);
            float Ymin = Mathf.Min(startPoint.y, currentPoint.y);
            float Ymax = Mathf.Max(startPoint.y, currentPoint.y);

            GL.PushMatrix();//GL入栈
                            //在这里,只需要知道GL.PushMatrix()和GL.PopMatrix()分别是画图的开始和结束信号,画图指令写在它们中间
            if (!GLRectMat)
                return;

            GLRectMat.SetPass(0);//启用线框材质rectMat

            GL.LoadPixelMatrix();//设置用屏幕坐标绘图

            /*------第一步,绘制矩形------*/
            GL.Begin(GL.QUADS);//开始绘制矩形,这一段画的是框中间的半透明部分,不包括边界线

            GL.Color(GLRectColor);//设置矩形的颜色,注意GLRectColor务必设置为半透明

            //陈述矩形的四个顶点
            GL.Vertex3(Xmin, Ymin, 0);//陈述第一个点,即框的左下角点,记为点1
            GL.Vertex3(Xmin, Ymax, 0);//陈述第二个点,即框的左上角点,记为点2
            GL.Vertex3(Xmax, Ymax, 0);//陈述第三个点,即框的右上角点,记为点3
            GL.Vertex3(Xmax, Ymin, 0);//陈述第四个点,即框的右下角点,记为点4

            GL.End();//告一段落,此时画好了一个无边框的矩形


            /*------第二步,绘制矩形的边框------*/
            GL.Begin(GL.LINES);//开始绘制线,用来描出矩形的边框

            GL.Color(GLRectEdgeColor);//设置方框的边框颜色,建议设置为不透明的

            //描第一条边
            GL.Vertex3(Xmin, Ymin, 0);//起始于点1
            GL.Vertex3(Xmin, Ymax, 0);//终止于点2

            //描第二条边
            GL.Vertex3(Xmin, Ymax, 0);//起始于点2
            GL.Vertex3(Xmax, Ymax, 0);//终止于点3

            //描第三条边
            GL.Vertex3(Xmax, Ymax, 0);//起始于点3
            GL.Vertex3(Xmax, Ymin, 0);//终止于点4

            //描第四条边
            GL.Vertex3(Xmax, Ymin, 0);//起始于点4
            GL.Vertex3(Xmin, Ymin, 0);//返回到点1

            GL.End();//画好啦!

            GL.PopMatrix();//GL出栈
        }
    }
}

现在进行效果测试。运行游戏,在屏幕上画框来圈选Chan和Rin两个人物,排除Yuna。

操作完成后,控制台将会打印出两个被圈定的角色名字。

【Vic的小课堂】Unity实现游戏功能(1)—矩形框选_第11张图片

 到这里,整个功能的实现终于大功告成!我们现在可以在屏幕上画出矩形选定框,并正确地拾取出位于框内的游戏物体啦。

1.6 What's more......

依照你所开发的游戏类型的不同,你可能还需要以自己的方式进一步扩充代码。后续,你需要对被你选中的物体采取各种操作,来实现你想要的游戏功能。以下是一些Victor认为常见且简易可行的后续操作,你可以用来参考:

·将所有被选中的物体放入一个列表(List),表明当前选中了这些物体,然后以适当方式对它们进行群体控制;

·为被选中的所有物体开启“被选中效果”,例如闪烁高亮描边在脚下显示光环在头上显示生命值条,等等;

示例效果:

·选中物体后弹出一个UI框以询问玩家要对选中的目标做什么,例如【升级】【出售】;

·当选中了一个或多个角色时,播放相应的角色应答台词;

......

有关Unity中矩形框选的实现方式,讲到这里就算全部完成啦!不知你是否已经阅读了讲解并拷贝了代码,取得了自己的收获呢?

后面,Victor还会为大家讲述更多的Unity套路与技巧。欲知后文如何,请看下回分解!Bye~

你可能感兴趣的:(Unity,C#,游戏开发,unity3d)