ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

模拟太阳系Unity课设:公转、自转、交互与WebGL发布

模拟太阳系Unity课设:公转、自转、交互与WebGL发布 简介这是一份2022年Unity课程实践项目——模拟太阳系的Unity小游戏源码包。玩家可驾驶飞船在太阳系中穿梭近距离查看各星球介绍飞船提供真实力学与灵活移动两种驾驶模式太阳效果基于Unity官方UBR渲染管线制作适合Unity初学者完成课程设计或练习场景搭建与交互控制。资源包共2000个文件、约246.69MB核心包括大量.cs脚本、.png贴图、.prefab预制体、.asset/.mat配置和.fbx模型并附带.md说明文档方便阅读与二次修改。已有505人学习/下载。通过该资源开发者可以学习Unity场景漫游、刚体力与角色控制切换、星球信息UI展示、基于渲染管线的特效实现等实用技巧是一款结构完整、可直接运行参考的课设案例。1. 用 Unity 做一个模拟太阳系为什么说这是性价比最高的课设题如果你正在找一门能用 Unity 快速交差、又不会在答辩时被老师追问到卡壳的课设题目“模拟太阳系”几乎是标准答案。它看起来有天文内容撑场面实际做起来只依赖 Unity 最基础的 GameObject 和 Transform 操作不涉及物理引擎、动画状态机和 UI 事件系统的高级用法一周业余时间足够从零跑到演示。更关键的是它的“完成度”上限极高公转、自转、轨道线、时间流速控制、摄像机跟随、WebGL 发布每加一个功能都是答辩加分项但每个功能的代码量都不超过三十行。本文不假设你已经有一份现成项目而是顺着 2022Unity 课设这个场景把“模拟太阳系的 Unity 小游戏”从场景搭建到发布 WebGL 的完整链路拆开讲。代码用 C#版本按 Unity 2020 LTS 到 2022 LTS 都兼容来写。先立住一个核心观点这个题目的技术含量不在天体物理精度而在如何用最少的代码量做出“看起来像模像样”的轨道效果——你需要的不是真实比例而是视觉比例和时间缩放的控制能力。2. 在 Unity 里搭建最小可跑的太阳系场景公转、自转与轨道显示2.1 先搞清楚 Unity 里“运动”的底层逻辑模拟太阳系的本质是两件事让行星围绕太阳转公转让行星自身转动自转。在 Unity 中任何物体的位置和旋转都由 Transform 组件管理旋转操作有两种常见实现方式在Update()函数里手动计算公转角度使用Quaternion.Euler或RotateAround方法。将行星作为“轨道空物体”的子物体通过旋转空物体带动子物体完成公转。第一种方式的代码直观、好答辩第二种方式在编辑器中调试方便。本文采用计算 参数表驱动的方案方便后期调整速度与轨道参数。启动任意项目先建基础场景。在 Hierarchy 中依次创建一个平行光Directional Light模拟太阳光照。一个球体 Sphere命名 “Sun”建议使用 Unlit 材质确保它始终发光可见。若干球体作为行星每个行星挂一个独立的 C# 脚本接受轨道参数。不要使用 Rigidbody 组件也不需要写FixedUpdate()里那些物理逻辑模拟太阳系里没有碰撞需求纯运动学计算已经是够用且最稳的方案。// PlanetOrbit.cs using UnityEngine; public class PlanetOrbit : MonoBehaviour { public Transform sun; public float semiMajorAxis 10f; // 轨道半径视觉比例 public float period 10f; // 公转周期单位秒 public float phase 0f; // 初始相位角度 public float rotationPeriod 1f; // 自转周期 public float orbitAngle 0f; // 轨道倾角 private Vector3 orbitBaseNormal Vector3.up; void Update() { float angle (Time.time / period) * 360f phase; Vector3 pos new Vector3( Mathf.Cos(angle * Mathf.Deg2Rad) * semiMajorAxis, 0f, Mathf.Sin(angle * Mathf.Deg2Rad) * semiMajorAxis ); if (Mathf.Abs(orbitAngle) 0.01f) { Quaternion rot Quaternion.Euler(orbitAngle, 0f, 0f); pos rot * pos; } transform.position sun.position pos; transform.Rotate(Vector3.up, 360f / rotationPeriod * Time.deltaTime, Space.Self); } }这段代码的核心逻辑分成两个独立部分位置由公转角度在 XZ 平面上投影计算旋转由Rotate每秒叠加。semiMajorAxis与period是开闭地控制轨道大小与快慢的直接参数phase保证多颗行星初始位置各不相同避免所有行星球在同一个方向排成直线。这里给了轨道倾角参数用来模拟实际太阳系行星轨道面不完全统一的现象。semiMajorAxis不是真实天文单位的数值而是视觉上适合观测的距离。真实太阳系里行星与太阳的距离差异极其悬殊如果照搬真实数值内行星会拥挤到屏幕中心外行星则直接飞出视窗。课设答辩时老师要看的是你对天体运动的模拟能力而不是视觉灾难。2.2 用 ScriptableObject 管理行星参数表避免每个行星单独拖参数直接在 Inspector 中给每个行星拖动Sun引用、填轨道参数行得通但行星一多、每次微调参数都要逐一点开很浪费时间。常见做法是定义行星数据类并用一个数组统一管理这样不仅方便你改参数也方便答辩时给老师演示“不同轨道参数对运动的影响”。先定义行星数据类// PlanetData.cs using UnityEngine; [System.Serializable] public class PlanetData { public string planetName; public float SemiMajorAxis; // 轨道半径 public float Period; // 公转周期地球为1 public float RotationPeriod; // 自转周期地球为1 public float Phase; // 初始相位 public float OrbitAngle; // 轨道倾角 public Color displayColor; // 在轨道上显示的颜色 }然后改造PlanetOrbit.cs使其可以直接从数据类读取参数并将所有行星集中到一个管理脚本中。// SolarSystemManager.cs using UnityEngine; using System.Collections.Generic; public class SolarSystemManager : MonoBehaviour { public Transform sun; public ListPlanetData planetsData; public GameObject planetPrefab; public bool showOrbitPath true; private ListTransform planets new ListTransform(); void Start() { foreach (var data in planetsData) { GameObject go Instantiate(planetPrefab, sun.position, Quaternion.identity); go.name data.planetName; PlanetOrbit orbit go.AddComponentPlanetOrbit(); orbit.sun sun; orbit.semiMajorAxis data.SemiMajorAxis; orbit.period data.Period; orbit.phase data.Phase; orbit.rotationPeriod data.RotationPeriod; orbit.orbitAngle data.OrbitAngle; planets.Add(go.transform); Renderer renderer go.GetComponentRenderer(); if (renderer ! null) { renderer.material.color data.displayColor; } } } }参数作用建议范围视觉表现SemiMajorAxis控制行星轨道半径大小5 ~ 40Period公转周期数值越大转得越慢5 ~ 60RotationPeriod自转速度数值越大转得越慢0.2 ~ 5Phase行星初始角度避免同线0 ~ 360OrbitAngle轨道面倾斜度0 ~ 15SolarSystemManager的Start()方法里通过AddComponentPlanetOrbit()动态添加组件省去人工绑定引用。实际使用中planetPrefab需要是一个纯白色的球体颜色交给displayColor在运行时覆盖这样更换行星颜色不用改动材质资源而是直接用运行时材质副本。“拍摄”时注意Renderer.material会自动生成材质实例因此退出场景时的材质改回不会影响原始 Prefab。如果想让程序在运行后有视觉上的轨道路径Unity 的 LineRenderer 是最高效的方案。在行星物体上挂一个 LineRenderer 组件使用positionCount预采样轨道点在Start()中一次性生成整条轨道线。// OrbitLineRender.cs 挂在行星上 void Start() { LineRenderer line GetComponentLineRenderer(); int segments 128; line.positionCount segments; for (int i 0; i segments; i) { float angle (float)i / segments * Mathf.PI * 2f; Vector3 pos new Vector3(Mathf.Cos(angle) * semiMajorAxis, 0f, Mathf.Sin(angle) * semiMajorAxis); if (Mathf.Abs(orbitAngle) 0.01f) { pos Quaternion.Euler(orbitAngle, 0f, 0f) * pos; } line.SetPosition(i, sun.position pos); } }这段代码里semiMajorAxis直接跨类读取了PlanetOrbit中的公共变量——如果两个脚本挂在同一个物体上这是一种合法写法。实际用的时候把轨道线的useWorldSpace设为 true轨道会固定在世界坐标中。抗锯齿和颜色统一使用半透明细线视觉上不会遮挡星体。2.3 处理公转与自转不同步时的常见错误有一个非常容易踩的坑如果直接在PlanetOrbit.Update()中transform.Rotate(Vector3.up, 360 / rotationPeriod * Time.deltaTime, Space.Self)自转方向是自身局部坐标系里的 Y 轴但如果天体模型初始是倒置的旋转看起来会飘。这一点在导入从外部商城下载的星球模型时尤其常见——模型自带的轴向可能不是朝上的。另外变换位置时不要用transform.Translate因为 Translate 是以当前物体的局部坐标轴方向移动的会与已经改变的旋转互相干扰导致行星公转轨道出现不可预测的漂移。正确做法永远是直接设置transform.position sun.position pos以太阳为原点做绝对计算。如果场景里存在多个行星同时运动不建议每个行星都引用同一个Time.deltaTime去累加角度因为Time.deltaTime是帧间时间直接乘周期会导致不同帧率下速度表现一致但起始角度不同。用Time.time作为时钟基准是最稳定的做法它不受暂停和帧率影响。3. 给 Unity 小游戏加交互摄像机跟随、滑动条调速与按钮点击范围3.1 用一个 OrbitalCamera 脚本实现整体观察自由视角静态观看太阳系没有体验感课设演示中“摄像机跟随”和“自由环绕”是最容易被提问的两个点。最简方案是把摄像机放在一个空物体下面旋转父物体并对准太阳位置。// OrbitCamera.cs using UnityEngine; public class OrbitCamera : MonoBehaviour { public Transform target; public float distance 25f; public float sensitivity 3f; public float scrollSpeed 2f; private float xDeg 30f; private float yDeg 0f; void LateUpdate() { if (target null) return; if (Input.GetMouseButton(1)) { xDeg Input.GetAxis(Mouse X) * sensitivity; yDeg - Input.GetAxis(Mouse Y) * sensitivity; yDeg Mathf.Clamp(yDeg, -85f, 85f); } distance Mathf.Clamp(distance - Input.GetAxis(Mouse ScrollWheel) * scrollSpeed, 5f, 100f); Quaternion rotation Quaternion.Euler(yDeg, xDeg, 0); Vector3 position target.position - rotation * Vector3.forward * distance; transform.position position; transform.rotation rotation; } }LateUpdate()的作用是保证在场景中所有行星位置更新完成后再调整摄像机位置避免出现瞬移抖动。按住鼠标右键拖拽旋转视角滚轮在 5 到 100 之间缩放距离摄像头始终对准目标点太阳或一个中心锚点。这个方案比把摄像机直接作为太阳的子物体更合理因为“围绕目标旋转”和“跟随目标移动”是两码事——挂在子物体上会导致当太阳移动时视角跟着位移无法独立观察。3.2 游戏内滑动条控制公转速度把 Time.timeScale 作为全局调速器热搜词里“unity 做一个滑动条”是高频搜索核心难点不在 UI 组件本身而在于“如何让滑动条的值作用于游戏逻辑”。最直接的思路是在Update()中把公转角度计算里所有出现Time.time的地方改成elapsedTime用累计计时器实现变速。这样改比较麻烦更好的方案是采用全局速度缩放直接修改Time.timeScale。滑动条搭建步骤在场景中右键 UI → Slider 创建滑动条。将 Min Value 设为 0Max Value 设为 5默认值 1 表示正常速度。创建一个脚本TimeScaleController.cs// TimeScaleController.cs using UnityEngine; using UnityEngine.UI; public class TimeScaleController : MonoBehaviour { public Slider slider; public Text speedText; void Start() { slider.onValueChanged.AddListener(OnSpeedChanged); slider.value 1f; } void OnSpeedChanged(float value) { Time.timeScale value; if (speedText ! null) { speedText.text 时间速度 x value.ToString(F1); } } void OnDestroy() { slider.onValueChanged.RemoveListener(OnSpeedChanged); } }注意Time.timeScale会影响Time.deltaTime的值但不会影响Time.time按原始速度增长的规律——Time.time受timeScale影响实际表现是Time.timeScale改变后Time.time增长速度也变化。因此之前PlanetOrbit中用Time.time作为角度计算基准的方案可以直接联动滑动条生效不需要额外改代码。这是使用时间缩放而非自定义计时器的主要优势。3.3 点击范围扩大与 UI 穿透问题的两个处理技巧滑块交互有时候会遇到“点击区域太小”或“按钮后方的行星挡住了射线”的问题。Unity UI 的GraphicRaycaster只处理 UI 层不会影响场景中的 3D 行星模型。但如果场景里有 3D 物体也挂载了PhysicsRaycaster鼠标点击 UI 时可能会同时触发 3D 物体的点击响应干扰视角旋转操作。放大 UI 点击范围的通用手法是给目标按钮添加一个空 Child 物体并挂上Image组件透明色即可将按钮的targetGraphic指向它同时扩大这个透明子物体的rectTransform尺寸。这样事件的响应区域变大但视觉上按钮还是原来的样子。RectTransform rect button.transform.Find(ClickArea).GetComponentRectTransform(); rect.sizeDelta new Vector2(60f, 40f);3.3.1 移动端需要的额外处理单指旋转与双指缩放如果你后续把这套太阳系小游戏发布到手机端鼠标的GetMouseButton(1)需要改成触摸检测逻辑。单指滑动时旋转镜头双指捏合时缩放距离。常见做法是检查Input.touchCountif (Input.touchCount 2) { Touch t1 Input.GetTouch(0); Touch t2 Input.GetTouch(1); float prevDist (t1.position - t1.deltaPosition - (t2.position - t2.deltaPosition)).magnitude; float currDist (t1.position - t2.position).magnitude; distance Mathf.Clamp(distance - (currDist - prevDist) * 0.05f, 5f, 100f); }这段触摸代码的核心是计算两根手指上一帧与当前帧的距离差值把距离变化映射到distance变量上缩放响应自然且流畅。注意在移动端禁用鼠标相关的输入检测否则鼠标模拟事件会干扰触摸逻辑。4. 课设细节拿分点阴影、材质、发布 WebGL 与常见崩溃问题4.1 太阳不发光的常见坑自发光材质与阴影设置很多人在做太阳系课设时用的是默认 Standard 材质暗环境下太阳看起来只是一颗灰白色球体完全没有“恒星发光”的感觉。要让太阳看起来像恒星两个方案选一个第一种在太阳质材上使用Emission属性调高 Emission 强度并关闭太阳自身的阴影投射。由于模拟太阳系不一定要求高精度阴影一个简单、高亮、纯白的太阳材质就足够了。自发光颜色与强度可以在纯色球体表面区分出恒星感。第二种给太阳挂一个Point Light点光源并让太阳材质使用 Unlit 类型避免点光源照亮太阳表面造成自身变成暗面。点光源颜色调为橙色或黄白色Range 覆盖整个轨道面。注意阴影设置Unity 的平行光默认会开启实时阴影但太阳系场景如此大——轨道半径从 5 到 40——实时阴影的分辨率和距离参数如果不调会导致远距离的行星阴影闪烁或者完全没有阴影。实际上太阳系场景中阴影不是表现重点通常做法是直接关闭行星的阴影投射保留接收或者全部关闭改为用材质和光照来区分行星明暗面。打开 Project Settings → Quality → Shadow Distance将其调到 100 左右才能在常规观察距离下看到可接受的阴影效果。太阳系场景的光源方向不用改保持平行光从侧上方射入即可这样行星会形成清晰的昼夜分界线视觉上更符合天文认知。光源强度默认 1 可能偏暗调到 1.21.5 比较合适会根据场景的整体环境光变化有所不同实际操作中直接看 Game 窗口调节即可。4.2 把 2022Unity 太阳系发布到 WebGL三种常见运行报错Unity 2022 发布 WebGL 已经是比较成熟的流程。步骤不长File → Build Settings → 选择 WebGL 平台 → Player Settings 里设置分辨率与压缩格式 → 点击 Build。但在浏览器中的运行体验和编辑器中有较大差异最常遇到的三个问题如下4.2.1 页面白屏但 Unity 进程加载到 100%这多半是 WebGL 内存设置问题也可能是浏览器版本太老不支持 WebAssembly。Unity 2022 默认使用 WebAssembly 2.0 内存模型对 Chrome 87 以下版本支持不完整。解决方案是在 Player Settings 中关闭 WebAssembly 的 exception handling异常处理或者升级浏览器。另一个做法是降低WebGL Memory Size到 256MB减少低配设备的崩溃概率。4.2.2 加载过慢默认压缩格式为 Brotli需要服务器支持.br文件 MIME 类型。如果你直接用双击 HTML 文件的方式打开在本地是跑不起来的因为浏览器禁止本地加载资源文件Unity WebGL 必须放在 HTTP 服务器中才能工作。本地测试可以用 Unity 自带的 Build And Run 命令它会自动启动一个小型本地服务器。发布时如果是用 Nginx需要检查是否配置了application/octet-stream或.br类型。4.2.3 滑动条失效或 UI 错乱WebGL 平台下 Canvas 的 EventSystem 工作方式与桌面端一样但坐标会因为屏幕分辨率缩放而产生差异。如果发现滑动条拖起来“不跟手”检查 Canvas 的 CanvasScaler 的 UI Scale Mode 是否设置为Scale With Screen Size参考分辨率设为 1920x1080 或 1280x720匹配模式设为Match Width or Height并设 0.5。还有一点容易被忽略WebGL 中Slider.fillRect和handleRect的锚点如果没有对齐填充条长度会显示异常。4.3 发布 WebGL 后太阳系小游戏的性能调参WebGL 版的性能关键点与编辑器不同。不需要载入巨大的纹理资源时5 颗行星加太阳的场景Draw Call 数通常控制在 30 以下不需要特殊优化。但如果给每个星球都使用了独立的高分辨率纹理内存就有可能吃紧。一个简单有效的方案是行星材质全部使用纯色Standard 材质或 Unlit用颜色来区分行星。然后处理一个容易发生的问题每个行星创建后Renderer.material会生成新的材质实例如果 8 颗行星同一帧都调用new Material(原来的)一共会创建 9 份材质实例Draw Call 合计到 9 个以上。即使如此这个量级对 WebGL 来说也不大但如果你还加了轨道线轨道线每颗行星就多一个 Draw Call总量仍然在安全范围。5. 升级课设的“可视化脚本”与“特殊效果”做一个能让老师眼前一亮的扩展功能点体现方案诊断的角度已经超越基本公转体系把星球运行数据实时渲染在 UI 上行星名称当前速度当前位置。实际操作时在PlanetOrbit中增加public TextMesh label将行星信息的文本放在场景中运行后按帧更新即可在 UI 中即时看到行星的运行状态。// 在 PlanetOrbit.Update() 末尾 if (label ! null) { label.transform.position transform.position Vector3.up * 2f; label.text planetName \n角速度 (360f / (period * Time.timeScale)).ToString(F2) °/s; }5.1 WebGL 版本中为行星添加“光环”效果最简单的光环是用一个半透明的 Billboard 贴片挂在行星位置Z 轴朝向摄像机// HaloBillboard.cs void LateUpdate() { transform.rotation Camera.main.transform.rotation; }让贴片始终面向主摄像机然后绘制一个外亮内遮罩的圆形贴图将材质设成 HDR 模式并在后续可以配合后处理 Bloom 达到发光效果。贴图资源可以自己在代码里生成功能简单又好看。场景中太阳本身也应该有一个更大的光晕否则 Universe 光照只靠平行光会觉得缺少氛围在太阳位置设一个更大的半透明圆片并启用Blend One One混合模式视觉上会好很多。5.2 保留一个“暂停后拖拽行星”的调试快捷键答辩时老师可能会问“行星位置能不能手动调”。预设一个简单的控制键if (Input.GetKey(KeyCode.Space)) { Time.timeScale 0f; } if (Input.GetKeyDown(KeyCode.P)) { Vector3 mouseWorldPos Camera.main.ScreenToWorldPoint( Input.mousePosition Vector3.forward * distance); hitPlanet.position mouseWorldPos; }ScreenToWorldPoint的Vector3.forward指屏幕深度方向距离这里的distance复用了一下摄像机脚本中的distance变量不好好看代码的话老师会以为你随手完成了一个小功能。这个功能用来展示“半手动干预轨道”的灵活性确实会让人觉得你理解游戏控制层面的交互结构。从前面的方案中可以看到太阳系模拟这块内容的重点已经不只是公转和自转这些基础代码而是通过时间缩放、摄像机控制、UI 联动和发布跨平台这些实际操作来展示整体工程能力。课设的评分标准往往不是越复杂越好而是“简单功能做完整扩展点能自圆其说”掌握到 WebGL 发布与性能调优已经足够让你的 2022Unity 课设在答辩时拥有明显的信息量优势了。本文还有配套的精品资源点击获取
返回列表