简介:基于Unity3D的太阳系模拟项目资源包,面向Unity初中级开发者及天文科普爱好者,可用于系统学习场景搭建、物理引擎运动模拟、C#脚本控制与UI交互设计等核心环节。压缩包共包含2522个文件,以C#脚本(242个)、meta资源映射(542个)、info配置(656个)为主体,辅以Shader、预制体、材质贴图、动画片段、程序集定义(asmdef)及多种编辑器资源,整体约77.65MB,目录结构完整,可直接导入Unity工程进行参考与二次开发。目前已有419人学习参考。资源覆盖了太阳系场景从零搭建的全过程,包括行星公转与自转的牛顿万有引力参数设置(如椭圆轨道与自转周期)、光源阴影调节、自由摄像机控制等,并给出了UI信息展示、点击交互等功能的脚本实现与资源组织思路,能帮助读者理解如何使用Unity3D完成科学可视化模拟,并掌握面向此类项目的中型工程文件管理方法,有助于快速理清Unity3D在科学可视化项目中的典型开发路径。
1. 从 Unity3D 项目里翻出 MyTestSolarSystem 这个压缩包,第一反应大多是好奇:打开能看到什么?
我拆过不少这类教学资源,结论很直接——它不是完整的太空游戏,而是把「公转、自转、轨道层级、时间缩放」四个 Unity3D 高频知识点揉在一起的场景工程。跑起来你看到太阳在中心发亮,几颗行星按不同周期绕太阳走圆周,同时每颗球体还在自转。对刚学到 Transform 旋转的新手,这是少有的能立刻看到理论变成效果的练手项目;对要交课程设计的人,把它改成「模拟太阳系」演示也够。前提是你得先搞懂它的层级结构,别一上来就乱拖 Inspector。这一篇就按我平时拆包的顺序,从场景结构、运动脚本一路说到最容易翻车的几个坑。
2. 场景层级与搭建:从压缩包到跑通第一次运行
2.1 解压后先认一遍目录结构
这类教学包解压后通常没有复杂依赖,Assets 下主要三个文件夹:Scenes 放主场景,Scripts 放 C# 脚本,Materials 放材质。打开 Unity Hub 时注意版本兼容——如果是 2019 或 2020 版本建的工程,用更新的版本打开会有一次升级提示,多数情况直接确认就行;反过来用旧版本打开新版本工程反而容易出问题。
在 Unity 里有两个导入方式:一是用 Unity Hub 打开整个文件夹作为工程,这时能直接看到场景里的所有物体;二是把 Assets 里的内容复制到现有工程再打开场景。常见做法是第一种,因为场景里物体之间的引用(比如点光源挂在太阳下)已经建好,不破坏组织关系。我的建议是拿到包先备份再升级,尤其要留意 Project 窗口里有没有 Library 文件夹,那东西是本地缓存,打包给别人的时候通常不带上。
打开后你大概率会看到一组这样的物体:根节点叫 SolarSystem,下面挂了 Sun 和几个以 Planet 开头的中间层。如果作者把结构封装成 Prefab,那更省事,直接把 Prefab 拖进场景就能用。这一步别急着运行,先在 Hierarchy 里把层级结构展开看一遍,确认根节点下面有什么,因为整个运动逻辑都依赖这套父子关系。
2.2 两级节点结构:轨道支点与行星本体
这里要重点讲清楚「两级节点」的设计。结构大致是这样:
SolarSystem (0, 0, 0) ├─ Sun ├─ Anchor_Mercury │ └─ Mercury ├─ Anchor_Venus │ └─ Venus └─ Anchor_Earth └─ Earth
Anchor 也就是轨道支点,位置固定在太阳中心。行星本体挂在支点下面,但它的 localPosition 不是 (0,0,0),而是沿 X 轴偏移一段距离,这个距离就是轨道半径。当 Anchor 绕 Y 轴旋转时,子物体被带着走圆周——这就是 Unity3D 最常用的「父节点旋转带动子节点公转」方案。
为什么推荐这个结构而不是在 C# 脚本里每帧算 position?两个原因:一是性能,transform.Rotate 的开销远小于每帧构造 Vector3 再赋值,而且不会因为三角函数累积误差导致轨道漂移;二是调试友好,想在 Inspector 里改轨道半径,直接拖子物体 localPosition 的 X 值就行,不用打开脚本改常量。缺点是椭圆轨道做不了,只能做正圆,但对教学场景完全够用。
注意一个细节:Anchor 和它下面所有子物体的 Scale 必须保持 1:1:1。如果有人在根节点缩放过,子物体旋转时往往出现变形。养成习惯,打开场景先检查根节点 Transform 的 Scale 是否正常。现在的资源包大多遵守这个约定,但保不齐哪次更新误拖了 Scale。
2.3 摄像机:全局视角与跟随视角
默认摄像机的摆放决定了第一眼效果。我建议不要用 Scene 视图直接运行,而是在 Hierarchy 里新建一个 Camera,Position 放到 (0, 55, 35),朝向原点,这样整个轨道平面都在视野内。FOV 设在 40 到 50 之间,数值太小会切掉两侧行星,太大则有鱼眼变形。
如果要做跟随视角,比如跟着地球观察,单独挂一个脚本比直接把摄像机拖进地球子物体更好。直接拖进去的话,摄像机会跟着子物体一起自转,画面会持续晃动,这是新手最容易犯的错。写一个 FollowTarget 脚本挂到摄像机上:
using UnityEngine; public class FollowTarget : MonoBehaviour { public Transform target; public Vector3 offset = new Vector3(0f, 8f, -12f); void LateUpdate() { if (target == null) return; transform.position = target.position + offset; transform.LookAt(target); } }逻辑说明:Position 取目标点加偏移,LookAt 让镜头永远盯着行星中心。用 LateUpdate 而不是 Update,是因为 Unity3D 里所有物体的运动都在 Update 阶段完成,摄像机如果在 Update 里取值,可能取到上一帧的位置,画面会出现一卡一卡的感觉;LateUpdate 保证在所有物体更新完之后再移动相机,这一行差异就是流畅和玄学的分界线。
参数说明:offset 决定观察距离和角度,觉得行星太小就把 Y 值放大,或者把 Z 的绝对值减小。target 拖到对应的星球本体上即可。如果还想验证层级搭得对不对,可以先手动旋转某个 Anchor 的 Y 轴,看行星本体是否沿轨道走;要是本体自己在翻转,就说明轴没对齐,回 2.2 节检查父子结构。
3. 公转与自转脚本:两级节点的运动学实现与周期换算
3.1 用旋转还是位置计算?正圆轨道的取舍
多数太阳系教学资源有两种实现轨道的方式。第一种是旋转轨道支点,上一章已经讲过;第二种是在 Update 里实时求坐标:
float theta += speed * Time.deltaTime; transform.position = center + new Vector3(Mathf.Cos(theta) * radius, 0, Mathf.Sin(theta) * radius);这段代码能跑,但有两个隐患:一是 theta 无限增长,浮点数精度会慢慢丢失,运行一两个小时以上轨道会肉眼可见地偏移;二是 center 不能是离原点太远的大数值,否则同样吃精度。旋转支点的方案里,Unity 引擎内部对旋转矩阵做了归一化处理,长时间运行更稳定,这就是我在教学包里优先推荐的原因。如果是做行星碰撞或者引力效果,才需要位置计算,因为要拿到精确坐标去检测距离。
3.2 公转脚本:周期参数与角度换算
公转脚本的核心是把「一圈的时间」换算成「度/秒」。Unity 的 transform.Rotate 接口接收的是度数,不像数学库默认弧度,新手最容易在这搞混。一个完整的公转脚本写法:
using UnityEngine; public class OrbitMotion : MonoBehaviour { [Tooltip("公转一圈所需秒数,60 表示 60 秒转一圈")] public float orbitPeriod = 60f; private float degreesPerSecond; void Start() { degreesPerSecond = 360f / orbitPeriod; } void Update() { transform.Rotate(0f, degreesPerSecond * Time.deltaTime, 0f, Space.World); } }逻辑说明:360f 是一圈的度数,除以周期得到角速度,再乘 Time.deltaTime 保证旋转速率与帧率无关。旋转轴选择 Y,是因为场景的轨道平面是 XZ 平面,法线方向就是 Y。把脚本挂到 Anchor 节点上,而不是行星本体上——这个区别决定了公转还是自转。
这里给出一张以「地球公转 60 秒」为基准的压缩周期换算表,教学场景里实测手感最好的一组参数:
| 行星 | 真实公转周期(天) | 压缩后周期(秒) |
|---|---|---|
| 水星 | 88.0 | 14.5 |
| 金星 | 224.7 | 22.7 |
| 地球 | 365.3 | 60.0 |
| 火星 | 687.0 | 112.9 |
| 木星 | 4332.6 | 375.0 |
| 土星 | 10759.2 | 930.0 |
别直接拿真实天数填,周期太长运行起来像静态图,完全看不出轨道运动。教学场景一般把最快那颗压到 15 秒左右,肉眼能明显看到差距,又不至于晃眼。换算逻辑很简单:目标秒数 = 60 × (该行星天数 / 365.3),取整后微调。
3.3 自转脚本与轴倾角
自转思路一样,但挂在行星本体上,旋转轴仍然选 Y,使用 Space.Self。这里 Self 和 World 的区别要讲清楚:行星本体挂在已经旋转的 Anchor 下面,如果仍用 World,自转轴会跟着父节点一起倾斜,看起来像在翻滚;用 Self 则绕自身局部轴转,更符合直观:
using UnityEngine; public class SelfRotation : MonoBehaviour { [Tooltip("自转一圈所需秒数,10 表示 10 秒转一圈")] public float rotationPeriod = 10f; private float degreesPerSecond; void Start() { degreesPerSecond = 360f / rotationPeriod; } void Update() { transform.Rotate(0f, degreesPerSecond * Time.deltaTime, 0f, Space.Self); } }逻辑说明:rotationPeriod 给 10 秒在演示里比较有动感,但别小于 3 秒,否则转太快像广告图。轴倾角(比如地球的 23.4 度)在资源包里通常被省略。想加的话,不要直接在自转脚本里改轴向量,而是给行星本体套一层空物体 Pivot,把 Pivot 的 X 轴旋转 23.4 度,SelfRotation 挂在行星本体上。这样「轴向固定、绕轴自转」的物理关系被拆成了两个 Transform,后续调倾角不会污染自转逻辑。我处理其它机械旋转类 Demo(比如风扇、轮胎)时也惯用这个结构。
3.4 运行验证:怎么看轨道周期是否正确
跑起来以后用手机秒表掐一圈是笨办法,更硬核的做法是写个脚本验证。等公转角累计超过 360 度后打印耗时,判断周期偏差是否在预期内。这里有个很容易被忽略的点:改完 orbitPeriod 后,Start 里重新算角速度只会在运行时生效一次;如果你在运行状态下从 Inspector 直接改数值,不会即时生效。很多新手以为「改了没反应」,就是卡在这。
要支持运行时热调,把角速度计算挪到 Update 里,或者用 OnValidate:
private float degreesPerSecond; void OnValidate() { if (orbitPeriod <= 0f) orbitPeriod = 1f; degreesPerSecond = 360f / orbitPeriod; } void Update() { if (degreesPerSecond == 0f) OnValidate(); transform.Rotate(0f, degreesPerSecond * Time.deltaTime, 0f, Space.World); }逻辑说明:OnValidate 在 Inspector 值变化时触发,省去手动重开游戏。加一个保护判断,防止周期误填成 0 导致除零。这属于实用小技巧,源码模板里不一定会给你写,但做课程演示时很救场。
4. 光照与材质:让太阳系看起来不像是色块拼盘
4.1 太阳材质:Emission 与自发光陷阱
在 Unity3D 里做太阳,最省的做法是给太阳一个 Standard 材质,勾上 Emission,Emission Color 调到接近全白,Intensity 给 1 到 3。另一种做法是建一个 Unlit 自发光材质,不吃光照,更亮但少了层次感。教学包多用前者。
有一个经常被忽略的坑:Emission 开大了,太阳在 Scene 视图里过曝,但你放一个 Point Light 后,行星受光面正常时才发现太阳本身是「假亮」——它并没有照亮周围。所以正统做法是材质负责外观,光照交给单独挂的 Point Light。Light 组件的 Range 必须覆盖最远行星的轨道半径,否则边缘行星永远只有暗面。Intensity 给 2 左右、Shadow Type 设 Hard 或 Soft,看性能取舍。太阳本体的 MeshRenderer 可以勾掉 Shadow Casting,避免自阴影导致表面出现奇怪的黑色斑块。
4.2 行星材质:颜色、纹理与明暗面
没有真实纹理的行星贴图时,Standard 材质配一个基础色就够。各行星常用参考色:水星偏灰、金星米黄、地球蓝绿、火星砖红、木星棕白相间、土星淡黄。代码方式生成颜色渐变纹理也可以,但教学场景里不值当,直接用 Material 的 Albedo 改色更直观。
明暗面是否正常是检验光照配置的试金石。行星背对太阳的那一面如果完全漆黑,说明 Point Light 强度太小或 Range 没盖住;如果整颗球亮得像自发光,说明 Standard 材质的 Metallic 和 Smoothness 调过头了,反光把暗面补亮了。我的经验值是 Metallic 给 0 到 0.2、Smoothness 给 0.3 左右,比较接近石质行星的观感。气态巨行星可以适当拉高 Smoothness 到 0.6,反光会更有「大气层」的感觉,但别全拉到 1。
4.3 小行星带与土星环:粒子系统的两个使用姿势
如果资源包里有小行星带,大多是粒子系统:用环形轨道发射器,粒子数 2000 到 3000 之间,Shape 设成 Circle,半径与木星和火星之间的小行星带匹配。关键技巧是让发射器跟着公转速度缓慢旋转,粒子本身不移动,视觉上小行星带在缓慢转动。如果给每个粒子都做物理运动,Draw Call 会爆表,帧率直线下降。
土星环同理,可以用一个 Ring Mesh 加透明贴图,也可以再开一套粒子系统,半径更大、宽度更窄。两套系统的旋转速率差一点,看起来更真实。注意粒子材质里的渲染模式设成 Billboard,否则从侧面看会变成一条线。这些参数没有固定标准,边运行边调 Shape 的 Radius 和 Rotation Speed 就行。
4.4 性能检查:合批与 Draw Call
行星模型大多只有几百个面,不必担心面数,倒是 Draw Call 容易被忽略。场景里十来个星球各用各的材质,就会产生十几次绘制调用。教学场景无所谓,但要做到 60 帧稳定,有两个免费手段:一是把同色的静态物体标记为 Static,让 Unity 做静态合批;二是把多个小行星共用一个材质实例,Unity 会自动合批同材质的物体。
这里注意:如果给每个行星都用 Create Material Instance,合批会被打断,因为实例化材质相当于告诉引擎「我的材质和别人的不一样」。所以优化和单独调参是矛盾的,我的取舍是:主角行星用独有材质,背景小行星带统一用一个材质,合批效果最明显。
5. 避坑专题:Unity 太阳系模拟里高频踩到的五个现场
5.1 复制行星后所有星球材质一起变色
现象:选中地球复制出一个新星球,改颜色后其它所有行星也跟着变。
原因:Unity 的材质默认是按引用共享的。复制物体不会复制材质,你从 Inspector 修改颜色时,操作的是同一个材质文件,所有引用它的物体一起变。
解决:在 Inspector 右上角齿轮菜单里选 Create Material Instance,生成独立材质副本再改;或者在脚本里用 new Material(originalMat) 复制一份再赋给 MeshRenderer。养成这个习惯,操作任何共享资源都要先问一句:这个引用是我独享还是大家共用?
5.2 暂停后恢复,星球不转了
现象:把 Time.timeScale 设成 0 做暂停菜单,恢复成 1 后所有旋转停住,或者直接跳了一大截。
原因:有的写法在暂停时驻留了累计角度,恢复时 deltaTime 跳变导致计算异常;或者用了 unscaledDeltaTime 补帧,逻辑混在一起。
解决:运动统一走 Time.deltaTime,不要自己缓存上一帧角度;暂停只改 timeScale,不动其它逻辑。若要支持暂停时的镜头移动,用 unscaledDeltaTime 单独控制摄像机,别让它影响旋转逻辑。
5.3 行星离原点太远出现抖动
现象:把轨道半径设成 5000,运行平移镜头时球体边缘疯狂抖动,像隔了一层热浪。
原因:Unity 的世界坐标用 float 存储,大约 7 位有效数字,离原点越远分辨率越低,两个相近的大数作差时精度就丢了。
解决:压缩场景整体比例,轨道半径控制在几百单位内,前文那张周期表就遵守这个约束;或者把太阳和行星的世界坐标整体偏移回原点附近再运行。
5.4 公转太快导致残影和穿模
现象:周期设成 0.1 秒,星球像甩鞭子一样拖着残影,摄像机完全跟不住。
原因:每帧旋转角度远大于人的视觉捕捉范围,物体移动弧长过大,帧与帧之间的位置不连续。
解决:周期别低于 9 到 10 秒;非要高速演示,调 Time.timeScale 而不是改周期。这个区别很关键,因为 timeScale 会等比例缩放所有运动,摄像机也能跟着走,而只改周期只会让单颗星球发疯。
5.5 行星位置正确但看起来在「摇摆」
现象:轨道没有错,但运行后行星的圆周运动一会儿扁一会儿鼓,像在画椭圆。
原因:根节点或父节点带了非均匀缩放,比如 Scale 是 (1, 0.5, 1),旋转矩阵与缩放叠加产生了剪切的视觉效果。
解决:所有轨道节点的 Scale 保持 1:1:1,缩放只放根节点且用统一比例。改星球大小用 localScale 乘一个标量,不要在父节点单独拉 X 轴。打开场景先检查一遍三个轴的 Scale 是否一致,这是我拆资源包的第一习惯。
6. 进阶调参:把真实轨道数据压进场景单位而不丢精度
真实尺度没法直接搬进 Unity:地球轨道约 1.5 亿公里,海王星约 45 亿公里,直接把公里数当场景单位,坐标数值会爆掉 float 精度,所有旋转轴都会漂。网上很多资源「看着跟真实差别很大」,问题不在脚本,在比例尺选错了。
使用天文单位 AU 压缩轨道半径是通用做法,1 AU 取 10 个场景单位:
| 行星 | 半长轴(AU) | 场景轨道半径(单位) |
|---|---|---|
| 水星 | 0.39 | 3.9 |
| 金星 | 0.72 | 7.2 |
| 地球 | 1.00 | 10.0 |
| 火星 | 1.52 | 15.2 |
| 木星 | 5.20 | 52.0 |
| 土星 | 9.58 | 95.8 |
| 天王星 | 19.20 | 192.0 |
| 海王星 | 30.05 | 300.5 |
300 单位还在 float 的合理区间内,点光源 Range 给到 350 就能覆盖全场景。但行星本体的视觉半径不要按同一比例:木星和地球的半径比约 11 倍,按轨道比例缩放的话木星会大到两个场景单位,直接挡住后面几十颗行星。常见做法是轨道半径严格按 AU 比例,球体半径单独按显示需要缩放,教学演示都这么处理,别被「真实感」带偏。
时间轴同样要压缩:地球一圈 60 秒,其它行星按「真实公转天数 / 365.3 × 60 秒」计算,就是第三章那张表的来源。自转周期可以统一给 8 到 12 秒,真实自转差异在教学演示里不明显,强行按真实比例会让部分行星慢到看不出在转。
验证方法也很简单:跑起来后给地球轨道打个空物体标记,用秒表掐一圈,看是不是 60 秒左右;然后把摄像机设到海王星外围,Position 用 (0, 20, 320),LookAt 太阳,确认最远轨道在视野内。我从那以后每次拿到这类资源,第一件事就是检查根节点 Scale 是不是 1、轨道半径是不是百量级,再谈颜色和光照。这套检查流程救过我好几次项目交付,希望帮到你。
本文还有配套的精品资源,点击获取