【游戏开发创新】上班通勤时间太长,做一个任意门,告别地铁与塞车(Unity | 建模 | ShaderGraph | 摇杆 | 角色控制)

一、前言

嗨,大家好,我是新发。
现在每天上班的通勤时间是一个多小时,加上下班的通勤时间,每天在路上就是两个半小时,在广州早高峰坐地铁简直要命,这里我不得不吐槽一下广州21号线,人流量超多,发车频率还低,导致每趟都堆积特别多人,每次都要等至少两三趟才能挤上,而且都好暴力,太疯狂了,这样真的容易出事,每次出地铁心里都在重复一句话:下次不搭21号线了!
还好有热心同事经常开车搭我上下班,老麻烦别人也很不好意思,不过,搭了几次车又感觉脸皮厚了-_-

要是有一个任意门可以连接家门口和公司门口就好了,现实中没有,那就在虚拟世界里做一个吧~

二、最终效果

我做的Demo最终效果如下,
家门口:
请添加图片描述
从家门口穿过传送门:
请添加图片描述
从公司经过传送门回家:
请添加图片描述
下面,我就来讲讲我的制作过程吧~

三、Blender建模

看过我前面两篇文章的同学应该知道,我最近自学了Blender建模,感兴趣的同学可以看下我之前两篇文章,
【游戏开发创新】当我学了Blender 建模,自制3D电脑桌面,回收站爆发了,把我做的模型都吐了出来(Blender | Unity | FBX)
【游戏开发创新】自学Blender建模,自制孔明灯,在Unity中点亮整个星空,愿新年,胜旧年(Unity | 建模 | 粒子系统 | 预设)

1、Blender下载安装

Blender官网:https://www.blender.org/
Blender中国社区:https://www.blendercn.org/
Blender中文手册:https://docs.blender.org/manual/zh-hans/2.79/about/introduction.html

我使用的Blender版本是2.93.4
在这里插入图片描述

注:关于Blender的教程网上蛮多的,这里我就不过多讲了,掌握基本操作和快捷键,很快就可以上手建模啦~

2、传送门建模

传送门最终模型如下:
请添加图片描述

3、房子建模

房子最终模型如下:
请添加图片描述

4、公司大楼建模

公司大楼最终模型如下:
请添加图片描述

5、文字模型

再做一些文字模型,
请添加图片描述

四、Blender导出FBX

Blender中点击菜单File / Export / FBX,将模型导出成FBX格式,
在这里插入图片描述
如下:
在这里插入图片描述

五、FBX导入Unity中

FBX文件导入到Unity工程中,
在这里插入图片描述
把模型放入场景中,现在都是默认的材质,所以都是灰白色的,不着急,下面我们就来做材质~
在这里插入图片描述

六、制作材质

1、传送门

材质球使用的shader我打算使用ShaderGraph来制作,我之前写过一篇ShaderGraph的文章:《ShaderGraph使用教程与各种特效案例:Unity2020》,推荐先看下这篇文章。

1.1、ShaderGraph准备

安装Universal RP插件,
在这里插入图片描述
Project视图中右键鼠标,点击菜单Create / Rendering / Universal Render Pipeline / Pipeline Asset (Forward Renderer)
在这里插入图片描述
创建UniversalRenderPipelineAsset,如下
在这里插入图片描述
点击菜单Edit / Project Settings...,打开Project Settings窗口,选择Graphics分页,把UniversalRenderPipelineAsset拖到Scriptable Render Pipeline Settings中,
在这里插入图片描述

1.2、创建ShaderGraph

Project视图中右键鼠标,点击菜单Create / Shader / Universal Render Pipeline / Lit Shader Graph,创建一个PBRShaderGraph
在这里插入图片描述
重命名为PortalCenter,作为传送门中心的shader
在这里插入图片描述

1.3、编辑ShaderGraph

双击PortalCenter打开编辑器,编辑节点如下,核心就是对泰森多边形(Voronio)进行UV旋涡旋转(Twirl)。
在这里插入图片描述

1.4、材质球使用ShaderGraph

创建一个材质球Material, 重命名为PortalCenter
在这里插入图片描述
设置材质球的shader为刚刚的ShaderGraph文件,
在这里插入图片描述

1.5、模型引用材质球

将材质球赋值给传送门模型,
在这里插入图片描述
效果如下,
请添加图片描述

2、房子材质球

同理,制作房子的材质,
在这里插入图片描述
效果如下,
在这里插入图片描述

3、公司大楼材质球

制作房子的材质,
在这里插入图片描述

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

七、地面

1、广州地铁图

找一张广州最新的地铁地图,我找到的是下面这张,
请添加图片描述

2、地面(Plane)

在场景中创建一个Plane平面,制作材质并引用这张图片,
在这里插入图片描述
在这里插入图片描述
效果如下:
请添加图片描述

八、主角

1、主角资源

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

将模型下载导入Unity中,
请添加图片描述

2、双摇杆制作

主角的移动和摄像头的角度旋转我想通过摇杆来控制,我们做一个双摇杆功能。
Canvas节点上右键点击菜单UI / Panel,创建一个Panel
在这里插入图片描述
Image组件禁用掉,因为我们不需要Panel显示出来,
在这里插入图片描述
Panel下创建一个Image,重命名为leftJointedArm,作为左摇杆的父节点,
在这里插入图片描述
设置它的锚点为bottom - left,即屏幕左下角,调整坐标和宽高,
在这里插入图片描述
像这样子,
在这里插入图片描述
把它的Coloralpha调为0,因为我们只需要利用它的区域来检测触碰,我们不需要肉眼看见它,
在这里插入图片描述
接着在它的子节点下创建两个Image,分别命名为bgcenter
在这里插入图片描述
它们的Source Image都设置为摇杆的图片资源,
在这里插入图片描述
分别调整下bgcenter的大小和颜色透明度,效果如下:
在这里插入图片描述
同理再做一个右摇杆,
在这里插入图片描述
效果如下:
在这里插入图片描述
接下来需要给摇杆加上逻辑,UnityUGUI提供了ScrollRect组件,非常适合用来制作摇杆,我们继承ScrollRect然后实现OnDragOnEndDrag方法,可以很方便地获取到摇杆的遥控数据,另外,为了检测区域点击,我们再实现IPointerDownHandler接口。

创建摇杆脚本JointedArm.cs,代码如下:

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using System;

/// <summary>
/// 摇杆
/// </summary>
public class JointedArm : ScrollRect, IPointerDownHandler
{
    public Action<Vector2> onDragCb;
    public Action onStopCb;

    protected float radius = 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();
        //计算摇杆块的半径
        radius = bgTrans.sizeDelta.x * 0.5f;
    }

    public override void OnDrag(PointerEventData eventData)
    {
        base.OnDrag(eventData);
        var contentPostion = this.content.anchoredPosition;
        if (contentPostion.magnitude > radius)
        {
            contentPostion = contentPostion.normalized * radius;
            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);
    }
}

JointedArm.cs分别挂到leftJointedArmrightJointedArm上,赋值对应的center
在这里插入图片描述
运行Unity,摇杆测试效果如下:
请添加图片描述
接下来我们要实现左摇杆控制主角移动并播放跑的动画,右摇杆控制摄像机角度旋转。

3、主角移动控制
3.1、动画控制

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

打开角色的动画控制器文件CharacterController
在这里插入图片描述
可以看到,两个动作,一个idle(站立)一个Run(跑),
在这里插入图片描述
Parameters(参数)里面有一个AnimationPar参数,这个参数就是用来控制站立与跑着两个动画的过渡条件的,
在这里插入图片描述
Idle过渡到Run的条件是AnimationPar等于1

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

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

// public Animator anim; 

// 站立 -> 跑
anim.SetInteger("AnimationPar", 1);
// 跑 -> 站立
anim.SetInteger("AnimationPar", 0);
3.2、移动控制

主角的移动控制包括坐标和角度的变化,当摇杆向左滑,主角向左移动,同时主角的朝向也跟着转向左边。
移动我们可以设置transfromposition属性来实现,转向我们可以设置transfromforward属性来实现,例:

// Vector3 moveDirection; 摇杆向量
// float speed; 移动速度
// float turnSpeed; 转向速度

transform.position += moveDirection * speed * Time.deltaTime;
transform.forward = Vector3.Lerp(transform.forward, moveDirection, turnSpeed * Time.deltaTime);
3.3、主角脚本代码

综上,我们封装一个主角脚本Player.cs,代码如下:

// Player.cs
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.AI;

/// <summary>
/// 主角脚本
/// </summary>
public class Player : MonoBehaviour
{
    // 移动速度
    public float speed = 1f;
    // 转向速度
    public float turnSpeed = 20f;

    public Animator anim;
    // 跟节点
    public Transform rootTrans;
    // 模型节点
    public Transform modelTrans;
    // 导航Agent
    public NavMeshAgent navAgent;
    
    // 是否在移动
    private bool moving = false;
    // 移动向量
    private Vector3 moveDirection = Vector3.zero;
    // 是否可移动
    private bool canMove = true;

    private void Awake() {
        companyPosParticle.Stop();
        homePosParticle.Stop();
    }

    void Update()
    {
        if (canMove && moving)
        {
            anim.SetInteger("AnimationPar", 1);

            rootTrans.position += moveDirection * speed * Time.deltaTime;
            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.cs脚本挂到主角物体上,在Inspector面板赋值脚本的成员变量,
在这里插入图片描述
我们再创建一个GameMgr.cs脚本来调度,

// GameMgr.cs

public Player player;
public JointedArm leftJointedArm;
private Transform playerTrans;
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(); };

创建一个空物体重命名为GameMgr,把GameMgr.cs挂到这个物体上,并在Inspector面板中赋值脚本的成员变量。

3.4、主角移动测试

运行测试效果如下:
请添加图片描述

4、摄像机跟随

我们创建一个CameraControler.cs脚本,实现摄像机跟随主角的逻辑,代码如下:

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

/// <summary>
/// 摄像机控制器
/// </summary>
public class CameraControler : MonoBehaviour
{
    // 限制摄像机角度范围
    private const float Y_ANGLE_MIN = 10f;
    private const float Y_ANGLE_MAX = 50.0f;

    // 摄像机看向的物体
    public Transform lookAt;
    // 摄像机Transform
    public Transform camTransform;
    // 摄像机距离目标物体的距离
    public float distance = 1.2f;
    // 原始距离
    private float originalDistance;
    // 旋转速度
    public float rotateSpeed = 0.01f;

    public float currentX = 0.0f;
    public float currentY = 20.0f;


    private void Start()
    {
        camTransform = transform;
        originalDistance = distance;
    }
    
    private void Update()
    {
        if (rotating)
        {
            currentX += rotateDelta.x;
            currentY += rotateDelta.y;
            currentY = Mathf.Clamp(currentY, Y_ANGLE_MIN, Y_ANGLE_MAX);
        }
    }

    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脚本挂到主摄像机上,在Inspector面板赋值脚本的成员变量,运行Unity,可以看到有跟随效果了,
请添加图片描述

5、摄像头角度控制

我们在上面的CameraControler.cs脚本中添加两个方法,如下:

// CameraControler.cs

private bool rotating;
private Vector2 rotateDelta;
    
public void RotateCam(Vector2 delta)
{
    rotateDelta = delta * rotateSpeed;
    rotating = true;
}

public void StopRotate()
{
    rotating = false;
}

然后在GameMgr.cs中添加右摇杆的调度,

// GameMgr.cs

// 右摇杆 ------------------------------------------
rightJointedArm.onDragCb = (direction) =>
{
    camCtrler.RotateCam(direction);
};
rightJointedArm.onStopCb = () => { camCtrler.StopRotate(); };

运行Unity,可以控制摄像头角度旋转了,
请添加图片描述

九、导航烘焙

如果我们想要实现点击地图某个位置,让主角走到目标点,可以使用Unity的寻路导航功能,另外,这个功能也可以限制主角的移动区域,为了防止主角走到地图外面,我们使用Navigation对场景进行导航烘焙。
首先选中地面,把地面设置为Static
在这里插入图片描述
然后点击菜单Window / AI / Navigation
在这里插入图片描述
点击Bake分页,点击Bake按钮,
在这里插入图片描述
看到地面蒙上了一层蓝色的网,就说明烘焙成功了,
请添加图片描述
你可以在场景文件所在目录中看到它生成了一个NavMesh文件,
在这里插入图片描述
另外,我们需要给主角添加NavMeshAgent组件,并根据主角模型大小设置RadiusHeight
在这里插入图片描述
如下:
请添加图片描述
运行Unity,测试一下移动到地面边界的效果,
请添加图片描述

十、传送门触发器

传送门传送,我使用了触发器,检测主角是否通过了传送门,然后出发传送逻辑。
给传送门的前后添加两个碰撞体,并勾选Is Trigger
在这里插入图片描述
如下:
请添加图片描述
分别在两个冲送门位置添加一个标记传送目标位置的空物体,
在这里插入图片描述
在这里插入图片描述

给主角脚本Player.cs添加传送的逻辑,

// Player.cs


public Transform homePos;
public Transform companyPos;
private string lastTrigger;

private void OnTriggerEnter(Collider other) 
{
    if("trigger1" == other.name || "trigger3" == other.name)        
    {
        lastTrigger = other.name;
    }
    else
    {
        if("trigger1" == lastTrigger && "trigger2" == other.name)
        {
             // 执行传送,从公司到家
            rootTrans.position = homePos.position;
        }
        else if("trigger3" == lastTrigger && "trigger4" == other.name)
        {
             // 执行传送,从家到公司
            rootTrans.position = companyPos.position;
        }
    }
}

这样,当主角传过传送门的时候就会按顺序触发触发器,最终指向传送逻辑。

十一、特效

传送时加多一个特效吧,使用PhotoShop画一个菱形,如下:
在这里插入图片描述

再画个菱形边框,
在这里插入图片描述
使用粒子系统制作特效,效果如下:
请添加图片描述
主要利用的是粒子系统的color over LifttimeSize over Lifetime来达到上面的效果,
在这里插入图片描述
关于粒子系统的相关教程,可以参见我之前写的这几篇文章:
【游戏开发实战】权游红袍女在火中看到了什么,我看到了…(Unity | 粒子系统 | 火焰特效 | ParticleSystem | 手把手制作)
【游戏开发实战】Unity使用ShaderGraph配合粒子系统,制作子弹拖尾特效(Fate/stay night金闪闪的大招效果)
【学Unity的猫】——第十五章:Unity粒子系统ParticleSystem,下雪啦下雪啦
【游戏开发实战】手把手教你使用Unity制作一个飞机喷射火焰尾气的粒子效果

十二、最终效果

家门口:
请添加图片描述
从家门口穿过传送门:
请添加图片描述
从公司经过传送门回家:
请添加图片描述

十三、工程源码

本工程我已上传到CODE CHINA,感兴趣的同学可自行下载学习。
地址:https://codechina.csdn.net/linxinfa/UnityPortalDemo
注:我使用的Unity版本为Unity 2021.1.9f1c1 (64-bit)
在这里插入图片描述

十四、完毕

好了,就到这里吧,最后希望广州地铁21号线高峰期可以提高发车频率,不然真的很痛苦。
我是林新发:https://blog.csdn.net/linxinfa
原创不易,若转载请注明出处,感谢大家~
喜欢我的可以点赞、关注、收藏,如果有什么技术上的疑问,欢迎留言或私信,我们下期见~

评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林新发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值