游戏昼夜循环中Quaternion欧拉角无故出现负值问题求助
昼夜循环太阳旋转异常问题
问题详情
我正在为游戏制作昼夜循环系统,通过每秒为DateTime对象增加秒数推进时间,同时计算minutes变量(公式:DateTime.Hours * 60 + DateTime.Minutes)。我希望基于这个变量旋转太阳,使用公式Quaternion.Euler(minutes / 4, 0, 0)。
一切运行正常,直到DateTime到达正午时分,太阳开始出现异常:旋转值变为负值,之后又恢复正常状态直至午夜。此外,Inspector面板显示的太阳旋转值与Debug.Log输出的结果不一致。

问题演示视频:画质欠佳,展示了正午时分太阳旋转异常的情况
怀疑是万向节锁(Gimbal lock)导致的,但对四元数了解有限,以下是我的代码:
public enum DayTime { Morning, Noon, AfterNoon, Evening, Night } public class MoonTime : MonoBehaviour { public DateTime date = new DateTime(2023, 1, 1, 0, 0, 0); [HideInInspector] public float minutes = 0; public float dayLength = 4; public float smoothness = 4.5f; [HideInInspector] public DayTime dayTime; public delegate void TimeChange(DateTime date); public event TimeChange onTimeChange; public delegate void DayTimeChange(DayTime dayTime); public event DayTimeChange onDayTimeChange; public Material daySkyBox; public Material nightSkyBox; public Light Sun; public Light Moon; private void Awake() { StartCoroutine(TimeClock()); onTimeChange += (d) => { var sunCurrentRot = Sun.transform.rotation.eulerAngles; Debug.Log("What rot is should be " + Quaternion.Euler(minutes / 4, sunCurrentRot.y, sunCurrentRot.z).eulerAngles); Sun.transform.rotation = Quaternion.Euler(minutes / 4, sunCurrentRot.y, sunCurrentRot.z); Debug.Log("What is it " + Sun.transform.rotation.eulerAngles); //var moonCurrentRot = Moon.transform.rotation.eulerAngles; //Moon.transform.rotation = Quaternion.Euler((minutes * 0.25f) + 180, moonCurrentRot.y, moonCurrentRot.z); }; } private void Update() { } public IEnumerator TimeClock() { while (true) { yield return new WaitForSecondsRealtime(1 / smoothness); float finalValue = 1440 / (dayLength * 60) / smoothness; date = date.AddSeconds(finalValue); minutes = (date.Hour * 60) + date.Minute; if (date.Hour >= 0 && date.Hour < 12) { if (dayTime != DayTime.Morning) { dayTime = DayTime.Morning; Debug.Log("Morning"); onDayTimeChange?.Invoke(DayTime.Morning); } } else if (date.Hour == 12) { if (dayTime != DayTime.Noon) { dayTime = DayTime.Noon; Debug.Log("Noon"); onDayTimeChange?.Invoke(DayTime.Noon); } } else if(date.Hour > 12 && date.Hour <= 15) { if (dayTime != DayTime.AfterNoon) { dayTime = DayTime.AfterNoon; Debug.Log("After Noon"); onDayTimeChange?.Invoke(DayTime.AfterNoon); } } else if (date.Hour > 15 && date.Hour <= 17) { if (dayTime != DayTime.Evening) { dayTime = DayTime.Evening; Debug.Log("Evening"); onDayTimeChange?.Invoke(DayTime.Evening); } } else { if (dayTime != DayTime.Night) { dayTime = DayTime.Night; Debug.Log("Night"); onDayTimeChange?.Invoke(DayTime.Night); } } onTimeChange?.Invoke(date); Debug.Log(date); } } }
解决方案
核心问题
这不是万向节锁导致的,问题出在Unity对欧拉角的归一化处理以及你的旋转更新逻辑:
- Unity的
transform.rotation.eulerAngles返回的角度永远在[0, 360)范围内,不会出现负数;但当你设置的欧拉角X值超过180度时,Unity会自动转换为等价的负角度(比如190度转为-170度),这就是Inspector显示负值的原因。 - 你每次更新旋转时都读取当前太阳的欧拉角再赋值,会因为归一化的偏差导致计算混乱。
修复代码
1. 修改太阳旋转逻辑
直接基于时间计算目标旋转,不依赖当前旋转值,同时调整角度公式避免跨180度的异常转换:
onTimeChange += (d) => { // 调整角度逻辑:0点对应-90度(地平线以下),12点对应90度(头顶),24点回到-90度 float sunXRot = (minutes / 4) - 90f; // 直接设置旋转,保持Y/Z轴为初始值(若不需要Y轴旋转) Sun.transform.rotation = Quaternion.Euler(sunXRot, 0, 0); // 查看原始计算值,而非Unity归一化后的结果 Debug.Log("太阳计算旋转值:" + sunXRot); Debug.Log("Unity显示旋转值:" + Sun.transform.rotation.eulerAngles); };
2. 优化时间推进逻辑(可选)
避免DateTime累加秒数的精度问题,直接维护minutes变量:
public IEnumerator TimeClock() { while (true) { yield return new WaitForSecondsRealtime(1 / smoothness); // 计算每帧增加的分钟数:一天1440分钟,按dayLength小时的周期推进 float minutesPerFrame = 1440f / (dayLength * 60f * smoothness); minutes += minutesPerFrame; // 超过一天后重置 if (minutes >= 1440f) { minutes -= 1440f; date = date.AddDays(1); } else { date = date.AddMinutes(minutesPerFrame); } // 以下昼夜判断逻辑保持不变 if (date.Hour >= 0 && date.Hour < 12) { if (dayTime != DayTime.Morning) { dayTime = DayTime.Morning; Debug.Log("Morning"); onDayTimeChange?.Invoke(DayTime.Morning); } } else if (date.Hour == 12) { if (dayTime != DayTime.Noon) { dayTime = DayTime.Noon; Debug.Log("Noon"); onDayTimeChange?.Invoke(DayTime.Noon); } } else if(date.Hour > 12 && date.Hour <= 15) { if (dayTime != DayTime.AfterNoon) { dayTime = DayTime.AfterNoon; Debug.Log("After Noon"); onDayTimeChange?.Invoke(DayTime.AfterNoon); } } else if (date.Hour > 15 && date.Hour <= 17) { if (dayTime != DayTime.Evening) { dayTime = DayTime.Evening; Debug.Log("Evening"); onDayTimeChange?.Invoke(DayTime.Evening); } } else { if (dayTime != DayTime.Night) { dayTime = DayTime.Night; Debug.Log("Night"); onDayTimeChange?.Invoke(DayTime.Night); } } onTimeChange?.Invoke(date); Debug.Log(date); } }
额外说明
- Inspector显示的欧拉角是Unity归一化后的等价角度,和你设置的原始值可能不同,但旋转效果完全一致。如果要验证计算逻辑,直接输出你计算的
sunXRot即可。 - 万向节锁是三个欧拉轴重合导致自由度丢失的情况,你仅修改X轴旋转,不会触发万向节锁。
内容的提问来源于stack exchange,提问作者Snapper
相关产品推荐
相关产品推荐

