[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

文章目录

一、前言

嗨,大家好,我是新发。
最近比较忙,好几天没写文章了,这两天广州疫情的新闻频频上热搜,身在广州无时无刻不吊着个心,部分同事所在的区域已经封起来了,只能远程上班,我住的地区暂时还比较安全,不过也已经做好远程工作的准备了。
话说回来,我在广州上学、生活、工作了十年了,对广州有着特殊的情愫。
我这两天做了一个Demo,我在Unity中搭建了整个广州地铁路线地图,并做了第三人称视角相机跟随,双摇杆控制,可以登上广州塔鸟瞰整个广州。以此献给我热爱的大广州,效果如下:

注:今天广州大规模核酸检测,下午刚检测完毕,回家写这篇文章直到现在(2021-6-5 23:44),广州加油,早日战胜疫情!

[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
下面我将讲解一下创作过程。

二、创建工程

1、创建工程

我用的Unity版本是2021.1.7f1c1 (64-bit),选择3D模板,工程名字起为GuangzhouGogo好了,点击创建。
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

创建成功,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

2、创建目录

养成好习惯,先规范目录结构,创建一些文件夹,目录结构如下:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

RawAssets目录主要是存放一些【生肉资源】或被场景依赖的资源;

注:关于【生肉资源】可以参见我之前写的这篇文章的说明:《Unity游戏开发——新发教你做游戏(三):3种资源加载方式》

Resources目录存放一些被代码动态加载 资源;
Scenes目录存放场景文件;
Scripts目录存放C#代码;
ThirdPart目录存放一些第三方工具或库;

三、地铁路线地图制作

1、广州地铁图

我们先找一下广州地铁图,我找到的最新版本是这个:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
截止到2020年6月30日,广州市已经开通的地铁线路有14条,全市地铁站点有213个
先把这个图弄到Unity中,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

图片格式设置为Sprite (2D and UI)
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
把地铁图拖到场景中,调整坐标、旋转、缩放,如下:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
效果如下:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

2、HexTiles:六变形3D瓦片工具的基本操作

2.1、HexTiles工具下载

地图我想做得比较风格化,于是我找了一个六变形3D瓦片工具:HexTiles,这个工具可以在GitHub上找到,地址:https://github.com/RoryDungan/HexTiles
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

注:2D瓦片工具可以参见我之前写的这篇文章:《[Unity 2D] 重温红白机经典FC游戏,顺便教你快速搭建2D游戏关卡(Tilemap | 场景 | 地图)》

下载下来后,放入工程的ThirdPart目录中,只需保留它的CodePlugins文件夹即可,如下:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

2.2、创建瓦片材质球

我们要使用工具来绘制3D瓦片,我们需要先为瓦片制作材质球,我们先做一个绿色的材质球,在RawAssets/Materials/tiles目录右键点击菜单Create/Material,创建材质球;
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
材质球重命名为green,设置材质球的颜色为绿色,然后我们不想要有反光的效果,可以调整光滑度为0
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
以此类推,把地铁路线的颜色都做一个对应的材质球~
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

2.3、创建瓦片容器

Hierarchy视图空白处右键点击菜单Hex tile map,创建瓦片容器,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
瓦片容器上会有个HexTileMap组件,可以看到对应的功能按钮,我们后续绘制的瓦片都会在这个Hex tile map的子节点下。
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

2.4、绘制瓦片

确保Scene视图的Gizmos按钮是激活状态的,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
点击添加瓦片按钮,把要使用的材质球拖到Material槽中,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
然后在场景中按住鼠标拖动即可绘制瓦片了,[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

2.5、设置瓦片高度

我们想要在更低一层绘制瓦片,可以调整Height offset,比如我调整为-0.5,顺便把材质球改成白色,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
现在我们绘制瓦片,可以看到是在原来的瓦片的下层绘制了,并且边缘衔接处会自动补上,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

2.6、擦除瓦片

我们想把绘制的瓦片擦除,可以点击擦除瓦片按钮,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
然后在场景中点击要擦除的瓦片,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

2.7、瓦片材质绘制:材质替换

点击材质绘制按钮,然后目标材质,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
然后点击要绘制的瓦片即可,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

2.8、大面积刷瓦片

上面我们是一个瓦片一个瓦片刷的,我们可以调整刷子的尺寸(Brush size),比如我调整为3
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

这样就可以大面积刷瓦片了,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

2.9、大面积擦除瓦片

同样,我们也可以大面积擦除瓦片,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

3、搭建地铁路线

开始沿着地铁路线铺路,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
铺啊铺,铺啊铺,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
把所有地铁路线图都铺好,[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

四、地铁站点制作

1、地铁站点模型

站点就用一个简单的柱体就好了,创建一个Cube
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
拉长,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
由两个柱体(底部和顶部)组成一个站点的模型,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

2、地铁站点材质球

创建两个材质球,分别作为站点底部和顶部的材质,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
将材质球赋值给上面的柱体,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

3、地铁站点名称:TextMeshPro

地铁站点的名字我使用了TextMeshPro来显示,它的好处是在3D空间下近距离观察文字也是很清晰的。

注:关于TextMeshPro的使用教程可以参见我之前的这篇文章:《手把手教,Unity使用TextMeshPro显示字体》

下面我讲下操作步骤。

3.1、导入字体文件

找一个你喜欢的字体(TTF格式),比如我找的是免费的思源字体,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
将其放入Unity工程中,

[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
3.2、安装TextMeshPro

点击菜单Window / P[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
Packages选择Unity Registry,然后搜索textmeshpro,选择TextMeshPro,点击Install按钮,如果你已经安装过,则没有Install按钮了。
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
安装成功后,可以看到Window菜单中多了一个TextMeshPro菜单,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

3.3、制作字符集

我们需要为TextMeshPro创建一个字符集(一个txt文件),把我们需要用到的字放在这个字符集文件里,如下,在TTF同级目录中创建一个txt文件(characters.txt),
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
把广州市所有的地铁站名字都放在这个characters.txt文件中,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

3.4、制作Font Asset

点击菜单 Window / TextMeshPro / Font Asset Creator
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
首次打开会弹出下面这个窗口,点击Import TMP Essentials按钮,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
Font Asset Creator窗口中,设置Source Font File为我们的字体TTF文件,设置Character SetCharacters from File,设置Character File为我们的字符集文件characters.txt,最后点击Generate Font Atlas按钮,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
此时会生成一个纹理,我们点击Save保存,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
保存到RawAssets/Fonts目录中,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
如下(font SDF.asset
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

3.5、显示地铁站名字

在地铁站节点下创建一个空物体,重命名为name
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
给这个name节点添加TextMeshPro - Text组件,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
Text Input中输入地铁站的名字,比如珠江新城
设置Font Asset为我们上面生成的font SDF
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
此时效果如下:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
设置一下字号,设置一下对其方式,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
设置一下坐标和显示区域大小,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
效果如下:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
为了能在四个面都看得到地铁站名字,我们再复制出另外三份,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
调整下坐标和旋转角度,效果如下:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

3.6、保存站点预设

养成好习惯,需要重复使用的物体(模板)我们最好保存成预设,将其保存到RawAssets/Prefabs目录中,如下:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

4、排放地铁站点

按照地铁线路,依次摆放地铁站点,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
摆呀摆,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
终于把地铁站全部弄好了,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
把站点按地铁路线收纳好,方便管理,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

5、地平面

创建一个Plan作为地平面,这样影子可以投射到地面上,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
给地面创建一个材质球,材质球赋值给Plan
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
调整材质球颜色,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

五、导航系统:NevMesh烘焙

地铁路线有了,接下来就给它烘焙NevMesh吧,以便后面支持导航功能。

1、设置烘焙对象为Static

因为NevMesh只对场景中的静态对象进行烘焙,所以我们需要先把地铁路线设置为Static的。
选择HexTileMap节点,将其设置为Static
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
点击Yes, change children,即所有的子节点都设置为Static
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

2、NevMesh烘焙

点击菜单Window / AI / Navigation
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
Navigation窗口中,点击Bake标签页,
调节一下Agent Radius,因为我们的地铁路面比较窄,所以这里的Agent Radius需要调小一点,
最后点击Bake按钮即可,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
烘焙成功后,可以看到路面上出现了蓝色的网格,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
同时,在场景文件目录中,会看到生成了一个与场景同名的文件夹,里面的NavMesh.asset保存的就是场景的导航烘焙信息,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

六、摇杆制作

地图有了,接下来就是主角了,不过在做主角之前,我们先把摇杆做一下吧~

1、摇杆图片

摇杆的图片很简单,一个圆就可以了,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

2、Canvas与UICamera

创建一个Canvas,作为后面UI的父节点,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

在创建一个Camera来专门渲染Canvas
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

将其重命名为UICamera
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

设置UICameraClear FlagsDepth only,并设置Culling MaskUI,这样它就只会渲染UI层,把Projection设置为Orthographic(正交),
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

接着把CanvasRender Mode(渲染模式)改为Screen Space - Camera(即由摄像机来渲染),然后把Render Camera设置为刚刚的UICamera
接着再设置下分辨率适配,把Canvas Scale组件的UI Scale Mode设置为Scale with Screen Size,把分辨率设置为1280, 720
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
另外,因为UI已交给UICamera来渲染,所以Main Camera不需要再渲染UI层了,把Main CameraCulling MaskUI勾选去掉,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

3、摇杆UI制作

Canvas节点上右键点击菜单UI / Panel,创建一个Panel
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
Image组件禁用掉,因为我们不需要Panel显示出来,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
Panel下创建一个Image,重命名为leftJointedArm,作为左摇杆的父节点,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

设置它的锚点为bottom - left,即屏幕左下角,调整坐标和宽高,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
像这样子,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

把它的Coloralpha调为0,因为我们只需要利用它的区域来检测触碰,我们不需要肉眼看见它,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
接着在它的子节点下创建两个Image,分别命名为bgcenter
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
它们的Source Image都设置为摇杆的图片资源,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
分别调整下bgcenter的大小和颜色透明度,效果如下:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
同理再做一个右摇杆,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
效果如下:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

3、摇杆逻辑代码

UnityUGUI提供了ScrollRect组件,非常适合用来制作摇杆,我们继承ScrollRect然后实现OnDragOnEndDrag方法,可以很方便地获取到摇杆的遥控数据,另外,为了检测区域点击,我们再实现IPointerDownHandler接口。
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

创建摇杆脚本JointedArm.cs,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
JointedArm .cs代码如下:

using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; using System;  public class JointedArm : ScrollRect, IPointerDownHandler {     public Action<Vector2> onDragCb;     public Action onStopCb;      protected float mRadius = 0f;          private Transform trans;     private RectTransform bgTrans;     private Camera uiCam;     private Vector3 originalPos;      protected override void Awake()     {         base.Awake();         trans = transform;         bgTrans = trans.Find("bg") as RectTransform;         uiCam = GameObject.Find("UICamera").GetComponent<Camera>();         originalPos = trans.localPosition;     }      void Update()     {         if (Input.GetMouseButtonUp(0))         {             //松手时,摇杆复位             trans.localPosition = originalPos;             this.content.localPosition = Vector3.zero;         }     }      protected override void Start()     {         base.Start();         //计算摇杆块的半径         mRadius = bgTrans.sizeDelta.x * 0.5f;     }      public override void OnDrag(PointerEventData eventData)     {         base.OnDrag(eventData);         var contentPostion = this.content.anchoredPosition;         if (contentPostion.magnitude > mRadius)         {             contentPostion = contentPostion.normalized * mRadius;             SetContentAnchoredPosition(contentPostion);         }         Debug.Log("摇杆滑动,方向:" + contentPostion);          if(null != onDragCb)             onDragCb(contentPostion);     }      public override void OnEndDrag(PointerEventData eventData)     {         base.OnEndDrag(eventData);         Debug.Log("摇杆拖动结束");         if (null != onStopCb)             onStopCb();     }      public void OnPointerDown(PointerEventData eventData)     {         //点击到摇杆的区域,摇杆移动到点击的位置         trans.position = uiCam.ScreenToWorldPoint(eventData.position);         trans.localPosition = new Vector3(trans.localPosition.x, trans.localPosition.y, 0);     } } 

4、挂摇杆逻脚本

JointedArm .cs分别挂到leftJointedArmrightJointedArm上,赋值对应的center
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

5、摇杆测试

运行Unity,摇杆测试效果如下:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

七、角色、动画与控制

1、角色模型下载

主角我在AssetStore上找到了一个心仪的模型,推荐给大家,
AssetStore地址:https://assetstore.unity.com/packages/3d/characters/humanoids/sci-fi/stylized-astronaut-114298
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

注:更多模型下载可以参见我之前写的这篇文章:
《Unity游戏开发——新发教你做游戏(二):60个Unity免费资源获取网站》

将模型下载导入Unity中,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

2、动画控制器

注:关于Animator组件的详细使用可以参见我之前写的这篇文章:《Unity动画状态机Animator使用》

打开角色的动画控制器文件CharacterController
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
可以看到,两个动作,一个idle(站立)一个Run(跑),
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
Parameters(参数)里面有一个AnimationPar参数,这个参数就是用来控制站立与跑着两个动画的过渡条件的,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
Run过渡到Idle的条件是AnimationPar等于1
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
Idle过渡到Run的条件是AnimationPar等于0
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

这样,我们就可以在代码中通过这个参数来控制动画的过渡了,例:

// public Animator anim;   // 站立 -> 跑 anim.SetInteger("AnimationPar", 1); // 跑 -> 站立 anim.SetInteger("AnimationPar", 0); 

3、主角出场

在场景中创建一个空物体,重命名为Player
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
把主角模型拖到Player子节点中,把主角模型也命名为Player
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
这样,场景中出现了我们的主角了,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
因为主角需要在地铁路线上跑,我们用了导航系统NevMesh,所以主角需要挂NevMeshAgent组件,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
调节Radius(半径)与Height(高度)使之与主角模型匹配,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

4、摇杆控制主角

写一个Player.cs脚本,主要逻辑如下,

// Player.cs   using UnityEngine;  public class Player : MonoBehaviour { 	public float speed = 1f; 	public float turnSpeed = 20f; 	 	public Animator anim; 	public Transform rootTrans; 	public Transform modelTrans; 	 	private bool moving = false; 	private Vector3 moveDirection = Vector3.zero; 	 	// ... 	 	void Update() 	{ 	    if (moving) 	    { 	    	// 播放跑动画 	        anim.SetInteger("AnimationPar", 1); 			// 更新主角坐标 	        rootTrans.position += moveDirection * speed * Time.deltaTime; 	        // 更新主角朝向,使用Vector3.Lerp进行插值运算,使得角度变化不那么生硬 	        modelTrans.forward = Vector3.Lerp(modelTrans.forward, moveDirection, turnSpeed * Time.deltaTime); 	    } 	    else 	    { 	    	// 播放站立动画 	        anim.SetInteger("AnimationPar", 0); 	    } 	} 	 	// 移动 	public void Move(Vector3 direction) 	{ 	    moveDirection = direction; 	    moving = true; 	} 	 	// 站立 	public void Stand() 	{ 	    moving = false; 	} 	 	// ... } 

将脚本挂到Player父节点上,赋值对应的变量,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
为了方便管理,我们再封装一个游戏管理器GameMgr.cs,由游戏管理器来调度摇杆与主角,

// GameMgr.cs  public Player player; // 左摇杆 public JointedArm leftJointedArm; // 摄像机的Transform private Transform camTrans;  // ...  leftJointedArm.onDragCb = (direction) => 	{ 		// 摇杆向量转世界坐标系下的向量 	    var realDirect = camTrans.localToWorldMatrix * new Vector3(direction.x, 0, direction.y); 	    realDirect.y = 0; 	    // 向量归一化 	    realDirect = realDirect.normalized; 	    // 主角根据向量移动 	    player.Move(realDirect); 	}; leftJointedArm.onStopCb = () => { player.Stand(); }; 

注:从摇杆的2D向量转换为控制主角的3D向量,这里我用了一个矩阵变换,camTrans.localToWorldMatrix,相当于把相对于摄像机的局部坐标转换为世界坐标。

这样我们的摇杆就可以控制主角移动了,不过现在摄像机并不会跟着主角移动,所以下一步我们就来做摄像机跟随吧~
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

八、摄像机控制:跟随主角与右摇杆控制旋转

1、跟随主角

摄像机需要始终看着主角,我们可以使用TransformLookAt方法;
摄像机要跟着主角移动,就是根据主角当前的坐标来设置摄像机的坐标,我们可以使用Transformposition属性。
创建一个CameraControler.cs脚本,实现摄像机控制的逻辑。

using System.Collections; using System.Collections.Generic; using UnityEngine;  // 摄像机控制 public class CameraControler : MonoBehaviour { 	// 摄像机看向的物体 	public Transform lookAt; 	// 摄像机自身的Transform     public Transform camTransform;     // 摄像机与目标物体的距离     public float distance = 1.2f; 	 	private float currentX = 0.0f;     private float currentY = 20.0f; 	 	// ... 	 	private void Start()     {         camTransform = transform;     }  	private void LateUpdate()     {         Vector3 dir = new Vector3(0, 0, -distance);         Quaternion rotation = Quaternion.Euler(currentY, currentX, 0);         camTransform.position = lookAt.position + rotation * dir;         camTransform.LookAt(lookAt.position);     } 	 	// ... } 

CameraControler.cs挂到摄像机上,赋值对应的变量,这样摄像机就可以跟着主角移动了,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

2、右摇杆控制旋转

CameraControler.cs脚本中加上旋转的逻辑,

// CameraControle.cs  // 旋转速度 public float rotateSpeed = 0.01f; private bool rotating; // 旋转偏量 private Vector2 rotateDelta;  // 限制旋转范围 private const float Y_ANGLE_MIN = 10f; private const float Y_ANGLE_MAX = 50.0f;  private void Update() {     if (rotating)     {     	// 限制旋转范围         currentX += rotateDelta.x;         currentY += rotateDelta.y;         currentY = Mathf.Clamp(currentY, Y_ANGLE_MIN, Y_ANGLE_MAX);     } }  // 设置旋转偏量 public void RotateCam(Vector2 delta) {     rotateDelta = delta * rotateSpeed;     rotating = true; }  // 停止旋转 public void StopRotate() {     rotating = false; }  private void LateUpdate() {     Vector3 dir = new Vector3(0, 0, -distance);     Quaternion rotation = Quaternion.Euler(currentY, currentX, 0);     // 设置相机坐标     camTransform.position = lookAt.position + rotation * dir;     // 设置相机角度看向目标物体     camTransform.LookAt(lookAt.position); }  // .. 

GameMgr.cs中添加右摇杆与相机旋转的调度,

// GameMgr.cs  public JointedArm rightJointedArm; public CameraControler camCtrler;  // ... rightJointedArm.onDragCb = (direction) => 	{ 	    camCtrler.RotateCam(direction); 	}; rightJointedArm.onStopCb = () => { camCtrler.StopRotate(); }; 

最后记得给GameMgr.cs赋值对应的变量,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
右摇杆效果如下:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

九、天空盒SkyBox与环境雾Fog

1、天空盒:SkyBox

现在天空比较单一,我们加上天空盒的效果。
天空盒的资源我是在AssetStore上下载的,地址:https://assetstore.unity.com/packages/2d/textures-materials/sky/farland-skies-cloudy-crown-60004

[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
下载下来,导入到Unity工程中,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

只需要把天空盒的材质球拖到场景中即可生效,或者菜单Window / Rendering / Lighting
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
点击Environment,然后设置Skybox Material为对应的天空盒材质球即可,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
我喜欢Sunset(日落)的天空效果,加上之后效果如下,是不是一下子就唯美了很多:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
我们也可以通过代码设置天空盒,例:

RenderSettings.skybox = skyMat; 

可以再欣赏下其他不同天空盒的效果:
清晨:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
中午:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

晚霞:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
午夜:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

2、环境雾:Fog

不同的天空盒,需要搭配不同颜色的环境雾效。
Lighting窗口的Environment标签页中即可开启环境雾,如下:
我们可以设置雾效的颜色、密度等参数。
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
我们可以对比下 没雾效有雾效的区别:[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

十、登顶广州塔

大家应该都知道广州的地标建筑物:广州塔(小蛮腰),必须安排上。

1、广州塔模型下载

我找到了广州塔的模型,模型下载地址:https://www.3dxy.com/3dmodel/148664.html
下载FBX格式的,导入Unity工程的RawAssets/Models目录中,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

2、广州塔放入场景中

把广州塔模型放入场景中,调整坐标到对应的位置,调整模型缩放,效果如下:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

3、检测主角到了广州塔底部:触发器

我用了触发器来检测主角是否到了广州塔底部,在广州塔底部创建一个物体,并挂上BoxCollider组件,调整碰撞体大小,如下:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
把碰撞体的Is Trigger勾选上,这样它就是一个触发器了,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
想要检测主角是否进入了触发器中,还需要给主角也挂上碰撞体(Collider)和刚体(Rigidbody),给主角安排上,因为我们不需要模拟重力,所以Use Gravity不要勾选,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
调整碰撞体大小的时候,如果看不清楚,可以把Scene视图的Shading Mode设置为Wireframe(线框),
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
这样就可以比较清楚得看到碰撞体了,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
接着写个广州塔触发器脚本CantonTowerTrigger.cs

using UnityEngine;  /// <summary> /// 广州塔触发器 /// </summary> public class CantonTowerTrigger : MonoBehaviour {     private void OnTriggerEnter(Collider other)     { 		// 进入了触发器     }      private void OnTriggerExit(Collider other)     { 		// 离开了触发器     } 	 	// ... } 

CantonTowerTrigger.cs脚本挂到触发器上,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
画成图是这样子:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

4、询问是否要上广州塔:UI界面

主角进入广州塔触发器时,弹出UI界面询问是否要上广州塔。
我们先做个询问的UI界面,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
层级结构如下:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
把界面保存为预设,放在Resources目录中,这样我们就可以通过Resources.Load来加载界面资源了。
封装一个界面管理器,方便界面的显示与关闭,封装一个界面基类BaseUIPanel,所有的界面都继承这个基类,画成关系图是这样子,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
界面管理器和界面基类代码如下:

using System.Collections.Generic; using UnityEngine;  // 界面管理器 public class UIPanelMgr {     public void Init()     {         canvas = GameObject.Find("Canvas").transform;     }      public void ShowPanel(string panelName)     {         var panel = GetPanelRes(panelName);         if(null != panel)             panel.Show();     }      public void HidePanel(string panelName)     {         if (!panels.ContainsKey(panelName))             return;          panels[panelName].Hide();     }      private BaseUIPanel GetPanelRes(string panelName)     {         if (panels.ContainsKey(panelName))             return panels[panelName];         var prefab = Resources.Load<GameObject>(panelName);         var go = Object.Instantiate(prefab);         go.transform.SetParent(canvas, false);         var panel = go.GetComponent<BaseUIPanel>();         panels.Add(panelName, panel);         return panel;     }      private Dictionary<string, BaseUIPanel> panels = new Dictionary<string, BaseUIPanel>();     private Transform canvas;      private static UIPanelMgr s_instance;     public static UIPanelMgr instance     {         get         {             if (null == s_instance)                 s_instance = new UIPanelMgr();             return s_instance;         }     } }  // 界面基类 public class BaseUIPanel : MonoBehaviour {     protected GameObject panelObj;      protected void Awake()     {         panelObj = gameObject;     }      public virtual void Show()     {         panelObj.SetActive(true);     }      public virtual void Hide()     {         panelObj.SetActive(false);     } } 

然后写一个询问是否上广州塔的界面类GoCantonTowerPanel .cs,它继承BaseUIPanel,代码如下,

using UnityEngine.UI;  public class GoCantonTowerPanel : BaseUIPanel {     public Button noBtn;     public Button okBtn;      private void Start()     {         noBtn.onClick.AddListener(Hide);         okBtn.onClick.AddListener(() =>          {             // TODO:前往广州塔顶部 		             Hide();         });     } } 

把脚本挂到界面根节点上,并赋值按钮对象,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
回到广州塔触发器中,补上显示界面的调用,

// CantonTowerTrigger.cs  // 广州塔触发器  private void OnTriggerEnter(Collider other) {     UIPanelMgr.instance.ShowPanel("GoCantonTowerPanel"); }  private void OnTriggerExit(Collider other) {     UIPanelMgr.instance.HidePanel("GoCantonTowerPanel"); } 

这样,我们就实现了经过广州塔底部地时候弹出询问框的功能了,效果如下:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

5、登上塔顶

点击前往按钮,主角要移动到塔顶,我们可以事先在塔顶创建一个空物体,作为一个定位,主角瞬间移动到这个位置即可,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

同理,我们也在塔底放一个空物体,作为从塔上下来时的定位。

[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
界面逻辑中如果直接操作Player类不是很合适,我们封装一个事件管理器,通过抛事件来解耦,补上前往按钮的点击逻辑,

// GoCantonTowerPanel.cs  okBtn.onClick.AddListener(() =>       {          // 前往广州塔顶部          EventDispatcher.instance.DispatchEvent(EventDef.GO_TO_CANTONTOWER_TOP);          UIPanelMgr.instance.ShowPanel("OnTopCantonTowerPanel");          Hide();      }); 

主角类中实现去塔顶的逻辑,

// Player.cs  /// <summary> /// 去广州塔顶部 /// </summary> public void GoToCantonTowerTop(Transform towerTop) {     canMove = false;     navAgent.enabled = false;     rootTrans.position = towerTop.position;     rootTrans.forward = towerTop.forward; } 

效果如下:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

6、塔顶调整摄像机距离

在塔顶鸟瞰广州,再做一个可以拉长镜头的功能,
这个功能就是在摄像机控制器CameraControler.cs中修改distance的值,也是通过事件来触发,响应函数如下:

// CameraControler.cs  private void OnEventChangeCamDistance(params object[] args) {     var offset = (float)args[0];     distance = originalDistance + offset * 20f; } 
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

十一、功能补充

1、粒子系统:烟花效果

用粒子系统做个烟花效果,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
用到的粒子图片如下,比较简单,可以自行用PhotoShop制作:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
粒子参数如下:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

注:关于粒子系统的教程,可以参见我之前写的这些文章:
《学Unity的猫——第十五章:Unity粒子系统ParticleSystem,下雪啦下雪啦》
《Unity使用ShaderGraph配合粒子系统,制作子弹拖尾特效(Fate/stay night金闪闪的大招效果)》
《手把手教你使用Unity制作一个飞机喷射火焰尾气的粒子效果》

在场景中克隆几个烟花粒子,用于循环复用,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
写个烟花脚本,实现随机坐标播放粒子的功能,

using System.Collections; using System.Collections.Generic; using UnityEngine;  [RequireComponent(typeof(ParticleSystem))] public class Fireworks : MonoBehaviour {     private ParticleSystem particle;     private Transform trans;      void Start()     {         trans = transform;         particle = GetComponent<ParticleSystem>();         StartCoroutine(RandomLoopFireworks());     }      IEnumerator RandomLoopFireworks()     {         while (true)         {             if (particle.isPlaying)                 yield return null;              // 随机坐标             trans.position = new Vector3(Random.Range(-20, 20), Random.Range(8, 15), Random.Range(-20, 20));             particle.Play();              yield return new WaitForSeconds(Random.Range(0.3f, 1.5f));         }     } } 

效果如下:
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

2、音乐播放:安妮的仙境

广州地铁站的经典背景音乐:安妮的仙境,导入到工程中,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
GameMgr挂上音源Audio Source组件,赋值Audio Clip安妮的仙境,勾选Play On Awake,这样一启动就会自动播放,勾选Loop,这样背景音乐就可以循环播放了,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

3、彩蛋:天空盒道具

我把天空盒做成了道具,碰到道具可以动态切换天空盒,原理也是用的触发器,
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)

十二、工程源码

本工程源码已上传到CodeChina,感兴趣的同学可自行下载学习。
地址:https://codechina.csdn.net/linxinfa/GuangzhouGogo
注:我使用的Unity版本:Unity 2021.1.7f1c1 (64-bit)
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)
注:本工程仅供学习使用,未经授权不得用于商业用途!
[原创] 用Unity等比例制作广州地铁,广州加油,早日战胜疫情(Unity | 地铁地图 | 第三人称视角)