尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Unity 3D游戏开发入门:C#脚本与物理碰撞实战指南

Unity 3D游戏开发入门:C#脚本与物理碰撞实战指南 1. 先搞清楚这个系列能帮你解决什么问题如果你刚开始接触 C# 和 Unity或者已经学了一些语法但不知道如何把它们串起来做成一个真正的 3D 游戏那么这个“从零构建五款完整 3D 游戏”的系列就是为你准备的。它最核心的价值不是教你零散的语法而是让你通过五个不同类型的游戏项目把 C# 基础逻辑、Unity 组件使用、物理系统、脚本协作这些知识点在一个个可运行、可扩展的工程里跑通。很多人学 C# 和 Unity 容易陷入两个误区要么只啃书本上的语法写一堆控制台程序对游戏开发没概念要么直接去抄复杂的项目代码看不懂出了问题也不知道从哪改起。这个系列的设计就是来打破这个僵局的——通过做游戏来学编程。五款游戏意味着你会反复练习变量、循环、条件判断、函数、类这些基础但每次练习的上下文都不一样理解会更深。同时你也能掌握 Unity 编辑器操作、预制体、刚体、碰撞器、输入控制这些必备技能。所以无论你是想转行游戏开发的学生还是想拓展技能栈的开发者这个系列都值得跟下来。它不是理论课是实打实的项目课。下面我就带你拆解一下上半部分的核心内容和上手前必须准备好的环境。1.1 五款游戏分别练什么虽然项目标题没具体说但根据“3D游戏逻辑 / 物理碰撞 / 脚本开发”这些关键词我们可以推断这五款游戏大概率是经典且教学意义明确的类型。上半部分可能包含以下两到三种3D 滚球或跑酷游戏这是 Unity 的经典入门项目。核心练的是刚体物理Rigidbody和力/速度的控制。你会学到如何用AddForce或直接修改velocity来控制一个球或角色移动如何处理跳跃以及如何通过碰撞检测来收集物品或触发死亡。第一人称或第三人称射击/探索游戏这类项目重点练摄像机控制和角色控制器。你会接触到CharacterController组件或者自己用刚体模拟移动学会处理鼠标视角旋转Input.GetAxis(“Mouse X/Y”)并实现简单的交互比如开门、拾取。物理解谜或破坏类游戏比如推箱子、打砖块或者“愤怒的小鸟”风格的弹射游戏。这类项目深度练习碰撞检测和关节Joint的使用。你会明白OnCollisionEnter和OnTriggerEnter的区别并学会用代码控制物体的连接与分离。每一款游戏都是一个独立的工程但知识点是递进和互补的。做完一个你对 C# 在 Unity 里该怎么写会有一个非常具体的画面。1.2 你需要提前准备什么在开始写第一行代码之前先把环境搭好。别小看这一步很多新手卡住就是因为环境没配对。Unity Hub Unity Editor这是必须的。去 Unity 官网下载 Unity Hub然后通过 Hub 安装一个 Long-Term Support (LTS) 版本的 Unity Editor。对于初学者2022.3 LTS或2023.3 LTS都是稳定且社区资源丰富的选择。安装时至少勾选“Windows Build Support”如果你是 Windows 用户或“macOS Build Support”以及“WebGL Build Support”如果你想发布到网页。至于热议的“Unity WebGL 背景透明”等问题那是后话初期不用管。代码编辑器Visual Studio 是官方默认且集成最好的安装 Unity 时通常会一起装上。如果你更喜欢轻量级的Visual Studio Code (VSCode)也是绝佳选择但需要额外配置。你需要安装 VSCode然后从扩展商店安装“C#”扩展和“Unity”扩展。之后在 Unity Editor 的Edit - Preferences - External Tools中将外部脚本编辑器设置为 VSCode。这样在 Unity 里双击 C# 脚本就能用 VSCode 打开了。基础概念确保你理解一些最基础的术语比如“场景 (Scene)”、“游戏对象 (GameObject)”、“组件 (Component)”、“预制体 (Prefab)”、“变换 (Transform)”。如果不懂打开 Unity 新建一个空项目随便拖几个立方体看看 Inspector 面板半小时就能有个直观感受。环境准备好后我建议先新建一个空项目不跟教程自己试着创建一个立方体挂个脚本让它旋转。这个“Hello World”能帮你验证环境是否真正可用。2. 第一个项目搭建场景与理解游戏对象我们假设第一个项目是一个简单的 3D 滚球收集硬币游戏。这是理解 Unity 工作流最直接的起点。2.1 创建基础场景地板、玩家和硬币打开 Unity新建一个 3D 项目Core 或 URP 模板都可以初学者用 Core 更纯粹。创建地板在 Hierarchy 窗口右键 - 3D Object - Plane。这将是我们的地面。在 Inspector 里你可以修改它的 Scale比如放大到 10倍或者给它一个绿色的材质来区分。创建玩家球同样右键 - 3D Object - Sphere。重命名为 “Player”。调整它的位置让它悬在地板之上。添加物理属性选中 Player 球体在 Inspector 中点击 “Add Component”搜索并添加Rigidbody组件。这样球体就会受到重力影响掉下去。你可能会发现球直接穿过了地板这是因为 Plane 默认没有碰撞器。选中 Plane确保它有Mesh Collider组件默认就有。现在运行游戏球应该会掉落在平面上。创建可收集物右键 - 3D Object - Cube。重命名为 “Coin”。缩小它Scale 设为 0.5, 0.5, 0.5。为了美观可以给它加个金色的材质。关键一步将这个 Cube 从 Hierarchy 拖到 Project 窗口的 Assets 文件夹里这会创建一个Prefab预制体。预制体是你的蓝图之后可以在场景里复制很多个实例修改预制体所有实例都会更新。创建完预制体后可以删除 Hierarchy 里那个原始的 Coin 立方体。现在你的场景里有一个带刚体的球玩家和一个地板。游戏对象GameObject就是场景中的一切而组件如 Transform, Rigidbody, Mesh Collider则定义了它们的行为和属性。2.2 编写第一个 C# 脚本让玩家动起来在 Project 窗口的 Assets 文件夹右键 - Create - C# Script命名为PlayerController。双击用 VSCode 或 Visual Studio 打开你会看到一个默认的类。我们修改它实现用键盘控制球体移动。using UnityEngine; public class PlayerController : MonoBehaviour { // 声明一个公共变量用于控制移动速度 // 在Inspector中可以直接修改这个值无需改代码 public float moveSpeed 10f; // 声明一个私有变量引用刚体组件 private Rigidbody rb; // Start 在游戏开始时调用一次用于初始化 void Start() { // 获取挂载在同一游戏对象上的Rigidbody组件 rb GetComponentRigidbody(); } // FixedUpdate 在固定的物理时间步长调用适合处理物理运动 void FixedUpdate() { // 获取键盘的水平A/D或左右箭头和垂直W/S或上下箭头输入 float moveHorizontal Input.GetAxis(Horizontal); float moveVertical Input.GetAxis(Vertical); // 创建一个三维向量来表示移动方向 Vector3 movement new Vector3(moveHorizontal, 0.0f, moveVertical); // 给刚体施加一个力实现移动 rb.AddForce(movement * moveSpeed); } }保存脚本回到 Unity。将PlayerController脚本从 Project 窗口拖拽到 Hierarchy 中的 Player 球体上。运行游戏按下 WASD 或方向键球体应该会受力移动。为什么用FixedUpdate和AddForceUpdate每帧调用帧率不稳定会导致物理运动不平滑。FixedUpdate调用间隔固定与物理引擎同步是做物理运动的首选。AddForce是给刚体施加力模拟真实的物理运动有惯性。你也可以直接修改rb.velocity移动会更直接、更“飘”取决于你想要的手感。2.3 完善硬币碰撞检测与销毁现在来处理硬币。我们需要做两件事1. 让硬币能被收集碰撞后消失2. 给玩家一个分数。为硬币预制体添加碰撞和脚本在 Project 窗口选中 Coin 预制体确保它有Box Collider组件。在 Box Collider 组件上勾选Is Trigger。这意味着这个碰撞器将作为一个“触发器”物理引擎不会计算它的物理碰撞比如阻挡玩家但会检测到有其他碰撞器进入它。为 Coin 预制体创建一个新脚本Coin并挂载上去。using UnityEngine; public class Coin : MonoBehaviour { // 当有其他碰撞器进入这个触发器时调用 private void OnTriggerEnter(Collider other) { // 检查进入触发器的对象是否是玩家通过Tag if (other.CompareTag(Player)) { // 通知游戏管理器或其他脚本增加分数 // 这里我们先简单调用一个静态方法后续优化 GameManager.Instance.AddScore(10); // 销毁这个硬币实例 Destroy(gameObject); } } }设置玩家标签在 Hierarchy 中选中 Player 球体在 Inspector 顶部的 Tag 下拉菜单中选择 “Add Tag…”创建一个新标签 “Player”。然后再次选中 Player将其 Tag 设置为 “Player”。创建简单的游戏管理器在场景中创建一个空的 GameObject重命名为 “GameManager”。为其创建脚本GameManager。using UnityEngine; using UnityEngine.UI; // 如果需要UI public class GameManager : MonoBehaviour { // 单例模式方便其他脚本访问 public static GameManager Instance; public int score 0; public Text scoreText; // 在Inspector中关联一个UI Text组件 void Awake() { if (Instance null) Instance this; else Destroy(gameObject); } public void AddScore(int points) { score points; UpdateScoreUI(); } void UpdateScoreUI() { if (scoreText ! null) scoreText.text Score: score; } }创建 UI 显示分数在 Hierarchy 右键 - UI - Text – TextMeshPro如果第一次使用会导入 TMP 资源。将 Canvas 下的 Text 对象重命名为 “ScoreText”调整位置和大小。将 GameManager 脚本中的scoreText变量拖拽绑定到这个 Text 对象。在场景中放置硬币从 Project 窗口将 Coin 预制体拖入 Scene 视图多复制几个摆放在地板的不同位置。现在运行游戏控制球体去触碰硬币硬币会消失并且屏幕上的分数会增加。至此你完成了第一个迷你游戏的核心循环控制 - 移动 - 碰撞检测 - 状态更新分数 - 物体管理销毁。3. 深入物理碰撞与输入处理第一个项目跑通后你会发现很多细节需要打磨。比如球太滑、碰撞不精确、输入不够灵活。这部分我们来深入。3.1 刚体参数调优让手感更好选中 Player 球体查看它的 Rigidbody 组件有几个关键参数影响手感Mass质量影响惯性。质量越大启动和停止越慢。对于玩家角色一般不用太大。Drag阻力空气阻力。增大它可以让物体更快停下防止“溜冰”感。可以设为 1-5 试试。Angular Drag角阻力旋转阻力。对于球体可以适当增加防止它乱转。Constraints约束可以冻结某个轴的位置或旋转。比如在一个 2.5D 游戏中你可能想冻结 Y 轴位置和 X、Z 轴的旋转。一个常见的手感优化是使用AddForce时采用ForceMode.VelocityChange或ForceMode.Impulse它们忽略质量提供更直接的响应。你可以修改PlayerController的AddForce行rb.AddForce(movement * moveSpeed, ForceMode.VelocityChange);多试试不同的力和模式组合找到最适合你游戏的手感。3.2 碰撞检测的两种方式Collider 与 Trigger你已经用到了OnTriggerEnter。这里系统梳理一下普通碰撞器Is Trigger false物理引擎会计算碰撞反应反弹、阻挡。使用OnCollisionEnter(Collision collisionInfo)函数来响应。可以通过collisionInfo.rigidbody获取对方刚体collisionInfo.contacts获取碰撞点。适用于墙壁、地面、需要物理交互的物体。触发器Is Trigger true物理引擎忽略其碰撞体积物体可以穿透。使用OnTriggerEnter(Collider other)函数来响应。适用于收集品、陷阱区域、检测区域等。一个关键点触发器检测要求至少一方有刚体。通常是把刚体放在运动的物体上如玩家触发器放在静态物体上如硬币。3.3 更灵活的输入管理目前我们直接用Input.GetAxis。Unity 新的输入系统Input System Package更强大支持手柄、重映射等但对于初学者旧系统足够。你可以创建自定义输入轴 打开Edit - Project Settings - Input Manager你可以看到 Horizontal 和 Vertical 的定义。你可以复制它们创建新的轴比如 “Jump”绑定空格键。然后在脚本中用Input.GetButtonDown(“Jump”)来检测跳跃。跳跃的实现示例在PlayerController的FixedUpdate外添加void Update() // 检测按键用Update更及时 { if (Input.GetButtonDown(Jump)) { // 检查是否在地面上简单版射线检测 if (Physics.Raycast(transform.position, Vector3.down, 1.1f)) { rb.AddForce(Vector3.up * 10, ForceMode.Impulse); } } }这里用到了Physics.Raycast进行射线检测判断玩家是否在地面上这是实现可靠跳跃的常用方法。4. 项目架构与脚本通信优化当游戏功能变多把所有代码写在一个脚本里会变得混乱。我们需要更好的架构。4.1 使用单例模式与事件中心之前的GameManager用了简单的单例模式。对于小型项目够用。但更好的方式是使用事件Event来解耦脚本。例如硬币被收集时它不需要知道谁在管理分数它只需要广播一个“硬币被收集”的事件。GameManager或其他需要响应的脚本如音效管理器、UI管理器来订阅这个事件。C# 的Action或UnityEvent可以实现// 在某个全局可访问的类中如EventManager public static class GameEvents { public static Actionint OnCoinCollected; // 参数可以是分数值 } // 在Coin脚本中触发事件 private void OnTriggerEnter(Collider other) { if (other.CompareTag(Player)) { GameEvents.OnCoinCollected?.Invoke(10); // 触发事件 Destroy(gameObject); } } // 在GameManager或其他脚本中订阅事件 void OnEnable() { GameEvents.OnCoinCollected AddScore; } void OnDisable() { GameEvents.OnCoinCollected - AddScore; } void AddScore(int points) { /* ... */ }这样硬币脚本和分数管理脚本就完全解耦了新增一个播放收集音效的脚本也变得很容易。4.2 对象池管理优化硬币的生成与回收当前硬币被收集后直接Destroy。如果游戏需要大量、频繁生成和销毁硬币比如射击游戏的子弹频繁的Instantiate和Destroy会产生垃圾回收GC可能导致卡顿。对象池Object Pooling是解决方案。思路是游戏开始时预先创建一堆硬币或任何可重用对象并禁用它们放在一个“池子”里。需要时从池中取一个激活它用完后不是销毁而是禁用它并放回池中。这是一个简化的硬币对象池示例public class CoinPool : MonoBehaviour { public GameObject coinPrefab; public int poolSize 20; private QueueGameObject coinPool new QueueGameObject(); void Start() { for (int i 0; i poolSize; i) { GameObject coin Instantiate(coinPrefab); coin.SetActive(false); coinPool.Enqueue(coin); } } public GameObject GetCoin() { if (coinPool.Count 0) { GameObject coin coinPool.Dequeue(); coin.SetActive(true); return coin; } else { // 池子空了可以动态扩容 GameObject coin Instantiate(coinPrefab); return coin; } } public void ReturnCoin(GameObject coin) { coin.SetActive(false); coinPool.Enqueue(coin); } }然后在Coin脚本中被收集时调用CoinPool.Instance.ReturnCoin(this.gameObject)而不是Destroy。生成新硬币时也通过对象池获取。对于第一个小游戏这可能过度设计但这是非常重要的性能优化思想在后续更复杂的项目中会用到。4.3 配置化与数据驱动不要把所有数值都硬编码在脚本里。比如硬币的分数值、玩家的移动速度、跳跃力度。你可以创建 ScriptableObject 来管理游戏数据或者简单地在 Inspector 中配置公共变量。在PlayerController中我们把moveSpeed声明为public就是为了能在 Inspector 中随时调整而无需重新编译代码。对于更复杂的数据如关卡列表、敌人属性表ScriptableObject 是更好的选择。它本质上是一个存储在项目中的资源文件可以在多个对象间共享和复用。上半部分的核心就是通过这样一个完整的迷你项目闭环让你把 C# 语法变量、函数、类、条件判断、循环和 Unity 的核心概念GameObject, Component, Prefab, Rigidbody, Collider, Trigger以及基本工作流创建、脚本、调试、优化串联起来。当你成功让球滚动起来、收集硬币、看到分数变化时你已经跨过了从“学语法”到“做东西”最关键的一步。接下来的项目会在这些基础上引入更复杂的控制如摄像机跟随、更多的物理交互如使用关节、状态管理如玩家生命值、游戏关卡以及基本的 UI 交互。每一步都会巩固已有知识并引入一两个新概念像搭积木一样最终让你有能力独立设计和实现自己的 3D 游戏原型。记住遇到问题先自己查Unity 手册、API 文档是最好老师再参考社区这样成长最快。
返回列表