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

Posted 林新发

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了游戏开发创新上班通勤时间太长,做一个任意门,告别地铁与塞车(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
    {
 

以上是关于游戏开发创新上班通勤时间太长,做一个任意门,告别地铁与塞车(Unity | 建模 | ShaderGraph | 摇杆 | 角色控制)的主要内容,如果未能解决你的问题,请参考以下文章

使用React制作一个可配置的页面生成器[0]

告别砸钱深挖潜力 动漫音频游戏重构IP新玩法

创新产品的需求分析:未来的图书会是什么样子?

上班划水,给男朋友做个数字炸弹游戏

上班划水,给女朋友做个猜数字游戏

Build 2016: 发布明天的云创新来服务今天的开发者