其他分享
首页 > 其他分享> > [原创] 用Unity等比例制作广州地铁,广州加油,战胜疫情(Unity | 地图 | 第三人称视角)

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

作者:互联网

文章目录

一、前言

嗨,大家好,我是新发。
最近比较忙,好几天没写文章了,这两天广州疫情的新闻频频上热搜,身在广州无时无刻不吊着个心,部分同事所在的区域已经封起来了,只能远程上班,我住的地区暂时还比较安全,不过也已经做好远程工作的准备了。
话说回来,我在广州上学、生活、工作了十年了,对广州有着特殊的情愫,希望广州早日战胜疫情,加油。
我这两天做了一个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目录中,只需保留它的CodePlugins文件夹即可,如下:
在这里插入图片描述

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 SetCharacters 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
在这里插入图片描述

1、NevMesh烘焙

点击菜单Window / AI / Navigation
在这里插入图片描述
Navigation窗口中,点击Bake标签页,
调节一下Agent Radius,因为我们的地铁路面比较窄,所以这里的Agent Radius需要调小一点,
最后点击Bake按钮即可,
在这里插入图片描述
烘焙成功后,可以看到路面上出现了蓝色的网格,
在这里插入图片描述
同时,在场景文件目录中,会看到生成了一个与场景同名的文件夹,里面的NavMesh.asset保存的就是场景的导航烘焙信息,
在这里插入图片描述

五、摇杆制作

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

1、摇杆图片

摇杆的图片很简单,一个圆就可以了,
在这里插入图片描述

2、Canvas与UICamera

创建一个Canvas,作为后面UI的父节点,
在这里插入图片描述

在这里插入图片描述

在创建一个Camera来专门渲染Canvas
在这里插入图片描述

将其重命名为UICamera
在这里插入图片描述

设置UICameraClear FlagsDepth only,并设置Culling MaskUI,这样它就只会渲染UI层,把Projection设置为Orthographic(正交),
在这里插入图片描述

接着把CanvasRender Mode(渲染模式)改为Screen Space - Camera(即由摄像机来渲染),然后把Render Camera设置为刚刚的UICamera
接着再设置下分辨率适配,把Canvas Scale组件的UI Scale Mode设置为Scale with Screen Size,把分辨率设置为1280, 720
在这里插入图片描述
另外,因为UI已交给UICamera来渲染,所以Main Camera不需要再渲染UI层了,把Main CameraCulling MaskUI勾选去掉,
在这里插入图片描述

3、摇杆UI制作

Canvas节点上右键点击菜单UI / Panel,创建一个Panel
在这里插入图片描述
Image组件禁用掉,因为我们不需要Panel显示出来,
在这里插入图片描述
Panel下创建一个Image,重命名为leftJointedArm,作为左摇杆的父节点,
在这里插入图片描述

设置它的锚点为bottom - left,即屏幕左下角,调整坐标和宽高,
在这里插入图片描述
像这样子,
在这里插入图片描述

把它的Coloralpha调为0,因为我们只需要利用它的区域来检测触碰,我们不需要肉眼看见它,
在这里插入图片描述
接着在它的子节点下创建两个Image,分别命名为bgcenter
在这里插入图片描述
它们的Source Image都设置为摇杆的图片资源,
在这里插入图片描述
分别调整下bgcenter的大小和颜色透明度,效果如下:
在这里插入图片描述
同理再做一个右摇杆,
在这里插入图片描述
效果如下:
在这里插入图片描述

3、摇杆逻辑代码

UnityUGUI提供了ScrollRect组件,非常适合用来制作摇杆,我们继承ScrollRect然后实现OnDragOnEndDrag方法,可以很方便地获取到摇杆的遥控数据,另外,为了检测区域点击,我们再实现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 m_trans;
    private RectTransform m_bgTrans;
    private Camera m_uiCam;
    private Vector3 m_originalPos;

    protected override void Awake()
    {
        base.Awake();
        m_trans = transform;
        m_bgTrans = m_trans.Find("bg") as RectTransform;
        m_uiCam = GameObject.Find("UICamera").GetComponent<Camera>();
        m_originalPos = m_trans.localPosition;
    }

    void Update()
    {
        if (Input.GetMouseButtonUp(0))
        {
            //松手时,摇杆复位
            m_trans.localPosition = m_originalPos;
            this.content.localPosition = Vector3.zero;
        }
    }

    protected override void Start()
    {
        base.Start();
        //计算摇杆块的半径
        mRadius = m_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)
    {
        //点击到摇杆的区域,摇杆移动到点击的位置
        m_trans.position = m_uiCam.ScreenToWorldPoint(eventData.position);
        m_trans.localPosition = new Vector3(m_trans.localPosition.x, m_trans.localPosition.y, 0);
    }
}

4、挂摇杆逻脚本

JointedArm .cs分别挂到leftJointedArmrightJointedArm上,赋值对应的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 m_rootTrans;
	public Transform m_modelTrans;
	
	private bool m_moving = false;
	private Vector3 m_moveDirection = Vector3.zero;
	
	// ...
	
	void Update()
	{
	    if (m_canMove && m_moving)
	    {
	        anim.SetInteger("AnimationPar", 1);
	
	        m_rootTrans.position += m_moveDirection * speed * Time.deltaTime;
	        m_modelTrans.forward = Vector3.Lerp(m_modelTrans.forward, m_moveDirection, turnSpeed * Time.deltaTime);
	    }
	    else
	    {
	        anim.SetInteger("AnimationPar", 0);
	    }
	}
	
	public void Move(Vector3 direction)
	{
	    m_moveDirection = direction;
	    m_moving = true;
	}
	
	public void Stand()
	{
	    m_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、跟随主角

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

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

// 摄像机控制
public class CameraControler : MonoBehaviour
{
	public Transform lookAt;
    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 m_rotating;
private Vector2 m_rotateDelta;

// 限制旋转范围
private const float Y_ANGLE_MIN = 10f;
private const float Y_ANGLE_MAX = 50.0f;

private void Update()
{
    if (m_rotating)
    {
    	// 限制旋转范围
        currentX += m_rotateDelta.x;
        currentY += m_rotateDelta.y;
        currentY = Mathf.Clamp(currentY, Y_ANGLE_MIN, Y_ANGLE_MAX);
    }
}

// 设置旋转偏量
public void RotateCam(Vector2 delta)
{
    m_rotateDelta = delta * rotateSpeed;
    m_rotating = true;
}

// 停止旋转
public void StopRotate()
{
    m_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()
    {
        m_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 (!m_panels.ContainsKey(panelName))
            return;

        m_panels[panelName].Hide();
    }

    private BaseUIPanel GetPanelRes(string panelName)
    {
        if (m_panels.ContainsKey(panelName))
            return m_panels[panelName];
        var prefab = Resources.Load<GameObject>(panelName);
        var go = Object.Instantiate(prefab);
        go.transform.SetParent(m_canvas, false);
        var panel = go.GetComponent<BaseUIPanel>();
        m_panels.Add(panelName, panel);
        return panel;
    }

    private Dictionary<string, BaseUIPanel> m_panels = new Dictionary<string, BaseUIPanel>();
    private Transform m_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 m_panelObj;

    protected void Awake()
    {
        m_panelObj = gameObject;
    }

    public virtual void Show()
    {
        m_panelObj.SetActive(true);
    }

    public virtual void Hide()
    {
        m_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)
{
    m_canMove = false;
    m_navAgent.enabled = false;
    m_rootTrans.position = towerTop.position;
    m_rootTrans.forward = towerTop.forward;
}

效果如下:
在这里插入图片描述

6、塔顶调整摄像机距离

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

// CameraControler.cs

private void OnEventChangeCamDistance(params object[] args)
{
    var offset = (float)args[0];
    distance = m_originalDistance + offset * 20f;
}

在这里插入图片描述

十、补充功能

1、粒子系统:烟花效果

用粒子系统做个烟花效果,
在这里插入图片描述

注:关于粒子系统的教程,可以参见我之前写的这些文章:
《学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 m_particle;
    private Transform m_trans;

    void Start()
    {
        m_trans = transform;
        m_particle = GetComponent<ParticleSystem>();
        StartCoroutine(RandomLoopFireworks());
    }

    IEnumerator RandomLoopFireworks()
    {
        while (true)
        {
            if (m_particle.isPlaying)
                yield return null;

            // 随机坐标
            m_trans.position = new Vector3(Random.Range(-20, 20), Random.Range(8, 15), Random.Range(-20, 20));
            m_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)
在这里插入图片描述
注:本工程仅供学习使用,未经授权不得用于商业用途!
在这里插入图片描述

标签:广州,第三人称,void,private,摇杆,Unity,瓦片,public
来源: https://blog.csdn.net/linxinfa/article/details/117536057