【unity实战】时间控制 昼夜交替 四季变化 天气变化效果

简介: 【unity实战】时间控制 昼夜交替 四季变化 天气变化效果

日期季节控制

public class TimeManager : MonoBehaviour
{
    public static TimeManager Instance { get; private set; }

    // 定义一个事件,在每天过去时触发
    public UnityEvent OnDayPass = new UnityEvent();

    // 季节枚举类型
    public enum Season
    {
        Spring,
        Summer,
        Fall, 
        Winter
    }

    // 当前季节
    public Season currentSeason = Season.Spring;
    private int daysPerSeason = 30;//每季天数
    private int daysInCurrentSeason = 1;//当前季节天数

    //日期枚举类型
    public enum DayOfWeek
    {
        Monday,
        Tuesday,
        Wednesday,
        Thursday,
        Friday,
        Saturday,
        Sunday
    }
    public DayOfWeek currentDayOfWeek = DayOfWeek.Monday;

    public int dayInGame = 1;//游戏中的天数
    public int yearInGame = 0;//游戏中的年数
    public TextMeshProUGUI dayUIText;

    private void Awake()
    {
        Instance = this;
    }

    private void Start()
    {
        UpdateUI();
    }

    // 触发过渡到下一天的方法
    public void TriggerNextDay()
    {
        // 增加游戏中的天数和当前季节的天数
        dayInGame += 1;
        daysInCurrentSeason += 1;

        currentDayOfWeek = (DayOfWeek)(((int)currentDayOfWeek + 1) % 7);

        // 检查是否当前季节天数已达到季节总天数
        if (daysInCurrentSeason > daysPerSeason)
        {
            // 切换到下一个季节,并重置季节天数计数
            daysInCurrentSeason = 1;
            currentSeason = GetNextSeason();
        }

        // 更新UI显示,触发当天过去事件
        UpdateUI();
        OnDayPass.Invoke();
    }

    // 获取下一个季节的方法
    private Season GetNextSeason()
    {
        // 计算当前季节索引和下一个季节索引
        int currentSeasonIndex = (int)currentSeason; // 获取当前季节索引
        int nextSeasonIndex = (currentSeasonIndex + 1) % 4; // 计算下一个季节索引

        // 如果下一个季节索引为0(春季),增加游戏年份
        if (nextSeasonIndex == 0)
        {
            yearInGame += 1;
        }

        // 返回下一个季节
        return (Season)nextSeasonIndex;
    }

    // 更新UI显示的方法
    private void UpdateUI()
    {
        string currentDayOfWeekChinese = getCurrentDayOfWeekChinese(currentDayOfWeek);
        string currentSeasonChinese = getCurrentSeasonChinese(currentSeason);
        dayUIText.text = $"{currentDayOfWeekChinese} 第 {daysInCurrentSeason} 天,{currentSeasonChinese}";
    }

    //获取中文日期
    private string getCurrentDayOfWeekChinese(DayOfWeek currentDayOfWeek)
    {
        string currentDayOfWeekChinese = "";
        switch (currentDayOfWeek)
        {
            case DayOfWeek.Monday:
                currentDayOfWeekChinese = "星期一";
                break;
            case DayOfWeek.Tuesday:
                currentDayOfWeekChinese = "星期二";
                break;
            case DayOfWeek.Wednesday:
                currentDayOfWeekChinese = "星期三";
                break;
            case DayOfWeek.Thursday:
                currentDayOfWeekChinese = "星期四";
                break;
            case DayOfWeek.Friday:
                currentDayOfWeekChinese = "星期五";
                break;
            case DayOfWeek.Saturday:
                currentDayOfWeekChinese = "星期六";
                break;
            case DayOfWeek.Sunday:
                currentDayOfWeekChinese = "星期日";
                break;
            default:
                break;
        }
        return currentDayOfWeekChinese;
    }

    //获取中文季节
    private string getCurrentSeasonChinese(Season currentSeason)
    {
        string currentSeasonChinese = "";
        switch (currentSeason)
        {
            case Season.Spring:
                currentSeasonChinese = "春";
                break;
            case Season.Summer:
                currentSeasonChinese = "夏";
                break;
            case Season.Fall:
                currentSeasonChinese = "秋";
                break;
            case Season.Winter:
                currentSeasonChinese = "冬";
                break;
            default:
                break;
        }
        return currentSeasonChinese;
    }
}

配置

效果

时间昼夜交替

素材

https://assetstore.unity.com/packages/2d/textures-materials/sky/fantasy-skybox-free-18353

如果没有天空盒,需要自己配置

新增SkyboxBlendingShader.shader,控制天空盒平滑过渡交替变化

Shader "Custom/SkyboxTransition"
{
    Properties
    {
        _TransitionFactor("Transition Factor", Range(0, 1)) = 0.0
        _AtmosphereTex("Atmosphere CubeMap", Cube) = "" {}
        _SpaceTex("Space CubeMap", Cube) = "" {}
    }

    SubShader
    {
        Tags { "Queue"="Background" }
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata_t
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float3 pos : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            float _TransitionFactor;
            samplerCUBE _AtmosphereTex;
            samplerCUBE _SpaceTex;

            v2f vert(appdata_t v)
            {
                v2f o;
                o.pos = v.vertex.xyz;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            half4 frag(v2f i) : SV_Target
            {
                // Use the transition factor to blend between the two skybox cube maps
                half4 atmosphereColor = texCUBE(_AtmosphereTex, i.pos);
                half4 spaceColor = texCUBE(_SpaceTex, i.pos);
                half4 finalColor = lerp(atmosphereColor, spaceColor, _TransitionFactor);

                return finalColor;
            }
            ENDCG
        }
    }
    FallBack "Skybox/Cubemap"
}

配置不同时间过渡材质

新增DayNightSystem,负责管理游戏的昼夜系统

public class DayNightSystem : MonoBehaviour
{
    // 控制方向光的引用
    public Light directionalLight;

    // 一整天的持续时间(以秒为单位)
    public float dayDurationInSeconds = 24.0f; // 调整一整天的持续时间(以秒为单位)

    // 当前的小时
    public int currentHour;
    // 当前的分钟
    public int currentMinute;

    // 当前时间在一天中所占比例(范围在0到1之间)
    float currentTimeOfDay = 0.35f;

    // 存储不同时间段对应的天空盒
    public List<SkyboxTimeMapping> timeMappings;

    // 用于插值的值(范围在0到1之间)
    float blendedValue = 0.0f;

    // 是否锁定下一个白天触发
    bool lockNextDayTrigger = false;

    // 用于显示时间的UI元素
    public TextMeshProUGUI timeUI;

    // 在每帧更新
    void Update()
    {
        // 根据游戏时间计算当前的时间
        currentTimeOfDay += Time.deltaTime / dayDurationInSeconds;
        currentTimeOfDay = currentTimeOfDay % 1; // 确保值在0到1之间

        // 计算当前的小时
        currentHour = Mathf.FloorToInt(currentTimeOfDay * 24);
        // 计算当前的分钟数
        currentMinute = Mathf.FloorToInt(currentTimeOfDay * 1440) % 60; // 一天有 24 小时 * 60 分钟 = 1440 分钟
        //更新时间UI
        timeUI.text = $"{currentHour:D2}:{currentMinute:D2}";

        // 更新方向光的旋转
        directionalLight.transform.rotation = Quaternion.Euler(new Vector3((currentTimeOfDay * 360) - 90, 170, 0));

        // 根据时间更新天空盒材质
        UpdateSkybox();
    }

    // 更新天空盒材质的方法
    private void UpdateSkybox()
    {
        // 寻找当前小时对应的天空盒材质
        Material currentSkybox = null;
        foreach (SkyboxTimeMapping mapping in timeMappings)
        {
            if (currentHour == mapping.hour)
            {
                currentSkybox = mapping.skyboxMaterial;
                if (currentSkybox.shader != null)
                {
                    if (currentSkybox.shader.name == "Custom/SkyboxTransition")
                    {
                        blendedValue += Time.deltaTime;
                        blendedValue = Mathf.Clamp01(blendedValue);
                        currentSkybox.SetFloat("_TransitionFactor", blendedValue);
                    }
                    else
                    {
                        blendedValue = 0;
                    }
                }
                break;
            }
        }
        if (currentHour == 0 && lockNextDayTrigger == false)
        {
            TimeManager.Instance.TriggerNextDay();
            lockNextDayTrigger = true;
        }
        if (currentHour != 0)
        {
            lockNextDayTrigger = false;
        }

        // 如果找到了对应的天空盒材质,则更新RenderSettings的skybox
        if (currentSkybox != null)
        {
            RenderSettings.skybox = currentSkybox;
        }
    }
}

// 存储时间和对应天空盒材质的类
[System.Serializable]
public class SkyboxTimeMapping
{
    public string phaseName;
    public int hour; // 小时
    public Material skyboxMaterial; // 对应的天空盒材质
}

配置

效果

下雨

下雨粒子效果

这里只做个简单的,想要更复杂的下雨效果,可以看我之前的文章:【实现100个unity特效之7】unity 3d实现各种粒子效果

默认禁用雨

控制雨一直跟随玩家,但是旋转不跟随

//跟随玩家
public class FollowPlayer : MonoBehaviour
{
    public Transform player;
    public Vector3 offset;
    private void LateUpdate()
    {
        if (player != null)
        {
            Vector3 targetPosition = player.position + offset;
            transform.position = targetPosition;
        }
    }
}

配置

控制不同天气

新增WeatherSystem

public class WeatherSystem : MonoBehaviour
{
    [Range(0f, 1f)]
    public float chanceToRainSpring = 0.3f; // 春季下雨概率(30%)
    [Range(0f, 1f)]
    public float chanceToRainSummer = 0.7f; // 夏季下雨概率(70%)
    [Range(0f, 1f)]
    public float chanceToRainFall = 0.4f; // 秋季下雨概率(40%)
    [Range(0f, 1f)]
    public float chanceToRainWinter = 0f; // 冬季下雨概率(0%)

    public GameObject rainEffect; // 下雨特效
    public Material rainSkyBox; // 下雨天气的天空盒材质
    public bool isSpecialWeather; // 是否是特殊天气
    public AudioSource rainChannel; // 下雨音效的音频源
    public AudioClip rainSound; // 下雨音效

    public enum WeatherCondition
    {
        Sunny, // 晴天
        Rainy, // 下雨
        Snowy // 下雪(未在代码中实现)
    }

    private WeatherCondition currentWeather = WeatherCondition.Sunny; // 当前天气默认为晴天

    private void Start()
    {
        // 监听一天的流逝事件,用于生成随机天气
        TimeManager.Instance.OnDayPass.AddListener(GenerateRandomWeather);
    }

    private void GenerateRandomWeather()
    {
        TimeManager.Season currentSeason = TimeManager.Instance.currentSeason;
        float chanceToRain = 0f;

        // 根据当前季节设定下雨概率
        switch (currentSeason)
        {
            case TimeManager.Season.Spring:
                chanceToRain = chanceToRainSpring;
                break;
            case TimeManager.Season.Summer:
                chanceToRain = chanceToRainSummer;
                break;
            case TimeManager.Season.Fall:
                chanceToRain = chanceToRainFall;
                break;
            case TimeManager.Season.Winter:
                chanceToRain = chanceToRainWinter;
                break;
        }

        // 生成一个随机数,用于判断是否下雨
        if (Random.value < chanceToRain)
        {
            currentWeather = WeatherCondition.Rainy; // 设置为下雨天气
            isSpecialWeather = true; // 标记为特殊天气
            Invoke("StartRain", 1f); // 延迟1秒开始下雨效果
        }
        else
        {
            currentWeather = WeatherCondition.Sunny; // 设置为晴天
            isSpecialWeather = false; // 标记为非特殊天气
            StopRain(); // 停止下雨效果
        }
    }

    private void StartRain()
    {
        if (rainChannel.isPlaying == false)
        {
            rainChannel.clip = rainSound;
            rainChannel.loop = true;
            rainChannel.Play();
        }
        RenderSettings.skybox = rainSkyBox; // 切换天空盒为下雨天气材质
        rainEffect.SetActive(true); // 激活下雨特效
    }

    private void StopRain()
    {
        if (rainChannel.isPlaying)
        {
            rainChannel.Stop(); // 停止下雨音效
        }
        rainEffect.SetActive(false); // 关闭下雨特效
    }
}

修改DayNightSystem

public WeatherSystem weatherSystem;

void Update()
{
    // 。。。

    if (currentHour == 0 && lockNextDayTrigger == false)
    {
        TimeManager.Instance.TriggerNextDay();
        lockNextDayTrigger = true;
    }
    if (currentHour != 0)
    {
        lockNextDayTrigger = false;
    }

    // 根据时间更新天空盒材质
    if(weatherSystem.isSpecialWeather == false) UpdateSkybox();
}

// 更新天空盒材质的方法
private void UpdateSkybox()
{
    // 寻找当前小时对应的天空盒材质
    Material currentSkybox = null;
    foreach (SkyboxTimeMapping mapping in timeMappings)
    {
        if (currentHour == mapping.hour)
        {
            currentSkybox = mapping.skyboxMaterial;
            if (currentSkybox.shader != null)
            {
                if (currentSkybox.shader.name == "Custom/SkyboxTransition")
                {
                    blendedValue += Time.deltaTime;
                    blendedValue = Mathf.Clamp01(blendedValue);
                    currentSkybox.SetFloat("_TransitionFactor", blendedValue);
                }
                else
                {
                    blendedValue = 0;
                }
            }
            break;
        }
    }
    // 如果找到了对应的天空盒材质,则更新RenderSettings的skybox
    if (currentSkybox != null)
    {
        RenderSettings.skybox = currentSkybox;
    }
}

配置,雨声我这里就不做配置了

效果

image.png

源码

整理好了我会放上来

目录
相关文章
|
9天前
|
存储 JSON 关系型数据库
【unity实战】制作unity数据保存和加载系统——大型游戏存储的最优解
【unity实战】制作unity数据保存和加载系统——大型游戏存储的最优解
18 2
|
9天前
|
人工智能 定位技术 图形学
【unity实战】制作敌人的AI,使用有限状态机、继承和抽象类多态 定义不同状态的敌人行为
【unity实战】制作敌人的AI,使用有限状态机、继承和抽象类多态 定义不同状态的敌人行为
51 1
|
9天前
|
安全 图形学
【unity实战】事件(Event)的基本实战使用
【unity实战】事件(Event)的基本实战使用
8 1
|
9天前
|
Rust 图形学
【unity实战】使用unity制作一个类似Rust的3D生存建造建筑系统,具有很好的吸附性(附项目源码)
【unity实战】使用unity制作一个类似Rust的3D生存建造建筑系统,具有很好的吸附性(附项目源码)
15 1
|
9天前
|
图形学
【unity实战】Unity中基于瓦片的网格库存系统——类似《逃离塔科夫》的库存系统(下)
【unity实战】Unity中基于瓦片的网格库存系统——类似《逃离塔科夫》的库存系统
10 0
|
9天前
|
图形学 容器
【unity实战】Unity中基于瓦片的网格库存系统——类似《逃离塔科夫》的库存系统(上)
【unity实战】Unity中基于瓦片的网格库存系统——类似《逃离塔科夫》的库存系统
11 0
|
9天前
|
存储 JSON 图形学
【unity实战】制作unity数据保存和加载系统——小型游戏存储的最优解
【unity实战】制作unity数据保存和加载系统——小型游戏存储的最优解
10 0
|
9天前
|
图形学
【unity实战】3D水系统,游泳,潜水,钓鱼功能实现
【unity实战】3D水系统,游泳,潜水,钓鱼功能实现
6 0
|
9天前
|
图形学
【unity小技巧】unity3d环境带雾的昼夜系统变化
【unity小技巧】unity3d环境带雾的昼夜系统变化
7 0
|
9天前
|
图形学
【unity实战】实现实体子弹射击
【unity实战】实现实体子弹射击
8 0