当前位置: 首页> 教育> 幼教 > 【Unity】3D功能开发入门系列(五)

【Unity】3D功能开发入门系列(五)

时间:2025/8/27 15:05:14来源:https://blog.csdn.net/wanchen_Gabby/article/details/140923198 浏览次数:0次

Unity3D功能开发入门系列(五)

  • 一、预制体
    • (一)预制体
    • (二)预制体的创建
    • (三)预制体实例
    • (四)预制体的编辑
  • 二、动态创建实例
    • (一)动态创建实例
    • (二)实例的初始化
    • (三)实例的销毁
    • (四)练习:火控参数&按键控制
  • 三、物理系统
    • (一)物理系统
    • (二)物理碰撞
    • (三)反射与摩擦
  • 四、碰撞检测
    • (一)运动学刚体
    • (二)碰撞检测
    • (三)碰撞体的编辑
    • (四)练习:击毁目标
  • 五、游戏项目实战 — 射击小游戏

一、预制体

(一)预制体

预制体 Prefab,即预先制作好的物体。使用预制体,可以快速创建物体,提高开发效率。

演示:导出 RacingCar 资源包

  • 在 Prefab 目录下,是预制体资源,* .prefab
  • 用预制体来构造物体
    在这里插入图片描述

在 Prefab 中,多级节点 / 父子关系,是常见的情况

(二)预制体的创建

  1. 先制作好一个样本节点
  2. 做好以后,直接拖到 Assets 窗口,则自动生成一个 *.prefab 资源
  3. 原始物体不再需要,可以删除
    在这里插入图片描述

导出 prefab 资源时,应将依赖文件一并导出prefab 只是记录了节点信息,文件中不包含材质、贴图数据,仅包含引用在这里插入图片描述

(三)预制体实例

1. prefab Instance,由预制体创建得到的对象
特征: 在 Hierarchy 中,节点图标不同、右键菜单 | Prefab、上下文工具 | Prefab
在这里插入图片描述
在这里插入图片描述
2. Prefab Instance 和原 Prefab 之间存在关联
右键菜单 Prefab | Unpack,则解除关联,成为普通物体
在这里插入图片描述

在这里插入图片描述

(四)预制体的编辑

*.prefab 相当于是一个模板,可以再次编辑(其实例也会同步改变)

1. 单独编辑

  • 双击 Prefab,进入 单独编辑 模式
  • 编辑节点和组件
  • 退出,完成编辑
    在这里插入图片描述

2. 原位编辑

  • 选择 Prefab Instance
  • 在检查器中 Open
  • Context 显示:Normal / Gray / Hidden (此时,仅选中的物体被编辑,其余物体是陪衬
  • 编辑节点
  • 退出,完成编辑
    在这里插入图片描述
    在这里插入图片描述

3. 覆盖编辑

  • 选择 Prefab Instance
  • 直接在场景中编辑
  • 编辑完后 Override | Apply 应用编辑、Override | Revert 取消编辑
    在这里插入图片描述

二、动态创建实例

(一)动态创建实例

创建 Prefab 之后,用 API 动态创建实例

Object.Instance(original,parent);

演示:

  1. 准备子弹 prefab
  2. 添加火控脚本 FireLogic
  • 添加变量
public GameObject bulletPrefab;
  • 克隆实例
GameObject node = Instance(bulletPrefab,null);
node.transform.position = Vector3.zero;
node.tansform.localEulerAngles = Vector3.zero;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class FireLogic : MonoBehaviour
{// 对子弹 prefab 资源的引用public GameObject bulletPrefab;// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){if(Input.GetMouseButtonDown(0)){TestFire();}}private void TestFire(){Debug.Log("* 创建子弹的实例 ..");GameObject node = Object.Instantiate(bulletPrefab, null);node.transform.position = Vector3.zero;node.transform.localEulerAngles = Vector3.zero;}
}

在这里插入图片描述

(二)实例的初始化

创建 Prefab Instance 之后,应做初始化:
1. parent,设置父节点。 为便于节点的管理,应单独创建一个父节点
在这里插入图片描述

2. position / localPosition,出生节点。 为便于操作,应显示标记一个出生点
在这里插入图片描述
3. eulerAngles / localEulerAngles / rotation 旋转。 对子弹来说,子弹角度应与炮塔旋转角度一致
在这里插入图片描述

4. Script,自带的控制脚本。 子弹自带了 BulletLogic,可以控制其飞行速度
在这里插入图片描述

在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class BulletLogic : MonoBehaviour
{public float speed;// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){this.transform.Translate(0, 0, speed, Space.Self);}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class FireLogic : MonoBehaviour
{// 对子弹 prefab 资源的引用public GameObject bulletPrefab;// 父子关系靠 Transform 维持,后面创建实例要找父级public Transform bulletFolder;// 出生点位置的指定public Transform firePoint;// 得到炮塔的旋转角度public Transform cannon;// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){if(Input.GetMouseButtonDown(0)){TestFire();}}private void TestFire(){Debug.Log("* 创建子弹的实例 ..");GameObject node = Object.Instantiate(bulletPrefab, bulletFolder);node.transform.position = this.firePoint.position; //Vector3.zero;node.transform.localEulerAngles = this.cannon.eulerAngles; //Vector3.zero;// node.transform.rotation = this.cannon.rotation;BulletLogic script = node.GetComponent<BulletLogic>();script.speed = 0.5f;}
}

  • 一般引用 Transform,而 GameObject 是没有存在感的
  • 可以使用空物体,标记一个空间位置

(三)实例的销毁

一般的,创建实例之后,也要负责销毁。

对于子弹来说

  • 飞出屏幕时,销毁 X
  • 射程 / 飞行时间 V
  • 击中目标时,销毁 V

Object.Destroy( obj ),用于销毁一个实例

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class BulletLogic : MonoBehaviour
{public float speed;public float maxDistance;// Start is called before the first frame updatevoid Start(){float lifetime = 1;if(speed > 0){lifetime = maxDistance / speed;}Invoke("SelfDestroy", lifetime);}// Update is called once per framevoid Update(){this.transform.Translate(0, 0, speed, Space.Self);}private void SelfDestroy(){Debug.Log("* 自毁 !");Object.Destroy(this.gameObject);}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class FireLogic : MonoBehaviour
{// 对子弹 prefab 资源的引用public GameObject bulletPrefab;// 父子关系靠 Transform 维持,后面创建实例要找父级public Transform bulletFolder;// 出生点位置的指定public Transform firePoint;// 得到炮塔的旋转角度public Transform cannon;// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){if(Input.GetMouseButtonDown(0)){TestFire();}}private void TestFire(){Debug.Log("* 创建子弹的实例 ..");GameObject node = Object.Instantiate(bulletPrefab, bulletFolder);node.transform.position = this.firePoint.position; //Vector3.zero;node.transform.localEulerAngles = this.cannon.eulerAngles; //Vector3.zero;// node.transform.rotation = this.cannon.rotation;// 指定初始飞行速度BulletLogic script = node.GetComponent<BulletLogic>();script.speed = 0.5f;script.maxDistance = script.speed * 10;}
}

在这里插入图片描述

  • Destroy( this ) 这样写是错误的,我们要销毁物体,而不是组件
  • Destroy( ) 不会立即执行,而是在本轮 Update 之后才执行

(四)练习:火控参数&按键控制

练习1:火控参数的完善

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class FireLogic : MonoBehaviour
{// 对子弹 prefab 资源的引用public GameObject bulletPrefab;// 父子关系靠 Transform 维持,后面创建实例要找父级public Transform bulletFolder;// 出生点位置的指定public Transform firePoint;// 得到炮塔的旋转角度public Transform cannon;// 实现火控参数的动态管理public float bulletSpeed;public float bulletLifetime;// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){if(Input.GetMouseButtonDown(0)){TestFire();}}private void TestFire(){Debug.Log("* 创建子弹的实例 ..");GameObject node = Object.Instantiate(bulletPrefab, bulletFolder);node.transform.position = this.firePoint.position; //Vector3.zero;node.transform.localEulerAngles = this.cannon.eulerAngles; //Vector3.zero;// node.transform.rotation = this.cannon.rotation;// 指定初始飞行速度BulletLogic script = node.GetComponent<BulletLogic>();script.speed = this.bulletSpeed;script.maxDistance = script.speed * this.bulletLifetime;}
}

在这里插入图片描述

练习2:添加按键控制,旋转炮塔方向
官方文档:https://docs.unity.cn/cn/2022.3/ScriptReference/Transform-localEulerAngles.html,不建议使用rotate
在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class FireLogic : MonoBehaviour
{// 对子弹 prefab 资源的引用public GameObject bulletPrefab;// 父子关系靠 Transform 维持,后面创建实例要找父级[Tooltip("子弹节点管理")]public Transform bulletFolder;[Tooltip("子弹出生点")]public Transform firePoint;// 得到炮塔的旋转角度[Tooltip("炮塔")]public Transform cannon;// 实现火控参数的动态管理[Tooltip("子弹飞行速度")]public float bulletSpeed;[Tooltip("子弹飞行时长")]public float bulletLifetime;// 实现连发 + 方向控制[Tooltip("子弹发射间隔")]public float bulletInterval;[Tooltip("炮塔转速")]public float rotateSpeed;// 当前转角private Vector3 m_eulerAngles;void Start(){StartFire();}void Update(){float delta = rotateSpeed * Time.deltaTime;// 左右是绕 y 轴旋转,上下是绕 x 轴旋转(上左负)if(Input.GetKey(KeyCode.A)){//左转if (m_eulerAngles.y > -75)m_eulerAngles.y -= delta;}if(Input.GetKey(KeyCode.D)){//右转if (m_eulerAngles.y < 75)m_eulerAngles.y += delta;}if(Input.GetKey(KeyCode.W)){//上转if(m_eulerAngles.x > -60)m_eulerAngles.x -= delta;}if(Input.GetKey(KeyCode.S)){if(m_eulerAngles.x < 10)m_eulerAngles.x += delta;}cannon.transform.localEulerAngles = m_eulerAngles;}private void TestFire(){// 指定父节点GameObject node = Object.Instantiate(bulletPrefab, bulletFolder);// 指定出生点node.transform.position = this.firePoint.position; // 与炮塔角度一致node.transform.localEulerAngles = this.cannon.eulerAngles; // 指定初始飞行速度BulletLogic script = node.GetComponent<BulletLogic>();script.speed = this.bulletSpeed;script.maxDistance = script.speed * this.bulletLifetime;}// 开火public void StartFire(){if(!IsInvoking("TestFire")){InvokeRepeating("TestFire", bulletInterval, bulletInterval);}}public void StopFire(){CancelInvoke("TestFire");}
}

在这里插入图片描述


三、物理系统

(一)物理系统

  1. 物理系统 Physics,即由物理规律起作用的系统,确切的说,是牛顿运动定律(力、质量、速度)
    演示:布置一个场景,添加 ‘地面’,‘苹果’,运行游戏,‘苹果’ 悬空,这很不牛顿!

  2. 刚体组件 Rigidbody,物理学中的物体,当添加 Rigidbody 后,由 物理引擎负责刚体运动(物理引擎会根据它的质量、力、速度之间的关系自动做出运算,使其运动)

给 ‘苹果’ 添加一个 Rigidbody 组件:Physics | Rigidbody,运行游戏
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(二)物理碰撞

物理系统,不仅接管了刚体的运动,也接管了碰撞
碰撞体 Collider,描述了物体的碰撞范围。其中 Box Collider 长方碰撞体、Sphere Collider 球形碰撞体
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
绿框与绿框相碰,会被物理引擎捕捉到
在这里插入图片描述

(三)反射与摩擦

钢铁的反弹与摩擦,也归物理系统负责。
演示:

  • 新建 Physical Material,添加给小球
    在这里插入图片描述
    在这里插入图片描述
  • 设置 Friction、Bounciness
  • 观察小球的反弹
    在这里插入图片描述

四、碰撞检测

(一)运动学刚体

运动学刚体 Kinematic,即质量为 0 的刚体。由于质量为 0,所以此刚体不受牛顿约束。
在这里插入图片描述
此时,需要使用脚本使运动

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SimpleLogic : MonoBehaviour
{public Vector3 speed;// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){this.transform.Translate(speed * Time.deltaTime, Space.Self);}
}

(二)碰撞检测

对于运动学刚体,也支持碰撞检测。由 物理引擎 负责检测。
在这里插入图片描述
演示:

  1. 勾上 Rigidbody | Is Kinematic、Collider | Is Trigger(触发器)
  2. 挂一个脚本,添加消息函数
void OnTriggerEnter(Collider other){}

其中,Collider other 对方碰撞体、other.gameObject 对方节点、other.name 对方节点的名字

在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SimpleLogic : MonoBehaviour
{public Vector3 speed;// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){this.transform.Translate(speed * Time.deltaTime, Space.Self);}private void OnTriggerEnter(Collider other){Debug.Log("* 发生了碰撞,other=" + other.name);}
}

在这里插入图片描述

  1. 物理引擎 只负责探测 ( Trigger ) ,不会阻止物体或者反弹
  2. 物体引擎 计算的是 Collider 之间的碰撞,和物体自身形状无关
  3. 当检测到碰撞时,会调用 当前节点脚本中的 OnTriggerEnter 消息

(三)碰撞体的编辑

碰撞体 Collider 的形状,规定了碰撞的边界。其形状是可以编辑的:

  • Box Collider 盒形:
    Center 中心位置,相当于物体的轴心点;Size 长宽高。点 Edit Collider,可以直接编辑绿色框
  • Sphere Collider 球形:
    Center 中心位置,相当于物体的轴心点;Radius 半径大小。点 Edit Collider,可以直接编辑绿色框

练习:添加子弹物体

  1. 检查原先有没有碰撞体,如果有,则先移除
  2. 根据体型,选择合适形状的碰撞体:此处,添加一个 Box Collider
  3. 编辑碰撞体,调整边界:一般无需调整,Unity会自动创建合适的尺寸
    在这里插入图片描述

碰撞体的范围,不用太精确,大致覆盖物体即可

(四)练习:击毁目标

练习:发射一发子弹,击毁目标

  1. 运动学刚体设置 Rigidbody | Is Kinematic
  2. 触发器模式设置 Collider | Is Trigger
  3. 消息函数 void OnTriggerEnter()
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class BulletLogicDistruct : MonoBehaviour
{public Vector3 speed;// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){this.transform.Translate(speed * Time.deltaTime, Space.Self);}private void OnTriggerEnter(Collider other){if(other.name.Equals("目标")){Debug.Log("* 子弹发生碰撞,other=" + other.name);Object.Destroy(other.gameObject);Object.Destroy(this.gameObject);}}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class MainLogic : MonoBehaviour
{// Start is called before the first frame updatevoid Start(){Application.targetFrameRate = 60;}// Update is called once per framevoid Update(){}
}

在这里插入图片描述

碰撞的双方,只需一方设置为运动学刚体即可


五、游戏项目实战 — 射击小游戏

制作一个射击游戏
海空背景、玩家、子弹(无限数量)、怪兽(蛇皮走位,无限数量)、子弹特效(爆炸特效)、背景音乐

(1) 添加两个角色(预制体)玩家、敌人

在这里插入图片描述

(2)天空盒 Skybox,即游戏的背景
Windows | Rendering | Lighting,光照设置 —> Environment | Skybox Material,天空盒材质
在这里插入图片描述
在这里插入图片描述

调整摄像头视角
在这里插入图片描述
在这里插入图片描述

(3) 子弹和碰撞
添加子弹、子弹脚本、碰撞检测
在这里插入图片描述
在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class LiBulletLogic : MonoBehaviour
{[Tooltip("子弹飞行速度")]public float speed = 1;// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){transform.Translate(0, 0, speed * Time.deltaTime, Space.Self);}
}

在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class LiBulletLogic : MonoBehaviour
{[Tooltip("子弹飞行速度")]public float speed = 1;// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){transform.Translate(0, 0, speed * Time.deltaTime, Space.Self);}private void OnTriggerEnter(Collider other){Debug.Log("* 子弹碰撞,other=" + other.name);}
}

  1. 怪兽是一个空物体,其碰撞体要手动编辑
    在这里插入图片描述

在这里插入图片描述

  1. 主控脚本中帧率的设置(让CPU压力小一点)
    在这里插入图片描述
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class LiMainLogic : MonoBehaviour
{// Start is called before the first frame updatevoid Start(){Application.targetFrameRate = 60;}// Update is called once per framevoid Update(){}
}

(4)连续发射

1. 子弹

  • 增加自毁时间 lifetime
  • 把子弹做成 prefab
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class LiBulletLogic : MonoBehaviour
{[Tooltip("子弹飞行速度")]public float speed = 1;[Tooltip("子弹生命时长")]public float lifetime = 3;// Start is called before the first frame updatevoid Start(){Invoke("SelfDestroy", lifetime);}// Update is called once per framevoid Update(){transform.Translate(0, 0, speed * Time.deltaTime, Space.Self);}private void OnTriggerEnter(Collider other){Debug.Log("* 子弹碰撞,other=" + other.name);if (!other.name.StartsWith("怪兽")) return;Destroy(this.gameObject);Destroy(other.gameObject);}private void SelfDestroy(){Object.Destroy(this.gameObject);}
}

在这里插入图片描述

2. 玩家

  • 定义发射点 fire point
  • 定义子弹目录 bullet folder
  • 使用定时器,子弹连发

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class LiPlayerLogic : MonoBehaviour
{[Tooltip("子弹节点的预制体")]public GameObject bulletPrefab;[Tooltip("子弹节点的父节点")]public Transform bulletFolder;[Tooltip("子弹出生点")]public Transform firePoint;[Tooltip("开火间隔")]public float fireInterval = 0.5f;// Start is called before the first frame updatevoid Start(){InvokeRepeating("Fire", fireInterval, fireInterval);}// Update is called once per framevoid Update(){}private void Fire(){// 实例化一个子弹节点GameObject node = Instantiate(bulletPrefab, bulletFolder);// 调整子弹发射点位置node.transform.position = firePoint.position;}
}

(5)按键控制:添加按键控制,让玩家左右移动

using System.Collections;
using System.Collections.Generic;
using System.Data.SqlTypes;
using UnityEngine;public class LiPlayerLogic : MonoBehaviour
{[Tooltip("子弹节点的预制体")]public GameObject bulletPrefab;[Tooltip("子弹节点的父节点")]public Transform bulletFolder;[Tooltip("子弹出生点")]public Transform firePoint;[Tooltip("开火间隔")]public float fireInterval = 0.5f;[Tooltip("平移速度")]public float moveSpeed = 0.3f;// Start is called before the first frame updatevoid Start(){InvokeRepeating("Fire", fireInterval, fireInterval);}// Update is called once per framevoid Update(){float dx = 0;if (Input.GetKey(KeyCode.A))dx = -moveSpeed;if (Input.GetKey(KeyCode.D))dx = moveSpeed;this.transform.Translate(dx, 0, 0, Space.Self);}private void Fire(){// 实例化一个子弹节点GameObject node = Instantiate(bulletPrefab, bulletFolder);// 把子弹移动到出生点位置node.transform.position = firePoint.position;}
}

在这里插入图片描述

(6)怪兽控制:给怪兽添加脚本 EnemyLogic

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class LiEnemyLogic : MonoBehaviour
{[Tooltip("前进速度")]public float zSpeed = 10;// 横移速度float xSpeed = 0;// Start is called before the first frame updatevoid Start(){// 每秒改变一次横移速度InvokeRepeating("SnakeMove", 1f, 1f);}// Update is called once per framevoid Update(){float dz = zSpeed * Time.deltaTime;float dx = xSpeed * Time.deltaTime;this.transform.Translate(dx, 0 , dz, Space.Self);}// 蛇皮走位void SnakeMove(){// 4 种速度选项float[] options = { -10, -5, 5, 10 };int sel = Random.Range(0, options.Length);xSpeed = options[sel];}
}

(7)怪兽生成器:定时生成怪兽(实质还是预制体创建实例的技术)

更新预制体的资源,隐藏怪兽
在这里插入图片描述
在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class LiEnemyCreator : MonoBehaviour
{public GameObject enemyPrefab;public float interval = 1;// Start is called before the first frame updatevoid Start(){InvokeRepeating("CreateEnemy", 0.1f, interval);}// Update is called once per framevoid Update(){}private void CreateEnemy(){GameObject node = Instantiate(enemyPrefab, this.transform);node.transform.position = this.transform.position;// 把怪兽的投头转过来node.transform.localEulerAngles = new Vector3(0, 180, 0);// 出生之后,不要在原点呆着,而是左右偏一个位置,防止被立即击毙float dx = Random.Range(-30, 30);node.transform.Translate(dx, 0, 0, Space.Self);}
}

在这里插入图片描述
(8)子弹特效:添加子弹特效,粒子特效 Particle System

注意,修改之后要应用 Prefab

在这里插入图片描述

(9)爆炸特效 :在击中目标时,创建特效节点
在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class LiBulletLogic : MonoBehaviour
{[Tooltip("子弹飞行速度")]public float speed = 25;[Tooltip("子弹生命时长")]public float lifetime = 3;[Tooltip("爆炸粒子特效的Prefab")]public GameObject explosionEffect;// Start is called before the first frame updatevoid Start(){Invoke("SelfDestroy", lifetime);}// Update is called once per framevoid Update(){transform.Translate(0, 0, speed * Time.deltaTime, Space.Self);}private void OnTriggerEnter(Collider other){Debug.Log("* 子弹碰撞,other=" + other.name);if (!other.name.StartsWith("怪兽")) return;Destroy(this.gameObject);Destroy(other.gameObject);// 创建一个粒子特效,表现爆炸的效果GameObject effectNode = Instantiate(explosionEffect, null);effectNode.transform.position = this.transform.position;// 当粒子特效播放完,会自己销毁}private void SelfDestroy(){Object.Destroy(this.gameObject);}
}

在这里插入图片描述

最后再设置一下音乐
在这里插入图片描述


关键字:【Unity】3D功能开发入门系列(五)

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: