12.2 3D 射击游戏 12.2 3D 射击游戏:从零开始构建你的第一人称射击体验 欢迎来到 3D 射击游戏开发的精彩世界!在本章节中,我们将深入 Unity3D 引擎,从零开始构建一个基础但功能完善的第一人称射击游戏。你将学习到射击游戏开发的核心概念,并亲手编写代码,实现角色控制、武器系统、射击机制、目标交互以及简单的用户界面。 本章节旨在为你打下坚实的基础,让你能够理解 3D 射击游戏的开发流程,并为你后续更复杂的项目开发做好准备。让我们开始吧! 12.2.1 项目准备与场景搭建 在开始编写代码之前,我们需要先进行项目的准备工作,并搭建一个基础的游戏场景。 1. 创建 Unity 项目: 打开 Unity Hub,点击 “新建项目”。
欢迎来到 3D 射击游戏开发的精彩世界!在本章节中,我们将深入 Unity3D 引擎,从零开始构建一个基础但功能完善的第一人称射击游戏。你将学习到射击游戏开发的核心概念,并亲手编写代码,实现角色控制、武器系统、射击机制、目标交互以及简单的用户界面。
本章节旨在为你打下坚实的基础,让你能够理解 3D 射击游戏的开发流程,并为你后续更复杂的项目开发做好准备。让我们开始吧!
在开始编写代码之前,我们需要先进行项目的准备工作,并搭建一个基础的游戏场景。
1. 创建 Unity 项目:
打开 Unity Hub,点击 “新建项目”。
选择 “3D” 模板,并为你的项目命名 (例如:My3D射击游戏),选择合适的项目位置,点击 “创建”。
2. 导入必要的资源 (可选):
为了快速开始,我们可以导入一些免费资源来搭建场景和角色。Unity Asset Store 上有很多免费的资源包,例如:
Standard Assets: Unity 官方提供的资源包,包含角色控制器、摄像机控制、物理组件等。你可以从 Package Manager 中导入。
ProBuilder: 用于快速创建和编辑游戏关卡几何体的工具。同样可以在 Package Manager 中找到。
免费的角色、武器模型: 在 Asset Store 中搜索 "free character", "free weapon" 等关键词,可以找到很多免费的模型资源。
当然,你也可以使用 Unity 的基础几何体和材质来搭建场景,这取决于你的需求和资源情况。
3. 场景搭建:
创建地面: 在 Hierarchy 窗口中,右键点击 -> 3D Object -> Plane。将 Plane 命名为 Ground,并适当调整 Scale 属性,使其足够大作为游戏场景的地面。
创建环境 (可选): 可以使用 ProBuilder 或其他方式创建一些简单的墙壁、箱子等作为场景环境,增加游戏的趣味性。你也可以简单地添加一些 3D Object -> Cube 来充当障碍物。
添加光源: 确保场景中有足够的光源。默认场景中已经有一个 Directional Light。你可以根据需要调整光源的类型、颜色和强度。
创建玩家角色:
如果导入了 Standard Assets,可以直接使用 Assets/Standard Assets/Characters/FirstPersonCharacter/Prefabs/FPSController 预制体,将其拖拽到场景中。
如果手动创建,可以创建一个 Capsule 3D Object 作为角色模型,并添加 CharacterController 组件来实现基本的角色移动。 为了简化,我们这里假设你使用了 FPSController 预制体,或者已经创建了一个带有 CharacterController 的角色。
4. 调整摄像机:
FPSController 预制体已经包含了摄像机和第一人称视角控制。如果你手动创建角色,需要创建一个 Camera,并将其放置在角色模型的头部位置,并编写脚本控制摄像机的旋转,实现鼠标控制视角的功能。至此,我们已经完成了项目的基本准备和场景搭建。接下来,我们将进入代码实践环节。
角色移动和视角控制是第一人称射击游戏的基础。我们需要编写脚本来实现玩家的移动和视角旋转。
1. 角色移动脚本 (PlayerMovement.cs):
创建一个新的 C# 脚本,命名为 PlayerMovement.cs,并将其附加到你的玩家角色对象上 (例如 FPSController 或你手动创建的角色)。
using UnityEngine; public class PlayerMovement : MonoBehaviour { public float moveSpeed = 5f; // 移动速度 public float gravity = -9.81f; // 重力加速度 public Transform groundCheck; // 用于检测地面的 Transform public float groundDistance = 0.4f; // 检测地面的距离 public LayerMask groundMask; // 地面图层 private CharacterController controller; // 角色控制器组件 private Vector3 velocity; // 角色速度 private bool isGrounded; // 是否在地面上 void Start() { controller = GetComponent<CharacterController>(); if (controller == null) { Debug.LogError("PlayerMovement script requires a CharacterController component."); enabled = false; // 禁用脚本 } } void Update() { // 检测是否在地面上 isGrounded = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask); if (isGrounded && velocity.y < 0) { velocity.y = -2f; // 保持角色贴地,防止无限加速下落 } // 获取玩家输入 float x = Input.GetAxis("Horizontal"); // 水平输入 (A, D 或 左右方向键) float z = Input.GetAxis("Vertical"); // 垂直输入 (W, S 或 上下方向键) // 计算移动方向 Vector3 moveDirection = transform.right * x + transform.forward * z; // 移动角色 controller.Move(moveDirection * moveSpeed * Time.deltaTime); // 应用重力 velocity.y += gravity * Time.deltaTime; controller.Move(velocity * Time.deltaTime); } }
代码详解:
moveSpeed: 控制角色移动速度的变量。
gravity: 模拟重力效果。
groundCheck、groundDistance、groundMask: 用于检测角色是否在地面上。groundCheck 是一个 Transform 对象,你需要手动在角色模型上创建一个空物体作为 groundCheck,并将其拖拽到脚本的 groundCheck 属性中。groundMask 用于指定哪些图层被认为是地面。
CharacterController: Unity 提供的角色控制器组件,用于处理角色移动、碰撞和重力。
velocity: 角色当前的速度向量。
isGrounded: 布尔值,表示角色是否在地面上。
Physics.CheckSphere(): 检测在 groundCheck 位置周围半径为 groundDistance 的球形区域内是否有与 groundMask 图层碰撞器相交的物体,用于判断是否在地面。
Input.GetAxis(): 获取玩家的输入。"Horizontal" 和 "Vertical" 是 Unity 默认的输入轴,对应键盘的 A, D, W, S 键和方向键。
transform.right 和 transform.forward: 角色的局部坐标系的右方向和前方向。用于将输入转换为世界坐标系下的移动方向。
controller.Move(): 使用 CharacterController 组件的 Move() 方法来移动角色。Time.deltaTime 用于确保移动速度与帧率无关。
配置 PlayerMovement 脚本:
在你的玩家角色对象上创建一个空物体,命名为 GroundCheck,并将其放置在角色模型的脚底位置。
创建一个新的 Layer,命名为 Ground,并将你的地面对象 (Plane) 的 Layer 设置为 Ground。
选择你的玩家角色对象,在 Inspector 窗口中找到 PlayerMovement 脚本组件。
将 GroundCheck 对象拖拽到 Ground Check 属性中。
将 Ground Layer 选择到 Ground Mask 属性中。
调整 Move Speed 和 Ground Distance 等参数,直到角色移动感觉良好。
2. 视角控制脚本 (CameraController.cs):
创建一个新的 C# 脚本,命名为 CameraController.cs,并将其附加到你的摄像机对象上 (例如 Main Camera,如果使用的是 FPSController 预制体,则脚本应附加到 FPSController 对象上,并控制其子对象 Main Camera 的旋转)。
using UnityEngine; public class CameraController : MonoBehaviour { public float mouseSensitivity = 100f; // 鼠标灵敏度 public Transform playerBody; // 玩家身体 Transform (用于控制水平旋转) float xRotation = 0f; // X 轴旋转角度 void Start() { // 锁定鼠标光标到游戏窗口中心,并隐藏光标 Cursor.lockState = CursorLockMode.Locked; } void Update() { // 获取鼠标输入 float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime; float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime; // 计算垂直旋转角度 (限制上下视角范围) xRotation -= mouseY; xRotation = Mathf.Clamp(xRotation, -90f, 90f); // 限制上下视角在 -90 到 90 度之间 // 应用垂直旋转到摄像机自身 transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f); // 应用水平旋转到玩家身体 (控制角色水平旋转) playerBody.Rotate(Vector3.up * mouseX); } }
代码详解:
mouseSensitivity: 控制鼠标灵敏度的变量。
playerBody: 玩家身体的 Transform 对象。我们需要手动将玩家角色对象 (例如 FPSController 或你手动创建的角色) 拖拽到这个属性中。
xRotation: 摄像机在 X 轴 (水平轴) 上的旋转角度。
Cursor.lockState = CursorLockMode.Locked;: 锁定鼠标光标到游戏窗口中心,并隐藏光标,提供更好的第一人称视角体验。
Input.GetAxis("Mouse X") 和 Input.GetAxis("Mouse Y"): 获取鼠标的水平和垂直移动量。
Mathf.Clamp(): 限制 xRotation 的值在 -90 到 90 度之间,防止视角过度旋转。
transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f);: 将计算出的垂直旋转角度应用到摄像机的局部旋转。
playerBody.Rotate(Vector3.up * mouseX);: 将水平鼠标移动量应用到玩家身体的 Y 轴旋转,实现角色水平旋转。
配置 CameraController 脚本:
选择你的摄像机对象 (例如 Main Camera),在 Inspector 窗口中找到 CameraController 脚本组件。
将你的玩家角色对象 (例如 FPSController 或你手动创建的角色) 拖拽到 Player Body 属性中。
调整 Mouse Sensitivity 参数,直到视角控制感觉舒适。
完成以上步骤后,运行游戏,你应该可以使用 WASD 键控制角色移动,并使用鼠标控制视角旋转了。
接下来,我们将实现武器系统和射击机制。为了简化,我们先实现一个简单的单发武器。
1. 创建武器模型 (可选):
你可以从 Asset Store 导入一个武器模型,或者使用简单的 Cube 或 Cylinder 3D Object 创建一个简单的武器模型。将武器模型作为玩家角色的子对象,并调整其位置和旋转,使其看起来像玩家手中拿着武器。
2. 创建武器脚本 (Weapon.cs):
创建一个新的 C# 脚本,命名为 Weapon.cs,并将其附加到你的武器模型对象上。
using UnityEngine; public class Weapon : MonoBehaviour { public Transform firePoint; // 子弹发射点 public GameObject bulletPrefab; // 子弹预制体 public float fireForce = 20f; // 子弹发射力量 public float fireRate = 0.5f; // 射击频率 (每秒射击次数) private float nextFireTime = 0f; // 下次可以射击的时间 void Update() { if (Input.GetButtonDown("Fire1") && Time.time >= nextFireTime) // 检测鼠标左键按下 { Shoot(); nextFireTime = Time.time + 1f / fireRate; // 更新下次射击时间 } } void Shoot() { // 实例化子弹预制体 GameObject bullet = Instantiate(bulletPrefab, firePoint.position, firePoint.rotation); Rigidbody bulletRb = bullet.GetComponent<Rigidbody>(); if (bulletRb != null) { // 给子弹添加推力 bulletRb.AddForce(firePoint.forward * fireForce, ForceMode.Impulse); } // 子弹销毁 (可选,例如 3 秒后自动销毁) Destroy(bullet, 3f); } }
代码详解:
firePoint: 子弹发射点的 Transform 对象。你需要手动在武器模型上创建一个空物体作为 firePoint,并将其放置在枪口位置,并调整其方向 (Forward 方向应指向射击方向)。
bulletPrefab: 子弹的预制体。你需要创建一个子弹预制体 (例如使用 Sphere 3D Object 作为子弹模型,并添加 Rigidbody 和 Collider 组件),并将其拖拽到脚本的 Bullet Prefab 属性中。
fireForce: 子弹发射的力度。
fireRate: 射击频率,控制武器的射速。
nextFireTime: 记录下次可以射击的时间,用于控制射击频率。
Input.GetButtonDown("Fire1"): 检测鼠标左键是否被按下。"Fire1" 是 Unity 默认的射击输入按钮。
Time.time >= nextFireTime: 判断当前时间是否已经超过下次可以射击的时间,用于控制射击频率。
Instantiate(bulletPrefab, firePoint.position, firePoint.rotation): 实例化子弹预制体。
bulletRb.AddForce(firePoint.forward * fireForce, ForceMode.Impulse): 给子弹添加推力,使其向前飞行。ForceMode.Impulse 表示立即施加冲量。
Destroy(bullet, 3f): 销毁子弹对象,防止场景中子弹数量过多,影响性能。
3. 创建子弹预制体:
在 Project 窗口中,右键点击 -> Create -> 3D Object -> Sphere。将 Sphere 命名为 Bullet。
为 Bullet 对象添加 Rigidbody 组件和 Sphere Collider 组件。
调整 Bullet 的 Scale 和 Collider 的 Radius,使其大小合适。
创建一个新的 Material,例如 BulletMaterial,并将其应用到 Bullet 对象上,可以调整材质颜色使其更明显。
将 Bullet 对象拖拽到 Project 窗口中,创建一个预制体 BulletPrefab。
删除场景中的 Bullet 对象。
4. 配置 Weapon 脚本:
在你的武器模型对象上创建一个空物体,命名为 FirePoint,并将其放置在枪口位置,调整其位置和方向。
选择你的武器模型对象,在 Inspector 窗口中找到 Weapon 脚本组件。
将 FirePoint 对象拖拽到 Fire Point 属性中。
将 BulletPrefab 预制体拖拽到 Bullet Prefab 属性中。
调整 Fire Force 和 Fire Rate 等参数,直到射击感觉良好。
武器射击流程 Mermaid 图:
运行游戏,现在你应该可以使用鼠标左键射击了。子弹会从枪口射出,并向前飞行。
为了让射击更有意义,我们需要创建一些目标,并实现子弹击中目标时的反馈。
1. 创建目标对象:
可以使用 Cube 或其他 3D Object 创建目标模型。例如,创建一个 Cube,命名为 Target。
为 Target 对象添加 Box Collider 组件。
创建一个新的 Material,例如 TargetMaterial,并将其应用到 Target 对象上,使其与场景区分开。
2. 创建目标脚本 (Target.cs):
创建一个新的 C# 脚本,命名为 Target.cs,并将其附加到你的目标对象上。
using UnityEngine; public class Target : MonoBehaviour { public float health = 50f; // 目标生命值 public void TakeDamage(float damageAmount) { health -= damageAmount; if (health <= 0f) { Die(); } } void Die() { Destroy(gameObject); // 销毁目标对象 Debug.Log("Target Destroyed!"); // 可选:在控制台输出信息 } }
代码详解:
health: 目标的生命值。
TakeDamage(float damageAmount): 接收伤害的函数。当目标被击中时,调用此函数来减少目标的生命值。
Die(): 当目标生命值降为 0 或更低时,调用此函数来销毁目标对象。
3. 修改子弹脚本 (Bullet.cs):
创建一个新的 C# 脚本,命名为 Bullet.cs,并将其附加到你的子弹预制体 (BulletPrefab) 上,替换之前 Weapon.cs 中创建的临时子弹脚本 (如果之前有的话)。
using UnityEngine; public class Bullet : MonoBehaviour { public float damage = 10f; // 子弹伤害值 void OnCollisionEnter(Collision collision) { Target target = collision.gameObject.GetComponent<Target>(); if (target != null) { // 击中目标 target.TakeDamage(damage); } // 子弹击中任何物体后销毁自身 Destroy(gameObject); } }
代码详解:
damage: 子弹的伤害值。
OnCollisionEnter(Collision collision): Unity 的碰撞回调函数,当子弹与其他物体发生碰撞时被调用。
collision.gameObject.GetComponent<Target>(): 尝试获取碰撞物体的 Target 组件。
target.TakeDamage(damage): 如果碰撞物体有 Target 组件,则调用其 TakeDamage() 函数,造成伤害。
Destroy(gameObject): 子弹击中物体后销毁自身。
配置目标和子弹脚本:
将 Target.cs 脚本附加到你的目标对象 (Target) 上。
将 Bullet.cs 脚本附加到你的子弹预制体 (BulletPrefab) 上。
调整 Target 脚本中的 Health 和 Bullet 脚本中的 Damage 参数,以平衡游戏难度。
子弹碰撞检测流程 Mermaid 图:
现在运行游戏,射击目标,你应该可以看到目标在被击中后会被销毁。
为了完善游戏体验,我们可以添加一个简单的用户界面 (UI) 来显示游戏信息,例如得分、剩余弹药等。这里我们先实现一个简单的得分显示。
1. 创建 UI Text 对象:
在 Hierarchy 窗口中,右键点击 -> UI -> Text - TextMeshPro (如果你的项目没有导入 TextMeshPro,则选择 Text - Legacy)。
将 Text 对象命名为 ScoreText。
调整 ScoreText 的位置、大小、字体和颜色,使其在游戏界面上清晰可见。
2. 创建游戏管理器脚本 (GameManager.cs):
创建一个新的 C# 脚本,命名为 GameManager.cs,并创建一个空物体 GameManager,将脚本附加到 GameManager 对象上。
using UnityEngine; using TMPro; // 如果使用 TextMeshPro,需要引入此命名空间 public class GameManager : MonoBehaviour { public int score = 0; // 游戏得分 public TextMeshProUGUI scoreText; // 显示得分的 TextMeshProUGUI 对象 (或 Text 对象) public static GameManager Instance { get; private set; } // 单例模式 void Awake() { // 单例模式实现 if (Instance == null) { Instance = this; // DontDestroyOnLoad(gameObject); // 如果需要在场景切换时保持 GameManager 对象,可以添加此行 } else { Destroy(gameObject); return; } } void Start() { UpdateScoreText(); // 初始化显示得分 } public void AddScore(int points) { score += points; UpdateScoreText(); } void UpdateScoreText() { if (scoreText != null) { scoreText.text = "Score: " + score; } else { Debug.LogError("ScoreText is not assigned in GameManager script."); } } }
代码详解:
score: 游戏得分变量。
scoreText: 用于显示得分的 TextMeshProUGUI 对象 (或 Text 对象)。你需要手动将场景中的 ScoreText 对象拖拽到脚本的 Score Text 属性中。
Instance: 使用单例模式,确保在游戏中只有一个 GameManager 实例。
Awake(): 单例模式的初始化代码。
Start(): 在游戏开始时调用 UpdateScoreText() 初始化显示得分。
AddScore(int points): 增加得分的函数。当目标被销毁时,可以调用此函数来增加得分。
UpdateScoreText(): 更新 UI Text 对象显示的得分。
4. 修改 Target.cs 脚本,增加得分:
修改 Target.cs 脚本的 Die() 函数,在目标销毁时增加得分。
// ... (Target.cs 脚本的其他代码) void Die() { Destroy(gameObject); // 销毁目标对象 Debug.Log("Target Destroyed!"); // 增加得分 if (GameManager.Instance != null) { GameManager.Instance.AddScore(10); // 例如,每个目标增加 10 分 } }
配置 GameManager 脚本:
创建一个空物体,命名为 GameManager。
将 GameManager.cs 脚本附加到 GameManager 对象上。
选择 GameManager 对象,在 Inspector 窗口中找到 GameManager 脚本组件。
将场景中的 ScoreText 对象拖拽到 Score Text 属性中。
现在运行游戏,射击目标,你应该可以看到屏幕上的得分在增加。
恭喜你!你已经完成了本章节的学习,并成功构建了一个基础的 3D 射击游戏。在本章节中,我们学习了:
项目准备与场景搭建: 创建 Unity 项目,导入资源,搭建游戏场景。
角色移动与视角控制: 使用 CharacterController 组件和脚本实现角色移动和鼠标控制视角。
武器系统与射击机制: 创建武器脚本,实现子弹发射和射击频率控制。
目标交互与命中检测: 创建目标脚本,实现目标生命值、伤害接收和销毁,以及子弹的碰撞检测和伤害应用。
用户界面 (UI) 与游戏状态管理 (简易实现): 创建 UI Text 对象显示得分,并使用 GameManager 脚本管理游戏状态和得分。
扩展方向:
更复杂的武器系统: 实现多种武器切换、武器属性 (射速、伤害、弹匣容量等)、武器动画、武器音效等。
更智能的敌人 AI: 实现敌人巡逻、追踪玩家、攻击玩家等行为。
更丰富的游戏场景: 设计更复杂的关卡,添加更多的环境元素和互动元素。
更完善的用户界面: 添加血量显示、弹药显示、准星、游戏菜单、暂停菜单等。
音效和特效: 为游戏添加音效 (射击音效、爆炸音效、环境音效等) 和特效 (粒子特效、后处理特效等),提升游戏体验。
多人游戏: 使用 Unity Networking 或其他网络库,实现多人联机对战功能。
本章节只是一个开始,3D 射击游戏开发的世界充满无限可能。希望本章节的内容能够为你提供一个良好的起点,并激发你对游戏开发的热情。继续学习和实践,你一定能够创造出更精彩的游戏作品!