一、实验目的
1. 熟悉 Unity 引擎的基础操作界面与核心功能模块,掌握 3D 游戏开发的基础工作流,为后续复杂游戏开发打下实操基础。
2. 掌握第一人称射击游戏的核心功能开发逻辑,能够独立实现人物移动、跳跃、射击、换弹等基础交互功能。
3. 理解 Unity 中 C# 脚本与游戏对象、组件的关联方式,学会通过脚本实现游戏逻辑的驱动,以及音效、特效等资源的调用与播放。
4. 掌握子弹逻辑、射击特效的制作与触发方式,理解游戏开发中 “逻辑 - 视觉 - 听觉” 的联动设计思路。
5. 培养游戏开发的模块化思维,能够将复杂的游戏功能拆解为独立的开发模块,逐一实现并整合。
二、实验内容
1. 第一人称游戏场景的基础搭建,包括地面、相机视角等基础游戏对象的创建与参数设置。
2. 第一人称人物控制器的开发,实现人物的前后左右移动、跳跃等物理交互功能。
3. 射击系统核心逻辑开发,包括子弹的生成、发射、碰撞检测等子弹基础逻辑。
4. 枪械系统功能实现,包含射击触发、弹夹容量限制、换弹夹逻辑的编写与触发。
5. 游戏的视听效果优化,实现枪声、换弹声、跳跃声的播放,以及射击火光、弹壳弹出等射击特效的触发与展示。
6. 将所有功能模块进行整合,调试各模块之间的联动效果,确保游戏基础功能正常运行。
三、实验步骤(补充完整,需要有脚本和图片截图)
(一)实验前期准备:Unity 项目创建与界面熟悉
打开 Unity Hub,创建 3D 核心项目,选择合适的项目保存路径,等待项目加载完成。
在 Project 窗口中创建文件夹分类管理资源,分别命名为 “Scripts”(存放 C# 脚本)、“Audio”、“Effects”、“Prefabs”,导入实验所需的音频、模型等基础资源。
截图 1:Unity 项目创建界面及资源文件夹分类截图
-%E9%A1%B9%E7%9B%AE1-FPS%E5%B0%84%E5%87%BB_019d2047-e442-7618-b715-1428a9f18e54.png)
基础场景搭建:构建游戏基础环境
在 Hierarchy 窗口右键创建 3D 对象 - 平面(Plane)作为游戏地面,在 Inspector 窗口调整 Transform 组件的缩放参数为 (10,1,10),扩大地面范围。
创建空游戏对象,命名为 “Player”,作为人物根对象;在 Player 下创建 3D 对象 - 胶囊体(Capsule)作为人物模型,再创建相机(Main Camera)作为第一人称视角相机,调整相机的位置为 (0,1.6,0),与人物视角高度匹配,移除场景默认的 Main Camera。
为 Player 对象添加 CharacterController 组件,用于人物运动控制,设置 Radius 为 0.3、Height 为 1.8、Center 为 (0,0.9,0),适配胶囊体模型。
截图 2:游戏场景层级结构及 Player 对象参数设置截图
人物控制器开发:实现移动与跳跃功能
在 Scripts 文件夹右键创建 C# 脚本,命名为 PlayerControl,双击打开 Visual Studio 编写脚本。
脚本中定义私有变量:Rigidbody rBody(物理运动控制)、AudioSource footPlayer(脚步声播放)、bool isGround(地面状态判断);
在 Start 方法中获取 Rigidbody 组件与 AudioSource 组件,完成组件初始化。
在 Update 方法中:
检测空格键输入(Input.GetKeyDown(KeyCode.Space)),结合 isGround 变量判断是否处于地面,满足条件时为刚体施加向上的力(AddForce(Vector3.up * 200))实现跳跃;
通过 Input.GetAxis("Horizontal") 和 Input.GetAxis("Vertical") 获取水平、垂直输入轴信息,判断是否存在移动输入;
当人物在地面且存在移动输入时,播放脚步声音频;若未移动或不在地面,则停止播放脚步声。
通过 OnCollisionEnter 和 OnCollisionExit 方法,检测与标记为 Ground 的地面碰撞状态,更新 isGround 变量,确保仅在地面时可触发跳跃。
将 PlayerControl 脚本挂载到 Player 对象上,为 Player 对象添加 Rigidbody 组件与 AudioSource 组件,为场景地面对象设置 Tag 为 Ground;运行游戏调试移动、跳跃及脚步声功能,根据实际体验调整跳跃力、音频播放等参数。
核心脚本代码(PlayerControl):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerControl : MonoBehaviour
{
// Start is called before the first frame update
private Rigidbody rBody;
private AudioSource footPlayer;
private bool isGround;
void Start()
{
rBody = GetComponent<Rigidbody>();
footPlayer = GetComponent<AudioSource>();
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Space) && isGround == true)
{
rBody.AddForce(Vector3.up * 200);
}
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
if((horizontal!=0||vertical!=0)&&isGround==true)
{
if(footPlayer.isPlaying==false)
{
footPlayer.Play();
}
}else
{
footPlayer.Stop();
}
}
private void OnCollisionEnter(Collision collision)
{
if (collision.collider.tag == "Ground")
isGround = true;
}
private void OnCollisionExit(Collision collision)
{
if (collision.collider.tag == "Ground")
isGround = false;
}
}截图 3:PlayerController 挂载参数截图.
-%E9%A1%B9%E7%9B%AE1-FPS%E5%B0%84%E5%87%BB_019d2047-e75e-72bc-b713-2437970174d7.png)
射击系统开发:实现子弹逻辑与射击触发
创建子弹预制体:在 Hierarchy 窗口右键创建 3D 对象 - 球体(Sphere),命名为 “Bullet”,调整缩放为 (0.2,0.2,0.2),为其添加 Rigidbody 组件,将 Bullet 拖入 Prefabs 文件夹作为预制体,删除场景中的 Bullet 对象。
在 Scripts 文件夹创建 C# 脚本,命名为 BulltControl,编写脚本:在 Start 方法中获取 Rigidbody 组件,为子弹施加向前的力(transform.forward * 800)实现发射;在 OnCollisionEnter 方法中编写碰撞检测逻辑,触发碰撞时销毁子弹。
在 Player 对象下创建空对象命名为 “FirePoint”,调整位置为 (0.2,1.5,0.5) 作为射击点;在 Player 对象下编写射击逻辑脚本,在鼠标左键输入时实例化 Bullet 预制体到 FirePoint 位置。
将 BulltControl 脚本挂载到 Bullet 预制体上,运行游戏调试子弹发射与碰撞销毁功能。
子弹脚本代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BulltControl : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
GetComponent<Rigidbody>().AddForce(transform.forward * 800);
}
private void OnCollisionEnter(Collision collision)
{
Destroy(gameObject);
}
void Update()
{ }}截图 4:子弹预制体参数及射击系统脚本截图
-%E9%A1%B9%E7%9B%AE1-FPS%E5%B0%84%E5%87%BB_019d2047-e91b-700e-814c-1cb37ba53bcc.png)
枪械系统开发:实现换弹夹逻辑
1. 在 Scripts 文件夹创建 C# 脚本,命名为 NewBehaviourScript,编写脚本逻辑:
定义枪口 / 子弹预制体、发射点、子弹数量、冷却时间等公共变量;定义音效、UI 文本及换弹状态标记 isReloading 等私有变量。
2. 在 Start 方法中获取音频组件并初始化 UI 显示;在 Update 方法中控制射击冷却,判断非换弹状态、有子弹时方可射击,触发实例化子弹、扣减子弹数、播放音效;按下 R 键且非换弹状态时触发换弹逻辑。
编写 ReloadLogic 方法开启换弹、触发换弹动画、播放音效;编写 Reload 方法重置子弹数量、解锁换弹状态;编写 UpdateBulletUI 方法实时更新子弹数量 UI 显示。
将 NewBehaviourScript 脚本挂载到 Player 对象,为 firePoint、fierPre、bulletPoint、bulletPre 等变量赋值,绑定 UI 中的 TextMeshProUGUI 组件,配置音效资源。
3. 运行游戏调试换弹、子弹计数及 UI 显示功能,检查换弹状态锁定、子弹数扣减与重置、UI 数据同步的正确性,调整参数优化游戏体验。
枪械核心脚本代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class NewBehaviourScript : MonoBehaviour
{
[Header("枪口与子弹")]
public Transform firePoint;
public GameObject fierPre;
public Transform bulltPoint;
public GameObject bulltPre;
[Header("子弹设置")]
private int bulltCount = 10;
public int maxBullet = 10;
private float cd = 0.2f;
private float timer = 0;
[Header("音效")]
private AudioSource gunPlay;
public AudioClip clip;
public AudioClip check;
[Header("UI显示")]
public TextMeshProUGUI bulletText;
// 🔥 关键:换弹中标记(正在换弹时不能开枪)
private bool isReloading = false;
void Start()
{
gunPlay = GetComponent<AudioSource>();
UpdateBulletUI();
}
void Update()
{
timer += Time.deltaTime;
// ✅ 加了判断:不在换弹中 才能射击
if (timer > cd && Input.GetMouseButton(0) && bulltCount > 0 && !isReloading)
{
timer = 0;
Instantiate(fierPre, firePoint.position, firePoint.rotation);
Instantiate(bulltPre, bulltPoint.position, bulltPoint.rotation);
bulltCount--;
UpdateBulletUI();
gunPlay.PlayOneShot(clip);
if (bulltCount == 0)
{
ReloadLogic();
}
}
// R键换弹
if (Input.GetKeyDown(KeyCode.R) && bulltCount < maxBullet && !isReloading)
{
ReloadLogic();
}
}
void ReloadLogic()
{
isReloading = true; // 🔥 开始换弹 = 锁定射击
GetComponent<Animator>().SetTrigger("Reload");
Invoke("Reload", 1.5f);
gunPlay.PlayOneShot(check);
}
void Reload()
{
bulltCount = maxBullet;
isReloading = false; // 🔥 换弹结束 = 解锁射击
UpdateBulletUI();
}
void UpdateBulletUI()
{
if (bulletText != null)
{
bulletText.text = bulltCount.ToString();
}
}
}截图 5:枪械系统脚本代码及参数调试截图
(六)视听效果添加:音效播放与射击特效
视听效果资源赋值截图:
-%E9%A1%B9%E7%9B%AE1-FPS%E5%B0%84%E5%87%BB_019d2047-ed2f-700d-9ec9-d42d38c0ccb4.png)
实验结果
本次实验基于 Unity 引擎完成了第一人称射击游戏基础功能开发,实现了以下核心效果:
人物控制:实现了 WASD 移动、空格键跳跃,在地面时可正常跳跃,移动时同步播放脚步声。
射击系统:点击鼠标左键可从指定射击点发射子弹,子弹飞行后碰到物体自动销毁,同时有枪口火光与弹壳弹出特效。
枪械与 UI:实现了子弹计数、射击冷却、R 键换弹功能,换弹时会播放换弹动画与音效,换弹期间无法射击,子弹数量实时显示在 UI 上。
整体运行:各模块联动正常,无明显逻辑冲突,游戏可稳定运行,满足第一人称射击游戏的基础操作体验。
讨论与结论(对实验现象、实验故障及处理方法、实验中存在的问题等进行分析和讨论,对实验的进一步想法或改进意见。)
(一)实验现象分析
人物移动与跳跃通过 Rigidbody 物理组件实现,地面碰撞检测保证了只有在地面时才能跳跃,避免了空中多次跳跃的问题。
射击与换弹通过状态变量 isReloading 控制,换弹期间锁定射击功能,避免了操作逻辑冲突,UI 实时显示子弹数让玩家清晰了解当前弹药状态。
音效与特效的同步播放,让操作有明确反馈,提升了游戏的沉浸感。
(二)实验故障及处理方法
故障:换弹时仍能射击,导致子弹数异常。
处理:添加 isReloading 布尔变量,换弹时设为 true,换弹结束后设为 false,在射击判断中加入!isReloading 条件,解决了换弹期间可射击的问题。
故障:子弹 UI 不更新。
处理:在扣减子弹和换弹完成后主动调用 UpdateBulletUI() 方法,确保子弹数量变化时 UI 同步刷新。
故障:跳跃时人物浮空。
处理:通过 OnCollisionEnter 和 OnCollisionExit 检测地面碰撞,只有 isGround 为 true 时才能触发跳跃,保证了跳跃的合理性。
(三)实验存在的问题
性能方面:子弹采用实例化 + 销毁的方式,频繁射击时会产生一定性能开销。
体验方面:没有准星辅助瞄准,射击精度依赖玩家视角控制;没有敌人与伤害系统,游戏可玩性不足。
代码方面:脚本功能耦合度较高,不利于后续功能扩展。
(四)改进方向
引入对象池技术复用子弹对象,减少频繁创建销毁带来的性能消耗。
添加准星 UI、敌人 AI 与血条系统,提升游戏可玩性。
优化代码结构,将射击、换弹、UI 等功能拆分为独立模块,降低耦合度。
本次实验完成了第一人称射击游戏的核心基础功能,包括人物移动跳跃、子弹发射、换弹逻辑、音效特效与 UI 显示等模块,验证了 Unity 组件化开发与 C# 脚本驱动游戏逻辑的可行性。通过调试解决了碰撞检测、状态锁定、UI 同步等问题,积累了 3D 游戏开发的实操经验,为后续完善游戏功能、提升用户体验打下了基础和以后的虚拟现实开发学习。
评论交流
欢迎留下你的想法