一、实验目的

1. 熟悉 Unity 引擎的基础操作界面与核心功能模块,掌握 3D 游戏开发的基础工作流,为后续复杂游戏开发打下实操基础。

2. 掌握第一人称射击游戏的核心功能开发逻辑,能够独立实现人物移动、跳跃、射击、换弹等基础交互功能。

3. 理解 Unity 中 C# 脚本与游戏对象、组件的关联方式,学会通过脚本实现游戏逻辑的驱动,以及音效、特效等资源的调用与播放。

4. 掌握子弹逻辑、射击特效的制作与触发方式,理解游戏开发中 “逻辑 - 视觉 - 听觉” 的联动设计思路。

5. 培养游戏开发的模块化思维,能够将复杂的游戏功能拆解为独立的开发模块,逐一实现并整合。

二、实验内容

1. 第一人称游戏场景的基础搭建,包括地面、相机视角等基础游戏对象的创建与参数设置。

2. 第一人称人物控制器的开发,实现人物的前后左右移动、跳跃等物理交互功能。

3. 射击系统核心逻辑开发,包括子弹的生成、发射、碰撞检测等子弹基础逻辑。

4. 枪械系统功能实现,包含射击触发、弹夹容量限制、换弹夹逻辑的编写与触发。

5. 游戏的视听效果优化,实现枪声、换弹声、跳跃声的播放,以及射击火光、弹壳弹出等射击特效的触发与展示。

6. 将所有功能模块进行整合,调试各模块之间的联动效果,确保游戏基础功能正常运行。

三、实验步骤(补充完整,需要有脚本和图片截图)

(一)实验前期准备:Unity 项目创建与界面熟悉

  1. 打开 Unity Hub,创建 3D 核心项目,选择合适的项目保存路径,等待项目加载完成。

  2. 在 Project 窗口中创建文件夹分类管理资源,分别命名为 “Scripts”(存放 C# 脚本)、“Audio”、“Effects”、“Prefabs”,导入实验所需的音频、模型等基础资源。

截图 1:Unity 项目创建界面及资源文件夹分类截图

  1. 基础场景搭建:构建游戏基础环境

  2. 在 Hierarchy 窗口右键创建 3D 对象 - 平面(Plane)作为游戏地面,在 Inspector 窗口调整 Transform 组件的缩放参数为 (10,1,10),扩大地面范围。

  3. 创建空游戏对象,命名为 “Player”,作为人物根对象;在 Player 下创建 3D 对象 - 胶囊体(Capsule)作为人物模型,再创建相机(Main Camera)作为第一人称视角相机,调整相机的位置为 (0,1.6,0),与人物视角高度匹配,移除场景默认的 Main Camera。

  4. 为 Player 对象添加 CharacterController 组件,用于人物运动控制,设置 Radius 为 0.3、Height 为 1.8、Center 为 (0,0.9,0),适配胶囊体模型。

截图 2:游戏场景层级结构及 Player 对象参数设置截图

  1. 人物控制器开发:实现移动与跳跃功能

在 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 挂载参数截图.

  1. 射击系统开发:实现子弹逻辑与射击触发

  2. 创建子弹预制体:在 Hierarchy 窗口右键创建 3D 对象 - 球体(Sphere),命名为 “Bullet”,调整缩放为 (0.2,0.2,0.2),为其添加 Rigidbody 组件,将 Bullet 拖入 Prefabs 文件夹作为预制体,删除场景中的 Bullet 对象。

  3. 在 Scripts 文件夹创建 C# 脚本,命名为 BulltControl,编写脚本:在 Start 方法中获取 Rigidbody 组件,为子弹施加向前的力(transform.forward * 800)实现发射;在 OnCollisionEnter 方法中编写碰撞检测逻辑,触发碰撞时销毁子弹。

  4. 在 Player 对象下创建空对象命名为 “FirePoint”,调整位置为 (0.2,1.5,0.5) 作为射击点;在 Player 对象下编写射击逻辑脚本,在鼠标左键输入时实例化 Bullet 预制体到 FirePoint 位置。

  5. 将 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:子弹预制体参数及射击系统脚本截图

  1. 枪械系统开发:实现换弹夹逻辑

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:枪械系统脚本代码及参数调试截图

(六)视听效果添加:音效播放与射击特效

视听效果资源赋值截图:

实验结果

本次实验基于 Unity 引擎完成了第一人称射击游戏基础功能开发,实现了以下核心效果:

  1. 人物控制:实现了 WASD 移动、空格键跳跃,在地面时可正常跳跃,移动时同步播放脚步声。

  2. 射击系统:点击鼠标左键可从指定射击点发射子弹,子弹飞行后碰到物体自动销毁,同时有枪口火光与弹壳弹出特效。

  3. 枪械与 UI:实现了子弹计数、射击冷却、R 键换弹功能,换弹时会播放换弹动画与音效,换弹期间无法射击,子弹数量实时显示在 UI 上。

  4. 整体运行:各模块联动正常,无明显逻辑冲突,游戏可稳定运行,满足第一人称射击游戏的基础操作体验。

讨论与结论(对实验现象、实验故障及处理方法、实验中存在的问题等进行分析和讨论,对实验的进一步想法或改进意见。)

(一)实验现象分析

人物移动与跳跃通过 Rigidbody 物理组件实现,地面碰撞检测保证了只有在地面时才能跳跃,避免了空中多次跳跃的问题。

射击与换弹通过状态变量 isReloading 控制,换弹期间锁定射击功能,避免了操作逻辑冲突,UI 实时显示子弹数让玩家清晰了解当前弹药状态。

音效与特效的同步播放,让操作有明确反馈,提升了游戏的沉浸感。

(二)实验故障及处理方法

故障:换弹时仍能射击,导致子弹数异常。

处理:添加 isReloading 布尔变量,换弹时设为 true,换弹结束后设为 false,在射击判断中加入!isReloading 条件,解决了换弹期间可射击的问题。

故障:子弹 UI 不更新。

处理:在扣减子弹和换弹完成后主动调用 UpdateBulletUI() 方法,确保子弹数量变化时 UI 同步刷新。

故障:跳跃时人物浮空。

处理:通过 OnCollisionEnter 和 OnCollisionExit 检测地面碰撞,只有 isGround 为 true 时才能触发跳跃,保证了跳跃的合理性。

(三)实验存在的问题

性能方面:子弹采用实例化 + 销毁的方式,频繁射击时会产生一定性能开销。

体验方面:没有准星辅助瞄准,射击精度依赖玩家视角控制;没有敌人与伤害系统,游戏可玩性不足。

代码方面:脚本功能耦合度较高,不利于后续功能扩展。

(四)改进方向

引入对象池技术复用子弹对象,减少频繁创建销毁带来的性能消耗。

添加准星 UI、敌人 AI 与血条系统,提升游戏可玩性。

优化代码结构,将射击、换弹、UI 等功能拆分为独立模块,降低耦合度。

本次实验完成了第一人称射击游戏的核心基础功能,包括人物移动跳跃、子弹发射、换弹逻辑、音效特效与 UI 显示等模块,验证了 Unity 组件化开发与 C# 脚本驱动游戏逻辑的可行性。通过调试解决了碰撞检测、状态锁定、UI 同步等问题,积累了 3D 游戏开发的实操经验,为后续完善游戏功能、提升用户体验打下了基础和以后的虚拟现实开发学习。