·略带吐槽的序言
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场景视图中的框选效果
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);//输出上述坐标
}
}
}
保存,运行游戏。将鼠标指针移动到屏幕的不同位置并单击左键,观察控制台的输出。
可以看到,在单击左键时,我们的代码正确识别了鼠标指针在屏幕上的位置,并将其打印了出来。
在这个测试完成后,框选功能的第一部分已经触手可及:我们只要检测玩家按下/放开鼠标左键的操作,然后在对应的时机调取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);
}
}
}
再次运行游戏,然后你可以像在真正的游戏中一样,执行一次画框动作——在屏幕上的一个点按下鼠标左键,拖移到另一个点,然后放开。如下图所示。
观察控制台的输出,结果大致会是这样:
注意,我们尚未编写任何【绘制】方框的代码,因此执行这个动作的全过程不会看到任何反应。但通过控制台的输出,我们可以确信——现在我们已经能够识别到玩家的画框操作,并正确获取框的起始点和终止点。
1.4 实现第二部分:绘制方框
俗话说得好,”光说不练假把式”。既然捕捉到了玩家所画的框,如果不能把它漂漂亮亮地显示到屏幕上,那显然也没有什么本领可言。如何根据玩家的鼠标动作,实现前面列举过的各款游戏的效果——在屏幕上绘出矩形选定框呢?
Q:对于这个我们想要画出的方框,我们能够知道它的哪些信息?
A:当玩家通过鼠标指针拖移进行画框时,我们可以知道两个屏幕坐标,即框的起始点和当前鼠标指针所在点。
(注意:框需要在玩家拖移过程中的每一帧都画出,并跟着玩家的指针不断变形,而不是仅在鼠标左键放开时绘制一次;因此当前鼠标指针所在点,也就是上面那段代码中的currentPoint,在画框时应被认为是框的终止点。这里的终止点指的是图形上的终止点,而不是框选动作生效时,框在逻辑上的终止点。)
所以——我们画框的目标就是下图的样子。
对吗?
Victor上高中的时候,英语考试有一道题目“短文改错”,要求我们在短文中找到用错的单词,并用斜杠" \ "将其划掉。我们的英语老师,Miss Ma,经常提醒我们:【划斜杠的时候一定要“从左上到右下”】,即划成" \ "形,而不要划另一种方向" / ",否则在阅卷时可能不被识别。
哈哈哈~就是这个问题!
玩家在拖移鼠标指针来画框时,画框的方向有四种可能:从左上到右下↘,从右下到左上↖,从右上到左下↙、从左下到右上↗。鉴于玩家才是大爷,我们当然要让这四种不同的画框方向都能够正确地画出框来,而不是只识别“从左上到右下”这一种。
“画框有四种方向”是一个极易被忽略的“坑”,但在发现这个“坑”之后,处理起来的难度只不过是Hello World级别而已。通过初一的数学知识,就可以轻松想到确定这个框的四角坐标的方法:对于玩家给出的起始点和终止点,比较两个点x值的大小,记为Xmin和Xmax;再比较两个点y值的大小,记为Ymin和Ymax,如下图。由图可见,无论起止点具体属于哪种情况,我们都可以简单明了地获知矩形方框四个角的坐标。
到这里,我们已经能够计算所需矩形框四个角的坐标,可以着手绘制了;对于初步熟悉Unity功能的同学来说,可能最先想到的,就是使用Unity自带的UI系统,即OnGUI方法。
1.4.1 通过UGUI绘制矩形框
OnGUI方法其实藏着一个大坑(这与Unity UI系统的历史沿革有关)——在GUI相关方法内,对屏幕坐标系的规定与Unity其它地方的规定不一样。GUI指令中的屏幕坐标系是以屏幕左上角为原点,x轴正方向指向右方,y轴正方向指向下方的。
Unity在一般情况下的屏幕坐标系规定
Unity在UGUI系统中的屏幕坐标系规定
因此,在后面将要展示的OnGUI()相关代码内,某些坐标值已经经过了必要的换算。如果你一时觉得有疑问,可以自行探究验证。
通过UGUI中的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如下图所示;当然你也可以采用自己喜欢的图片。
再次运行游戏!拿起鼠标,在屏幕上拖移画框,这次可以看到预期的效果啦。
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内设置如下:
(喜欢什么颜色就设置什么颜色,只要内部颜色是半透明,边框颜色不透明即可)
运行游戏,这次可以画出十分标准的矩形框了,完美地复刻了《星际争霸2》级别的效果;
1.5 实现第三部分:依据选定框,捕获游戏物体
到这里,我们实现了矩形框的绘制,但这个画框的动作在游戏中还没有实际的功能。回到我们一开始举的游戏效果示例:
在图中我们可以看到矩形选定框的功能。通过矩形选定框,游戏应能识别出哪些可交互物体在画面上位于框内,并将它们拾取出来;同时排除不在框内的物体。
如何将框中的物体拾取出来?要实现这一点,我们需要在框选事件发生时,将可能被选中的物体的坐标转换为屏幕坐标,然后判断物体的屏幕坐标是否位于框内。
在Unity中,对于一个游戏物体GameObject A,要将一个游戏内的普通3D坐标,转换为主摄像机视图的屏幕坐标,方法如下:
Vector3 ScreenPos = Camera.main.WorldToScreenPoint(A.transform.position)
是不是很简单?
有了这一方法后,我们只需来讨论一下,一个屏幕坐标在什么情况下“位于框内”。
回到先前用来讲解”框“的那张图:
通过中学的线性规划知识,我们不难知道,一个点(x0,y0)位于由图中四个蓝点围成的框内部的条件是
x0 > Xmin;
x0 < Xmax;
y0 > Ymin;
y0 < Ymax.
归纳前面的分析可知,要用矩形选定框捕获游戏物体,流程应该是以下这样:
第1步.确定框选的范围
当玩家放开鼠标左键来结束框选时,创建一个选定框对象,并依次算出该选定框的左边界Xmin、右边界Ymin、下边界Ymin、上边界Ymax;
第2步.执行框选事件
对于所有可能被选中的物体(通常是具备某一特定Tag或者Layer的物体),依次判定它们的屏幕坐标是否位于框内;
第3步.执行游戏的选定功能
如果位于框内,则执行相应的游戏功能事件(例如选中该物体,将其高亮显示,等等)。
为了实现并演示这一部分,在场景中加入三个人物模型,分别名为Chan、Yuna和Rin。将它们的Tag设置为“Unit”。
下面,按照刚刚总结过的三步流程,继续扩充RectRender.cs,形成完整的功能演示代码。扩充后的代码包含CheckSelection方法,该方法会在每次画框完成后,找出所有具备Unit标签且位于框内的物体,并在控制台打印出所有这些物体的名字。
这个版本的代码可以作为完整的框选功能模块了。你可以拷贝它,继续扩充以加入你自己想要的个性化功能。
using UnityEngine;
public class RectRender : MonoBehaviour
{
/// <summary>
/// 这是一个自定义类,用来表示一个生效的选定框;创建它时会自动算出包含四角坐标的四项数据
/// </summary>
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。
操作完成后,控制台将会打印出两个被圈定的角色名字。
到这里,整个功能的实现终于大功告成!我们现在可以在屏幕上画出矩形选定框,并正确地拾取出位于框内的游戏物体啦。
1.6 What's more......
依照你所开发的游戏类型的不同,你可能还需要以自己的方式进一步扩充代码。后续,你需要对被你选中的物体采取各种操作,来实现你想要的游戏功能。以下是一些Victor认为常见且简易可行的后续操作,你可以用来参考:
·将所有被选中的物体放入一个列表(List),表明当前选中了这些物体,然后以适当方式对它们进行群体控制;
·为被选中的所有物体开启“被选中效果”,例如闪烁、高亮描边、在脚下显示光环、在头上显示生命值条,等等;
示例效果:
·选中物体后弹出一个UI框以询问玩家要对选中的目标做什么,例如【升级】【出售】;
·当选中了一个或多个角色时,播放相应的角色应答台词;
......
有关Unity中矩形框选的实现方式,讲到这里就算全部完成啦!不知你是否已经阅读了讲解并拷贝了代码,取得了自己的收获呢?
后面,Victor还会为大家讲述更多的Unity套路与技巧。欲知后文如何,请看下回分解!Bye~