目標
在飢荒(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秒。
如果人物有“手電筒”一類的裝備,則效果如下圖: