[Original] Use Unity to make Guangzhou Metro, come on Guangzhou, and defeat the epidemic as soon as possible (Unity | Metro Map | Third-person Perspective)

Article Directory

I. Introduction

Hi everyone, I'm Xinfa.
I’ve been busy recently and haven’t written an article for several days. In the past two days, the news about the Guangzhou epidemic has been frequently searched. I am in Guangzhou all the time. The area where some colleagues are located has been sealed, and I can only go to work remotely. The area I live in is relatively safe for the time being, but I am ready to work remotely.
Having said that, I have been studying, living and working in Guangzhou for ten years, and I have a special affection for Guangzhou.
I made one in the past two days Demo. I Unitybuilt the entire Guangzhou subway route map and made a third-person camera to follow. With dual joystick control, you can climb the Canton Tower and have a bird's eye view of Guangzhou. Dedicated to my beloved Greater Guangzhou, the effect is as follows:

Note: Today, the large-scale nucleic acid test in Guangzhou was completed in the afternoon. I went home and wrote this article until now ( 2021-6-5 23:44). Come on, Guangzhou, and defeat the epidemic as soon as possible!
Insert picture description here


Insert picture description here


Insert picture description here


Insert picture description here


I will explain the creation process below.

Two, create a project

1. Create a project

The Unityversion I use is 2021.1.7f1c1 (64-bit), select the 3Dtemplate, start with the project name GuangzhouGogo, and click Create.

Insert picture description here

Created successfully,

Insert picture description here

2. Create a directory

To develop a good habit, first standardize the directory structure and create some folders. The directory structure is as follows:

Insert picture description here

RawAssetsThe directory is mainly to store some [raw meat resources] or resources that are dependent on the scene;

Note: For [raw meat resources], please refer to the description of this article I wrote before: "Unity Game Development-Xinfa teaches you to make games (3): 3 ways to load resources"

ResourcesThe directory stores some resources dynamically loaded by code; the
Scenesdirectory stores scene files; the
Scriptsdirectory stores C#code; the
ThirdPartdirectory stores some third-party tools or libraries;

3. Making the subway route map

1. Guangzhou subway map

Let's first look for the Guangzhou subway map. The latest version I found is this:

Insert picture description here


As of June 30, 2020, there are subway lines in Guangzhou 14条and subway stations in the city 213个.
First get this picture into the Unitymiddle,

Insert picture description here

The picture format is set to Sprite (2D and UI),

Insert picture description here


drag the subway map to the scene, adjust the coordinates, rotation, and zoom, as follows: The

Insert picture description here


effect is as follows:

Insert picture description here

2. HexTiles: the basic operation of the six-deformation 3D tile tool

2.1, HexTiles tool download

I want to make the map more stylized, so I found a six-deformed 3Dtile tool:, HexTilesthis tool can be found GitHubon the address: https://github.com/RoryDungan/HexTiles

Insert picture description here
Note: For the 2Dtile tool, please refer to this article I wrote before: "[Unity 2D] Review the classic FC game of red and white consoles, by the way, teach you to quickly build 2D game levels (Tilemap | Scene | Map)"

After downloading, put it into the project ThirdPartdirectory, just keep its Codeand Pluginsfolder, as follows:

Insert picture description here
2.2, create a tile shader

We need to use tools to draw 3Dtiles, we need to make a shader for the tiles first, we first make a green shader, RawAssets/Materials/tilesright-click the menu in the directory Create/Material, create a shader;

Insert picture description here


rename the greenshader to , set the shader's color to Green, and then we don’t want the reflective effect, we can adjust the smoothness to 0;

Insert picture description here


and so on, make a corresponding shader for the color of the subway line~

Insert picture description here
2.3, create a tile container

HierarchyRight-click the menu in the blank space of the view to Hex tile mapcreate a tile container

Insert picture description here


. There will be a HexTileMapcomponent on the tile container, and you can see the corresponding function button. The tiles we will draw later will all be under this Hex tile mapsub-node.

Insert picture description here
2.4, drawing tiles

Make sure that Scenethe Gizmosbutton of the view is activated,

Insert picture description here


click the 添加瓦片button, drag the shader you want to use to the Materialslot,

Insert picture description here


and then hold down the mouse and drag in the scene to draw the tiles.

Insert picture description here
2.5, set the tile height

We want to draw the tiles in the lower layer, which can be adjusted Height offset. For example, I adjusted it to -0.5, by the way, change the shader to white.

Insert picture description here


Now we draw the tiles, we can see that they are drawn on the lower layer of the original tiles, and the edges The connection point will be automatically filled,

Insert picture description here
2.6, erase tiles

We want to erase the drawn tiles, we can click the erase tiles button,

Insert picture description here


and then click the tiles to be erased in the scene,

Insert picture description here
2.7. Tile material drawing: material replacement

Click the material drawing button, then the target material,

Insert picture description here


and then click the tile to be drawn,

Insert picture description here
2.8, large area brush tiles

Above we are a tile brush, we can adjust the size of the brush ( Brush size), for example, I adjusted it to 3,

Insert picture description here

In this way, you can brush the tiles on a large area,

Insert picture description here
2.9. Wipe tiles in a large area

Similarly, we can also erase tiles in a large area,

Insert picture description here
Insert picture description here

3. Build a subway route

Start paving along the subway route, paving and paving, paving and paving, and

Insert picture description here


paving

Insert picture description here


all the subway maps,

Insert picture description here

Four, subway station production

1. Metro station model

Just use a simple column for the site. Create a model Cubethat is

Insert picture description here


elongated and

Insert picture description here


composed of two columns (bottom and top) to form a site.

Insert picture description here

2. Material ball of subway station

Create two shaders as the materials at the bottom and top of the site, and

Insert picture description here


assign the shaders to the upper cylinders.

Insert picture description here

3. Metro station name: TextMeshPro

I used the name of the subway station TextMeshProto display it. Its advantage is 3Dthat the text is very clear when viewed from a close distance in the space.

Note: For TextMeshProthe usage tutorial, please refer to my previous article: "Teach by hand, Unity uses TextMeshPro to display fonts"

Let me talk about the steps below.

3.1. Import font files

Find a font ( TTFformat) you like . For example, I am looking for a free Siyuan font and

Insert picture description here


put it into the Unityproject.

Insert picture description here
3.2, install TextMeshPro

Click the menu Window / Package, open the Package Mangerwindow,

Insert picture description here


Packagesselect Unity Registry, then search textmeshpro, select TextMeshPro, and click the Installbutton. If you have already installed it, there is no Installbutton.

Insert picture description here


After the installation is successful, you can see Windowthat there is an additional TextMeshPromenu in the menu,

Insert picture description here
3.3, make a character set

We need to TextMeshProcreate a character set (a txtfile), put the words we need in this character set file, as follows, TTFcreate a txtfile ( characters.txt) in the same level directory, and

Insert picture description here


put all the subway station names in Guangzhou In this characters.txtfile,

Insert picture description here
3.4, make Font Asset

Click on the menu Window / TextMeshPro / Font Asset Creator

Insert picture description here


for the first time to open the following window will pop up, click on the Import TMP Essentialsbutton

Insert picture description here


in the Font Asset Creatorwindow, set Source Font Fileour font TTFfile, set Character Setto Characters from Fileset Character Filefor our character set file characters.txt, and finally click on Generate Font Atlasthe button

Insert picture description here


at this time will generate a texture, we click Savesave,

Insert picture description here


Save to the RawAssets/Fontsdirectory,

Insert picture description here


as follows ( font SDF.asset)

Insert picture description here
3.5, display the name of the subway station

In the subway station to create a space object node, rename name,

Insert picture description here


to this namenode to add TextMeshPro - Textcomponents,

Insert picture description here


in Text Inputthe name entered in the subway station, for example 珠江新城,
is set Font Assetfor us to generate the above font SDF,

Insert picture description here


and the effect is as follows:

Insert picture description here


set about the font size, set about their Way,

Insert picture description here


set the coordinates and display area size, the

Insert picture description here


effect is as follows:

Insert picture description here


In order to be able to see the name of the subway station on all four sides, we copy the other three copies,

Insert picture description here


adjust the coordinates and rotation angle, the effect is as follows:

Insert picture description here
3.6, save site presets

养成好习惯,需要重复使用的物体(模板)我们最好保存成预设,将其保存到RawAssets/Prefabs目录中,如下:

Insert picture description here

4、排放地铁站点

按照地铁线路,依次摆放地铁站点,

Insert picture description here


摆呀摆,

Insert picture description here


终于把地铁站全部弄好了,

Insert picture description here


把站点按地铁路线收纳好,方便管理,

Insert picture description here

5、地平面

创建一个Plan作为地平面,这样影子可以投射到地面上,

Insert picture description here


给地面创建一个材质球,材质球赋值给Plan

Insert picture description here


调整材质球颜色,

Insert picture description here

五、导航系统:NevMesh烘焙

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

1、设置烘焙对象为Static

因为NevMesh只对场景中的静态对象进行烘焙,所以我们需要先把地铁路线设置为Static的。
选择HexTileMap节点,将其设置为Static

Insert picture description here


点击Yes, change children,即所有的子节点都设置为Static

Insert picture description here

2、NevMesh烘焙

点击菜单Window / AI / Navigation

Insert picture description here


Navigation窗口中,点击Bake标签页,
调节一下Agent Radius,因为我们的地铁路面比较窄,所以这里的Agent Radius需要调小一点,
最后点击Bake按钮即可,

Insert picture description here


烘焙成功后,可以看到路面上出现了蓝色的网格,

Insert picture description here


同时,在场景文件目录中,会看到生成了一个与场景同名的文件夹,里面的NavMesh.asset保存的就是场景的导航烘焙信息,

Insert picture description here

六、摇杆制作

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

1、摇杆图片

摇杆的图片很简单,一个圆就可以了,

Insert picture description here

2、Canvas与UICamera

创建一个Canvas,作为后面UI的父节点,

Insert picture description here
Insert picture description here

在创建一个Camera来专门渲染Canvas

Insert picture description here

将其重命名为UICamera

Insert picture description here

设置UICameraClear FlagsDepth only,并设置Culling MaskUI,这样它就只会渲染UI层,把Projection设置为Orthographic(正交),

Insert picture description here

接着把CanvasRender Mode(渲染模式)改为Screen Space - Camera(即由摄像机来渲染),然后把Render Camera设置为刚刚的UICamera
接着再设置下分辨率适配,把Canvas Scale组件的UI Scale Mode设置为Scale with Screen Size,把分辨率设置为1280, 720

Insert picture description here


另外,因为UI已交给UICamera来渲染,所以Main Camera不需要再渲染UI层了,把Main CameraCulling MaskUI勾选去掉,

Insert picture description here

3、摇杆UI制作

Canvas节点上右键点击菜单UI / Panel,创建一个Panel

Insert picture description here


Image组件禁用掉,因为我们不需要Panel显示出来,

Insert picture description here


Panel下创建一个Image,重命名为leftJointedArm,作为左摇杆的父节点,

Insert picture description here

设置它的锚点为bottom - left,即屏幕左下角,调整坐标和宽高,

Insert picture description here


像这样子,

Insert picture description here

把它的Coloralpha调为0,因为我们只需要利用它的区域来检测触碰,我们不需要肉眼看见它,

Insert picture description here


接着在它的子节点下创建两个Image,分别命名为bgcenter

Insert picture description here


它们的Source Image都设置为摇杆的图片资源,

Insert picture description here


分别调整下bgcenter的大小和颜色透明度,效果如下:

Insert picture description here


同理再做一个右摇杆,

Insert picture description here


效果如下:

Insert picture description here

3、摇杆逻辑代码

UnityUGUI提供了ScrollRect组件,非常适合用来制作摇杆,我们继承ScrollRect然后实现OnDragOnEndDrag方法,可以很方便地获取到摇杆的遥控数据,另外,为了检测区域点击,我们再实现IPointerDownHandler接口。

Insert picture description here

创建摇杆脚本JointedArm.cs,

Insert picture description here


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

Insert picture description here

5、摇杆测试

运行Unity,摇杆测试效果如下:

Insert picture description here

七、角色、动画与控制

1、角色模型下载

主角我在AssetStore上找到了一个心仪的模型,推荐给大家,
AssetStore地址:https://assetstore.unity.com/packages/3d/characters/humanoids/sci-fi/stylized-astronaut-114298

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

将模型下载导入Unity中,

Insert picture description here

2、动画控制器

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

打开角色的动画控制器文件CharacterController

Insert picture description here


可以看到,两个动作,一个idle(站立)一个Run(跑),

Insert picture description here


Parameters(参数)里面有一个AnimationPar参数,这个参数就是用来控制站立与跑着两个动画的过渡条件的,

Insert picture description here


Run过渡到Idle的条件是AnimationPar等于1

Insert picture description here


Idle过渡到Run的条件是AnimationPar等于0

Insert picture description here

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

// public Animator anim; 

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

3、主角出场

在场景中创建一个空物体,重命名为Player

Insert picture description here


把主角模型拖到Player子节点中,把主角模型也命名为Player

Insert picture description here


这样,场景中出现了我们的主角了,

Insert picture description here


因为主角需要在地铁路线上跑,我们用了导航系统NevMesh,所以主角需要挂NevMeshAgent组件,

Insert picture description here


调节Radius(半径)与Height(高度)使之与主角模型匹配,

Insert picture description here


Insert picture description here

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父节点上,赋值对应的变量,

Insert picture description here


为了方便管理,我们再封装一个游戏管理器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,相当于把相对于摄像机的局部坐标转换为世界坐标。

这样我们的摇杆就可以控制主角移动了,不过现在摄像机并不会跟着主角移动,所以下一步我们就来做摄像机跟随吧~

Insert picture description here

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

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挂到摄像机上,赋值对应的变量,这样摄像机就可以跟着主角移动了,

Insert picture description here

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赋值对应的变量,

Insert picture description here


右摇杆效果如下:

Insert picture description here

九、天空盒SkyBox与环境雾Fog

1、天空盒:SkyBox

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

Insert picture description here


下载下来,导入到Unity工程中,

Insert picture description here

只需要把天空盒的材质球拖到场景中即可生效,或者菜单Window / Rendering / Lighting

Insert picture description here


点击Environment,然后设置Skybox Material为对应的天空盒材质球即可,

Insert picture description here


我喜欢Sunset(日落)的天空效果,加上之后效果如下,是不是一下子就唯美了很多:

Insert picture description here


我们也可以通过代码设置天空盒,例:

RenderSettings.skybox = skyMat;

可以再欣赏下其他不同天空盒的效果:
清晨:

Insert picture description here


中午:

Insert picture description here

晚霞:

Insert picture description here


午夜:

Insert picture description here

2、环境雾:Fog

不同的天空盒,需要搭配不同颜色的环境雾效。
Lighting窗口的Environment标签页中即可开启环境雾,如下:
我们可以设置雾效的颜色、密度等参数。

Insert picture description here


我们可以对比下 没雾效有雾效的区别:

Insert picture description here

十、登顶广州塔

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

1、广州塔模型下载

我找到了广州塔的模型,模型下载地址:https://www.3dxy.com/3dmodel/148664.html
下载FBX格式的,导入Unity工程的RawAssets/Models目录中,

Insert picture description here


Insert picture description here

2、广州塔放入场景中

把广州塔模型放入场景中,调整坐标到对应的位置,调整模型缩放,效果如下:

Insert picture description here

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

我用了触发器来检测主角是否到了广州塔底部,在广州塔底部创建一个物体,并挂上BoxCollider组件,调整碰撞体大小,如下:

Insert picture description here


Insert picture description here


把碰撞体的Is Trigger勾选上,这样它就是一个触发器了,

Insert picture description here


想要检测主角是否进入了触发器中,还需要给主角也挂上碰撞体(Collider)和刚体(Rigidbody),给主角安排上,因为我们不需要模拟重力,所以Use Gravity不要勾选,

Insert picture description here


Insert picture description here


调整碰撞体大小的时候,如果看不清楚,可以把Scene视图的Shading Mode设置为Wireframe(线框),

Insert picture description here


这样就可以比较清楚得看到碰撞体了,

Insert picture description here


接着写个广州塔触发器脚本CantonTowerTrigger.cs

using UnityEngine;

/// <summary>
/// 广州塔触发器
/// </summary>
public class CantonTowerTrigger : MonoBehaviour
{
    private void OnTriggerEnter(Collider other)
    {
		// 进入了触发器
    }

    private void OnTriggerExit(Collider other)
    {
		// 离开了触发器
    }
	
	// ...
}

CantonTowerTrigger.cs脚本挂到触发器上,

Insert picture description here


画成图是这样子:

Insert picture description here

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

主角进入广州塔触发器时,弹出UI界面询问是否要上广州塔。
我们先做个询问的UI界面,

Insert picture description here


层级结构如下:

Insert picture description here


把界面保存为预设,放在Resources目录中,这样我们就可以通过Resources.Load来加载界面资源了。
封装一个界面管理器,方便界面的显示与关闭,封装一个界面基类BaseUIPanel,所有的界面都继承这个基类,画成关系图是这样子,

Insert picture description here


界面管理器和界面基类代码如下:

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();
        });
    }
}

把脚本挂到界面根节点上,并赋值按钮对象,

Insert picture description here


回到广州塔触发器中,补上显示界面的调用,

// CantonTowerTrigger.cs

// 广州塔触发器

private void OnTriggerEnter(Collider other)
{
    UIPanelMgr.instance.ShowPanel("GoCantonTowerPanel");
}

private void OnTriggerExit(Collider other)
{
    UIPanelMgr.instance.HidePanel("GoCantonTowerPanel");
}

这样,我们就实现了经过广州塔底部地时候弹出询问框的功能了,效果如下:

Insert picture description here

5、登上塔顶

点击前往按钮,主角要移动到塔顶,我们可以事先在塔顶创建一个空物体,作为一个定位,主角瞬间移动到这个位置即可,

Insert picture description here

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

Insert picture description here


界面逻辑中如果直接操作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;
}

效果如下:

Insert picture description here

6、塔顶调整摄像机距离

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

// CameraControler.cs

private void OnEventChangeCamDistance(params object[] args)
{
    var offset = (float)args[0];
    distance = originalDistance + offset * 20f;
}
Insert picture description here

十一、功能补充

1、粒子系统:烟花效果

用粒子系统做个烟花效果,

Insert picture description here


用到的粒子图片如下,比较简单,可以自行用PhotoShop制作:

Insert picture description here


粒子参数如下:

Insert picture description here


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

在场景中克隆几个烟花粒子,用于循环复用,

Insert picture description here


写个烟花脚本,实现随机坐标播放粒子的功能,

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));
        }
    }
}

效果如下:

Insert picture description here

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

广州地铁站的经典背景音乐:安妮的仙境,导入到工程中,

Insert picture description here


GameMgr挂上音源Audio Source组件,赋值Audio Clip安妮的仙境,勾选Play On Awake,这样一启动就会自动播放,勾选Loop,这样背景音乐就可以循环播放了,

Insert picture description here

3、彩蛋:天空盒道具

我把天空盒做成了道具,碰到道具可以动态切换天空盒,原理也是用的触发器,

Insert picture description here

十二、工程源码

The source code of this project has been uploaded CodeChina, and interested students can download and study by themselves.
Address: https://codechina.csdn.net/linxinfa/GuangzhouGogo
Note: I am using Unityversion:Unity 2021.1.7f1c1 (64-bit) .

Insert picture description here


Note: This project is for learning purposes only, and cannot be used for commercial purposes without authorization!

Insert picture description here