I'm currently making a 2D Game Engine using C#, GDI and have setup a simple frame cap. The game can only render 60fps.
As far as I know there's no issue with the code however, I would just like a cleaner way of rendering 60fps and no more.
Here's my code, any help would be great
public void Run()
{
window.Show();
window.Focus();
Initialize();
isRunning = true;
canRender = true;
timer = new Stopwatch();
timer.Start();
// the amount of milliseconds needed to pass before rendering next frame
double frameCapCounter = 16.666666;
while (isRunning)
{
Application.DoEvents();
if (window.Focused)
{
if (timer.ElapsedMilliseconds >= frameCapCounter)
{
canRender = true;
frames += 1; // update amount of frames
frameCapCounter += 16.666666; // increment counter
}
else
{
canRender = false;
}
// this is used to check if a second has passed, and if so
// we set the fps variable to the amount of frames rendered
// and reset all variables.
if (timer.ElapsedMilliseconds >= 1000)
{
fps = frames;
frames = 0;
frameCapCounter = 0;
timer.Restart();
}
Update();
LateUpdate();
if (canRender)
Render();
else
{
Thread.Sleep(1);
}
}
}
}
In your scenario, instead of keeping track of time elapsed, frames rendered, etc. etc., you could simply sleep the number of milliseconds to cap your FPS, example:
public void Run()
{
window.Show();
window.Focus();
Initialize();
isRunning = true;
while (isRunning)
{
if (window.Focused)
{
Update();
LateUpdate();
Render();
Thread.Sleep(16); // hard cap
}
}
}
The problem with this, however, is that up to the sleep, the rest of that code could take longer than 16 milliseconds; to this, you could do some performance measurements and do something like 10ms instead.
Another way, keeping a timer active would be something like the following:
public void Run()
{
window.Show();
window.Focus();
Initialize();
isRunning = true;
long limit = 1000 / 60;
while (isRunning)
{
if (window.Focused)
{
timer.Restart();
Update();
LateUpdate();
Render();
while (timer.ElapsedMilliseconds < limit) {
Thread.Sleep(0);
}
}
}
}
This avoids the "hard" cap while also taking into consideration the fact that all code up to the sleep could take longer than 16ms, thus not enter the "wait" loop if the requisite time has passed.
Also, a few notes to consider with your posted code:
First, if you are not using any Windows Form elements (like a PictureBox, Button, TextBox, etc.), and instead are drawing all elements your self (e.g. calling something like Graphics.DrawLine, etc.), you do not need to call Application.DoEvents; doing so causes the calling thread to wait until all other Windows Forms messages have been processed, thus slowing down the render loop. If you do indeed need to call Application.DoEvents it would be better to do it after your Render(), so as to include that function call time in your frame limit.
Lastly, when specifying a Thread.Sleep of anything less than approximately 20 milliseconds on a non-real-time Windows systems, chances are that the Sleep will actually sleep for longer due to the time slice dedicated to a thread on a Windows system. So the Thread.Sleep(1) might sleep for 1 millisecond, OR it might sleep for 6 milliseconds; and if the time cap is already at 14 milliseconds, a 6 millisecond sleep would put your total time upwards of 20 milliseconds, thus slowing your frame rate.
Specifying a sleep of 0 milliseconds relinquishes the remainder of the time slice for the calling thread to another thread of equal priority that is ready to run; if no other threads are ready, then the calling thread continues (so 0-1 milliseconds of wait, vs 6+). You could also use Thread.Yield but that acts much different than Sleep at the kernel level.
Hope that can help.
Related
I am trying to create a timer, which, for example, every 3 seconds during eg 15 seconds will perform an action.
I tried to use gameTime.ElapsedGameTime.TotalSeconds and loop, but unfortunately it doesn't work.
I have an Attack () function that reduces player statistics when an enemy attacks it. I would like that in case of one particular enemy, this function for a specified period of time would subtract player's HP, eg for every 3 seconds. I guess it should be done in the Update function to access gameTime, unfortunately, I have no idea how to do it.
public override Stats Attack()
{
attack = true;
return new Stats(0, -stats.Damage, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}
public override void Update(GameTime gameTime)
{
spriteDirection = Vector2.Zero; // reset input
Move(Direction); // gets the state of my keyborad
float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds; // make movement framerate independant
spriteDirection *= Speed; // add hero's speed to movement
position += (spriteDirection * deltaTime); // adding deltaTime to stabilize movement
totalPosition = new Vector2((int)((BottomBoundingBox.Center.X) / 32.0f), (int)((BottomBoundingBox.Center.Y) / 32.0f));
base.Update(gameTime);
}
I will make it simple, so you need to modify my code to achieve your desire result.
My best guess is that you want to have a special effect when your monsters hit your player.
First, you need to check if the monster actually hits the player (if collision is detected):
if (collision)//if it's true
{
// Apply your special effect if it is better than
// the one currently affecting the target :
if (player.PoisonModifier <= poisonModifier) {
player.PoisonModifier = poisonModifier;
player.ModifierDuration = modifierDuration;
}
//player.setColor(Color.Blue);//change color to blue
player.hitPoints -= Poision.Damage;//or enemy.PoisonDamage or whatever you define here
hit.Expire();//this can be for the arrow or bullet from your enemy or simply just a normal hit
}
In your Player class, you need:
public float ModifierDuration {
get {
return modifierDuration;
}
set {
modifierDuration = value;
modiferCurrentTime = 0;
}
}
Then in Update method of Player class:
// If the modifier has finished,
if (modiferCurrentTime > modifierDuration) {
// reset the modifier.
//stop losing HP code is here
modiferCurrentTime = 0;//set the time to zero
setColor(Color.White);//set back the color of your player
}
count += gameTime.ElapsedGameTime.TotalSeconds;//timer for actions every 3s
if (posionModifier != 0 && modiferCurrentTime <= modifierDuration) {
// Modify the hp of the enemy.
player.setHP(player.getCurrentHP() - posionDamage);
//Or change it to every 3s
//if (count > 3) {
// count = 0;
//DoSubtractHP(player);
//}
// Update the modifier timer.
modiferCurrentTime += (float) gameTime.ElapsedGameTime.TotalSeconds;
setColor(Color.Blue);//change the color to match the special effect
}
Hope this helps!
You need to store the start time, or the last time that the action was carried out. Then during each update compare the elapsed time to the stored time. If 3 seconds have passed then perform the action, store the current time and repeat the process.
I do not know monogame, but if I were doing this in one of my C# applications, I would use a timer, and pass in anything that the timer would need to modify.
There is good info here https://learn.microsoft.com/en-us/dotnet/api/system.timers.timer?view=netframework-4.8 and I stole a bit of code from here and modified it as an example to demonstrate my idea. I extended the System.Timer to allow it to run for a duration and stop itself. You can set the frequency and duration and forget about it. Assuming that you are able to update this information from a timer.
class Program
{
private static FixedDurationTimer aTimer;
static void Main(string[] args)
{
// Create a timer and set a two second interval.
aTimer = new FixedDurationTimer();
aTimer.Interval = 2000;
// Hook up the Elapsed event for the timer.
aTimer.Elapsed += OnTimedEvent;
// Start the timer
aTimer.StartWithDuration(TimeSpan.FromSeconds(15));
Console.WriteLine("Press the Enter key to exit the program at any time... ");
Console.ReadLine();
}
private static void OnTimedEvent(Object source, System.Timers.ElapsedEventArgs e)
{
FixedDurationTimer timer = source as FixedDurationTimer;
if (timer.Enabled)
{
Console.WriteLine("The Elapsed event was raised at {0}", e.SignalTime);
}
}
public class FixedDurationTimer : System.Timers.Timer
{
public TimeSpan Duration { get; set; }
private Stopwatch _stopwatch;
public void StartWithDuration(TimeSpan duration)
{
Duration = duration;
_stopwatch = new Stopwatch();
Start();
_stopwatch.Start();
}
public FixedDurationTimer()
{
Elapsed += StopWhenDurationIsReached;
}
private void StopWhenDurationIsReached(object sender, ElapsedEventArgs e)
{
if (_stopwatch != null && Duration != null)
{
if (_stopwatch.Elapsed > Duration)
{
Console.WriteLine("Duration has been met, stopping");
Stop();
}
}
}
}
}
You could see examples of how to pass objects into the timer here (#JaredPar's example) How do I pass an object into a timer event?
string theString = ...;
timer.Elapsed += (sender, e) => MyElapsedMethod(sender, e, theString);
static void MyElapsedMethod(object sender, ElapsedEventArgs e, string theString) {
...
}
One way to do this would be to use coroutines. MonoGame does not have built-in support for them like other game engines, but they are not too complicated to implement yourself. You need some knowledge of the yield keyword and enumerators to understand them, but once abstracted away they make your game code way easier to write and understand.
Here's an example of what your gameplay logic would look using a Coroutine system like the one described below:
public void Attack(Enemy enemyAttacking)
{
if (enemyAttacking.Type == "OneParticularEnemy")
{
StartCoroutine(RunDamageOverTimeAttack());
}
}
// This coroutine starts a second coroutine that applies damage over time, it
// then waits 15 seconds before terminating the second coroutine.
public IEnumerator RunDamageOverTimeAttack()
{
var cr = StartCoroutine(ApplyDamageOverTime());
yield return 15000; // in milleseconds (ms), i.e. 15000 ms is 15 seconds
cr.IsFinished = true;
}
// This coroutine applies the damage every 3 seconds until the coroutine is finished
public IEnumerator ApplyDamageOverTime()
{
while (true)
{
ApplyDamageToPlayer();
yield return 3000;
}
}
The code reads very close to the way you described the actual problem you're trying to solve. Now for the coroutine system...
The StartCouroutine method creates a Coroutine class instance and stores it. During the Update step of the game loop you iterate through the coroutines and update them, providing gameTime to calculate when the next step of the method should run. Each step executes the code in the routine until a yield is found OR until the method ends naturally. Once the coroutine is finished you clear them out. This logic looks something like this:
private List<Coroutine> coroutines = new List<Coroutine>();
public Coroutine StartCoroutine(IEnumerator routine)
{
var cr = new Coroutine(routine);
couroutines.Add(cr);
return cr;
}
public void UpdateCoroutines(GameTime gameTime)
{
// copied in case list is modified during coroutine updates
var coroutinesToUpdate = coroutines.ToArray();
foreach (coroutine in coroutinesToUpdate)
coroutine.Update(gameTime);
coroutines.RemoveAll(c => c.IsFinished);
}
public void Update(GameTime gameTime)
{
// normal update logic that would invoke Attack(), then...
UpdateCoroutines(gameTime);
}
A Coroutine class is responsible for tracking the time remaining between steps of the routine, and tracking when the routine is finished. It looks something like this:
public class Coroutine
{
private IEnumerator routine;
private double? wait;
public Coroutine(IEnumerator routine)
{
this.routine = routine;
}
public bool IsFinished { get; set; }
public void Update(GameTime gameTime)
{
if (IsFinished) return;
if (wait.HasValue)
{
var timeRemaining = wait.Value - gameTime.ElapsedGameTime.TotalMilliseconds;
wait = timeRemaining < 0 ? null : timeRemaining;
// If wait has a value we still have time to burn before the
// the next increment, so we return here.
if (wait.HasValue) return;
}
if (!routine.MoveNext())
{
IsFinished= true;
}
else
{
wait = routine.Current as double?;
}
}
}
This may seem considerably more complex than other solutions provided here, and it may be overkill, but Coroutines allow you to forgo tracking a bunch of state in tracking variables, making complex scenarios easier to follow and cleaner to read. For example, here's a arrow spawning strategy I used Coroutines for in Ludum Dare 37. It spawns 3 arrows 600 milleseconds apart with a 3 second wait between them: https://github.com/srakowski/LD37/blob/477cf515d599eba7c4b55c3f57952865d894f741/src/LD37/GameObjects/BurstArrowSpawnBehavior.cs
If you'd like more social proof of the value of Coroutines take a look at Unity. Unity is one of the more popular game engines, and it has Coroutine support. They describe a scenario where it is useful in their documentation: https://docs.unity3d.com/Manual/Coroutines.html.
I use this for my game :
Public Async Function DelayTask(Time As Double) As Threading.Tasks.Task
Await Threading.Tasks.Task.Delay(TimeSpan.FromSeconds(Time))
End Function
Converted to C# :
public async System.Threading.Tasks.Task DelayTask(double Time)
{
await System.Threading.Tasks.Task.Delay(TimeSpan.FromSeconds(Time));
}
You would use it like this in an Async Function :
Await DelayTask(1.5);
The number is in seconds, you can change this by changing the TimeSpan.whateverformat.
Considering that you'll have various things that affect your stats maybe you're better off at having an update subroutine in your Stats class that will check a list of effects that are scheduled to update after one point in time.
This would be better for performance than having each effect relying on its own thread.
I have a problem in Unity. I need a countdown in my project. But this countdown will count 2 times. For example, 3 seconds a job will be done, 2 seconds another job will be done and these works will continue.
Couroutines (and more Coroutines)
Coroutines do exactly the thing you want to do.
private IEnumerator Countdown2() {
while(true) {
yield return new WaitForSeconds(2); //wait 2 seconds
//do thing
}
}
private IEnumerator Countdown3() {
while(true) {
yield return new WaitForSeconds(3); //wait 3 seconds
//do other thing
}
}
You then start them off by calling in your Start() method (or where ever):
void Start() {
StartCoroutine(Countdown2());
StartCoroutine(Countdown3());
}
Note that both countdown methods will do their thing forever unless one of three things happens:
StopCoroutine(...) is called, passing in the reference returned by StartCoroutine
The countdown function itself returns (which will not happen unless it breaks out of the infinite while(true) loop)
The coundown function itself calls yield break
Also note that in the event both coroutines should resume at the same time (e.g. at 6 seconds) coroutine 2 will execute first (as it was started first), unless some other effect intervenes (e.g. one of the loops has another yield instruction, one of the loops is terminated, etc).
Based in the example of use you gave where an object first turn right, then turns left:
consider an object. When the program start, turn right for 3 seconds,
after turn left for 1 second. they will repeat continuously. two
counters will follow each other.
Below I give you two timers which are executed sequentially: first one will last 3 seconds and the second one other one 2 seconds. Once one counter finish the other one will starts, this will be repeated in an infinite loop.
float counterTask1 = 3f;
float counterTask2 = 2f;
float timer;
bool chooseTask;
void Start(){
//Initialize timer with value 0
timer = 0;
chooseTask = 1;
}
void Update ()
{
// Add the time since Update was last called to the timer.
timer += Time.deltaTime;
// This will trigger an action every 2 seconds
if(chooseTask && timer >= counterTask1)
{
timer -= counterTask1;
chooseTask = 0;
#Do Task 1 Here
}else if(!chooseTask && timer >= counterTask2)
{
timer -= counterTask2;
chooseTask = 1;
#Do Task 2 Here
}
}
I am not sure if this is what you were looking for. In any case there are lot of questions already asked about timers in Unity. Check some of them:
https://stackoverflow.com/search?q=timer+in+Unity
I'm going to make a rhythm game.
music playing time is variable which change by music playing in millisecond
List is a float list which have the time(float second) I filled , let me to access it and use for compare with music playing time to instance object .
when list time = music time then instant object.
but sometimes will missing(not found value in list but value is really exist in list).
Here is my assumption.
I think List.indexOf performance are not good and thread have some delay.
Music playing time change every millisecond , a little delay from unity or others will cause missing(doesn't entering if statement)
I don't know if it's correct.
Could any one help me.
Here is my code.
IEnumerator Execute(MethodDelegate Start,MethodDelegate Stop)
{
while (true) {
int res = result.IndexOf ((float)System.Math.Round (GameObject.Find ("Music").GetComponent<AudioSource> ().time, DigitalAdjust)-TimeAdjust);
if (res!=-1) {
if (res == result.Count-2) {
Stop.Invoke ();
print ("CoroutineStop");
StopCoroutine (_Execute);
}
//execute
num=Positoin[res];
print (res);
Start.Invoke();
}
yield return null;
}
}
Thanks.
Chances are that you are correct. You might miss some if statement because you don't match the millisecond exactly. Here are some things that could help:
It you game reaches 60 FPS (the usual rate for a smooth rendering), each frame will take around 16 milliseconds. If you have events that must trigger at exact milliseconds you will miss some because your Execute function calls are separated by around 16ms (a coroutine is called once per frame).
A solution to this is remember the last time the Execute function was called and check everything in between:
private float _lastTime;
IEnumerator Execute(MethodDelegate Start,MethodDelegate Stop)
{
while (true) {
// here your code must check all the event between _lastTime and Time.time
var lastTimeMilliseconds = (int)(_lastTime*1000);
var currentTimeMilliseconds = (int)(Time.time*1000);
for(int time = lastTimeMilliseconds+1; time <= currentTimeMillisedons; time++)
{
// let's say last frame was at time 1000ms
// This frame occurs at time 1016ms
// we have to check your list for events that have occured since last frame (at time 1001, 1002, 1003, ...)
// so we have a for loop starting at 1001 until 1016 and check the list
int res = result.IndexOf ((float)System.Math.Round (time, DigitalAdjust)-TimeAdjust);
if (res!=-1)
{
if (res == result.Count-2)
{
Stop.Invoke ();
print ("CoroutineStop");
StopCoroutine (_Execute);
}
//execute
num=Positoin[res];
print (res);
Start.Invoke();
}
}
// At the end, remember the time:
_lastTime = Time.time;
yield return null;
}
}
Check the Time class, you also have access to Time.deltaTime to know the time elapsed between two frames, if that helps.
EDIT:
As you requested in comment, I added some bit of code from your example to explain better this idea. Note that I don't know what your variables do so you will have to adapt. For instance, Time.time gives that time since app start. You will likely need to adapt this use the time since you started the audio
Another important thing:
GameObject.Find must look in all the objects. It is really slow and shouldn't be used every frame
GetComponent looks for all your scripts and is slow as well. It shouldn't be used every frame.
Instead, do this:
private AudioSource _audioSource;
private void Start()
{
_audioSource = GameObject.Find ("Music").GetComponent<AudioSource> ();
}
This will retrieve the source only once. Then in your code you simply call
_audioSource.time;
This question already has answers here:
Coroutines and while loop
(3 answers)
Closed 6 years ago.
I wish to move an object over time using coroutines. I wanted to get an object from point A to point B over say 2 seconds. To achieve this I used the following code:
IEnumerator _MoveObjectBySpeed()
{
while (TheCoroutine_CanRun)
{
if(myObject.transform.position.y <= UpperBoundary.position.y)
{
myObject.transform.position = new Vector3(myObject.transform.position.x,
myObject.transform.position.y + Step, myObject.transform.position.z);
}
yield return new WaitForSeconds(_smoothness);
}
TheCoroutine_CanRun = true;
moveAlreadyStarted = false;
}
and the Step is calculated like
private void CalculateSpeed()
{
Step = _smoothness * allDistance / timeToReachTop;
}
where allDistance is the distance between the bottom and the top boundary.
_smoothness is a fix value. The thing is, the bigger I get this value, the more accurate the time gets to get from bottom to up. Note that a small value here means smoother movement. This smoothness is the time the coroutine waits in between moving the myObject.
The time is measured like this:
void FixedUpdate()
{
DEBUG_TIMER();
}
#region DEBUG TIME
public float timer = 0.0f;
bool allowed = false;
public void DEBUG_TIMER()
{
if (Input.GetButtonDown("Jump"))
{
StartTimer();
}
if (myObject.transform.position.y >= UpperBoundary.position.y)
{
StopTimer();
Debug.Log(timer.ToString());
//timer = 0.0f;
}
if (allowed)
{
timer += Time.fixedDeltaTime;
}
}
void StartTimer()
{
timer = 0;
allowed = true;
}
void StopTimer()
{
allowed = false;
}
#endregion
The results were:
When I wanted the object to reach the top under 1 second and set the _smoothness to 0.01, the time the myObject took to get to the top was 1.67 seconds. When _smoothness was 0.2s, the time to actually reach the top was 1.04s.
So why is this so inaccurate and how to make it work fine?
This smoothness is the time the coroutine waits in between moving the myObject
The mistake you're making is assuming that a co-routine waits the perfect time before executing. Rather, it probably executes on the next frame after the timeout has finished.
Assuming you want smooth motion, you want to move the object every frame (e.g. in Update or co-routine that uses 'yield return null').
Note: Each frame may take a different duration (consider 144fps vs 15fps), and you can discover this in Time.deltaTime . https://docs.unity3d.com/520/Documentation/ScriptReference/Time-deltaTime.html
basically I have a countdown timer with in my game. What I basically want to do is continue my timer playing even if I close out of my application and play it again, I want the timer to continue counting down. Kind of like clash of clan when the counter is still work when the app is closed. For example: if I exit the game and the timer is on 1:30 (1 minute, 30 seconds). Then if I restart the game 30 seconds later, the timer should show 1:00 (1 minute, 0 seconds) Or if I close the game 30 seconds later, the timer should show 1:00 (1 minute, 0 seconds)
So far this is as far as I've got:
public class TimeManager: MonoBehaviour {
public Text timer;
float minutes = 5;
float seconds = 0;
float miliseconds = 0;
public int curHealth;
public int maxHealth = 3;
void Start ()
{
curHealth = maxHealth;
}
void Awake ()
{
if (PlayerPrefs.HasKey("TimeOnExit"))
{
var x = DateTime.Now - DateTime.Parse(PlayerPrefs.GetString("TimeOnExit"));
PlayerPrefs.DeleteKey("TimeOnExit");
}
}
void Update(){
if(miliseconds <= 0){
if(seconds <= 0){
minutes--;
seconds = 59;
}
else if(seconds >= 0){
seconds--;
}
miliseconds = 100;
}
miliseconds -= Time.deltaTime * 100;
//Debug.Log(string.Format("{0}:{1}:{2}", minutes, seconds, (int)miliseconds));
timer.text = string.Format("{0}:{1}", minutes, seconds, (int)miliseconds);
}
private void OnApplicationQuit()
{
PlayerPrefs.SetString("TimeOnExit", DateTime.Now.ToShortTimeString());
}
}
TimeOnExit is good but you also need to store either the target time or remaining time of each countdown timer (in case you have more than one - e.g., multiple Woodcutters producing Wood).
Then, in the startup code for the game, you need to run through your game loop once per second that the user was absent. If a timer would have triggered an event, you trigger that event in this loop (Woodcutter adds 1 Wood to Barn).
If you've ever seen a game with a progress bar at startup, there's a good chance that's part of what's going on.
Depending on your game and how long the user was gone, you might need to simulate multiple iterations of the same timer (Woodcutter adds 1 Wood to Barn and then starts working on the next Wood - over and over until the time is caught up).
Finally, you would need to re-instantiate the actual timers that existed and need to continue during live play. Be sure to figure out where they are within the 5-minute loop. If all of the timers are off by a few seconds, nobody will notice but if a restart means they all line up, that will seem strange.
Once you get sophisticated, there are techniques that are faster than a second-by-second (or whatever time period makes sense for your game) simulation of the time that went by but that's a good place to start.
Here's a hint: If you store an array of the target times and what event should trigger, it could be easy enough to cycle through the ones that are in the past and trigger them in order. Be sure to insert new target times into the array for repeating events.
Good Luck!