So I wrote a day-night cycle script the other day and I have the sun/moon cycle working (its a really rough script, not perfect yet) but one of the other things I wanted to do was to be able to calculate the current time inside the game working off of that day/night cycle.
I have a ticker that is working so far but it is not scaling correctly to the percentage of the day.
Can anyone help me out with this because I think this is beyond my skillset with maths right now.
Basically I just want to solve the current time of day as relative to the % we are moving through the day/night cycle.
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
public class DayNightCycle : MonoBehaviour
{
public static DayNightCycle instance;
public Light sun, moon;
public float secondsInFullDay = 3600f;
[Range(0, 1)]
public float currentTimeOfDay = 0f;
[HideInInspector]
public float timeMultiplier = 1f;
float sunInitialIntensity;
public Camera mainCam;
public Material skyboxDay, skyBoxNight;
public float gameTime;
public float fSeconds;
public int totalSeconds, iSeconds, minutes, hours, days;
public int currentSecond, currentMinute, currentHour, currentDay;
public float previousTime;
private void Awake()
{
instance = this;
}
// Start is called before the first frame update
void Start()
{
sunInitialIntensity = sun.intensity;
}
// Update is called once per frame
void Update()
{
UpdateSun();
currentTimeOfDay += (Time.deltaTime / secondsInFullDay) * timeMultiplier;
if (currentTimeOfDay >= 1)
{
currentTimeOfDay = 0;
}
gameTime += Time.deltaTime;
// seconds / total seconds = percentage
// percentage * seconds = total seconds
// seconds = total seconds * percentage
totalSeconds = (int)gameTime;
fSeconds = (secondsInFullDay * currentTimeOfDay);
currentSecond = (int)fSeconds;
if (currentSecond >= 60)
IncrementMinutes();
if (currentMinute >= 60)
IncrementHours();
if (currentHour >= 24)
IncrementDays();
previousTime = (int)gameTime;
}
void UpdateSun()
{
sun.transform.localRotation = Quaternion.Euler((currentTimeOfDay * 360f) - 90, 170, 0);
moon.transform.localRotation = Quaternion.Euler((currentTimeOfDay * 360f) - 90, 170, 0);
float intensityMultiplier = 1f;
float moonIntensityMult = 0.025f;
if (currentTimeOfDay <= 0.23f || currentTimeOfDay >= 0.75f)
{
RenderSettings.skybox = skyBoxNight;
intensityMultiplier = 0f;
moonIntensityMult = 0.025f;
}
else if (currentTimeOfDay <= 0.25f)
{
RenderSettings.skybox = skyBoxNight;
intensityMultiplier = Mathf.Clamp01((currentTimeOfDay - 0.23f) * (1 / 0.02f));
moonIntensityMult = 0f;
}
else if (currentTimeOfDay >= 0.73f)
{
RenderSettings.skybox = skyboxDay;
intensityMultiplier = Mathf.Clamp01(1 - ((currentTimeOfDay - 0.73f) * (1 / 0.02f)));
moonIntensityMult = 0f;
}
sun.intensity = sunInitialIntensity * intensityMultiplier;
moon.intensity = moonIntensityMult;
}
public float GetTimeOfDayInSeconds
{
get { return currentTimeOfDay; }
set { return; }
}
void IncrementMinutes()
{
currentMinute++;
currentSecond = 0;
}
void IncrementHours()
{
currentHour++;
currentSecond = 0;
currentMinute = 0;
}
void IncrementDays()
{
currentDay++;
currentSecond = 0;
currentMinute = 0;
currentHour = 0;
}
}
I think keeping track of second, minute hour and increment each separtedly is not the right approach. You need to now the scale or proprotion factor between your game time and the real time, and handle the one time variable at once.
Find this trial function to obtain the sacaled time I think you need.
using System;
using UnityEngine;
public class DayNightCycle : MonoBehaviour {
// This is public to check with manual input if the obtained time is the one we expect.
// In the real method, this should not exist and should be calculated with the elapsed time of the game,
// commented belowin the getGameTime(int secodsDayDurationInGame) method
public double elapsedRealTime;
float startingGameTime;
DateTime startingGameDate;
private void Start() {
startingGameTime = Time.time;
startingGameDate = DateTime.Now; // choose the starting date you like
}
private float secondsOfARealDay = 24 * 60 * 60;
DateTime getGameTime(int secodsDayDurationInGame) {
float scaledElapsedSecondInGame = secondsOfARealDay / secodsDayDurationInGame; // second equivalent in your game
//float elapsedRealTime = Time.time - startingGameTime; // uncomment to calculate with elapsed real time.
DateTime gateDateTime = startingGameDate.AddSeconds(elapsedRealTime * scaledElapsedSecondInGame);
return gateDateTime;
}
void OnMouseDown() { // this makes the cube clickable
Debug.LogError(getGameTime(3600).ToString());
}
}
You can try it making a cube clickable to print the output when you update the elapsed time in the public variable.
It is for 3600 second day in your game, but if you whant another game day duration you can just make that variabel puclic and try.
Important to have a collider, if not, cube wont be clickable. However It is added by default when you add the primitive right click in the scene -> 3D Object -> Cube.
You can check for example that if you add 3600 to the elapsed time public variable, and you click the cube you obtain tomorrows date in the console.
When you check the function works according to your needs you can uncomment the line //float elapsedRealTime = Time.time - startingGameTime; to use the real time elapsed or the one you wish for the date calculation.
Okay so I got it to where it is correctly synched now with the day/night cycle I just need to clean this code up quite a bit. Note: The way I'm increasing days will start to fail if you use an exceptionally long in game day I think. The boolean might work but it might also give you a new day before the end of the current day. I was testing this on a 3600 second long day.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
public class DayNightCycle : MonoBehaviour
{
public static DayNightCycle instance;
public Light sun, moon;
public float secondsInFullDay = 3600f;
[Range(0, 1)]
public float currentTimeOfDay = 0f;
[HideInInspector]
public float timeMultiplier = 1f;
float sunInitialIntensity;
public Material skyboxDay, skyBoxNight;
public double elapsedRealTime;
public float secondsPerDayMultiplier;
[SerializeField]
private float speedOfGameSecond, speedOfGameMinute, speedOfGameHour, speedOfGameDay;
[SerializeField]
private float currentGameSecond, currentGameMinute, currentGameHour, currentGameDay;
private bool stopIncrementingDay;
private void Awake()
{
instance = this;
}
// Start is called before the first frame update
void Start()
{
speedOfGameSecond = secondsInFullDay / 24 / 60 / 60;
speedOfGameMinute = secondsInFullDay / 24 / 60;
speedOfGameHour = secondsInFullDay / 24;
speedOfGameDay = secondsInFullDay;
sunInitialIntensity = sun.intensity;
}
// Update is called once per frame
void Update()
{
UpdateSun();
currentTimeOfDay += (Time.deltaTime / secondsInFullDay) * timeMultiplier;
if (currentTimeOfDay >= 1)
{
currentTimeOfDay = 0;
}
secondsPerDayMultiplier = currentTimeOfDay * secondsInFullDay;
// seconds / total seconds = percentage
// percentage * seconds = total seconds
// seconds = total seconds * percentage
currentGameSecond = secondsPerDayMultiplier / speedOfGameSecond;
currentGameMinute = secondsPerDayMultiplier / speedOfGameMinute;
currentGameHour = secondsPerDayMultiplier / speedOfGameHour;
if(!stopIncrementingDay && currentGameHour >= 23.999)
{
IncrementDay();
stopIncrementingDay = true;
} else if(currentGameHour <= 23.999)
{
stopIncrementingDay = false;
}
elapsedRealTime += Time.deltaTime;
previousTime = Time.deltaTime;
}
void UpdateSun()
{
sun.transform.localRotation = Quaternion.Euler((currentTimeOfDay * 360f) - 90, 170, 0);
moon.transform.localRotation = Quaternion.Euler((currentTimeOfDay * 360f) - 90, 170, 0);
float intensityMultiplier = 1f;
float moonIntensityMult = 0.025f;
if (currentTimeOfDay <= 0.23f || currentTimeOfDay >= 0.85f)
{
RenderSettings.skybox = skyBoxNight;
intensityMultiplier = 0f;
moonIntensityMult = 0.025f;
}
else if (currentTimeOfDay <= 0.25f)
{
RenderSettings.skybox = skyBoxNight;
intensityMultiplier = Mathf.Clamp01((currentTimeOfDay - 0.23f) * (1 / 0.02f));
moonIntensityMult = 0f;
}
else if (currentTimeOfDay >= 0.83f)
{
RenderSettings.skybox = skyboxDay;
intensityMultiplier = Mathf.Clamp01(1 - ((currentTimeOfDay - 0.83f) * (1 / 0.02f)));
moonIntensityMult = 0f;
}
sun.intensity = sunInitialIntensity * intensityMultiplier;
moon.intensity = moonIntensityMult;
}
public float GetTimeOfDayPercent
{
get { return currentTimeOfDay; }
set { return; }
}
public float GetSecondsPerDay()
{
return secondsInFullDay;
}
private void IncrementDay()
{
currentGameSecond = 0;
currentGameMinute = 0;
currentGameHour = 0;
currentGameDay++;
}
public void GetTimeOfDay()
{
// now to work on this
}
}
Related
I ran into a little issue whilst using C#/Unity in combination with a litte countdown timer. The countdown is working fine an as expected, as long as the number of total timeToDisplay is not too big (e.g. more than a day)
r/Unity2D - Asking for Help: Countdown strangely stops when there is too much remaining time
As you can see, the user has the possibility to add time to that countdown, which (again) works fine, until it's too much.
Using TextMeshPro and TextMeshPro-Buttons.
Image of Countdown + Buttons to add Seconds/Minutes/Hours/Days
However... here's the code:
using UnityEngine;
using TMPro;
public class Controller : MonoBehaviour
{
public float timeValue = 78000;
public TMP_Text timerText;
// I also tried FixedUpdate, but error still occured
void Update()
{
if (timeValue > 0)
{
timeValue -= Time.deltaTime;
}
else
{
timeValue = 0;
}
DisplayTime(timeValue);
}
void DisplayTime(float timeToDisplay)
{
float days = Mathf.FloorToInt(timeToDisplay / 86400);
timeToDisplay = timeToDisplay % 86400;
float hours = Mathf.FloorToInt(timeToDisplay / 3600);
timeToDisplay = timeToDisplay % 3600;
float minutes = Mathf.FloorToInt(timeToDisplay / 60);
timeToDisplay = timeToDisplay % 60;
float seconds = Mathf.FloorToInt(timeToDisplay);
if (seconds < 0)
{
seconds = 0;
}
timerText.text = string.Format("{0:00} days {1:00} hours {2:00} minutes {3:00} seconds", days, hours, minutes, seconds);
}
public void AddDay()
{
/* 86400 seconds/day */
timeValue += 86400;
}
public void AddHour()
{
/* 3600 seconds/hour */
timeValue += 3600;
}
public void AddMinute()
{
timeValue += 60;
}
public void AddSecond()
{
timeValue += 1;
}
}
Does anybody know what I'm missing here?
problem on here: timeValue -= Time.deltaTime , float have a little deviation
public float timeValue = 78000;
float beginTime = 0;
void Start()
{
beginTime = Time.time;
}
// I also tried FixedUpdate, but error still occured
void Update()
{
float usedTime = Time.time - beginTime;
if( timeValue - usedTime > 0 )
{
DisplayTime(timeValue - usedTime);
}
else
{
DisplayTime(0);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.PlayerLoop;
public class ChangeShaders : MonoBehaviour
{
private int val;
// Start is called before the first frame update
void Start()
{
//StartCoroutine(EffectSliderChanger());
}
private void Update()
{
gameObject.GetComponent<Renderer>().material.SetFloat("Effect Slider", val);
Mathf.Lerp(-1, 1, Time.deltaTime);
}
/*IEnumerator EffectSliderChanger()
{
gameObject.GetComponent<Renderer>().material.SetFloat("Effect Slider", 1);
}*/
}
I want to change the effect value between -1 and 1 nonstop from -1 to 1 when it's getting to 1 back to -1 and so on.
I'm not sure how to do it and if to use StartCoroutine or to do it inside the Update.
You could use Mathf.PingPong for this
Material material;
// Adjust via Inspector
public float duration = 1;
private void Awake()
{
material = GetComponent<Renderer>().material;
}
void Update()
{
var currentValue = Mathf.Lerp(-1 , 1, Mathf.PingPong(Time.time / duration, 1));
material.SetFloat("Effect Slider", currentValue);
}
Alternatively you could also simply shift the range like
This will move between -1 and 1 using the result of PingPong as the interpolation factor which moves between 0 and 1 in the given duration.
Alternatively you could also directly shift the range of PingPong like
var currentValue = Mathf.PingPong(Time.time / duration, 2) - 1f;
Try this (I haven't tested it but it should work):
public class ChangeShaders : MonoBehaviour
{
private float fromValue = -1f;
private float toValue = 1f;
private float timeStep = 0f;
private float val = 0f;
private void Update()
{
gameObject.GetComponent<Renderer>().material.SetFloat("Effect Slider", val);
val = Mathf.Lerp(fromValue, toValue, timeStep);
timeStep += Time.deltaTime;
// If you want the values to go back and forth faster use the line below instead
// timeStep += 0.2f + Time.deltaTime;
if (timeStep >= 1f) {
float tempVal = toValue;
toValue = fromValue;
fromValue = tempVal;
timeStep = 0f;
}
}
}
so I've found a script online for Unity that is a Day and Night cycle however it works in decimals, 0.1 to 1, however I want it to be 1 to 24.
The code I am using is below, I have tried fiddling around with the decimal values however I can't get it correct.
using UnityEngine;
using System.Collections;
public class DayNightCycle : MonoBehaviour {
public Light sun;
public float secondsInFullDay = 120f;
[Range(0,24)]
public float currentTimeOfDay = 0;
[HideInInspector]
public float timeMultiplier = 1f;
float sunInitialIntensity;
void Start() {
sunInitialIntensity = sun.intensity;
}
void Update() {
UpdateSun();
currentTimeOfDay += (Time.deltaTime / secondsInFullDay) * timeMultiplier;
if (currentTimeOfDay >= 24) {
currentTimeOfDay = 0;
}
}
void UpdateSun() {
sun.transform.localRotation = Quaternion.Euler((currentTimeOfDay * 360f) - 90, 170, 0);
float intensityMultiplier = 1;
if (currentTimeOfDay <= 0.23f || currentTimeOfDay >= 0.75f) {
intensityMultiplier = 0;
}
else if (currentTimeOfDay <= 0.25f) {
intensityMultiplier = Mathf.Clamp01((currentTimeOfDay - 0.23f) * (1 / 0.02f));
}
else if (currentTimeOfDay >= 0.73f) {
intensityMultiplier = Mathf.Clamp01(1 - ((currentTimeOfDay - 0.73f) * (1 / 0.02f)));
}
sun.intensity = sunInitialIntensity * intensityMultiplier;
}
}
The result I want is for the day and night cycle to work from 1 to 24 and not 0 to 1, so that there's 24 hours in the game, and that it's easier to modify when using a sleep script I've made.
To convert a value between 0 and 1 (interval [0, 1]) you just have to multiply by the size of your new interval then add the first value of your interval.
So if you have a variable value and want an interval: [MIN, MAX] the calculation is the following:
var newValue = (value * (MAX - MIN)) + MIN;
In you case you want the interval [1, 24] so it is:
var newValue = (value * 23) + 1;
Simple maths, hope it helps.
I want my shots to follow a specific pattern (I also need the arc and gap between the shots to be adjustable). Right now I've got my shooting script down but the shots go in a straight line which is not what I want (don't want a straight line now but I'll need it later when designing other weapons).
Here's a screenshot with example of said saidpatters:
I don't know much about quaternions and angles so all I tried is modifying the angles after x time and the velocity after x time but none worked (it might be the solution but I have 0 clue how to use angles in unity so I couldn't get it to work).
Another thing please provide an explanation along with your answer because I want to learn why something works the way it does so I don't have to ask again later.
Here's my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class Player_Shooting : MonoBehaviour
{
[SerializeField]
private Transform shootingPoint;
[SerializeField]
private GameObject shot; //this is what I'm shooting, shot also has a script but all it does is apply velocity upwards and do damage to enemy if it hits
private bool shootAgain = true;
private int dexterity = Player_Stats.GetDexterity();
private int numberofshots = 2; //amount of shots
private int shotGap = 5; //how many degrees between the shots
void Update()
{
Vector3 mousepos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 direction = new Vector2(mousepos.x - transform.position.x, mousepos.y - transform.position.y);
transform.up = direction;
if (Input.GetButton("Fire1") && shootAgain == true)
{
shootAgain = false;
StartCoroutine(RateOfFire(dexterity));
}
}
private void Shoot()
{
Vector3 temp = transform.rotation.eulerAngles;
Quaternion angle = Quaternion.Euler(temp.x, temp.y, temp.z);
for (int i = 0; i < numberofshots; i++)
{
int multiplier = i + 1;
if (numberofshots % 2 == 1)
{
Instantiate(shot, shootingPoint.position, angle);
if (i % 2 == 0)
{
temp.z -= shotGap * multiplier;
angle = Quaternion.Euler(temp.x, temp.y, temp.z);
}
else
{
temp.z += shotGap * multiplier;
angle = Quaternion.Euler(temp.x, temp.y, temp.z);
}
}
else if (numberofshots % 2 == 0)
{
if (i % 2 == 0)
{
temp.z -= shotGap * multiplier;
angle = Quaternion.Euler(temp.x, temp.y, temp.z);
}
else
{
temp.z += shotGap * multiplier;
angle = Quaternion.Euler(temp.x, temp.y, temp.z);
}
Instantiate(shot, shootingPoint.position, angle);
}
}
}
IEnumerator RateOfFire(int dex)
{
Shoot();
float time = dex / 75;
time *= 6.5f;
time += 1.5f;
yield return new WaitForSeconds(1 / time);
shootAgain = true;
}
}
This is what i came up with after a few hours.
it can be improved upon for your needs but it works and with less code.
i used a separate script on another gameObject to Instantiate the projectiles. The bullet script is attached to a sprite with a trail
it should be easy to manipulate the firing sequence from there.
comments explain what most things do.
i added a bool function to fire in opposing angles.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class bullet : MonoBehaviour{
public float turnLength = 0.5f; // how long it turns for 0.0+
public float turnSpeed = 5f; // how fast the projectile turns 0.0+
public float anglePauseTime = 0.2f; // Optional wave form variable. coupled with high turnrate + curve speed = higher frequency sine wave.
public float shotAngle = -12f; // the angle the shot is taken as an offset (usually nagative value) 0- or turnspeed*2.25 for straight shots
public float projectileSpeed = 50; // obvious
public bool opositeAngles = false;
// Start is called before the first frame update
void Start(){
if(opositeAngles){
transform.Rotate(0, 0, -shotAngle);
}
else{
transform.Rotate(0, 0, shotAngle);
}
StartCoroutine(WaveForm(turnLength, turnSpeed, anglePauseTime, opositeAngles));
}
// Update is called once per frame
void Update(){
transform.position += transform.right * Time.deltaTime * projectileSpeed;
}
IEnumerator WaveForm(float seconds, float aglSpeed, float pause, bool reverse){
// multiplier correlates to waitForSeconds(seconds)
// faster update time = smoother curves for fast projectiles
// less cycles = shorter Corutine time.
//10, 0.1 100cycles/second (shallow waves, jagged on higher frequency waves, doesnt last long)
//10, 0.05 200cycles/second (probably best)
//100, 0.02 500cycles/second (smooth curves all around. requires smaller adjustment numbers)
// i had to up it for the waveform to last longer.
float newSeconds = seconds * 10;
for (int i = 0; i < newSeconds; i++) {
for (int j = 0; j < newSeconds; j++) {
yield return new WaitForSeconds(0.05f); // controls update time in fractions of a second.
if(reverse){
transform.Rotate(0, 0, -aglSpeed, Space.Self);
}
else {
transform.Rotate(0, 0, aglSpeed, Space.Self);
}
}
yield return new WaitForSeconds(pause);
aglSpeed = -aglSpeed;
}
}
}
Example image
I'm making a day/night cycle and have the lerp on Time.time, because if I use Time.deltaTime it turns night.day around so its day at 12am. However I digress.
The problem I'm running into now too is that with any Time setting it will make the lerp instant and not - well.. 'lerp'. Any idea on fixing this? I'm a C# newbie
I got it working with this as the time script:
using UnityEngine;
using System.Collections;
public class timeFlow : MonoBehaviour
{
public float Hours = 00;
public float Minutes = 00;
void Update()
{
if(Hours <= 23){
if(Minutes >= 60)
{
Minutes = 0;
if(Minutes <= 59)
{
Hours++;
}
else
{
Minutes = 0;
Hours = 0;
guiText.text = Hours.ToString("f0") + ":0" + Minutes.ToString("f0");
}
}
else
{
Minutes += UnityEngine.Time.deltaTime * 100;
}
if(Mathf.Round(Minutes) <= 9)
{
guiText.text = Hours.ToString("f0") + ":0" + Minutes.ToString("f0");
}
else
{
guiText.text = Hours.ToString("f0") + ":" + Minutes.ToString("f0");
}
}
else {
Hours = 0;
}
}
}
And this is the lerp script:
using UnityEngine;
using System.Collections;
public class cycleFlow : MonoBehaviour {
public Color32 night = new Color32(30, 30, 30, 255);
public Color32 day = new Color32(255, 255, 255, 255);
public GameObject Timer;
private timeFlow TimeFlow;
void Awake () {
TimeFlow = Timer.GetComponent<timeFlow> ();
}
void Update () {
DayNightCycle ();
}
void DayNightCycle()
{
foreach (SpriteRenderer child in transform.GetComponentsInChildren<SpriteRenderer>()) {
if (TimeFlow.Hours == 18){
child.color = Color.Lerp(day, night, Time.time);
}
if (TimeFlow.Hours == 6) {
child.color = Color.Lerp(night, day, Time.time);
}
}
}
}
You can write something like this, Lerp takes actual (from) value, final (to) value and fraction value. (here Time.deltaTime) Now when your timer will reach hour 18, your Color will change to color of night in few Update function calls (you can control change time by multiplying Time.deltaTime) :)
foreach ( SpriteRenderer child in transform.GetComponentsInChildren<SpriteRenderer>() ) {
if ( TimeFlow.Hours == 18 ) {
child.color = Color.Lerp(child.color, night, Time.deltaTime);
// here two times faster
// child.color = Color.Lerp(child.color, night, Time.deltaTime * 2.0f);
}
if ( TimeFlow.Hours == 6 ) {
child.color = Color.Lerp(child.color, day, Time.deltaTime);
// here half slower :)
// child.color = Color.Lerp(child.color, night, Time.deltaTime * 0.5f);
}
}
I had a hard time understanding lerp when used together with a time.
Maybe this example helps someone:
// lerp values between 0 and 10 in a duration of 3 seconds:
private float minValue = 0.0f;
private float maxValue = 10.0f;
private float totalDuration = 3.0f;
private float timePassed = 0.0f;
private float currentValue;
void Update()
{
timePassed += Time.deltaTime;
// Lerp expects a value between 0 and 1 as the third parameter,
// so we need to divide by the duration:
currentValue = Mathf.Lerp(minValue, maxValue, timePassed/totalDuration);
}
Note: you need to add some logic for:
handling when to start/stop lerping
when to reset timePassed