文章目录
- 一、前言
- 二、创建工程
- 1、创建工程
- 2、创建目录
- 三、地铁路线地图制作
- 1、广州地铁图
- 2、HexTiles:六变形3D瓦片工具的基本操作
- 2.1、HexTiles工具下载
- 2.2、创建瓦片材质球
- 2.3、创建瓦片容器
- 2.4、绘制瓦片
- 2.5、设置瓦片高度
- 2.6、擦除瓦片
- 2.7、瓦片材质绘制:材质替换
- 2.8、大面积刷瓦片
- 2.9、大面积擦除瓦片
- 3、搭建地铁路线
- 四、地铁站点制作
- 1、地铁站点模型
- 2、地铁站点材质球
- 3、地铁站点名称:TextMeshPro
- 3.1、导入字体文件
- 3.2、安装TextMeshPro
- 3.3、制作字符集
- 3.4、制作Font Asset
- 3.5、显示地铁站名字
- 3.6、保存站点预设
- 4、排放地铁站点
- 5、地平面
- 五、导航系统:NevMesh烘焙
- 1、设置烘焙对象为Static
- 2、NevMesh烘焙
- 六、摇杆制作
- 1、摇杆图片
- 2、Canvas与UICamera
- 3、摇杆UI制作
- 3、摇杆逻辑代码
- 4、挂摇杆逻脚本
- 5、摇杆测试
- 七、角色、动画与控制
- 1、角色模型下载
- 2、动画控制器
- 3、主角出场
- 4、摇杆控制主角
- 八、摄像机控制:跟随主角与右摇杆控制旋转
- 1、跟随主角
- 2、右摇杆控制旋转
- 九、天空盒SkyBox与环境雾Fog
- 1、天空盒:SkyBox
- 2、环境雾:Fog
- 十、登顶广州塔
- 1、广州塔模型下载
- 2、广州塔放入场景中
- 3、检测主角到了广州塔底部:触发器
- 4、询问是否要上广州塔:UI界面
- 5、登上塔顶
- 6、塔顶调整摄像机距离
- 十一、功能补充
- 1、粒子系统:烟花效果
- 2、音乐播放:安妮的仙境
- 3、彩蛋:天空盒道具
- 十二、工程源码
一、前言
嗨,大家好,我是新发。
最近比较忙,好几天没写文章了,这两天广州疫情的新闻频频上热搜,身在广州无时无刻不吊着个心,部分同事所在的区域已经封起来了,只能远程上班,我住的地区暂时还比较安全,不过也已经做好远程工作的准备了。
话说回来,我在广州上学、生活、工作了十年了,对广州有着特殊的情愫。
我这两天做了一个Demo
,我在Unity
中搭建了整个广州地铁路线地图,并做了第三人称视角相机跟随,双摇杆控制,可以登上广州塔鸟瞰整个广州。以此献给我热爱的大广州,效果如下:
注:今天广州大规模核酸检测,下午刚检测完毕,回家写这篇文章直到现在(
2021-6-5 23:44
),广州加油,早日战胜疫情!
下面我将讲解一下创作过程。
二、创建工程
1、创建工程
我用的Unity
版本是2021.1.7f1c1 (64-bit)
,选择3D
模板,工程名字起为GuangzhouGogo
好了,点击创建。
创建成功,
2、创建目录
养成好习惯,先规范目录结构,创建一些文件夹,目录结构如下:
RawAssets
目录主要是存放一些【生肉资源】或被场景依赖的资源;
注:关于【生肉资源】可以参见我之前写的这篇文章的说明:《Unity游戏开发——新发教你做游戏(三):3种资源加载方式》
Resources
目录存放一些被代码动态加载 资源;
Scenes
目录存放场景文件;
Scripts
目录存放C#
代码;
ThirdPart
目录存放一些第三方工具或库;
三、地铁路线地图制作
1、广州地铁图
我们先找一下广州地铁图,我找到的最新版本是这个:
截止到2020年6月30日,广州市已经开通的地铁线路有14条
,全市地铁站点有213个
。
先把这个图弄到Unity
中,
图片格式设置为Sprite (2D and UI)
,
把地铁图拖到场景中,调整坐标、旋转、缩放,如下:
效果如下:
2、HexTiles:六变形3D瓦片工具的基本操作
2.1、HexTiles工具下载
地图我想做得比较风格化,于是我找了一个六变形3D
瓦片工具:HexTiles
,这个工具可以在GitHub
上找到,地址:https://github.com/RoryDungan/HexTiles
注:
2D
瓦片工具可以参见我之前写的这篇文章:《[Unity 2D] 重温红白机经典FC游戏,顺便教你快速搭建2D游戏关卡(Tilemap | 场景 | 地图)》
下载下来后,放入工程的ThirdPart
目录中,只需保留它的Code
和Plugins
文件夹即可,如下:
2.2、创建瓦片材质球
我们要使用工具来绘制3D
瓦片,我们需要先为瓦片制作材质球,我们先做一个绿色的材质球,在RawAssets/Materials/tiles
目录右键点击菜单Create/Material
,创建材质球;
材质球重命名为green
,设置材质球的颜色为绿色,然后我们不想要有反光的效果,可以调整光滑度为0
;
以此类推,把地铁路线的颜色都做一个对应的材质球~
2.3、创建瓦片容器
在Hierarchy
视图空白处右键点击菜单Hex tile map
,创建瓦片容器,
瓦片容器上会有个HexTileMap
组件,可以看到对应的功能按钮,我们后续绘制的瓦片都会在这个Hex tile map
的子节点下。
2.4、绘制瓦片
确保Scene
视图的Gizmos
按钮是激活状态的,
点击添加瓦片
按钮,把要使用的材质球拖到Material
槽中,
然后在场景中按住鼠标拖动即可绘制瓦片了,
2.5、设置瓦片高度
我们想要在更低一层绘制瓦片,可以调整Height offset
,比如我调整为-0.5
,顺便把材质球改成白色,
现在我们绘制瓦片,可以看到是在原来的瓦片的下层绘制了,并且边缘衔接处会自动补上,
2.6、擦除瓦片
我们想把绘制的瓦片擦除,可以点击擦除瓦片按钮,
然后在场景中点击要擦除的瓦片,
2.7、瓦片材质绘制:材质替换
点击材质绘制按钮,然后目标材质,
然后点击要绘制的瓦片即可,
2.8、大面积刷瓦片
上面我们是一个瓦片一个瓦片刷的,我们可以调整刷子的尺寸(Brush size
),比如我调整为3
,
这样就可以大面积刷瓦片了,
2.9、大面积擦除瓦片
同样,我们也可以大面积擦除瓦片,
3、搭建地铁路线
开始沿着地铁路线铺路,
铺啊铺,铺啊铺,
把所有地铁路线图都铺好,
四、地铁站点制作
1、地铁站点模型
站点就用一个简单的柱体就好了,创建一个Cube
,
拉长,
由两个柱体(底部和顶部)组成一个站点的模型,
2、地铁站点材质球
创建两个材质球,分别作为站点底部和顶部的材质,
将材质球赋值给上面的柱体,
3、地铁站点名称:TextMeshPro
地铁站点的名字我使用了TextMeshPro
来显示,它的好处是在3D
空间下近距离观察文字也是很清晰的。
注:关于
TextMeshPro
的使用教程可以参见我之前的这篇文章:《手把手教,Unity使用TextMeshPro显示字体》
下面我讲下操作步骤。
3.1、导入字体文件
找一个你喜欢的字体(TTF
格式),比如我找的是免费的思源字体,
将其放入Unity
工程中,
3.2、安装TextMeshPro
点击菜单Window / Package
,打开Package Manger
窗口,
Packages
选择Unity Registry
,然后搜索textmeshpro
,选择TextMeshPro
,点击Install
按钮,如果你已经安装过,则没有Install
按钮了。
安装成功后,可以看到Window
菜单中多了一个TextMeshPro
菜单,
3.3、制作字符集
我们需要为TextMeshPro
创建一个字符集(一个txt
文件),把我们需要用到的字放在这个字符集文件里,如下,在TTF
同级目录中创建一个txt
文件(characters.txt
),
把广州市所有的地铁站名字都放在这个characters.txt
文件中,
3.4、制作Font Asset
点击菜单 Window / TextMeshPro / Font Asset Creator
首次打开会弹出下面这个窗口,点击Import TMP Essentials
按钮,
在Font Asset Creator
窗口中,设置Source Font File
为我们的字体TTF
文件,设置Character Set
为Characters from File
,设置Character File
为我们的字符集文件characters.txt
,最后点击Generate Font Atlas
按钮,
此时会生成一个纹理,我们点击Save
保存,
保存到RawAssets/Fonts
目录中,
如下(font SDF.asset
)
3.5、显示地铁站名字
在地铁站节点下创建一个空物体,重命名为name
,
给这个name
节点添加TextMeshPro - Text
组件,
在Text Input
中输入地铁站的名字,比如珠江新城
,
设置Font Asset
为我们上面生成的font SDF
,
此时效果如下:
设置一下字号,设置一下对其方式,
设置一下坐标和显示区域大小,
效果如下:
为了能在四个面都看得到地铁站名字,我们再复制出另外三份,
调整下坐标和旋转角度,效果如下:
3.6、保存站点预设
养成好习惯,需要重复使用的物体(模板)我们最好保存成预设,将其保存到RawAssets/Prefabs
目录中,如下:
4、排放地铁站点
按照地铁线路,依次摆放地铁站点,
摆呀摆,
终于把地铁站全部弄好了,
把站点按地铁路线收纳好,方便管理,
5、地平面
创建一个Plan
作为地平面,这样影子可以投射到地面上,
给地面创建一个材质球,材质球赋值给Plan
,
调整材质球颜色,
五、导航系统:NevMesh烘焙
地铁路线有了,接下来就给它烘焙NevMesh
吧,以便后面支持导航功能。
1、设置烘焙对象为Static
因为NevMesh
只对场景中的静态对象进行烘焙,所以我们需要先把地铁路线设置为Static
的。
选择HexTileMap
节点,将其设置为Static
,
点击Yes, change children
,即所有的子节点都设置为Static
,
2、NevMesh烘焙
点击菜单Window / AI / Navigation
,
在Navigation
窗口中,点击Bake
标签页,
调节一下Agent Radius
,因为我们的地铁路面比较窄,所以这里的Agent Radius
需要调小一点,
最后点击Bake
按钮即可,
烘焙成功后,可以看到路面上出现了蓝色的网格,
同时,在场景文件目录中,会看到生成了一个与场景同名的文件夹,里面的NavMesh.asset
保存的就是场景的导航烘焙信息,
六、摇杆制作
地图有了,接下来就是主角了,不过在做主角之前,我们先把摇杆做一下吧~
1、摇杆图片
摇杆的图片很简单,一个圆就可以了,
2、Canvas与UICamera
创建一个Canvas
,作为后面UI
的父节点,
在创建一个Camera
来专门渲染Canvas
,
将其重命名为UICamera
,
设置UICamera
的Clear Flags
为Depth only
,并设置Culling Mask
为UI
,这样它就只会渲染UI
层,把Projection
设置为Orthographic
(正交),
接着把Canvas
的Render Mode
(渲染模式)改为Screen Space - Camera
(即由摄像机来渲染),然后把Render Camera
设置为刚刚的UICamera
,
接着再设置下分辨率适配,把Canvas Scale
组件的UI Scale Mode
设置为Scale with Screen Size
,把分辨率设置为1280, 720
,
另外,因为UI
已交给UICamera
来渲染,所以Main Camera
不需要再渲染UI
层了,把Main Camera
的Culling Mask
的UI
勾选去掉,
3、摇杆UI制作
在Canvas
节点上右键点击菜单UI / Panel
,创建一个Panel
,
把Image
组件禁用掉,因为我们不需要Panel
显示出来,
在Panel
下创建一个Image
,重命名为leftJointedArm
,作为左摇杆的父节点,
设置它的锚点为bottom - left
,即屏幕左下角,调整坐标和宽高,
像这样子,
把它的Color
的alpha
调为0
,因为我们只需要利用它的区域来检测触碰,我们不需要肉眼看见它,
接着在它的子节点下创建两个Image
,分别命名为bg
和center
,
它们的Source Image
都设置为摇杆的图片资源,
分别调整下bg
和center
的大小和颜色透明度,效果如下:
同理再做一个右摇杆,
效果如下:
3、摇杆逻辑代码
Unity
的UGUI
提供了ScrollRect
组件,非常适合用来制作摇杆,我们继承ScrollRect
然后实现OnDrag
和OnEndDrag
方法,可以很方便地获取到摇杆的遥控数据,另外,为了检测区域点击,我们再实现IPointerDownHandler
接口。
创建摇杆脚本JointedArm.cs
,
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
分别挂到leftJointedArm
和rightJointedArm
上,赋值对应的center
,
5、摇杆测试
运行Unity
,摇杆测试效果如下:
七、角色、动画与控制
1、角色模型下载
主角我在AssetStore
上找到了一个心仪的模型,推荐给大家,
AssetStore
地址:https://assetstore.unity.com/packages/3d/characters/humanoids/sci-fi/stylized-astronaut-114298
注:更多模型下载可以参见我之前写的这篇文章:
《Unity游戏开发——新发教你做游戏(二):60个Unity免费资源获取网站》
将模型下载导入Unity
中,
2、动画控制器
注:关于
Animator
组件的详细使用可以参见我之前写的这篇文章:《Unity动画状态机Animator使用》
打开角色的动画控制器文件CharacterController
,
可以看到,两个动作,一个idle
(站立)一个Run
(跑),
到Parameters
(参数)里面有一个AnimationPar
参数,这个参数就是用来控制站立与跑着两个动画的过渡条件的,
从Run
过渡到Idle
的条件是AnimationPar
等于1
,
从Idle
过渡到Run
的条件是AnimationPar
等于0
,
这样,我们就可以在代码中通过这个参数来控制动画的过渡了,例:
// public Animator anim;
// 站立 -> 跑
anim.SetInteger("AnimationPar", 1);
// 跑 -> 站立
anim.SetInteger("AnimationPar", 0);
3、主角出场
在场景中创建一个空物体,重命名为Player
,
把主角模型拖到Player
子节点中,把主角模型也命名为Player
,
这样,场景中出现了我们的主角了,
因为主角需要在地铁路线上跑,我们用了导航系统NevMesh
,所以主角需要挂NevMeshAgent
组件,
调节Radius
(半径)与Height
(高度)使之与主角模型匹配,
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
父节点上,赋值对应的变量,
为了方便管理,我们再封装一个游戏管理器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
,相当于把相对于摄像机的局部坐标转换为世界坐标。
这样我们的摇杆就可以控制主角移动了,不过现在摄像机并不会跟着主角移动,所以下一步我们就来做摄像机跟随吧~
八、摄像机控制:跟随主角与右摇杆控制旋转
1、跟随主角
摄像机需要始终看着主角,我们可以使用Transform
的LookAt
方法;
摄像机要跟着主角移动,就是根据主角当前的坐标来设置摄像机的坐标,我们可以使用Transform
的position
属性。
创建一个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
挂到摄像机上,赋值对应的变量,这样摄像机就可以跟着主角移动了,
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
赋值对应的变量,
右摇杆效果如下:
九、天空盒SkyBox与环境雾Fog
1、天空盒:SkyBox
现在天空比较单一,我们加上天空盒的效果。
天空盒的资源我是在AssetStore
上下载的,地址:https://assetstore.unity.com/packages/2d/textures-materials/sky/farland-skies-cloudy-crown-60004
下载下来,导入到Unity
工程中,
只需要把天空盒的材质球拖到场景中即可生效,或者菜单Window / Rendering / Lighting
,
点击Environment
,然后设置Skybox Material
为对应的天空盒材质球即可,
我喜欢Sunset
(日落)的天空效果,加上之后效果如下,是不是一下子就唯美了很多:
我们也可以通过代码设置天空盒,例:
RenderSettings.skybox = skyMat;
可以再欣赏下其他不同天空盒的效果:
清晨:
中午:
晚霞:
午夜:
2、环境雾:Fog
不同的天空盒,需要搭配不同颜色的环境雾效。
在Lighting
窗口的Environment
标签页中即可开启环境雾,如下:
我们可以设置雾效的颜色、密度等参数。
我们可以对比下 没雾效
与 有雾效
的区别:
十、登顶广州塔
大家应该都知道广州的地标建筑物:广州塔(小蛮腰),必须安排上。
1、广州塔模型下载
我找到了广州塔的模型,模型下载地址:https://www.3dxy.com/3dmodel/148664.html
下载FBX
格式的,导入Unity
工程的RawAssets/Models
目录中,
2、广州塔放入场景中
把广州塔模型放入场景中,调整坐标到对应的位置,调整模型缩放,效果如下:
3、检测主角到了广州塔底部:触发器
我用了触发器来检测主角是否到了广州塔底部,在广州塔底部创建一个物体,并挂上BoxCollider
组件,调整碰撞体大小,如下:
把碰撞体的Is Trigger
勾选上,这样它就是一个触发器了,
想要检测主角是否进入了触发器中,还需要给主角也挂上碰撞体(Collider
)和刚体(Rigidbody
),给主角安排上,因为我们不需要模拟重力,所以Use Gravity
不要勾选,
调整碰撞体大小的时候,如果看不清楚,可以把Scene
视图的Shading Mode
设置为Wireframe
(线框),
这样就可以比较清楚得看到碰撞体了,
接着写个广州塔触发器脚本CantonTowerTrigger.cs
,
using UnityEngine;
/// <summary>
/// 广州塔触发器
/// </summary>
public class CantonTowerTrigger : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
// 进入了触发器
}
private void OnTriggerExit(Collider other)
{
// 离开了触发器
}
// ...
}
把CantonTowerTrigger.cs
脚本挂到触发器上,
画成图是这样子:
4、询问是否要上广州塔:UI界面
主角进入广州塔触发器时,弹出UI
界面询问是否要上广州塔。
我们先做个询问的UI
界面,
层级结构如下:
把界面保存为预设,放在Resources
目录中,这样我们就可以通过Resources.Load
来加载界面资源了。
封装一个界面管理器,方便界面的显示与关闭,封装一个界面基类BaseUIPanel
,所有的界面都继承这个基类,画成关系图是这样子,
界面管理器和界面基类代码如下:
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();
});
}
}
把脚本挂到界面根节点上,并赋值按钮对象,
回到广州塔触发器中,补上显示界面的调用,
// CantonTowerTrigger.cs
// 广州塔触发器
private void OnTriggerEnter(Collider other)
{
UIPanelMgr.instance.ShowPanel("GoCantonTowerPanel");
}
private void OnTriggerExit(Collider other)
{
UIPanelMgr.instance.HidePanel("GoCantonTowerPanel");
}
这样,我们就实现了经过广州塔底部地时候弹出询问框的功能了,效果如下:
5、登上塔顶
点击前往按钮,主角要移动到塔顶,我们可以事先在塔顶创建一个空物体,作为一个定位,主角瞬间移动到这个位置即可,
同理,我们也在塔底放一个空物体,作为从塔上下来时的定位。
界面逻辑中如果直接操作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;
}
效果如下:
6、塔顶调整摄像机距离
在塔顶鸟瞰广州,再做一个可以拉长镜头的功能,
这个功能就是在摄像机控制器CameraControler.cs
中修改distance
的值,也是通过事件来触发,响应函数如下:
// CameraControler.cs
private void OnEventChangeCamDistance(params object[] args)
{
var offset = (float)args[0];
distance = originalDistance + offset * 20f;
}
十一、功能补充
1、粒子系统:烟花效果
用粒子系统做个烟花效果,
用到的粒子图片如下,比较简单,可以自行用PhotoShop
制作:
粒子参数如下:
注:关于粒子系统的教程,可以参见我之前写的这些文章:
《学Unity的猫——第十五章:Unity粒子系统ParticleSystem,下雪啦下雪啦》
《Unity使用ShaderGraph配合粒子系统,制作子弹拖尾特效(Fate/stay night金闪闪的大招效果)》
《手把手教你使用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));
}
}
}
效果如下:
2、音乐播放:安妮的仙境
广州地铁站的经典背景音乐:安妮的仙境,导入到工程中,
给GameMgr
挂上音源Audio Source
组件,赋值Audio Clip
为安妮的仙境
,勾选Play On Awake
,这样一启动就会自动播放,勾选Loop
,这样背景音乐就可以循环播放了,
3、彩蛋:天空盒道具
我把天空盒做成了道具,碰到道具可以动态切换天空盒,原理也是用的触发器,
十二、工程源码
本工程源码已上传到CodeChina
,感兴趣的同学可自行下载学习。
地址:https://codechina.csdn.net/linxinfa/GuangzhouGogo
注:我使用的Unity
版本:Unity 2021.1.7f1c1 (64-bit)
。
注:本工程仅供学习使用,未经授权不得用于商业用途!