3D游戏实战开发 | 青训营笔记

104 阅读7分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 16 天

R-C.jfif

课程笔记

3D实体搭建

在游戏中搭建静态物体,物体模型是游戏中的关键部分,模型的好坏关乎着游戏游玩时的视觉效果。
通过Unity3D我们可以快速上手建造一些普通的常用模型。

3D实体

3D 游戏是由一个个具有形状的实体组成的。每个实体在空间中存在于特定的位置,有特定的姿态(旋转角度)。

image.png

3D实体的位姿态 (Transform)

  • 位置 Position(x,y,z),是一个三维向量坐标
  • 旋转 Rotation(x,y,z),是一个三维向量坐标
  • 缩放 Scale(x,y,z), 是一个三维向量坐标

在Unity中,绝大部分情况下,是先缩放,后旋转,最后平移。

image.png

3D实体的创建

  • 通过加载 3D 模型创建,如 fbx、gltf、obj。
  • 通过组合参数化的基本几何体创建。

image.png

3D实体的绘制

  • 材质
  • 颜色
  • 纹理

image.png

3D实体的三个特性我们可以在Inspector面板调整和设置。

预制作

  • 将游戏对象保存在工程中,在需要的时候创建出来,这就是预制体 (prefab)。
  • 预制体存储着一个游戏对象,包括游戏对象的所有组件以及其下的所有子游戏对象 。

image.png

上图就是一个敌机模型预制体实例。

相机、光照、天空盒

相机

相机的常用参数:

  • Clear Flag。
  • 背景颜色。
  • Culling Mask。
  • 投影(透视、正交)

image.png

透视投影 (Perspective) 与正交投影 (Orthographic)

我们可以通过如下示意图来区分这两种投影方式。

image.png

光照

关于光照可以调整的参数有:

  • 类型:点光源、平行光、聚光灯、面积光。
  • 颜色。
  • 强度。
  • 阴影类型。

image.png

Inspector面板我们可以设置Light栏中的内容来调整光照参数。

天空盒

天空盒开启方式:

  • 相机的清除标志设为“天空盒”。
  • 窗口-渲染-照明设置。
  • 环境-天空盒材质。

image.png

控制与碰撞

实时游戏时序

image.png

上图为实时游戏时序图,在这里我们可以看到游戏在每个时间段所执行的操作与进程。

基本流程为:

初始化->物理模块->输入->游戏逻辑->渲染->停顿->销毁

其中除了头部的初始化和尾部的销毁,剩余的部分即游戏中的1帧

添加控制逻辑

一个实例---主角飞船添加逻辑控制:

  • 添加刚体组件。
    • Add Component > Physics > Rigidbody。
    • Use Gravity 设置为 false,忽略重力的影响 。
    • isKinematic 设置为 true,飞船通过脚本而非力影响运动属性 。
    • 设置 Constraints,冻结 z 轴位移以及 ×、y、z 轴旋转。
  • 添加自定义脚本 。
    • Add Component > New Script

脚本

MonoBehaviour 是一个基类,所有 Unity 脚本都派生自该类。

  • Start()

    在首次调用任何Update方法之前在帧上调用Start。

  • Update()

    每帧调用Update。

  • FixedUpdate()

    用于物理计算且独立于帧率。具有物理系统的频率;每个固定帧率帧调用该函数。

  • LateUpdate() LateUpdate 在每一次调用 Update 函数后调用。

  • OnGUI() 系统调用 OnGUI 来渲染和处理 GUI 事件。OnGUI 实现可以每帧调用多次( 每个事件调用一次)。

  • OnDisable()

    该函数在对象被禁用时调用。对象销毁时也会调用该函数。

  • OnEnable()

    该函数在对象变为启用和激活状态时调用。

实例中主角飞船脚本代码如下:

using System.collections;
using System.collections.Generic;using UnityEngine;
public class Hero : MonoBehaviour
{

static public Hero S; //单例对象
[Header("set in Inspector")]
public float speed = 30;
//以下字段用来控制飞船的运动
public float ro11Mult = -45;
public float pitchMult = 30;
[Header( "set Dynamically")]
public float shieldLevel = 1;
//Start is called before the first frame update
void Start(
{
    if(S==null){
    s = this;//设置单例对象
    } else {
         Debug.LogError("尝试重复设置Hero 实例");
         }
}
// Update is called once per frame
void Update()
{
    //从 Input(用户输入)类中获取信息
    float xAxis = Input.GetAxis( "Horizontal");
    float yAxis = Input.GetAxis("Vertical");
    //基于获取的水平轴和竖直轴信息修改transorm. position
    Vector3 pos = transform.position;
    pos.x+=xAxis *speed*Time.deltaTime;
    pos.y += yAxis *speed *Time.deltaTime;
    transform. position= pos;
    //位置变化时让飞船旋转—个角度,让飞船更有动感
    transform.rotation = Quaternion.Euler(yAxis*pitchMult,xAxis*rollMult,0);
    }
}

添加敌机也同理:

  • 为每架敌机预制体添加一个刚体。

    • 选中敌机预制体,在菜单栏执行Component > Physics > Rigidbody
    • 在新添加的刚体组件中,将Use Gravity设置为 false。
    • 将isKinematic设置为true。
    • 打开Constraints旁边的三角形展开按钮,冻结Z轴的坐标和XYZ轴的旋转。
  • 建立敌机的脚本Enemey.cs。

  • 为每架敌机预制体均添加脚本Enemy.cs。

设置标签、图层和物理规则

游戏中存在不同类型的游戏对象,它们需要放置在不同的图层中,并与其他游戏对象发生不同的交互。

  • 主角飞船(Hero):会与敌机、敌机炮弹、升级道具碰撞,但不会与主角飞船的炮弹相碰撞。
  • 主角飞船的炮弹(ProjectileHero):与敌机、敌机的炮弹相碰撞。
  • 敌机(Enemy):与主角飞船和主角飞船的炮弹相碰撞。
  • 敌机的炮弹(ProjectileEnemy):只与主角飞船相碰撞。
  • 升级道具(PowerUp):只与主角飞船相碰撞。

标签和图层管理器

  • 菜单栏中执行Edit > Project Settings > Tags and Layers命令。
  • 打开Tags左侧的三角形展开按钮。单击标签下方的+符号并输入标签名称。
  • 单击Layers旁边的三角形展开按钮从 User Layer 8开始,依次输入图层名称。

image.png

image.png

物理管理器

菜单栏中执行Edit > Project Settings > Physics命令。

image.png

为游戏对象指定合适的图层

  • 在层级面板中选中_Hero,然后在检视面板中从Layer下拉菜单中选择"Hero"选项。Unity 会询问是否将_Hero的子对象也指定到该图层上,选择"Yes, changechildren”选项。
  • 在检视面板中,在Tag 下拉菜单中选择Hero选项,为_Hero设置标签。不需要修改Hero子对象的标签。
  • 从项目面板中选择这5个敌机预设,设置图层为Enemy。如果出现提示,同样选择"Yes, change children”选项。
  • 设置每个敌机预设的标签为Enemy。不需要修改它们的子对象的标签。

碰撞

  • 为主角飞船和敌机添加碰撞体。球体碰撞检测的效率较高。我们将主角飞船与敌机飞船及其子组件上已有的碰撞盒去掉,每个对象的母体上Add Component > Sphere Collider。

image.png

  • 在Hero类中添加OnTriggerEnter函数。
void OnTriggerEnter(Collider other) {
    //可以用下面这行代码在Console窗口输出碰撞对象的名称
    // print("触发碰撞事件:" + other.gameObject.name);
    if(other.tag == "Enemy"){
        Destroy(this.gameObject);
    }
}

玩法逻辑与UI

为主角飞船增加射击功能

  • 新建一个新的预制体,命名为ProjectileHero。模型为一个立方体,Position与Rotation 均为[0,0,0],Scale为[0.25,1,0.5]。保留默认的 Box Collider。Box Collider的 Size.z设置为10.
  • 创建一个名为 Mat_Projectile的新材质,将着色器指定为ProtoTools > UnlitAlpha,并将新材质应用到 ProjectileHero 上。
  • 为ProjectileHero游戏对象添加一个新的刚体组件,设置如下:
    • Use Gravity为 false
    • isKinematic为 false
    • Collision Detection为 Continuous
    • Constraints冻结Z坐标与X、Y、Z旋转轴
  • Tag和Layer均设置为ProjectileHero。

按下空格后实例化新炮弹

public class Hero : MonoBehaviour
{
    .......
    public GameObject projectilePrefab;
    public float projectileSpeed = 40;
    .......
    void Update()
    {
    .......
    //按下空格后飞船开火
    if (Lnput.GetKeyDown(KeyCode.Space)){
        TempFire();
        }
    }
    void TempFire(){
    GameObject projGO = lnstantiate<GameObject>(projectilePrefab);
    projGO.transform.position = transform.position;
    Rigidbody rigidB = projGo.GetComponent<Rigidbody>0;
    rigidB.velocity = Vector3.up *projectileSpeed;
    }
    .......
}

屏幕右上显示计分板

  • 在菜单栏中执行GameObject > Ul>Text命令。
    • Canvas:匹配游戏面板尺寸的画布。
    • EventSystem:用于运转按钮、滚动条等交互元素。
  • 选中Text对象,修改名称为Score。
  • 设置Text对象的Anchors、Pivot、Pos、Width、Hight、Text、Font Style、Font Size、Color等显示属性,如右图所示。

image.png

知识点总结

  • 学习在Unity中创建实体,给实体设置位姿、材质、刚体、脚本。
  • 学会配置相机、光、天空盒。
  • 理解实时游戏update的时序机制。
  • 通过Input.GetAxis()与lnputManager监听玩家输入。
  • 用Instantiate()动态生成游戏实体实例。
  • 从Camera.main获得相机参数,设置场景中的物体在画面中的位置。
  • 利用标签、图层管理器区分实体种类并设置物理规则。
  • 通过Collider组件与OnTrigger函数添加碰撞事件。
  • 通过图形用户界面(GUI)管理游戏界面。