I wrote a code that calculates the elapsed time of the date from Day 1 independent of the real world time. However, two shortcomings were found, and no solution was found.
The first is Time.deltaTime. Time.deltaTime is continuously added to the timeCount parameter to determine that 1 second has passed if it is greater than or equal to 1.
This is not exactly 1, so there will be some decimal error than the actual time lapse.
Second, I feel that I use too many if statements. Is there a way to write code more efficiently without compromising readability?
I ask for your opinion on these two issues.
function UpdateTime will be invoked periodically in other code update statements.
This is the code I wrote below.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayTime
{
public int Day { get; private set; }
public int Hour { get; private set; }
public int Minute { get; private set; }
public float Second { get; private set; }
private float timeCount;
public PlayTime()
{
Day = 0;
Hour = 0;
Minute = 0;
Second = 0;
timeCount = 0;
}
public void UpdateTime()
{
timeCount += Time.deltaTime;
if (timeCount >= 1)
{
timeCount = 0;
Second++;
}
if (Second >= 60)
{
Second = 0;
Minute++;
}
if (Minute >= 60)
{
Minute = 0;
Hour++;
}
if (Hour >= 24)
{
Hour = 0;
Day++;
}
}
}
Instead of
timeCount = 0;
you could use
timeCount -= 1f;
which already compensates the first inaccuracy.
You could further not use a continuous call at all but rather use absolute time values and only do calculations when you actually access the values
private float initTime;
public PlayTime()
{
initTime = Time.time;
}
and then rather do something like e.g.
public readonly struct TimeData
{
private const int SecondsPerMinute = 60;
private const int SecondsPerHour = 60 * SecondsPerMinute;
private const int SecondsPerDay = 24 * SecondsPerHour;
public int Day {get;}
public int Hour {get;}
public int Minute {get;}
public int Second {get;}
public TimeData (float time)
{
// first round the delta to int -> no floating point inaccuracies
// according to your needs you could also use Floor or Ceil here
var seconds = Mathf.RoundToInt(time);
// Now we can use the reminder free integer divisions to get the target values
Day = seconds / SecondsPerDay;
seconds -= Day * SecondsPerDay;
Hour = time / SecondsPerHour;
seconds -= Hour * SecondsPerHour;
Minute = seconds / SecondsPerMinute;
seconds -= Minute * SecondsPerMinute;
Second = seconds;
}
}
public TimeData GetTime()
{
return new TimeData (Time.time - initTime);
}
Time.time is the absolute time in (scaled) seconds since you started the app.
You could basically do the same using e.g. DateTime which basically works the same way but using precise system ticks which makes it also quite expensive though.
for that you would store initially
private DateTime initTime;
public PlayTime()
{
initTime = DateTime.Now;
}
and then e.g. return
public TimeSpan GetTime()
{
return DateTime.Now - initTime;
}
which returns a TimeSpan based on the system time and ticks
Related
I have such a script:
public class SystemLives : MonoBehaviour
{
public int lives;
public int maxLives;
public Image[] Live;
public Sprite FullHearts;
public Sprite EmptyHearts;
public void setLives(int time)
{
lives += Mathf.RoundToInt(Time.deltaTime * time);
if (lives > maxLives)
{
lives = maxLives;
}
for (int i = 0; i < Live.Length; i++)
{
if (i < lives)
{
Live[i].sprite = FullHearts;
}
else
{
Live[i].sprite = EmptyHearts;
}
}
}
public void TakeHit(int damage)
{
lives -= damage;
if (lives <= 0)
{
Debug.Log("Игра окончена");
}
for (int i = 0; i < Live.Length; i++)
{
if (i < Mathf.RoundToInt(lives))
{
Live[i].sprite = FullHearts;
}
else
{
Live[i].sprite = EmptyHearts;
}
}
}
n which there are functions for subtracting and adding life, I hung this script on an empty element, I call it like this:
public GameObject ScriptLives;
private SystemLives systemLives;
public int times=1;
public void Btn()
{
foreach (var s in strings)
{
if (s.Compare() == true)
{
b++;
}
else
{
prob++;
}
}
systemLives = ScriptLives.GetComponent<SystemLives>();
systemLives.setLives(times);
systemLives = ScriptLives.GetComponent<SystemLives>();
systemLives.TakeHit(prob);
Debug.Log($"{b}");
}
I call it like this: It turns out to take lives, but for some reason it is not added. maybe the problem is in the first script? please tell me what the reason may be and how it can be fixed?
You're calling
lives += Mathf.RoundToInt(Time.deltaTime * time);
but Time.deltaTime is the time between frame draws. Even an awful game might still have 10 fps, which means Time.deltaTime is 0.1 seconds. You're passing 1 in as the argument to that function, so (0.1*1) = 0.1.
Then you take 0.1, round it to an integer, so it rounds to 0. Then you increment lives by zero.
I really can't tell what the goal is here with the time dependency on adding lives, so unfortunately I've got no suggestions, but hopefully this helps.
An easy way to ask your question is to use InvokeRepeating, which works as follows:
public void Start()
{
InvokeRepeating(nameof(AddLife), 0f, 2f);
}
public void AddLife() // add 1 life every 2 sec
{
life = Mathf.Min(++life, maxLives);
Debug.Log("Life: "+life);
}
If you want to be time dependent, change the life adjustment algorithm to Time.deltaTime to Time.time.
public void Update()
{
SetLife(3f); // set life every 3 sec for e.g.
}
public void setLives(float repeatTime)
{
life = (int) (Time.time / repeatTime);
}
I am trying to wrap my head around a custom date control system. I have this for advancing time on Unity:
private void AdvanceTime() {
if (paused) return;
currentTimer -= Time.deltaTime * (fastForwarding ? fastForwardMultiplier : 1f);
if(currentTimer <= 0) {
currentTimer += dayDuration;
dayPassed++;
}
}
I am calling this method on Update method and it works.
My question is, how can I get custom dates like this?For the project I'm on, I'll ignore months but will introduce seasons (60days) as months. If I start with a random Date like 13 Spring 420, how can I get what I want?
I don't know if it's a proper question. Actually it's my first here.
Thank you all in advance.
I'm not entirely sure what 13 Spring 420 means, but here's how you could turn the amount of days into a year/season/day format. I'm assuming dayPassed is the total amount of days (plural) that have passed.
Getting the days in a year/season/day format
We'll start by getting the year, we can call it year. To get the value we want, we divide the dayPassed with the amount of days in a year and round down. If we do an integer division the rounding is already taken care of for us.
int year = dayPassed / amountOfDaysInYear;
To get the season number we need to divide the remaining days after the last calculation (we can call this variable yearRemainder) by the season length (in your case, 60). We can get the remainder of a division with the remainder operator % (also known as the modulo operator).
int yearRemainder = dayPassed % amountOfDaysInYear;
int season = yearRemainder / amountOfDaysInSeason;
Finally, the remaining days will just be the remainder from the previous calculation.
int day = yearRemainder % amountOfDaysInSeason;
An example
I'll give it a quick rundown with an example. Let's say we are trying to get day 703 in a year/season/day format. A year is 240 days long and a season is 60 days long.
year = 703 / 240 // = 2
yearRemainder = 703 % 240 // = 223
season = 223 / 60 // = 3
day = 223 % 60 // = 43
So the answer is that it's year 2, season 3 (0-indexed), and day 43.
We can double check by making sure the years, seasons and days add up.
2 * 240 = 480
3 * 60 = 180
43 * 1 = 43
480 + 180 + 43 = 703
Hope that helps :)
this is controller:
public class TimeController : MonoBehaviour {
public const string DateChanged = "DateChanged";
[Header("Settings")]
[SerializeField] private Date date;
[SerializeField, Range(1, 10)] private float timeScale = 1f;
[SerializeField, Range(2, 32)] private int fastForwardModifier = 4;
[SerializeField] private bool paused;
[SerializeField] private bool fastForwarding;
[Header("Properties")]
[SerializeField, ReadOnly] private int totalDaysPassed;
public bool Paused { get { return paused; } }
public bool FastForwarding { get { return fastForwarding; } }
private float timer;
private void OnEnable() {
date = new Date();
}
private void Start() {
InitializeTimer();
}
private void InitializeTimer() {
timer = timeScale;
}
private void Update() {
AdvanceTime();
}
private void AdvanceTime() {
if(paused) return;
timer -= Time.deltaTime * (fastForwarding ? fastForwardModifier : 1);
if(timer <= 0) {
timer += timeScale;
OnDayPassed();
}
}
private void OnDayPassed() {
totalDaysPassed++;
date.AdvanceDay();
this.PostEvent(DateChanged, new OnDateChanged(date));
}
public void PauseOrResume() {
paused = !paused;
}
}
public class OnDateChanged : EventData {
public Date Date { get; private set; }
public OnDateChanged(Date date) {
Date = date;
}
}
this is Date class:
public enum Seasons {
Spring = 1,
Summer = 2,
Autumn = 3,
Winter = 4
}
[System.Serializable]
public class Date {
[Header("General")]
[SerializeField, ReadOnly] private int DaysInMonth = 29;
[SerializeField, Min(1)] private float day;
[SerializeField, Min(1)] private int season;
[SerializeField, Min(1)] private int year;
public float Day { get => day; private set => day = value; }
public int Season { get => season; private set => season = value; }
public int Year { get => year; private set => year = value; }
public Seasons currentSeason;
public Date(int startDay = 1, int startSeason = 1, int startYear = 420) {
Day = startDay;
Season = startSeason;
currentSeason = (Seasons)Season;
Year = startYear;
}
public void AdvanceDay() {
Day++;
if(Day > DaysInMonth) {
Day = 1;
AdvanceSeason();
}
}
private void AdvanceSeason() {
Season++;
currentSeason = (Seasons)Season;
if(Season > 4) {
Season = 1;
AdvanceYear();
}
}
public Seasons GetCurrentSeason() {
return (Seasons)Season;
}
private void AdvanceYear() {
Year++;
}
}
}
I'm trying to populate objects using read memory. I'm using structure and it goes pretty fast, at least it seems like it.
float x = 0;
while (true)
{
st.Start();
List<DataChild> DataChildList = new List<DataChild>();
var Data = Algorithm.Data;
Data.ChildData.pData = IntPtr.Add(Data.ChildData.pData, 0x20);
for (int i = 0; i < Data.NumberOfObjects; i++)
{
ChildData childData = Data.ChildDatas.ReadValue(i, true);
if (childData.Render.X != x)
{
Console.WriteLine("Change detected");
x = childData.Render.X;
}
DataChildList.Add(childData)
}
Algorithm.Data.Datas = DataChildList;
st.Stop();
Console.WriteLine($"populated in {st.Elapsed.TotalMilliseconds} ms");
st.Reset();
}
}
There are my structs :
[StructLayout(LayoutKind.Explicit)]
public struct ChildData
{
[FieldOffset(0x0)]
public IntPtr BasePointer;
[FieldOffset(0x400)]
public IntPtr pRender;
public Vector3 Render
{
get
{
return M.Read<Render>(this.pRender);
}
}
}
[StructLayout(LayoutKind.Explicit)]
public struct Render
{
[FieldOffset(0x30)]
public float X;
public float posX
{
get
{
return this.X;
}
}
}
The StopWatch says I populate the data in 0.005 ms.
When I press a key, it change the value of childData.Render.X (the only one in the list for now).
But it displays it in between 0.5 and 1 sec which seems to be very slow compared to speed of the loop.
Have to say that this code is running into a Thread.
Any ideas on why it takes so long? Thanks!
I increase a value from 0 to 1 and decrease it back to 0.
private Image cycleBar;
private float currentTime = 0;
private float cycleDuration = 5;
private void Start()
{
cycleBar = GetComponent<Image>();
UpdateCycleBar();
}
private void Update() // TimeHandler
{
currentTime = Mathf.PingPong(Time.time * 1 / cycleDuration, 1); // handle the time
UpdateCycleBar();
}
private void UpdateCycleBar()
{
// the missing part
}
So now the value got its logic. How can I visualize it like this example here
When reaching the value 1, the bar has to move on to the right "zero number". After that it just resets on the left side again.
You can try this approach. But don't forget to call Initialize(float value, TimeMod mod) method if you want initialize it from Dusk for example.
using System;
using UnityEngine;
using UnityEngine.UI;
public class CycleBar : MonoBehaviour
{
[SerializeField]
private Image cycleBar;
private TimeMod currentMod = TimeMod.AM;
public void Initialize(float value, TimeMod mod)
{
currentMod = mod;
cycleBar.fillAmount = GetProgressbarValue(value);
}
public void UpdateValue(float value)
{
CheckTimeMod(value);
cycleBar.fillAmount = GetProgressbarValue(value);
}
private void CheckTimeMod(float value)
{
if (Mathf.Abs(value - 1) < 0.01f)
{
currentMod = TimeMod.PM;
}
if (Mathf.Abs(value) < 0.01f)
{
currentMod = TimeMod.AM;
}
}
private float GetProgressbarValue(float value)
{
switch (currentMod)
{
case TimeMod.AM:
return value / 2;
case TimeMod.PM:
return 0.5f + Mathf.Abs(value-1) / 2;
default:
throw new ArgumentOutOfRangeException("currentMod", currentMod, null);
}
}
public enum TimeMod
{
AM,
PM
}
}
And controller:
using UnityEngine;
public class Controller : MonoBehaviour
{
[SerializeField]
private CycleBar cycleBar;
private void Update() // TimeHandler
{
var value = Mathf.PingPong(Time.time, 1); // handle the time
cycleBar.UpdateValue(value);
}
}
But if it possible, use more simple way with the range [-1;1]. For example you can use Slider from UnityEngine.UI
If it helps you, mark please this post as Correct Answer.
you can create a new image in UI, then on 2D resize mode, resize it to your complete state. now you can scale X from zero to 1 in your code.
I am trying to make a timing system in C#, and I am having trouble calculating delta time.
Here is my code:
private static long lastTime = System.Environment.TickCount;
private static int fps = 1;
private static int frames;
private static float deltaTime = 0.005f;
public static void Update()
{
if(System.Environment.TickCount - lastTime >= 1000)
{
fps = frames;
frames = 0;
lastTime = System.Environment.TickCount;
}
frames++;
deltaTime = System.Environment.TickCount - lastTime;
}
public static int getFPS()
{
return fps;
}
public static float getDeltaTime()
{
return (deltaTime / 1000.0f);
}
The FPS counting is working correctly, but the delta time is faster than it should be.
Value of System.Environment.TickCount changes during the execution of your function which is causing deltaTime to move faster than you expect.
Try
private static long lastTime = System.Environment.TickCount;
private static int fps = 1;
private static int frames;
private static float deltaTime = 0.005f;
public static void Update()
{
var currentTick = System.Environment.TickCount;
if(currentTick - lastTime >= 1000)
{
fps = frames;
frames = 0;
lastTime = currentTick ;
}
frames++;
deltaTime = currentTick - lastTime;
}
public static int getFPS()
{
return fps;
}
public static float getDeltaTime()
{
return (deltaTime / 1000.0f);
}