Optimization: Set or Subtract? - c#

a very simple question here.
I'm coding a multi-player game in XNA, what would be a better option here - and why?
(Line 7 is the only changed line in each option)
Option 1:
const float SyncFrequency = (1 / 30f);
float Sync;
void Sync(GameTime GT)
{
Sync += (float)GT.ElapsedGameTime.TotalSeconds;
if (Sync >= SyncFrequency) { Sync = 0; SyncAll(); }
}
void SyncAll() { /*Syncing Code Here*/ }
Option 2:
const float SyncFrequency = (1 / 30f);
float Sync;
void Sync(GameTime GT)
{
Sync += (float)GT.ElapsedGameTime.TotalSeconds;
if (Sync >= SyncFrequency) { Sync -= SyncFrequency; SyncAll(); }
}
void SyncAll() { /*Syncing Code Here*/ }
Also:
Notice I use floats:
const float SyncFrequency = (1 / 30f);
Would using extra-precision be needed at all? E.g:
const double SyncFrequency = (1 / 30d);

Mathematically it makes sense to subtract instead of resetting.
Example: Standard 60fps means updating approximately every 16.666ms. If you have a network connection that you poll on every 30ms, upon the second Update, the time passed will have been 33.333ms, and this means that if you reset, you will be back at square one again. Whereas if you subtract 30ms from the current, you're down on 3.333ms. So over the course of a few seconds, you'll have run a more accurate amount of polls on the network connection.
It all depends on the situation really. In the above mentioned instance, you might run into situation where the application will poll the network connection 33 times, and other times it may do it 32, or 34 times, due to the time from the previous runs. All this can of course be calculated.
This may lead to uneven (probably unnoticeable, but still) gameplay, if it is actually used for network polling.
However for things such as Animating, it makes sense, since you'll most likely ALWAYS want the smoothest animating your hardware can deliver.
Especially for movement, since some PCs can't update 60 times per second (either because of hardware limitations, or the software simply only supports 30 or maybe 24fps). This will also lead to uneven movement, if you don't actually use the time passed (16.666ms in 60fps) to calculate movement and the likes.
Rant ended

To further expand on Bjarke's great explanation, I coded this for my use, but feel free to use it:
public static class Timers
{
private static Dictionary<string, Timer> Array;
public static void Add(string Name, double Interval)
{
if (Array == null) Array = new Dictionary<string, Timer>();
if (!Array.ContainsKey(Name)) Array.Add(Name, new Timer(Interval));
}
public static void Remove(string Name) { if ((Array != null) && Array.ContainsKey(Name)) Array.Remove(Name); }
public static void Clear() { Array.Clear(); Array = null; }
public static void Update(GameTime GameTime)
{
if (Array != null)
foreach (Timer Timer in Array.Values)
{
if (Timer.Time >= Timer.Interval) Timer.Time -= Timer.Interval;
Timer.Time += GameTime.ElapsedGameTime.TotalSeconds;
}
}
public static bool Tick(string Name) { return ((Array != null) && Array.ContainsKey(Name) && (Array[Name].Time >= Array[Name].Interval)); }
private class Timer
{
public double Interval, Time;
public Timer(double Interval) { this.Interval = Interval; }
}
}
To add a timer:
//Parameter 1: The name of the timer for your use.
//Parameter 2: The time (of interval) for each tick.
Timers.Add("Name of your timer!", (1 / 30d));
// (1 / 30d) = 30 times a second
(add your timer in the load function of your game)
To use your timer:
In your game update method (at the start of it), call:
Timers.Update(gameTime);
Then you can use any timers that you've added like so:
if (Tick("Name of your timer!"))
{
//Code here to execute on the tick of the timer
}

Related

How to keep time reference during mobile window switch? (Unity) [duplicate]

This question already has answers here:
How to pass data (and references) between scenes in Unity
(6 answers)
Closed 2 years ago.
I have a game where I have a queue matchup system.
I would like to show the player how long they been in the current queue. It works well, until the player presses the menu/app overview button on their phone, which basically freezes the timer, and it only continues counting when the player switches back to full screen mode on their phone.
I tried looking for an app lifecycle method (somewhat like onApplicationPause, but it didn't work for me)
I also tried syncing the time by saving it in the db and then loading from the database actually, but Firebase puts on some delay, so it won't be exact.
How could I solve this, so it will keep counting when the user presses their app overview/menu button on their phone?
For now, I have this code which counts the user's queue time:
private void Update() {
if(startedCounting) {
timer += Time.deltaTime;
int seconds = Mathf.FloorToInt(timer % 60);
int minutes = Mathf.FloorToInt(timer / 60);
queueStatusText.text = "You are in the queue\n"
+ string.Format("{0:00}:{1:00}", minutes, seconds);
}
}
There are different aproaches, some using static class or singleton pattern. It is better to not update this time variable each time on Update() as it takes the computation time each update (if you don't need this time for anything else). Also user doesn't need to have exact time by frames so you can avoid things like adding Time.deltaTime.
I'll show you example with static class, it can hold this information. Also note that this script is only added as C# file, but you don't attach it to any GameObject :
public static class QueueTimerInformation //It is not inheriting from MonoBehavior!
{
private static DateTime dt;
private static bool isRunning = false;
//Save current DateTime when user did the action
public static void Start()
{
if(!isRunning)
{
dt = DateTime.Now;
isRunning = true;
}
}
public static void Reset()
{
isRunning = false;
}
// This gets the actual time in String value
// Usually it is better to return just `elapsedTime` and format it later
public static string GetTimeElapsed()
{
if(!isRunning) return "00:00"; //Not running, return some default
var elapsedTime = (DateTime.Now - dt);
return $"{elapsedTime:mm\\:ss}";
}
}
Usage
//On 1st time enter lobby
QueueTimerInformation.Start();
//In update method
var result = QueueTimerInformation.GetTimeElapsed();

Question On Sequential Attacks / Finishing Sequence Early

I'm working on a top down game where when you press the attack button multiple times there's a 3 hit sequence. I have the 3 hit sequence working just fine, but I have two problems I can't seem to find a solution to through my google fu.
When the sequence is finished, I don't have a good way to transition back to the idle state.
When you press the button once or twice, I'm not sure how I can tell whether you haven't pressed the button again in a long enough time to decide the sequence has been cancelled.
(I'm using a state machine I made for the character controller, and the states don't inherit from monobehavior so I don't have access to coroutines in this attack state)
Here is the code, I would really appreciate some feedback and help.
private string currentAttack = "Attack1";
public AttackingState(Character character, StateMachine stateMachine)
: base(character, stateMachine)
{
}
public override void Action()
{
if (Input.GetKeyDown(KeyCode.Space))
{
switch (currentAttack)
{
case "Attack1":
{
this.character.SetTrigger("Attack2");
this.currentAttack = "Attack2";
break;
}
case "Attack2":
{
this.character.SetTrigger("Attack3");
this.currentAttack = "Attack3";
break;
}
case "Attack3":
{
this.character.SetTrigger("Attack1");
this.currentAttack = "Attack1";
break;
}
default:
break;
}
}
if (Input.GetAxis("Horizontal") != 0 || Input.GetAxis("Vertical") != 0)
{
character.SetTrigger("AttackCancelled");
stateMachine.SetState<WalkingState>(new WalkingState(character, stateMachine));
currentAttack = "Attack1";
}
}
public override void OnStateEnter()
{
base.OnStateEnter();
character.SetTrigger("Attack1");
}
you could track a timestamp since the last press and do something like
// stores the last attack time
private float lastAttackTime;
// whereever you want to assign this value from
// The delay before the attack is reset and starts over from 1
private const float MAX_ATTACK_INTERVAL = 1f;
if (Input.GetKeyDown(KeyCode.Space))
{
// Time.time is the time in seconds since the app was started
if(Time.time - lastAttackTime > MAX_ATTACK_INTERVAL)
{
// start over
// mimics the attack3 since there you start over at 1
currentAttack = "Attack3";
}
lastAttackTime = Time.time;
switch (currentAttack)
{
...
}
}
In general instead of string I would recommend either a simple int or an enum instead.

Is there a funcion in C# to decrease the value of a int or double?

i am trying to make a script for players health stamina hunger and thirst. I am trying to make hunger and thirst decrease over time by 0.1 over a time of 3 minutes but i don't know if there is a function to do so (also i did the stamina as a int beacuse i want to keep it round). I am new to programing so it might sound like a dumb question. Here's the code i am working with.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player_Stats : MonoBehaviour {
public double Health;
public double Thirst;
public double Hunger;
public int Stamina;
// Use this for initialization
void Start () {
Health = 100;
Thirst = 100;
Hunger = 100;
Stamina = 100;
}
// Update is called once per frame
void Update () {
}
}
The best way would be to use a coroutine, like this
IEnumerator DecreaseStats(){
float startTime = time.now;
while(time.now - startTime < 3*60){ // less than 3 mins has passed
hunger -= 0.1f;
thirst -= 0.1f;
yeild return new WaitForSeconds(0.5f); // wait for 0.5 seconds (tweak this time)
}
}
Then call this function whenever you want this decrease to begin
StartCoroutine(DecreaseStats());
This will run alongside your update method, and you can call it wherever you like in code
First of all, you need to keep track of time.
double time;
void Update () {
...
time += Time.deltaTime;
...
}
Then you need to check if the time has passed
double targetTime;
void Update () {
...
if ( time >= targetTime ) {
time -= targetTime;
hunger -= some number
thirst -= some number
}
...
}
I understand that you want to decrease Hunger and Thirst by 0.1 every 3 minutes, A specific function like that in C# it doesn't exist as i know but you can create one with the Sleep() method, Here is the code:
public static void Update (double health,double thirst,double hunger,int stamina)
{
while(true)
{
Thread.Sleep(3000);
hunger = hunger - 0.1;
thirst = thirst - 0.1;
//Console.WriteLine("Hunger: "+hunger.ToString()+" Thirst: "+thirst.ToString());
}
}
It wait 3000 milliseconds (if you want 3 minutes you have to make it 3*60*1000 milliseconds), and then it decrease the two variables.
This is just a basic example you have to work on it and i think you must run it as a separate thread. I Hope i did help you & good luck with your game.

Right way to manage time event in unity

I have to do some time based task(simulation) like,
As the game started:
After 2 mintues start TrainA.
After 6 mintues star Train B
after 12 minutes start Train C and so on
But remember If i want to quickly view the simulation, the timer should be speed able.
Now i am considering two approaches (other approaches are also welcome which is the purpose of the question)
Each Object (Train) script manage its time by WaitForSeconds:
void Start()
{
StartCoroutine("MyEvent");
}
private IEnumerator MyEvent()
{
yield return new WaitForSeconds(120f); // wait two minutes
//Launch Train
}
This kind of script attach to every object that requires action after a certain time:
Problem:
How do I speed up its time?
Maybe Performance intensive. As each script managing its own co-routine(Maybe I am wrong)
One Global script for Timer:
function Update ()
{
Timer += Time.deltaTime; //Time.deltaTime will increase the value with 1 every second.
if (timer>= 120){
//launch train, an so one conditions
//Or get timer variable in other script and compare time on update
}
}
Now using the above script I can get the Timer variable in another script and can execute my task based on time in the update method.
The question is how do I manage it? Either first way or second way or third way (by you)?
Because I also want to speed up the time too which seems impossible within co-routine once it registers.
Need your help folks!!
You use either of the two ways and simply change Time.timescale if you want to view it faster/slower etc in the example by pressing Space:
public class Example : MonoBehaviour
{
// Toggles the time scale between 1 (normal) and 0.5 (twice as fast)
// whenever the user hits the Space key.
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
if (Time.timeScale == 1.0f)
{
Time.timeScale = 0.5f;
}
else
{
Time.timeScale = 1.0f;
}
// Also adjust fixed delta time according to timescale
// The fixed delta time will now be 0.02 frames per real-time second
Time.fixedDeltaTime = 0.02f * Time.timeScale;
}
}
}
For the Coroutine this works without you having to change anything because WaitForSeconds is affected by the Time.timescale:
The actual time suspended is equal to the given time multiplied by Time.timeScale.
Time.deltaTime afaik is not affacted by the Time.timescale so in order to allow faster replay you would have to do
private void Update ()
{
Timer += Time.deltaTime * (1 / Time.timescale);
if (timer >= 120)
{
//launch train, an so one conditions
//Or get timer variable in other script and compare time on update
}
}
Whether one Update or multiple Coroutines are more performant depends a lot on the specific usecase. Afaik it isn't really noticable until you have like maybe 10.000 Coroutines running (don't nail me on numbers here ;) ).
In your case for only raising one or multiple events it might be better to stick with the one Update() method instead and invoke an event or something like that.
But - why not simply have one single Coroutine instead of the Update at all:
public class GlobalTimer : MonoBehaviour
{
public float Delay = 2.0f;
public UnityEvent onTimerDone;
// Unity allows to use Start as IEnumerator instead of a void
private IEnumerator Start()
{
yield return new WaitforSeconds(delay);
onTimerDone.Invoke();
}
}
than you have only one invoked method and can add callbacks to that timer like
globalTimerReference.onTimerDone.AddListener(() => {
Debug.LogFormat("Timer of {0} seconds is done now.", globalTimerReference.Delay);
});
or via the inspector (like in a UI.Button.onClick).
For multiple events I just came up with a quick and dirty solution so you can simply define multiple events via the inspector and add various callbacks and stuff:
public class GlobalTimer : MonoBehaviour
{
public List<UnityEvent> events;
public List<float> delays;
private void Start()
{
var validPairs = Mathf.Min(events.Count, delays.Count);
for (int i = 0; i < validPairs; i++)
{
StartCoroutine(InvokeDelayed(events[i], delays[i]));
}
}
private IEnumerator InvokeDelayed(UnityEvent unityEvent, float delay)
{
yield return new WaitForSeconds(delay);
unityEvent.Invoke();
}
}
Just make sure that for every event there is a delay in the list. In the future you might want to write a proper CustomEditor in order to edit this more beautiful in the inspector.
Update
or you can take mine :D
public class ExampleScript : MonoBehaviour
{
[SerializeField] private List<EventDelayPair> EventDelayPairs;
private void Start()
{
foreach (var eventDelayPair in EventDelayPairs)
{
StartCoroutine(InvokeDelayed(eventDelayPair.unityEvent, eventDelayPair.Delay));
}
}
private IEnumerator InvokeDelayed(UnityEvent unityEvent, float delay)
{
yield return new WaitForSeconds(delay);
unityEvent.Invoke();
}
[Serializable]
private class EventDelayPair
{
public UnityEvent unityEvent;
public float Delay;
}
}
[CustomEditor(typeof(ExampleScript))]
public class ExampleInspector : Editor
{
private SerializedProperty EventDelayPairs;
private ReorderableList list;
private ExampleScript _exampleScript;
private void OnEnable()
{
_exampleScript = (ExampleScript)target;
EventDelayPairs = serializedObject.FindProperty("EventDelayPairs");
list = new ReorderableList(serializedObject, EventDelayPairs)
{
draggable = true,
displayAdd = true,
displayRemove = true,
drawHeaderCallback = rect =>
{
EditorGUI.LabelField(rect, "DelayedEvents");
},
drawElementCallback = (rect, index, sel, act) =>
{
var element = EventDelayPairs.GetArrayElementAtIndex(index);
var unityEvent = element.FindPropertyRelative("unityEvent");
var delay = element.FindPropertyRelative("Delay");
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), delay);
rect.y += EditorGUIUtility.singleLineHeight;
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUI.GetPropertyHeight(unityEvent)), unityEvent);
},
elementHeightCallback = index =>
{
var element = EventDelayPairs.GetArrayElementAtIndex(index);
var unityEvent = element.FindPropertyRelative("unityEvent");
var height = EditorGUI.GetPropertyHeight(unityEvent) + EditorGUIUtility.singleLineHeight;
return height;
}
};
}
public override void OnInspectorGUI()
{
DrawScriptField();
serializedObject.Update();
list.DoLayoutList();
serializedObject.ApplyModifiedProperties();
}
private void DrawScriptField()
{
// Disable editing
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField("Script", MonoScript.FromMonoBehaviour(_exampleScript), typeof(ExampleScript), false);
EditorGUI.EndDisabledGroup();
EditorGUILayout.Space();
}
}
Example
or with a preview of the delays for debugging
public class ExampleScript : MonoBehaviour
{
public List<EventDelayPair> EventDelayPairs;
private void Start()
{
foreach (var eventDelayPair in EventDelayPairs)
{
StartCoroutine(InvokeDelayed(eventDelayPair));
}
}
private IEnumerator InvokeDelayed(EventDelayPair pair)
{
var timer = pair.Delay;
do
{
timer -= Time.deltaTime * (1 / Time.timeScale);
pair.Delay = timer;
yield return null;
} while (timer > 0);
pair.Delay = 0;
pair.unityEvent.Invoke();
}
[Serializable]
public class EventDelayPair
{
public UnityEvent unityEvent;
public float Delay;
}
}
Btw your comment
//Time.deltaTime will increase the value with 1 every second.
is not formulated correctly. Instead it should be:
Time.deltaTime will increase the value every frame about the time passed in seconds since the last frame was rendered.
EDIT - Reduce delays afterwards
I understood from the question that you wanted to speed up the entire playback.
From the comments I learned now that instead you wanted to rather reduce the delay afterwards. So you can't use Time.timescale.
For this you can use the second example a bit altered:
[Serializable]
public class EventDelayPair
{
public UnityEvent unityEvent;
public float Delay;
// add a time multiplicator for each delay with default 1
public float TimeMultiplicator = 1.0f;
}
Note: You'll have to also add it to the EditorScript if you use it - I leave this as your homework ;)
private IEnumerator InvokeDelayed(EventDelayPair pair)
{
var timer = pair.Delay;
do
{
timer -= Time.deltaTime * pair.TimeMultiplicator;
pair.Delay = timer;
yield return null;
} while (timer > 0);
pair.Delay = 0;
pair.unityEvent.Invoke();
}
so you can in the Inspector or also by script
exampleScriptReference.EventDelayPairs[0].TimeMultiplicator = 2;
reduce the Delay faster.
You might also want to add an overall multiplicator like
// Again you have to add it to the inspector if you use it
public float overallMultiplicator = 1.0f;
//...
timer -= Time.deltaTime * pair.TimeMultiplicator * overallMultiplicator;

Chow to count of 1/10 of a second

I am trying to figure out how to count 10 times each second and display it.
I have an int called countpoints which is what the user starts with points. Lets say 800.
I would like to drop 10 points each second but showing each point falling instead of every 10 points like my script below does.
here is how I have done so far:
if(miliseconds <= 0){
if(seconds <= 0){
minutes--;
seconds = 59;
}
else if(seconds >= 0){
seconds--;
countpoints = countpoints-10;
}
miliseconds = 100;
}
miliseconds -= Time.deltaTime * 100;
This runs in void update and here countpoints falls by 10 each second. but i would like to be able to show the numbers drop down like a stopwatch each second. How do i do that?
Any help is appreciated and thanks in advance :-)
You should use coroutine for that calculation instead of in Update(). It can be very easy with the coroutine. You just need to start croutine and then wait for 0.1 seconds and decrease counterpoints by 1. Again call that coroutine inside that to keep it running. Just add condition whenever you want to keep on calling it.
private int countpoints=800;
private float t=0.1f;
private int noOfSeconds=90;
private int min;
private int sec;
private int temp=0;
void Start ()
{
StartCoroutine(StartTimer());
}
IEnumerator StartTimer ()
{
yield return new WaitForSeconds(t);
countpoints--;
temp++;
if(temp==10)
{
temp=0;
noOfSeconds--;
}
min = noOfSeconds/60;
sec = noOfSeconds%60;
if(noOfSeconds>0)
{
StartCoroutine(StartTimer());
}
}
void OnGUI ()
{
GUI.Label(new Rect(100f,100f,100f,50f),countpoints.ToString());
GUI.Label(new Rect(100f,160f,100f,50f),"Time : "+min.ToString("00")+":"+sec.ToString("00"));
}
You could just do it in the Update method and decrement the points once every 100ms, you need to take care of not rounding things up or down as the error would be systematic and you'll get choppy results.
Using coroutines won't work as expected as the interval is not guaranteed.
private float _milliseconds = 0;
private int points = 800;
void Update()
{
_milliseconds += Time.delta * 1000;
if( _milliseconds > 100 )
{
points--;
//add updating GUI code here for points
_milliseconds -= 100;
}
}
You won't get choppy decrements as the _milliseconds are decremented by 100, so even if there are differences in the duration of the frames in the long run you'll get a proper handling.
One problem with the script is if frames take a lot more than 100ms consistenly , but if it takes that long you probably have bigger problems:D

Categories

Resources