前言
实时天气在Unity3d三维数字沙盘中的作用非常重要,它能够增强虚拟环境的真实感和互动性,实时天气数据的应用可以提供更为精准和直观的天气信息支持,如果真实的数据加上特效、声音和模型反馈会提高产品档次,提高真实感。
目前Unity3d虽然没有自带天气系统,不过天气关联的插件也是琳琅满目,这里个人推荐,项目中使用过的UniStorm插件,其提供了天气系统、时间系统(日夜切换),天气过度(动画)、物理效果、音效、多平台支持(pc/移动端/ARVR等),而且它还具有高度可扩展性和自定义性。
有了UniStorm插件的天气系统作为外壳支撑,真实的天气数据使用彩云天气API接入实现,它提供实况数据、分钟级降水、小时级和天级数据、还有天气预警数据。目前我只需要其实际数据接口,注册即送10000/日调用量,这里作为测试和技术研究完全够用。
关注并私信 U3D实时天气 免费获取测试程序(底部公众号)。
效果
实时效果:
大雨
雷雨
雾霾
其他效果:
萤火虫
火暴雨
雷暴雪
晴转阴
实现
这里的实现思路很简单,就是通过天气API接口获取实时的天气数据,通过数据接口返回数据解析出需要的实时数据,将数据显示在UI上,并通过UniStorm插件的UniStormManager管理类直接调用变更天气类型,并同步了三维地图上的天实时气系统效果。本工程基于Unity2019.4.22f1c1实现
UI搭建
这里的UI系统主要是展示一些文字信息:天气、温度、湿度等,接口提供了很多数据,可以按需解析显示。这里的UI简单搭建:
即用于文字显示天气、温度、湿度等,图标显示天气图标,按钮点击同步天气。
经纬度数据
天气数据的获取需要位置信息,数字沙盘大部分都是运行在PC平台上,获取经纬度直接通过设备的GPS并不适用,因为PC通常没有内建的GPS模块,使用外部API或服务来获取基于IP地址的地理位置。这边采用的代码如下(仅供参考,该接口偶尔会请求异常):
//获取ip地址信息
IEnumerator GetIPLocation()
{
UnityWebRequest request = UnityWebRequest.Get("https://ipinfo.io/json");
yield return request.SendWebRequest();
// 检查网络错误或 HTTP 错误
if (request.isNetworkError || request.isHttpError)
Debug.LogError("请求失败: " + request.error);
else
{
// 解析 JSON 数据
string jsonResponse = request.downloadHandler.text;
LocationData locationData = JsonUtility.FromJson<LocationData>(jsonResponse);
string[] latLong = locationData.loc.Split(',');
if (latLong.Length == 2)
{
Latitude = latLong[0];
Longitude = latLong[1];
Debug.Log("Latitude: " + Latitude + ", Longitude: " + Longitude);
}
else
{
Debug.LogWarning("经纬度解析失败!");
}
}
}
其中返回的json数据类如下:
[System.Serializable]
public class LocationData
{
public string ip;
public string city;
public string region;
public string country;
public string loc; // "latitude,longitude"
}
天气数据
数据通过接口直接前往api开放平台注册登录,然后创建应用:
创建应用后选择接口类型(天气),应用场景(随便选),填写应用名称后点击创建即可,创建后在应用管理列表能看到Token和用量等信息,Token后续需要使用,还需注意QPS为1,无法支撑请求频繁情况。
请求接口:
curl "https://api.caiyunapp.com/v2.6/TAkhjf8d1nlSlspN/101.6656,39.2072/realtime"
请求代码如下:
IEnumerator GetWeatherInfo()
{
string url = $"{BaseUrl}/{ApiKey}/{Longitude},{Latitude}/realtime";
//Debug.Log("请求地址: " + url);
using (UnityWebRequest request = UnityWebRequest.Get(url))
{
yield return request.SendWebRequest();
// 检查网络错误或 HTTP 错误
if (request.isNetworkError || request.isHttpError)
Debug.LogError("请求失败: " + request.error);
else
{
Debug.Log("请求成功: " + request.downloadHandler.text);
}
}
}
请求返回实时数据结构:
{
"status": "ok", // 返回状态
"api_version": "v2.6", // API 版本
"api_status": "active", // API 服务状态
"lang": "zh_CN", // 返回语言
"unit": "metric", // 单位
"tzshift": 28800, // 时区偏移
"timezone": "Asia/Shanghai", // 时区
"server_time": 1640745758,
"location": [39.2072, 101.6656],
"result": {
"realtime": {
"status": "ok",
"temperature": -7, // 地表 2 米气温
"humidity": 0.58, // 地表 2 米湿度相对湿度(%)
"cloudrate": 0, // 总云量(0.0-1.0)
"skycon": "CLEAR_DAY", // 天气现象
"visibility": 7.8, // 地表水平能见度
"dswrf": 47.7, // 向下短波辐射通量(W/M2)
"wind": {
"speed": 1.8, // 地表 10 米风速
"direction": 22 // 地表 10 米风向
},
"pressure": 85583.47, // 地面气压
"apparent_temperature": -9.9, // 体感温度
"precipitation": {
"local": {
"status": "ok",
"datasource": "radar",
"intensity": 0 // 本地降水强度
},
"nearest": {
"status": "ok",
"distance": 10000, // 最近降水带与本地的距离
"intensity": 0 // 最近降水处的降水强度
}
},
"air_quality": {
"pm25": 45, // PM25 浓度(μg/m3)
"pm10": 49, // PM10 浓度(μg/m3)
"o3": 6, // 臭氧浓度(μg/m3)
"so2": 8, // 二氧化硫浓度(μg/m3)
"no2": 42, // 二氧化氮浓度(μg/m3)
"co": 1.1, // 一氧化碳浓度(mg/m3)
"aqi": {
"chn": 63, // 国标 AQI
"usa": 124
},
"description": {
"chn": "良",
"usa": "轻度污染"
}
},
"life_index": {
"ultraviolet": {
"index": 3,
"desc": "弱" // 参见 [生活指数](tables/lifeindex)
},
"comfort": {
"index": 12,
"desc": "湿冷" // 参见 [生活指数](tables/lifeindex)
}
}
},
"primary": 0
}
}
按这个请求格式,先根据需要的数据新建需要解析的数据结构:
// 定义数据结构(根据彩云天气 API 文档)
[System.Serializable]
public class WeatherData
{
public WeatherResult result;
}
[System.Serializable]
public class WeatherResult
{
public RealtimeWeather realtime;
}
[System.Serializable]
public class RealtimeWeather
{
public string skycon; // 天气状况
public float temperature; // 温度
public float humidity; // 湿度
public float pressure; // 地面气压
}
天气显示
上一步,通过接口获取了天气实时数据并进行了解析,这一步将通过数据的更新UI和场景中的天气类型。
UI的刷新Text内容直接数据赋值显示即可,图标和天气现象文字需要根据内容做个转换映射。天气现象代码和文字如下:
这里直接将其转为字典(Dictionary)存储,方便天气现象文字获取:
// 天气现象 映射表
private static readonly Dictionary<string, string> SkyconMap = new Dictionary<string, string>
{
{"CLEAR_DAY", "晴(白天)"},
{"CLEAR_NIGHT", "晴(夜间)"},
{"PARTLY_CLOUDY_DAY", "多云(白天)"},
{"PARTLY_CLOUDY_NIGHT", "多云(夜间)"},
{"CLOUDY", "阴"},
{"LIGHT_HAZE", "轻度雾霾"},
{"MODERATE_HAZE", "中度雾霾"},
{"HEAVY_HAZE", "重度雾霾"},
{"LIGHT_RAIN", "小雨"},
{"MODERATE_RAIN", "中雨"},
{"HEAVY_RAIN", "大雨"},
{"STORM_RAIN", "暴雨"},
{"FOG", "雾"},
{"LIGHT_SNOW", "小雪"},
{"MODERATE_SNOW", "中雪"},
{"HEAVY_SNOW", "大雪"},
{"STORM_SNOW", "暴雪"},
{"DUST", "浮尘"},
{"SAND", "沙尘"},
{"WIND", "大风"}
};
关于天气的图标使用的是UniStorm插件内置的图标,正式项目可以让美术搞一套完整的,我这里使用的就是很精简的:
这里的图标没法和天气接口的天气现象一一对应,所以这些图标也需要中间一次转换:
//获取图标类型
string GetIconType(string skycon)
{
string icon = "";
switch (skycon)
{
case "CLEAR_DAY":
case "CLEAR_NIGHT":
icon = "clear";
break;
case "PARTLY_CLOUDY_DAY":
case "PARTLY_CLOUDY_NIGHT":
icon = "partly_cloudy";
break;
case "CLOUDY":
case "FOG":
icon = "cloudy";
break;
case "LIGHT_HAZE":
case "MODERATE_HAZE":
case "HEAVY_HAZE":
icon = "haze";
break;
case "LIGHT_RAIN":
case "MODERATE_RAIN":
icon = "light_rain";
break;
case "HEAVY_RAIN":
case "STORM_RAIN":
icon = "heavy_rain";
break;
case "LIGHT_SNOW":
case "MODERATE_SNOW":
icon = "light_snow";
break;
case "HEAVY_SNOW":
case "STORM_SNOW":
icon = "heavy_snow";
break;
case "DUST":
case "SAND":
icon = "sand";
break;
default:
icon = "clear";
break;
}
return icon;
}
如上处理映射了天气现象和需要动态加载的天气图标,更新天气UI的完整代码如下:
//更新天气UI
void UpdateWeatherUI(string jsonData)
{
try
{
// 使用 Unity 内置的 JsonUtility 或第三方 JSON 解析工具(如 Newtonsoft.Json)解析数据
WeatherData weatherData = JsonUtility.FromJson<WeatherData>(jsonData);
if (weatherData != null && weatherData.result != null)
{
text_skycon.text = "天气:<color=#14FF00>" + (SkyconMap.ContainsKey(weatherData.result.realtime.skycon) ?
SkyconMap[weatherData.result.realtime.skycon] : weatherData.result.realtime.skycon) + "</color>";
text_temp.text = ($"温度:<color=#14FF00>{weatherData.result.realtime.temperature}℃</color>");
text_humidity.text = ($"湿度:<color=#14FF00>{weatherData.result.realtime.humidity * 100}%</color>");
text_pressure.text = ($"气压:<color=#14FF00>{weatherData.result.realtime.pressure} Pa</color>");
Icon.sprite = Resources.Load<Sprite>("Weather Icons/" + GetIconType(weatherData.result.realtime.skycon));
SwitchWeatherType(weatherData.result.realtime.skycon);
}
else
{
Debug.LogError("解析天气数据失败");
}
}
catch (Exception e)
{
Debug.LogError($"解析天气数据失败:" + e);
}
}
接下来处理三维场景中的天气系统,即将接口返回的天气现象转换到UniStorm插件的天气类型,并进行切换的操作,这里也存在两者之间没法直接一一对应,如下是插件的内置天气类型:
UniStorm插件内置天气类型说明
0 Clear //晴朗
1 Mostly Clear //大部分晴朗
2 Mostly Cloudy //大部分多云
3 Partly Cloudy //局部多云
4 Cloudy //多云
5 Lightning Bugs //萤火虫
6 Blowing Pollen //吹花粉
7 Blowing Leaves //吹落叶
8 Blowing Pine Needles //吹松针
9 Blowing Snow //飞雪
10 Foggy //雾
11 Overcast //阴
12 Hail //冰雹
13 Heavy Rain //暴雨
14 Rain //雨
15 Light Rain //小雨
16 Drizzle //毛毛雨
17 Heavy Snow //大雪
18 Snow //雪
19 Light Snow //小雪
20 Thunderstorm //雷雨
21 Thunder Snow //雷雪
22 Dust Storm //尘暴
23 Fire Rain //火星雨
24 Fire Storm //火星暴雨
这些是在UniStorm System节点上的AllWeatherTypes列表,上面的序号和类型是默认的,它新增了很多创意类型如Fire Rain、Fire Storm和Lightning Bugs等,默认如下:
当然您也可以去新增/修改自己的天气类型,但是如果修改过AllWeatherTypes列表,则没法直接套用。这里的天气映射和切换代码如下:
//切换场景中天气
void SwitchWeatherType(string skycon)
{
int idx = 0;
switch (skycon)
{
case "CLEAR_DAY":
case "CLEAR_NIGHT":
idx = 0;
break;
case "PARTLY_CLOUDY_DAY":
case "PARTLY_CLOUDY_NIGHT":
idx = 3;
break;
case "CLOUDY":
idx = 2;
break;
case "FOG":
idx = 10;
break;
case "LIGHT_HAZE":
case "MODERATE_HAZE":
case "HEAVY_HAZE":
idx = 22;
break;
case "LIGHT_RAIN":
idx = 15;
break;
case "MODERATE_RAIN":
idx = 14;
break;
case "HEAVY_RAIN":
case "STORM_RAIN":
idx = 13;
break;
case "LIGHT_SNOW":
idx = 19;
break;
case "MODERATE_SNOW":
idx = 18;
break;
case "HEAVY_SNOW":
case "STORM_SNOW":
idx = 17;
break;
case "DUST":
case "SAND":
idx = 22;
break;
default:
idx = 0;
break;
}
if (UniStormSystem.Instance != null && idx >= 0 && idx < UniStormSystem.Instance.AllWeatherTypes.Count)
UniStormManager.Instance?.ChangeWeatherInstantly(UniStormSystem.Instance.AllWeatherTypes[idx]);
}
其中ChangeWeatherInstantly方法是直接切换,没有过度动画等效果,当然您也可以采用ChangeWeatherWithTransition方法,这个带个过度,如雨转晴中间有个丝滑的过度效果:
源码工程
https://download.csdn.net/download/qq_33789001/90272787