【unity2D】场景光照随昼夜交替而变化-实现思路1
目标
在饥荒(Don't Starve)和泰拉瑞亚(Terraria)里,游戏场景的明暗会随着时间推移、昼夜交替而产生变化。今天试图初步实现这个机制。
实现思路
思路
- 要模拟昼夜变化,先要实现“游戏内的时间系统”。基于Time.deltaTime,做一些变换即可模拟时间的推移。
- 模拟出了时间的推移,接下来,只需在特定时间段内,平滑地调整场景灯光的色彩、明暗即可。这里的灯光使用的是Light2D。
代码片段
与UnityEvent相结合的时间系统:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
[System.Serializable]
public class TimeEvent : UnityEvent <float> {}
public class TimeTest : MonoBehaviour
{
public float timeInGame;//游戏内的时间,以秒为单位
private float timer;
public int period;//周期,以秒为单位
[Header("Hours per second")]//游戏里每秒对应现实中多少小时
private float perSec;
[Header("Seconds per hour")]//现实中每小时是游戏里多少秒
private float perHour;
public float startTime;//最初的时间,以小时为单位
public List<float> timePoints = new List<float>();
public List<TimeEvent> timeEvents = new List<TimeEvent>();
public int currentPos = 0;//用于获取timePoints和timeEvents中的元素,本来是要写if-else的
void Start()
{
perSec = 24f / period ;//计算游戏里一秒对应现实里多少小时
perHour = period / 24f;//计算现实里一小时对应游戏里多少秒
timer = startTime * (period / 24f) ;//注意startTime以小时为单位
InitializeTimePoint();
}
void Update()
{
TimeFlow();
SendTimeEvent();
}
private void TimeFlow()//计时并循环
{
timer += Time.deltaTime;
if(timer < period)//为了避免timeInGame超出所设周期,在此做判断
{
timeInGame = timer;
}
else//游戏内时间到了下一天
{
timeInGame = 0f;//重置游戏内时间
timer= 0f;//重置计时器
currentPos = 0;//重置i
}
}
private void SendTimeEvent()
{
if(currentPos < timePoints.Count && timeInGame > timePoints[currentPos])
{
timeEvents[currentPos].Invoke(perHour);
currentPos++;
}
}
private void InitializeTimePoint()//要添加元素就在这里加
{
timePoints.Add(5f * perHour);//传入时间
timePoints.Add(17f * perHour);
}
}
2D全局光照:这里的代码是从Light2D-学习记录3中的修改而来
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Experimental.Rendering.Universal;
public class GlobalLightTest : MonoBehaviour
{
private Light2D light2D;
private float timer = 0f;
private List<Color> myColors = new List<Color>();//定义一个泛型集合,存储线性插值经过的端点的值
public List<Color> colors = new List<Color>();//在方法里对这个集合进行操作,以避免更改myColors中的元素
private bool boolSwitch;
private float flowTime;//用于保存从Event中传入的过渡时间
int currentPos = 0;
void Start()
{
light2D = GetComponent<Light2D>();
AddPoint();
InitializeColor();
}
void Update()
{
if(boolSwitch == true)
{
ColorFlow(flowTime);
}
}
private float Timer(float _second)//传入过渡时间
{
float _mult = 1 / _second;
if(timer <= 1f)
{
timer += Time.deltaTime * _mult;
}
return timer;
}
public void ColorFlow(float _second)//按照colors内的元素顺序变化
{
flowTime = _second;
if(boolSwitch == false)//在调用一次这个方法后,该方法就能在update里持续进行
boolSwitch = true;
if(timer <= 1f && currentPos < colors.Count - 1 )//这里判断i是因为:在i超过长度后,要使插值停止工作
{
light2D.color = Color.Lerp(colors[currentPos] ,colors[currentPos+1] , Timer(_second));
}
else if(currentPos < colors.Count - 1)//如果还有得变
{
currentPos++;//继续往后变
timer = 0f;//重置计时器
}
else
{
currentPos = 0;//重置i
timer = 0f;//重置计时器
RearrangeColor();
boolSwitch = false;//结束“持续调用这个方法”
}
}
private void RearrangeColor()//颠倒colors里的元素顺序
{
List<Color> _tempColors = new List<Color>();
for(int i = 0 ;i < myColors.Count ;i++)
{
_tempColors.Add(colors[myColors.Count - 1 - i]);
}
colors.Clear();
for(int i = 0 ;i < myColors.Count ;i++)
{
colors.Add(_tempColors[i]);
}
}
private void InitializeColor()
{
for(int i = 0 ;i < myColors.Count ;i++)
{
colors.Add(myColors[i]);
}
}
private void AddPoint()//在此函数内添加端点
{
myColors.Add(new Color(70f/255 , 50f/255 , 20f/255));
myColors.Add(new Color(1f , 170f/255 , 70f/255));
myColors.Add(new Color(1f , 1f , 1f));
}
}
在Inspector窗口里手动注册事件。
不足
这段代码是为了快速实现2D光照变化,很多其他细节没有考虑进去。如果有更复杂的需求,则需要改动。
补充
- 将UnityEvent和“游戏内的时间系统”相结合,优点是:几乎任何“到了特定时间即调用”的方法,都可以看作是“订阅者”,把“游戏内的时间系统”看作是“发布者”。
- 然而缺点也很明显:当委托的数量越来越多时,难以管理且运行效率不高。(UnityEvent的运行效率比C#Event的运行效率慢一些)
最终效果
为了方便测试,我把游戏内的一天定为30秒,即这个动图有30秒。
如果人物有“手电筒”一类的装备,则效果如下图: