
Unity 3D开放世界生存游戏开发全流程第一篇——从零搭建场景与角色控制作为独立开发者或小团队想挑战开放世界生存游戏这个品类通常会卡在三件事项目规模撑不住、性能优化没有头绪、资源准备无从下手。尤其是电脑配置不算高的情况下很多新手启动 Unity 编辑器加载大场景就卡顿更别说流畅开发了。这篇文章会把开放世界生存游戏的第一阶段开发完整拆开从工程创建、地形搭建、角色控制、摄像机跟随到基础的生存数值系统都会给出可直接复制的脚本和配置思路。内容适合两类读者刚接触 Unity 的开发者按文章走一遍就能搭出可运行的小型生存游戏原型。有一定 Unity 基础但想把项目做得规范、可扩展、后续能继续叠玩法的同学。本文使用的示例将从中等配置电脑的开发场景出发尽量选择性能友好的渲染管线和资源方案避免一上来就堆高精度模型和高开销特效。1. 开放世界生存游戏到底由哪些核心模块组成1.1 生存游戏不是“能走路就能叫生存”很多新手在动手前会误以为生存游戏就等于一张大地图加一个角色。实际上“生存感”主要来自几个系统的循环反馈角色状态系统血量、饥饿值、体力值、温度等。资源采集与合成从树木、矿石、动物身上获取材料进而合成工具或建筑。时间与天气白天黑夜影响视野和刷怪行为天气影响移动或状态。敌人与威胁AI 生物的巡逻、追击、攻击逻辑决定了玩家是否时刻处于紧张状态。建造与经营让玩家的活动有长期目标而不是单纯在场景里闲逛。对于一个入门阶段的教程项目我们不需要一口气实现全部模块但架构设计上必须按可扩展的方式去搭。否则做到第七八大章时代码会越来越难以维护。1.2 “中配”开发意味着什么中配开发指的是一台中等性能的电脑上既要保证 Unity 编辑器能流畅运行又要让打包出来的游戏在同类配置上可运行。通常需要在以下方面做取舍渲染管线优先选 URP通用渲染管线而不是 HDRP。URP 在移动端和低配置 PC 上的表现更友好。场景灯光尽量用烘焙光照减少实时阴影数量。地形植被使用数量受控开启细节剔除。尽量不加载超大贴图纹理压缩格式按目标平台设置。这篇文章的环境搭建部分会把这些取舍落实到具体配置中。注意这不是一条“穷苦开发路线”而是相当多商业项目实际采用的做法。2. 环境准备Unity 版本选择与工程配置2.1 安装 Unity Hub 与编辑器版本在 Unity 官网下载 Unity Hub这是官方推荐的版本管理工具。安装完成后在 Hub 的“安装”页面选择对应 Unity 版本。本文下方的代码和配置以 Unity 2022.3 LTS 长期支持版本为例。LTS 版本稳定性更高适合做周期较长的项目。如果你的电脑安装的是 Unity 2021 或 Unity 6核心思路不变个别窗口位置和选项名称可能有差异按实际 UI 调整即可。安装 Unity 模块时建议勾选Windows Build Support (IL2CPP)后续如果要发布 Windows 平台需要。Android Build Support很多生存游戏会选择做移动端可以先装好。Visual Studio 或 Rider用于编写 C# 脚本。当前网络环境下安装过程可能比较慢耐心等待即可。如果安装包下载中断重新打开 Hub 继续下载断点续传一般可用。2.2 创建 3D 项目并选择 URP 管线打开 Unity Hub点击“新建项目”选择 3DBuilt-In Render Pipeline或 3D Sample SceneURP模板。我更推荐使用Universal 3D模板不同版本命名可能略有差异它自动创建了 URP 管线资源。如果当前模板列表里没有也可以直接新建普通 3D 项目之后通过 Package Manager 安装 Universal RP 包然后手动创建管线路由资源配置到 Graphics Settings。创建一个新项目后先确认 Render Pipeline Asset 是否生效具体位置Edit Project Settings Graphics Render Pipeline Asset如果该字段为空需要在 Project 面板右键创建Create Rendering Universal Render Pipeline Asset然后把创建的资产拖入 Graphics 设置和 Quality 设置中。这样处理后场景默认会使用 URP 渲染。URP 刚打开时很多新手会发现材质变成洋红色那是 Shader 未编译或管线未正确配置导致的。把管线路由资产拖入设置后重新打开场景即可恢复正常。2.3 中配电脑的编辑器性能优化在开发阶段场景可能还不复杂但编辑器本身也有一定开销。建议在开始前做好以下调整。进入 Edit Project Settings Quality把当前激活的 Quality Level 选中。建议关闭实时阴影的全局覆盖或者把 Shadow Distance 调为 50 以内。Pixel Light Count 保持默认 1 即可生存游戏场景很少需要多盏实时像素光。抗锯齿可以选择 FXAA 或 SMAA避免 MSAA 开销过大。在 Window Rendering Lighting 面板中把 Scene 的 Skybox 保留但不要急着开高强度的全局光照烘焙。初期场景直接 Skybox 一盏定向光照明即可。项目创建完成后基本开发环境就绪。如果你在安装阶段遇到 Unity Hub 打不开、编辑器安装失败等问题可以参考第 7 章的排查表。3. 搭建基础地形从空旷平面到可跑动的场景3.1 创建 Terrain 地形在 Hierarchy 面板右键选择 3D Object Terrain。场景中会出现一块默认的 1000×1000 单位的地形。这个尺寸对开放世界原型来说足够大。选中地形对象后在 Inspector 面板可以看到地形工具包括提升地形左键提升Shift左键降低。绘制纹理给地面刷草地、泥土等贴图。放置树木、细节种树和花草。先用提升工具做几道简单起伏再用绘制纹理工具刷一块草地区域。不需要追求美术效果这一阶段重点是让地面具有高低差方便后边测试角色碰撞和跳跃。刷地形时注意 Performance 提示窗口显示 Draw Calls 和 Triangle Count。如果三角形数量飙升过猛可以降低 Terrain Settings 中的 Resolution 或 Detail Density。3.2 设置天空与基础光照保持场景中的 Skybox 类型为默认这一步几乎不需要操作。如果你想要更明显的氛围感可以在 Window Rendering Lighting 的 Environment 标签下换一张 Skybox 材质或者调整 Ambient Color。定向光建议设置如下参数Rotation让光线斜照形成明显的明暗面。Intensity1.0 左右即可。Shadow TypeHard Shadows。避免打开太多实时阴影因为角色、植被交互时会产生密集的阴影计算对中低端配置不友好。3.3 场景边界的碰撞安全地形自带 Collider不需要额外添加 MeshColliderUnity 的 Terrain Collider 会自动跟随地形形状。如果场景中后续放置了岩石、树木等对象需要确认它们具备 Collider。岩石、木箱添加 BoxCollider 或 MeshCollider。带有资源的树木Terrain 的植物绘制会自动生成碰撞。新手最容易忽略的问题是地面放了一个 3D 模型角色直接掉下去。这是因为模型只有 MeshRenderer没有 Collider。排查时选中该对象在 Inspector 里查看是否有 Collider 组件即可。4. 角色控制Rigidbody 移动与速度获取4.1 创建玩家角色生存游戏目前阶段用第三人称视角展示角色比较直观。在 Hierarchy 中右键创建3D Object Capsule命名为 Player。设置 Transform 的 Position 为 (0, 1, 0)让胶囊体底部刚好贴在地面上。为 Player 添加组件RigidbodyCapsule ColliderCapsule Collider 默认就会匹配 Capsule 网格不需要调整。这里有个关键点很多新手会让 Rigidbody 使用 isKinematic true 来“防止角色乱动”我建议不要把移动角色设为 kinematic。Kinematic 物体不受物理力影响移动效果需要手动控制 Transform这会让碰撞检测变得很麻烦容易发生穿墙问题。本文的移动脚本将使用 Rigidbody 的 velocity 属性控制运动这是非常常见的方案。记住一个常识Rigidbody 是物理组件Transform 的移动适用于非物理对象使用物理移动时尽量改 velocity、AddForce 等物理接口而不是每帧直接改 position。4.2 编写第一版移动脚本在 Project 面板创建文件夹 Scripts然后把 PlayerController.cs 相关代码整理如下。这是我们第一个完整脚本负责处理键盘输入、移动速度、跳跃和地面检测。// 文件路径Assets/Scripts/PlayerController.cs using UnityEngine; public class PlayerController : MonoBehaviour { [Header(移动参数)] public float moveSpeed 6f; public float jumpForce 5f; public float groundCheckDistance 0.15f; [Header(组件引用)] private Rigidbody rb; private CapsuleCollider capsuleCollider; private bool isGrounded; void Start() { rb GetComponentRigidbody(); capsuleCollider GetComponentCapsuleCollider(); } void Update() { CheckGround(); HandleJump(); } void FixedUpdate() { HandleMove(); } void HandleMove() { float horizontal Input.GetAxis(Horizontal); float vertical Input.GetAxis(Vertical); // 计算相对于角色朝向的移动方向 Vector3 direction new Vector3(horizontal, 0f, vertical).normalized; Vector3 moveVelocity direction * moveSpeed; // 保留当前 Rigidbody 的垂直速度避免覆盖重力/跳跃 moveVelocity.y rb.velocity.y; rb.velocity moveVelocity; } void CheckGround() { Vector3 origin transform.position Vector3.down * (capsuleCollider.height * 0.5f); float radius capsuleCollider.radius * 0.9f; isGrounded Physics.SphereCast(origin, radius, Vector3.down, out RaycastHit hit, groundCheckDistance); } void HandleJump() { if (Input.GetButtonDown(Jump) isGrounded) { rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse); } } }代码逻辑拆解Update 中处理跳跃输入和地面检查FixedUpdate 中处理物理移动。这是 Unity 很经典的分工Update 跟帧率走FixedUpdate 按固定物理步长走物理相关的移动指令最好放在 FixedUpdate 里。HandleMove 中读取 Input.GetAxis得到 -1 到 1 的平滑输入值。direction.normalized 防止斜向移动时速度叠加为 1.414 倍导致角色斜着走得更快。直接设置 rb.velocity moveVelocity然后手动保留 y 分量。这是很多玩家问“Unity 物体速度怎么获取”背后的常规操作逻辑先取当前 rb.velocity.y再覆盖水平速度。关于 CheckGround它使用 SphereCast 从角色底部向下发射球形检测。这里有个容易踩的坑Physics.SphereCast 的原点如果已经在碰撞体内检测可能失败或产生怪异结果所以用胶囊体高度算底部位置。把脚本挂到 Player 对象上运行游戏即可用 WASD 移动空格跳跃。4.3 编写第三人称摄像机跟随这个脚本在生存游戏里几乎是标配可以分为两种实现思路固定角度跟随摄像机始终位于角色后方适合操作简单的原型。鼠标旋转跟随摄像机随鼠标转动角色朝向跟着变化。本文先实现第一种固定后方的跟随它最容易理解也不容易产生眩晕。// 文件路径Assets/Scripts/CameraFollow.cs using UnityEngine; public class CameraFollow : MonoBehaviour { public Transform target; public Vector3 offset new Vector3(0f, 2.5f, -4f); public float smoothSpeed 8f; void LateUpdate() { if (target null) { return; } Vector3 desiredPosition target.position offset; Vector3 smoothedPosition Vector3.Lerp(transform.position, desiredPosition, smoothSpeed * Time.deltaTime); transform.position smoothedPosition; transform.LookAt(target.position Vector3.up * 1.2f); } }LateUpdate 是摄像机跟随的核心它会在所有 Update 计算结束后再执行避免角色移动后摄像机位置出现滞后感。offset 的值可以根据角色模型高度调整。如果角色胶囊体高度是 2 单位视线中心大约在 y1 左右offset 的 y 设置为 2.5 让摄像机略微俯视角。transform.LookAt 让摄像机始终注视角色上半身这对后续加入瞄准射击玩法来说也更基础。把脚本挂到 Camera 上在 Inspector 里把 target 拖入 Player 对象。运行游戏后摄像机会平滑跟随角色。这里顺便解释一下为什么用 Vector3.Lerp 而不是直接赋值位置。直接赋值会导致摄像机完全锁定角色移动时视觉上很生硬插值到目标位置后会有轻微的缓冲感看起来更接近真实观感。4.4 角色视野与鼠标旋转上面这个版本是固定后方视角如果要在生存游戏中切到第一人称或允许玩家观察四周敌人最自然的做法是用鼠标控制摄像机横向旋转。可以给 CameraFollow 扩展以下功能public float mouseSensitivity 2f; private float yaw 0f; private float pitch 0f; void Update() { yaw Input.GetAxis(Mouse X) * mouseSensitivity; pitch - Input.GetAxis(Mouse Y) * mouseSensitivity; pitch Mathf.Clamp(pitch, -30f, 60f); }然后在 LateUpdate 中用四元数计算目标旋转而不是直接偏移固定方向。这里先不展开后续加入武器射击或近战攻击时再完整实现。目前阶段固定跟随已经足够测试地形与场景交互。5. 生存系统血量、饥饿与白天黑夜循环5.1 UI Canvas 基础搭建生存游戏一定要有可见的数值反馈。这一节搭建最小 UI用来显示血量和饥饿值。在 Hierarchy 右键创建 UI Canvas。Unity 会自动生成 EventSystem。Canvas 的默认 Render Mode 是 Screen Space - Overlay这对 2D UI 最方便不需要考虑摄像机渲染遮挡。在 Canvas 下创建两个 Slider一个命名为 HealthSlider用于血量显示。一个命名为 HungerSlider用于饥饿值显示。再分别添加子 Text或 TextMeshProUGUI取决于你项目里的 TMP 是否已导入来显示具体数字。为了减少依赖这里用旧版 UI Text 更简单在 Create UI Text 即可。Slider 的 Direction、Min Value、Max Value 先保持默认解释一下后续会由脚本控制。5.2 编写生存数值管理器生存数值的核心逻辑并不复杂每个属性有最大值、当前值每一帧按照增减速度变化当数值归零时触发后果。下面给出一个最简版本// 文件路径Assets/Scripts/PlayerStats.cs using UnityEngine; using UnityEngine.UI; public class PlayerStats : MonoBehaviour { [Header(血量)] public float maxHealth 100f; [SerializeField] private float currentHealth; [Header(饥饿值)] public float maxHunger 100f; [SerializeField] private float currentHunger; public float hungerDecreaseSpeed 1.5f; [Header(UI 引用)] public Slider healthSlider; public Slider hungerSlider; public bool IsDead { get; private set; } void Start() { currentHealth maxHealth; currentHunger maxHunger; UpdateUI(); } void Update() { if (IsDead) { return; } currentHunger - hungerDecreaseSpeed * Time.deltaTime; currentHunger Mathf.Clamp(currentHunger, 0f, maxHunger); // 饥饿到 0 时开始扣血 if (currentHunger 0f) { currentHealth - 2f * Time.deltaTime; } if (currentHealth 0f) { Die(); } UpdateUI(); } public void TakeDamage(float amount) { currentHealth - amount; currentHealth Mathf.Clamp(currentHealth, 0f, maxHealth); if (currentHealth 0f) { Die(); } UpdateUI(); } public void Eat(float amount) { currentHunger amount; currentHunger Mathf.Clamp(currentHunger, 0f, maxHunger); UpdateUI(); } void Die() { IsDead true; Debug.Log(角色死亡游戏结束); // 后续可以在这里接入死亡界面、重生逻辑 } void UpdateUI() { if (healthSlider ! null) { healthSlider.value currentHealth / maxHealth; } if (hungerSlider ! null) { hungerSlider.value currentHunger / maxHunger; } } }几个细节值得说明[SerializeField] 私有字段可以在 Inspector 显示但不暴露给外部直接修改比全部设成 public 更安全。饥饿值随时间自然下降归零后开始持续扣血。这是很多生存游戏的基础规则给玩家一种“必须找食物”的压迫感。Mathf.Clamp 把数值限制在 0 到最大值之间防止饥饿值变成负数导致 UI 显示异常。IsDead 属性用 { get; private set; } 对外只读防止其他脚本随意设置死亡状态。把 PlayerStats 挂到 Player 对象再把两个 Slider 拖到对应引用位置。运行游戏后你可以看到饥饿值随时间下降血量不变当饥饿值降到 0血量开始持续减少。如果你尝试让角色受伤可在控制台输入GameObject.Find(Player).GetComponentPlayerStats().TakeDamage(10f)测试。5.3 白天黑夜循环与 Time.timeScale时间系统是实现开放世界生存沉浸感的重要一环。这里给出最简单的昼夜循环脚本它通过旋转定向光来模拟太阳在天空的位置。// 文件路径Assets/Scripts/TimeSystem.cs using UnityEngine; public class TimeSystem : MonoBehaviour { public Light sunLight; public float dayDurationInMinutes 5f; [Range(0f, 1f)] public float timeOfDay 0.25f; float dayProgress; void Start() { dayProgress timeOfDay * 24f / 24f; } void Update() { float dayLengthInSeconds dayDurationInMinutes * 60f; timeOfDay Time.deltaTime / dayLengthInSeconds; if (timeOfDay 1f) { timeOfDay - 1f; } float sunAngle (timeOfDay * 360f) - 90f; sunLight.transform.rotation Quaternion.Euler(sunAngle, 30f, 0f); // 根据太阳角度调整亮度夜间关闭实时光源 bool isNight timeOfDay 0.75f || timeOfDay 0.2f; sunLight.intensity isNight ? 0f : 1f; } }timeOfDay 是一个 0 到 1 的小数0.25 大约对应早上六点左右0.5 为正午0.75 到 0 区间为夜间。把太阳光绕 X 轴旋转一圈就是一天 24 小时的模拟。项目中如果你想让时间暂停测试某些逻辑可以了解 Time.timeScaleTime.timeScale 0f // 暂停游戏但 UI 动画也停止 Time.timeScale 1f // 恢复注意Time.deltaTime 受 timeScale 影响。如果暂停后不想让 UI 或菜单卡住通常还要在 UI 动画中使用 unscaledDeltaTime。这个细节后面做暂停菜单时再深入。将 TimeSystem 挂到场景中的空物体上把定向光拖到 sunLight 字段上。运行后可以看到光源缓慢旋转营造昼夜变化。6. 简单敌人 AI追击与攻击逻辑6.1 创建敌人对象在 Hierarchy 右键创建 3D Object Cube命名为 Enemy。给它一个明显颜色方便在场景中区分。给 Enemy 添加组件Rigidbody使用 Constraints 冻结旋转避免倒下。BoxCollider用于碰撞。按照许多生存游戏的设定敌人通常会对玩家进行近战攻击。所以这里实现三种状态巡逻在出生点附近随机移动或等待。追击玩家进入检测范围后朝玩家移动。攻击到达攻击距离后对玩家造成伤害。6.2 编写追击 AI// 文件路径Assets/Scripts/EnemyAI.cs using UnityEngine; using UnityEngine.AI; public class EnemyAI : MonoBehaviour { public Transform player; public float detectionRange 10f; public float attackRange 2f; public float moveSpeed 3f; public float attackCooldown 1.5f; private float attackTimer 0f; private Rigidbody rb; private Vector3 patrolTarget; void Start() { rb GetComponentRigidbody(); player GameObject.FindGameObjectWithTag(Player).transform; SetNewPatrolTarget(); } void Update() { attackTimer - Time.deltaTime; float distanceToPlayer Vector3.Distance(transform.position, player.position); if (distanceToPlayer attackRange) { Attack(); } else if (distanceToPlayer detectionRange) { ChasePlayer(); } else { Patrol(); } } void ChasePlayer() { Vector3 direction (player.position - transform.position).normalized; direction.y 0f; rb.velocity direction * moveSpeed; // 让敌人面向玩家 transform.LookAt(player.position); } void Patrol() { Vector3 direction (patrolTarget - transform.position).normalized; direction.y 0f; rb.velocity direction * moveSpeed; if (Vector3.Distance(transform.position, patrolTarget) 1f) { SetNewPatrolTarget(); } } void SetNewPatrolTarget() { patrolTarget transform.position new Vector3(Random.Range(-8f, 8f), 0f, Random.Range(-8f, 8f)); } void Attack() { rb.velocity Vector3.zero; if (attackTimer 0f) { PlayerStats stats player.GetComponentPlayerStats(); if (stats ! null) { stats.TakeDamage(10f); } attackTimer attackCooldown; } } void OnDrawGizmosSelected() { Gizmos.color Color.yellow; Gizmos.DrawWireSphere(transform.position, detectionRange); Gizmos.color Color.red; Gizmos.DrawWireSphere(transform.position, attackRange); } }核心逻辑chase 和 patrol 使用 Rigidbody.velocity 驱动移动方向均基于位置差normalized 保证速度恒定。transform.LookAt 让敌人面朝玩家这是实现“追踪、警觉”互动非常直观的做法。Attack 内部直接调用 PlayerStats.TakeDamage把伤害逻辑委托给玩家系统敌人只负责发信号符合单一职责原则。OnDrawGizmosSelected 是编辑器的辅助调试方法在场景视图中可绘制检测范围线框不参与运行时游戏逻辑。把 EnemyAI 挂到 Enemy 上运行游戏后站在敌人附近你会看到它向你靠近接触后开始扣血。这里的攻击伤害是直接数值扣减没有攻击动画和特效后续可以在“敌人行为树”中加入动画状态机。6.3 给角色打上 Player 标签上面的脚本用GameObject.FindGameObjectWithTag(Player)查找玩家对象所以 Player 必须有 Player 标签。选中 Player在 Inspector 顶部 Tag 下拉框中如果已有 Player 标签直接选择。如果没有点击 Add Tag新建 Player 标签再返回应用。这一步很基础但非常容易忘。如果忘了设置标签运行后敌人脚本里 player 字段为 null会抛空引用异常。7. 常见问题与性能排查7.1 高频报错排查表问题现象常见原因解决思路场景中的材质全部显示为洋红色URP 管线路由未配置检查 Graphics/Quality 设置确认 Render Pipeline Asset 已指定Character Controller 或 Rigidbody 穿墙或掉出地面缺少 Collider 或 Collider 尺寸不对为对象添加合适的 Collider检查应禁用 Kinematic角色斜向移动更快移动方向向量未归一化对方向向量做 normalized 处理Input.GetAxis 无反应输入管理器未启用或按键设置被改检查 Edit Project Settings Input ManagerTimeSystem 中太阳旋转但亮度不变灯泡的 Shadow 或 Intensity 参数被覆盖检查代码中赋值是否短路或者光照组件被其他脚本改动敌人不追击玩家玩家没有 Player 标签为玩家对象添加 Player 标签摄像机台摄或抖动LateUpdate 中未使用平滑插值改用 Vector3.Lerp Time.deltaTime 平滑跟随Unity 安装或更新失败网络不稳或硬盘空间不足使用 Unity Hub 重试下载检查磁盘空间和防火墙7.2 编辑器卡顿与加载缓慢中配电脑运行 Unity 编辑器卡顿是最常见的痛点之一。可以尝试关闭 Scene View 的预览光照使用简单渲染模式。在 Game View 中降低分辨率例如 800×450。关闭不必要的实时阴影减少 UI 面板数量。使用 Window Asset Management 清理未使用的资源。如果项目里已经导入大量第三方资源导致加载很慢建议用 Addressables 做异步加载这个后边再开专题讲。7.3 碰撞异常角色从地形缝隙掉落当角色使用 Rigidbody 移动时如果移动速度过快可能发生“穿透”现象也就是从碰撞细缝里直接穿过。常用措施增大 Collider 的厚度。使用 Physics.SphereCast 或 CapsuleCast 做连续碰撞检测。调整 Rigidbody 的 Collision Detection 为 Continuous Dynamic。降低 moveSpeed 到合理范围。另外要注意地形接缝处如果手工搭建了多个平面缝隙会非常明显。最好直接使用 Unity Terrain 或者 ProBuilder 等工具确保表面连续。8. 最佳实践与后续扩展方向8.1 资源与包体优化中配开发中资源策略直接决定游戏最终能不能流畅运行。就目前这篇文章的项目阶段建议养成下面几个习惯纹理导入设置中Max Size 尽量控制在 2048 以内开发阶段甚至用 1024。模型导入时 Ensure Read/Write Enabled 只对需要动态变形、CPU 读取的模型开启其余不勾选。Terrain 的 Detail Density 设低一点植被风吹草动的绘制开销其实不小。背景音乐使用 Audio Source 时注意压缩格式WAV 大文件尽量先转成 OGG 或 MP3。如果后续要发布微信小游戏、移动端或 WebGL还要考虑 bundle 大小和加载策略这也是很多开发者关心的热点问题但这里不展开。8.2 存档与数据管理思路生存游戏的核心体验之一是“死了重来但学到的技术不丢失”所以存档系统一定要尽早规划。建议不要把所有存档逻辑塞在 PlayerStats 里。可以单独建立 SaveData 类只保存必要的数值和时间等核心状态。例如[System.Serializable] public class SaveData { public float health; public float hunger; public float timeOfDay; public Vector3 playerPosition; }存档机制用 PlayerPrefs 存储 Json 字符串即可后续版本如果数据量大再迁移到本地文件或云存档。8.3 后续功能扩展路线文章目前完成了一个“能跑、能跳、能看到数值变化”的开放世界生存游戏基础框架。下一步学习方向建议按顺序推进资源采集点击物体触发动画、更新背包数据。背包系统网格 UI、物品拖拽、合成逻辑。敌人行为升级使用 NavMesh Agent 替代简单 velocity 移动实现更自然的寻路。建筑系统通过预制体实例化放置地板、墙壁。武器与战斗动画加入攻击动画、受击反馈、粒子特效。音频管理背景音乐、环境音、脚步声音、打击声音。每个方向都可以作为一篇文章展开。尤其是资源采集和合成系统几乎决定了生存游戏的“可玩性曲线”把这一块做好项目就会从“技术演示”变成“准产品原型”。这篇文章主要解决了 Unity 3D 开放世界生存游戏开发的第一层地基场景搭建、角色移动、摄像机跟随、生存数值、基础敌人 AI。写脚本时刻意让组件之间尽量低耦合比如 EnemyAI 只负责决定何时攻击和朝哪移动真正扣血逻辑在 PlayerStats 中完成。这种解耦在后边扩展系统时会省下大量时间。演示代码可以直接复制到你的 Unity 项目中。建议不要急着加复杂功能先跑通这个闭环再逐步扩展。如果你在跟着做时遇到任何报错或异常现象欢迎在评论区带上截图和 Unity 版本信息讨论。