100500-я реализация дня и ночи (а также погодных эффектов)

Лучший способ помочь другим, поделиться своими находками.

100500-я реализация дня и ночи (а также погодных эффектов)

Сообщение gaussmake1994 12 авг 2015, 02:09

Реализовал смену "окружения" от времени.
Можно задать время переходов и (для каждого перехода) набор эффектов, включающих :
  • фоновый" цвет освещения (ambientSkyColor)
  • "осадки" (реализуются скриптом, применяющим MeshParticleEmitter)
  • фоновый звук
  • длительность перехода в секундах (в течение которого и будет происходить смена)
  • шанс получить этот эффект (сумма всех шансов для периода - 1).
Также содержится реализация внутриигрового времени.
Для смены скайбокса применён шейдер из http://wiki.unity3d.com/index.php?title=SkyboxBlended

Код наверняка ужасен :-) Критика желательна.
Архив с демкой - https://www.dropbox.com/s/7d9plyli7c05d50/game.zip?dl=0

Если интересует реализация - читайте ниже.
Globals, cинглтон для хранения глобального состояния и подписки на его изменение
Скрытый текст:
Синтаксис:
Используется csharp
using System.Collections.Generic;
using System;

public class GlobalSetEventArgs {
        public string key;
        public object value;
}

public class Globals {
        private static Globals instance = null;
        private Dictionary<string, object> Values;
        public delegate void SetEventHandler&#40;GlobalSetEventArgs e&#41;;
        public event SetEventHandler SetEvent;

        private Globals&#40;&#41;  {
                Values = new Dictionary<string, object> &#40;&#41;;
        }

        public static Globals Get&#40;&#41; {
                if &#40;Globals.instance == null&#41; {
                        Globals.instance = new Globals &#40;&#41;;
                }
                return Globals.instance;
        }

        public void SetValue&#40;string key, object value&#41; {
                if &#40;!Values.ContainsKey &#40;key&#41;&#41; {
                        Values.Add &#40;key, value&#41;;
                } else {
                        Values[key] = value;
                }
                GlobalSetEventArgs e = new GlobalSetEventArgs &#40;&#41;;
                if &#40;SetEvent != null&#41; {
                        e.key = key;
                        e.value = value;
                        SetEvent&#40;e&#41;;
                }
        }

        public object SetValue&#40;string name&#41; {
                return Values[name];
        }

        public object this[string key] {
                get {
                        return SetValue&#40;key&#41;;
                }
                set {
                        SetValue&#40;key, value&#41;;
                }
        }
}
 

Time, реализация игрового времени (крепится к постоянно существующему объекту, я использовал террейн)
Скрытый текст:
Синтаксис:
Используется csharp
using UnityEngine;
using System.Collections.Generic;
using System;

public class Time : MonoBehaviour {
        public DateTime time;

        [System.Serializable]
        public struct TimeData {
                public int year;
                public int month;
                public int day;
                public int hour;
                public int minute;
                public int second;
        }
        public TimeData startTime;
        public int cooficient;

        void Start &#40;&#41; {
                try {
                        time = &#40;System.DateTime&#41;Globals.Get &#40;&#41;["time"];
                }
                catch&#40;KeyNotFoundException&#41; {
                        time = new DateTime&#40;
                                startTime.year, startTime.month, startTime.day, startTime.hour, startTime.minute, startTime.second
                        &#41;;
                }
        }

        void Update &#40;&#41; {
                int addMilliseconds = &#40;int&#41;&#40;1000 * UnityEngine.Time.deltaTime * cooficient&#41;;
                time = time.AddMilliseconds &#40;addMilliseconds&#41;;
                Globals.Get &#40;&#41;["time"] = time;
        }
}
 

Назначение публичных полей вроде очевидно, но всё же - startTime - начальное время, если в Globals время ещё не задано, cooficient - коофициент ускорения относительно реального времени.
Fallout, управление "осадками". Крепится к префабу, включающему MeshParticleEmitter. Могут быть отключены по заданию Globals.Get()["playerInShelter"]=true, включены по false.
Скрытый текст:
Синтаксис:
Используется csharp
using UnityEngine;
using System.Collections;

public class Fallout : MonoBehaviour {
        MeshParticleEmitter emitter {
                get {
                        return gameObject.GetComponent<MeshParticleEmitter> &#40;&#41;;
                }
        }

        void SetIntensity&#40;float value&#41; {
                emitter.minEmission = value * emitter.maxEmission;
        }

        float GetIntensity&#40;&#41; {
                return emitter.minEmission / emitter.maxEmission;
        }

        public float instensity {
                get { return GetIntensity &#40;&#41;; }
                set { SetIntensity &#40;value&#41;; }
        }

        void ShelterEvent&#40;GlobalSetEventArgs e&#41; {
                if &#40;e.key == "playerInShelter"&#41; {
                        bool inShelter = &#40;bool&#41;e.value;
                        if &#40;inShelter&#41; {
                                StopEmitter&#40;&#41;;
                        } else {
                                StartEmitter&#40;&#41;;
                        }
                }
        }

        public void StartEmitter&#40;&#41; {
                emitter.emit = true;
        }

        public void StopEmitter&#40;&#41; {
                emitter.emit = false;
        }

        /**
         * Initializing
         */

        void Start&#40;&#41; {
                Globals.Get&#40;&#41;.SetEvent += ShelterEvent;
        }
       
        /**
         * Unloading
         */

        void OnDestroy&#40;&#41; {
                Globals.Get &#40;&#41;.SetEvent -= ShelterEvent;
        }
}
 

Weather, собственно - погода.
Скрытый текст:
Синтаксис:
Используется csharp
using UnityEngine;
using System.Collections;
using System;

/**
 * Unity3D weather system.
 * Needs "Skybox/Blended" &#40;or compatible&#41; shader to switch skyboxes
 */

public class Weather : MonoBehaviour {
        [System.Serializable]
        public class WeatherEffect {
                public float switchTime;
                public Material skybox;
                public double chance;
                public Color ambientColor;
                public AudioSource ambientSound;
                public GameObject falloutPrefab;
        }
        /**
         * Daytime period
         */

        [System.Serializable]
        public class WeatherPeriod {
                public int fromHour;
                public int fromMinute;
                public int toHour;
                public int toMinute;
                public WeatherEffect[] effects;
        }

        /**
         * Fallout prefabs instances will be binded to it &#40;prefferly - controller camera&#41;
         */

        public GameObject bindFalloutToObject;
        /**
         * Shader for skybox
         */

        public Shader skyboxShader;
        /**
         * Daytime periods
         */

        public WeatherPeriod[] periods;
        /**
         * Random number generator to switch effects
         */

        System.Random random;
        /**
         * is effect ready, or we need to play effect "animations"
         */

        bool effectReady = true;
        /**
         * Current period
         */

        WeatherPeriod activePeriod = null;
        /**
         * Current effect
         */

        WeatherEffect activeEffect = null;
        /**
         * Previous effect ambient color &#40;to animated change to new&#41;
         */

        Color previousAmbientColor;
        /**
         * Previous effect ambient sound &#40;to decrease volume&#41;
         */

        AudioSource previousAmbientSound = null;
        /**
         * Previous fallout effect &#40;to change intensivity&#41;
         */

        GameObject previousFallout = null;
        /**
         * Effect switching started at
         */

        DateTime switchStartedAt;
        /**
         * Current fallout effect
         */

        GameObject fallout = null;


        /**
         * Validate weather periods/effects information
         */

        void Validate&#40;&#41; {
                foreach &#40;WeatherPeriod period in periods&#41; {
                        if &#40;period.fromHour <0 || period.fromHour > 23&#41; {
                                throw new WeatherConfigurationException&#40;"Period &#39;fromHour&#39; must be in interval [0..23]"&#41;;
                        }
                        if &#40;period.toHour <0 || period.toHour > 24&#41; {
                                throw new WeatherConfigurationException&#40;"Period &#39;toHour&#39; must be in interval [0..24]"&#41;;
                        }
                        if &#40;period.fromMinute <0 || period.fromMinute > 59&#41; {
                                throw new WeatherConfigurationException&#40;"Period &#39;fromMinute&#39; must be in interval [0..59]"&#41;;
                        }
                        if &#40;period.toMinute <0 || period.toMinute > 60&#41; {
                                throw new WeatherConfigurationException&#40;"Period &#39;toMinute&#39; must be in interval [0..60]"&#41;;
                        }

                        int from = period.fromHour*60+period.fromMinute;
                        int to = period.toHour*60 + period.toMinute;
                        if &#40;from >= to&#41; {
                                throw new WeatherConfigurationException&#40;"Period start time must be less then end"&#41;;
                        }

                        double chanceSumm = 0;
                        foreach&#40;WeatherEffect effect in period.effects&#41; {
                                chanceSumm += effect.chance;
                        }
                        if &#40;chanceSumm < 1&#41; {
                                throw new WeatherConfigurationException&#40;"Sum of period effects chances summ must be 1"&#41;;
                        }
                }
        }

        /**
         * Initialize behaviour
         */

        void Start &#40;&#41; {
                Validate &#40;&#41;;
                random = new System.Random &#40;&#41;;
                Globals.Get&#40;&#41;.SetEvent += TimeEvent;
        }


        /**
         * Get the latest period that time is less or equal to time argument
         */

        WeatherPeriod GetPeriod&#40;DateTime time&#41; {
                WeatherPeriod[] sortedPeriods = periods;
                Array.Sort &#40;sortedPeriods, delegate&#40;WeatherPeriod p1, WeatherPeriod p2&#41; {
                        return p1.fromHour > p2.fromHour ? -1 : 1;
                }&#41;;
                foreach &#40;WeatherPeriod period in sortedPeriods&#41; {
                        if&#40;activePeriod == null &&
                           &#40;
                                &#40;period.fromHour == time.Hour && period.fromMinute <= time.Minute&#41; ||
                                &#40;period.fromHour < time.Hour&#41;
                                &#41;
                           &&
                           &#40;
                                &#40;period.toHour > time.Hour&#41; ||
                                &#40;period.toHour == time.Hour && period.toMinute >= time.Minute&#41;
                                &#41;
                           &#41; {
                                return period;
                        }
                        else if&#40;period.fromHour == time.Hour && time.Minute == 0 && time.Second == 0&#41; {
                                return period;
                        }
                }
                return null;
        }


        /**
         * Get random effect from given period
         */

        WeatherEffect GetEffect&#40;WeatherPeriod period&#41; {
                WeatherEffect[] effects = period.effects;
                //Sort periods by chance asc.
                // e.g. [0.6, 0.2, 0.4] will be sorted to [0.2, 0.4, 0.6]
                Array.Sort &#40;effects, delegate&#40;WeatherEffect e1, WeatherEffect e2&#41; {
                        return e1.chance > e2.chance ? -1 : 1;
                }&#41;;
                //Get number between 0.0 and 1.0
                double number = random.Next &#40;&#41; / &#40;double&#41;Int32.MaxValue;
                double sum = 0;
                foreach &#40;WeatherEffect effect in effects&#41; {
                        //compare number with chances.
                        sum += effect.chance;
                        if&#40;number <= sum&#41; {
                                return effect;
                        }
                }
                return null;
        }


        /**
         * Set skybox textures
         */

        void SetSkyboxTextures&#40;Material newSkybox&#41; {
                if &#40;RenderSettings.skybox == null&#41; {
                        RenderSettings.skybox = new Material&#40;newSkybox&#41;;
                }
                RenderSettings.skybox.shader = skyboxShader;
                string[] textureNames = {"_FrontTex", "_BackTex", "_LeftTex", "_RightTex", "_UpTex", "_DownTex"};
                foreach &#40;string textureName in textureNames&#41; {
                        //Move current textures to "second layer"
                        string newTextureName = textureName + "2";
                        Texture oldTexture = RenderSettings.skybox.GetTexture&#40;textureName&#41;;
                        RenderSettings.skybox.SetTexture&#40;newTextureName, oldTexture&#41;;
                        //Set new texture
                        RenderSettings.skybox.SetTexture&#40;textureName, newSkybox.GetTexture&#40;textureName&#41;&#41;;
                }
        }


        /**
         * Switch to selected period and effect
         */

        void UseEffect&#40;WeatherPeriod toPeriod, WeatherEffect toEffect&#41; {
                effectReady = false;
                activePeriod = toPeriod;
                if &#40;activeEffect != null&#41; {
                        previousAmbientSound = activeEffect.ambientSound;
                }
                activeEffect = toEffect;
                previousAmbientColor = RenderSettings.ambientSkyColor;
                SetSkyboxTextures &#40;activeEffect.skybox&#41;;
                if &#40;activeEffect.ambientSound != null&#41; {
                        activeEffect.ambientSound.volume = 0;
                        activeEffect.ambientSound.Play&#40;&#41;;
                }
                previousFallout = fallout;
                if &#40;activeEffect.falloutPrefab != null&#41; {
                        fallout = &#40;GameObject&#41;Instantiate &#40;activeEffect.falloutPrefab, bindFalloutToObject.transform.position, bindFalloutToObject.transform.rotation&#41;;
                        fallout.transform.parent = bindFalloutToObject.transform;
                } else {
                        fallout = null;
                }
        }


        /**
         * Start switching to new effect
         */

        void SelectNewEffect&#40;DateTime time&#41; {
                WeatherPeriod period = GetPeriod &#40;time&#41;;
                if &#40;period == null&#41; {
                        return;
                }
                WeatherEffect effect = GetEffect &#40;period&#41;;
                if &#40;effect == null&#41; {
                        return;
                }
                switchStartedAt = time;
                UseEffect &#40;period, effect&#41;;
        }


        /**
         * Get &#39;readiness&#39; of new effect
         */

        float GetEffectSwitchProportion&#40;DateTime time&#41; {
                TimeSpan deltaTime = time - switchStartedAt;
                float fromSwitch = deltaTime.Milliseconds/1000 + deltaTime.Seconds + deltaTime.Minutes * 60 + deltaTime.Hours * 3600;
                float proportion = fromSwitch / activeEffect.switchTime;
                return proportion;
        }


        /**
         * Effect switch animation
         */

        void SwithcEffectAnimation&#40;DateTime time&#41; {
                float proportion = GetEffectSwitchProportion &#40;time&#41;;

                Color deltaColor = activeEffect.ambientColor - previousAmbientColor;
                RenderSettings.ambientSkyColor += deltaColor * new Color &#40;proportion, proportion, proportion&#41;;
                if &#40;previousAmbientSound != null&#41; {
                        previousAmbientSound.volume = 1 - proportion;
                }
                if &#40;activeEffect.ambientSound != null&#41; {
                        activeEffect.ambientSound.volume = proportion;
                }
                if &#40;previousFallout != null&#41; {
                        previousFallout.GetComponent<Fallout>&#40;&#41;.instensity = 1 - proportion;
                }
                if &#40;fallout != null&#41; {
                        fallout.GetComponent<Fallout>&#40;&#41;.instensity = proportion;
                }
                RenderSettings.skybox.SetFloat &#40;"_Blend", 1 - proportion&#41;;
                if &#40;proportion >= 1&#41; {
                        effectReady = true;
                        if&#40;previousAmbientSound != null&#41; {
                                previousAmbientSound.Stop&#40;&#41;;
                        }
                        if &#40;previousFallout!=null&#41; {
                                Destroy&#40;previousFallout&#41;;
                                previousFallout = null;
                        }
                }
        }


        /**
         * Game time update handler
         */

        void TimeEvent&#40;GlobalSetEventArgs e&#41; {
                if &#40;e.key != "time"&#41; {
                        return;
                }
                DateTime time = &#40;DateTime&#41;e.value;
                if &#40;effectReady&#41; {
                        SelectNewEffect &#40;time&#41;;
                } else {
                        SwithcEffectAnimation &#40;time&#41;;
                }
        }

        /**
         * Unloading
         */

        void OnDestroy&#40;&#41; {
                Globals.Get &#40;&#41;.SetEvent -= TimeEvent;
        }
}
 

bindFalloutToObject - указывает объект, к которому прицепятся prefab-ы с осадками. Например - контроллер персонажа.
skyboxShader - указывает шейдер, используемый для скайбокса. В моём случае - "SkyboxBlended", но от шейдера требуется только совместимость по названиям текстур и свойству Blend.
periods - собственно, периоды. Содержат :
  • fromHour, fromMinute, toHour, toMinute - используются при выборе нового эффекта со сменой времени.
  • switchTime - время переключения эффектов (в течение которого наблюдаем затухание прошлого звука/нарастание нового, изменени скайбокса, смену осадков).
  • chance - шанс выпадения этого эффекта. Сумма всех шансов для интервала - 1.
  • ambientColor - цвет фонового освещения
  • skybox - собственно, скайбокс.
  • ambientSound - источник звука, воспроизводимого, пока эффект активен.
  • falloutPrefab - префаб осадков
Аватара пользователя
gaussmake1994
UNец
 
Сообщения: 2
Зарегистрирован: 12 авг 2015, 01:32
  • Сайт

Вернуться в Исходники (Копилка)

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 1