无苦集灭道 无智亦无得|

园龄:粉丝:关注:

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

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

实例项目:太空飞船大战

0x1 3D 实体搭建

  1. 3D 实体

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

  2. 3D 实体的位姿态

    • 位置Position(x, y, z)
    • 旋转Rotation(x, y, z)
    • 缩放Scale(x, y, z)

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

  3. 3D 实体的创建

    1. 通过加载 3D 模型创建,如 fbx、gltf、obj
    2. 通过组合参数化的基本几何体创建
  4. 3D 实体的绘制

    1. 材质
    2. 颜色
    3. 纹理
  5. 预制体

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

0x2 相机、光照、天空盒

  1. 相机

    1. Clear Flag
    2. 背景颜色
    3. Culling Mask
    4. 投影
      1. 透视投影(Perspective):具有近大远小的投影特点,如 写实类游戏
      2. 正交投影(Orthographic):不具备景深上近大远小的差别,如 策略类游戏
  2. 光照

    1. 光照类型
      1. 点光源
      2. 平行光
      3. 聚光灯
      4. 面积光
    2. 颜色
    3. 强度
    4. 阴影类型
  3. 天空盒

    1. 相机的清除标识为“天空盒”

    2. 窗口 - 渲染 - 照明设置

    3. 环境 - 天空盒材质

    更多天空盒

0x3 控制与碰撞

    1. 实时游戏时序

      1. 时序

         
    1.  
       
       
       
       
       
      1 帧
      初始化
      物理
      输入
      游戏逻辑
      渲染
      停顿
      销毁
    2. 为主角飞船添加控制逻辑

      1. 添加刚体组件

        • Add Component > Physics > Rigidbody
        • Use Gravity 设置为 false,忽略重力的影响
        • isKinematic 设置为 true,飞船通过脚本而非力影响运动属性
        • 设置 Constraints,冻结 Z 轴位移以及 XYZ 轴旋转
      2. 添加自定义脚本

        • Add Component > New Script

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

          Start():在首次调用任何 Update 方法之前在帧上调用 Start

          Update():每帧调用 Update

          FixedUpdate():用于物理计算且独立于帧率

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

          OnGUI():渲染和处理 GUI 事件

          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 rollMult = -45;
          public float pitchMult = 30;
          [Header("Set Dynamically")]
          public float shieldLevel = 1;
          void Start()
          {
          if(S == null)
          S = this; // 设置单例对象
          else
          Debug.LogError("尝试重复设置 Hero 实例");
          }
          void Update()
          {
          // 获取输入的信息
          float xAxis = Input.GetAxis("Horizontal");
          float yAxis = Input.GetAxis("Vertical");
          // 修改 transform.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);
          }
          }
    3. 添加敌机

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

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

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
        public class Enemy : MonoBehaviour
        {
        [Header("Set in Inspector: Enemy")]
        public float speed = 10f; // 运动速度,米每秒
        public float fireRate = 0.3f; // 发射频率
        public float health = 10; // 生命值
        public float score = 100; // 得分
        public Vector3 pos
        {
        get { return(this.transform.position); }
        set { this.transform.position = value; }
        }
        void Update()
        {
        Move();
        }
        public virtual void Move()
        {
        Vector3 tempPos = pos;
        tempPos.y -= speed * Time.deltaTime;
        pos = tempPos;
        }
        }
      • 为每架敌机预制体添加脚本 Enemy.ts

    4. 随机生成敌机

      新建一个名为 Main 的 C# 脚本,绑定到 Main Camera 上

      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      using UnityEngine.SceneManagement; // 用于加载和重载场景
      public class Main : MonoBehaviour
      {
      private float camWidth; // 游戏界面呈现的相机宽度
      private float camHeight; // 游戏界面呈现的相机高度
      static public Main S; // 设置 Main 单例
      [Header("set in Inpector")]
      public GameObject[] prefabEnemies; // Enemy 预设数组
      public float enemySpawnPerSecond = 0.5f; // 每秒产生敌机数
      public float enemySpawnPadding = 1.5f; // 填充敌机举例地图左右边界的位置
      void Start()
      {
      S = this;
      camHeight = Camera.main.orthographicSize; // 只有正交投影时有效
      camWidth = camHeight * Camera.main.aspect;
      Invoke("SpawnEnemy", 1f/enemySpawnPerSecond);
      }
      void Update()
      {
      // 随机选取一架敌机预设并实例化
      int ndx = Random.Range(0, prefabEnemies.Length);
      GameObject go = Instantiate<GameObject>(prefabEnemies(ndx));
      // 使用随机生成的 x 坐标,将敌机置于屏幕上方
      Vector3 pos = Vector3.zero;
      float xMax = camWidth - enemySpawnPadding;
      float xMin = -camWidth + enemySpawnPadding;
      pos.x = Random.Range(xMin, xMax);
      pos.y = camHeight + enemySpawnPadding;
      go.transform.position = pos;
      Invoke("SpawnEnemy", 1f/enemySpawnPerSecond);
      }
      }
  1. 输入管理器和Input.GetAxis()

    1. 输入管理器(InputManager)是 Unity 设置输入响应方式的管理列表 Edit > Project > Setting > Input
    2. Input.GetAxis():从 InputManager 中获取值,其中 GetAxis 方法用于获取坐标值
  2. 设置标签、图层和物理规则

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

    • 主角飞船会与敌机、敌机炮弹、升级道具等碰撞,但不会与主角飞船的炮弹碰撞

    • 主角飞船的炮弹会与敌机、敌机炮弹等碰撞

    • 敌机

    • 敌机的炮弹

    • 升级道具

    1. 标签和图层管理器

      1. Edit > Project Setting > Tags and Layers
      2. 打开 Tags 左侧的三角形展开按钮,在点击 + 号后输入标签名称
      3. 点击 Layers 旁边的三角形展开按钮,从User Layer 8 开始依次输入图层名称
    2. 物理管理器

      Edit > Project Setting > Physics

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

      1. 在层级面板中选择 _Hero,在检视面板中从 Layer 下拉菜单中选择 Hero 选项
      2. 在检视面板中,在 Tags 下拉菜单中选择 Hero 选项,为 _Hero 设置标签
      3. 从项目面板中,选择所有敌机预设并设置图层为 Enemy
  3. 碰撞

    1. 敌机碰撞主角飞船

      将主角飞船与敌机飞船及其子组件上已有的碰撞盒去掉,每个对象的母体上 Add Component > Sphere Collider

    2. 添加碰撞代码

      在 Hero 类中添加代码:

      void OnTriggerEnter(Collider other)
      {
      // print("触发碰撞事件:" + other.gameObject.name);
      if(other.tag == "Enemy")
      Destory(this.gameObject);
      }

0x4 玩法逻辑与 UI

  1. 主角飞船增加射击功能

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

    // Hero.ts
    public GameObject projectilePrefab;
    public float projectileSpeed = 40;
    void Update()
    {
    // 按下空格键开火
    if(Input.GetKeyDown(KeyCode.Space))
    TempFire();
    }
    void TempFire()
    {
    GameObject projGO = Instantiate<GameObject>(projectilePrefab);
    projGO.transform.position = transform.position;
    Rigidbody rigidB = projGO.GetComponent<Rigidbody>();
    rigidB.velocity = Vector3.up * projectileSpeed;
    }
  3. 为炮弹添加碰撞事件

    public class ProjectileHero: MonoBehaviour
    {
    private float camHeight;
    void Start()
    {
    this.camHeight = Camera.main.orthographicSize;
    }
    void Update()
    {
    // 飞出屏幕的炮弹自动销毁
    if(transform.position.y > this.camHeight)
    Destroy(this.gameObject);
    }
    void OnCollisionEnter(Collision other)
    {
    if(other.gameObject.tag == 'Enemy')
    {
    Destroy(other.gameObject);
    Destroy(this.gameObject);
    }
    }
    }
  4. 设置计分板

    • GameObject > UI > Text
      • Canvas:匹配游戏面板尺寸的画布
      • EventSystem:用于运转按钮等交互元素
    • 选择 Text 对象,修改名称为 Score
    • 设置 Text 对象
  5. 得分机制

    每次消灭敌机加 50 分

    using UnityEngine.UI;
    using TMPro;
    public class Hero : MonoBehaviour
    {
    static public Hero S;
    public TMP_Text scoreGT;
    public int score;
    }
    //======================\\
    public class ProjectileHero : MonoBehaviour
    {
    private float camHeight;
    void OnCollisionEnter(Collision other)
    {
    if(other.gameObject.tag == 'Enemy')
    {
    Hero.S.score += 50;
    Hero.S.scoreGT.text = "Score: " + Hero.S.score;
    }
    }
    }
  6. 游戏重新开始

    主角飞船被消灭 2 秒后重新开始游戏

    using UnityEngine.SceneManagement;
    public class Main : MonoBehaviour
    {
    public void DelayedRestart(float delay)
    {
    Invoke("Restart", delay);
    }
    public void Restart()
    {
    SceneManager.LoadScene("SimpleScene");
    }
    }
    //======================\\
    public class Hero : MonoBehaviour
    {
    static public Hero S;
    void OnTriggerEnter(Collider other)
    {
    if(other.tag == "Enemy")
    {
    Main.S.DelayedRestart(2f);
    }
    }
    }

本文作者:SRIGT

本文链接:https://www.cnblogs.com/SRIGT/p/18265033

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   SRIGT  阅读(13)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起