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.
Related
So I'm trying to make a timer that will ticks down from 30 seconds to 0 in monogame but I don't understand how I can do it. I'm very new to programing. I'm trying to put it into my enum so when the timer hit 0 it will change state from play to gameover.
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
switch (currentGameState)
{
case Gamestate.Start:
timeSinceLastFrame += gameTime.ElapsedGameTime.TotalSeconds;
if (timeSinceLastFrame >= timeBetweenFrame)
{
timeSinceLastFrame -= timeBetweenFrame;
currentFrame.X++;
if(currentFrame.X >= sheetSize.X)
{
currentFrame.X = 0;
currentFrame.Y++;
if (currentFrame.Y>=sheetSize.Y)
{
currentFrame.Y = 0;
}
}
}
posStone.Y = posStone.Y + 1;
if (Keyboard.GetState().IsKeyDown(Keys.Enter))
{
currentGameState = Gamestate.Play;
}
break;
case Gamestate.Play:
break;
case Gamestate.GameOver:
break;
}
}
Method 1
This approach I used in a game where I was storing a future DateTime representing the world game time with a future starting point and updated on every Unity physics frame in my Monobehaviour script. However the general idea can be used in your scenario. If you don't need to store the game time between saves then this might be overkill. Read Method 2 in that case.
You store an end time and a reference time (game time) for the game's current time. You'd want to store both as a class property.
DateTime referenceTime;
DateTime endTime;
Right before you set currentGameState to Gamestate.Play you would want to set these values
referenceTime = DateTime.Now; //Would be assigned from a global GameTime class object or your own custom game time
endTime = referenceTime.AddSeconds(30);
Then you would update your reference time on every physics frame. I used Millisecond instead of Seconds for accuracy (GameTime Documentation) since in most game engines I've worked with frames updates tend to happen in a fraction of a second. This would be placed under Gamestate.Play case branch
referenceTime.AddMilliseconds(gameTime.ElapsedGameTime.TotalMilliseconds);
Then you also would need to do the check in the same case branch (probably after the last code snippet).
if (referenceTime > endTime) {
currentGameState = Gamestate.GameOver;
}
Method 2
GameTime class has an alternative member totalGameTime which is also a System.TimeSpan struct value (GameTime Documentation, TimeSpan Documentation). So this means another option is you can used the total number of seconds from the beginning as the game time. The reference value would be again stored as a class member it is now an int this time since we will store the number of seconds since the game started.
int referenceTime;
You would set the reference time right before you switch currentGameState to Gamestate.Play . There are other ways of doing this but this is probably the simplest.
referenceTime = gameTime.totalGameTime.TotalSeconds;
Then you would add a check in your Gamestate.Play case statement as such. If you plan on repeatedly using gameTime.totalGameTime.TotalSeconds then you might want to store it in another variable for legibility.
if (gameTime.totalGameTime.TotalSeconds > referenceTime + 30) {
currentGameState = Gamestate.GameOver;
}
Have you looked into System.Timer class?
An example of the class is below so you can copy and paste into LinqPad to run and experiment with.
void Main()
{
/* What this does is raise an event every 5 seconds, according to value set in the interval
*/
var timer = new System.Timers.Timer(5000);
timer.Elapsed += OnTimedEvent;
timer.AutoReset = true; // Whether or not the event should be repeated
timer.Enabled = true; // Starts the timer
}
void OnTimedEvent(object sender, ElapsedEventArgs e)
{
Console.WriteLine("The Elapsed event was raised at {0:HH:mm:ss.fff}",
e.SignalTime);
}
Trying to repeat the function function OnAttack() continuously while a button is being held down.
Basically I'm looking for an equivalent to Update() { GetKeyDown() {//code }} But with the input system.
Edit: using a joystick, cant tell what button is being pressed.
Okay I solved it by using "press" in the interactions and giving that The trigger behavior "Press and release", then did
bool held = false;
Update()
{
if(held)
{
//animation
}
else if(!held)
{
//idle animation
}
}
OnAttack() {
held = !held;
}
This way if I press the button held goes to true so it repeats the animation every frame, letting go makes "held" untrue and does the idle animation
Essentially, the function you assign to the button will be triggered twice per button press, once when it is pressed (performed), and once when it is released (canceled). You can pass in this context at the beginning of your function, just make sure you are using the library seen at the top of this script.
Now you can toggle a bool on and off stating whether or not the button is pressed, then perform actions during update dependent on the state of the bool
using static UnityEngine.InputSystem.InputAction;
bool held = false;
Update()
{
if(held)
{
//Do hold action like shooting or whatever
}
else if(!held)
{
//do alternatice action. Not Else if required if no alternative action
}
}
//switch the status of held based on whether the button is being pressed or released. OnAttack is called every time the button is pressed and every time it is released, the if statements are what determine which of those two is currently happening.
OnAttack(CallbackContext ctx) {
if (ctx.performed)
held= true;
if (ctx.canceled)
held= false;
}
This is paraphrasing a solution I created for a click to move arpg mechanic.
using System.Threading.Tasks;
using UnityEngine;
[SerializeField] private InputAction pointerClickAction;
private bool pointerHeld;
void Start()
{
pointerClickAction.canceled += ClickMouseMove;
pointerClickAction.started += PointerHoldBegin;
pointerClickAction.performed += ClickMouseMove;
pointerClickAction.canceled += PointerHoldEnd;
}
private void OnEnable()
{
pointerClickAction.Enable();
pointerPositionAction.Enable();
}
private void OnDisable()
{
pointerClickAction.Disable();
pointerPositionAction.Disable();
}
public async void ClickMouseMove(InputAction.CallbackContext context)
{
while (pointerHeld)
{
DoSomething();
await Task.Delay(500);
}
}
public void PointerHoldBegin(InputAction.CallbackContext context)
{
pointerHeld = true;
}
public void PointerHoldEnd(InputAction.CallbackContext context)
{
pointerHeld = false;
}
public void DoSomething()
{
//Your Code
}
In Task.Delay() you can insert your own polling rate in milliseconds, using Task.Yield() seems to be faster than Update so I don't recommend that, you should poll at the minimum the same delay as your physics/fixed update, having a higher delay gives a performance boost if you don't need a high amount of repetitions per loop. I set mine to 500 since I don't need my character to plot its navigation that often. In regards to TC, you would set the delay to something sensible e.g the attack's animation length, or whatever the delay rate would be for how many attacks can be performed per second.
If you want your project to scale you might want to avoid as much as possible assertions(i.e if functions) in your Update/FixedUpdate/LateUpdate functions as they are executed constantly. I recommand you to read this article about coroutines https://gamedevbeginner.com/coroutines-in-unity-when-and-how-to-use-them/
You can build coroutines which act as local update() functions which are executed only when needed. This will lead you to a better organization of your code and might boost performance in some cases.
For exemple in your case you could use something like this.
bool held = false;
Update()
{
/* Whatever you want but the least assertion possible */
}
IEnumerator RenderHeldAnimation()
{
while (held)
{
// held animation
yield return new WaitForFixedUpdate(); /* Will block until next fixed frame right after FixedUpdate() function */
// yield return null /* Will block until next next frame right after Update() function */
}
}
IEnumerator RenderIdleAnimation()
{
while (!held)
{
// idle animation
yield return new WaitForFixedUpdate(); /* Will block until next fixed frame right after FixedUpdate() function */
// yield return null /* Will block until next next frame right after Update() function */
}
}
OnAttack() {
held = !held;
if (held) {
StartCoroutine(RenderHeldAnimation());
} else {
StartCoroutine(RenderIdleAnimation());
}
}
As mentioned in another answer, the context.canceled is not called when using the Press And Release interaction. As a follow up for documentation purposes as this is a top Google result, to correctly use a bool held without doing a blind toggle (held = !held) which may end up with drift, you can access context.control.IsPressed() like the following:
void OnAttack(CallbackContext context)
{
held = context.control.IsPressed();
}
I encountered the same issue and this was the method that seemed to work for me
private float _moveSpeed = 3f;
private float _moveDirection;
private void Update()
{
transform.Translate(_moveSpeed * _moveDirection * Time.deltaTime * transform.forward);
}
public void Move(InputAction.CallbackContext ctx)
{
_moveDirection = ctx.ReadValue<float>();
}
For some odd reason,the hold interaction works properly in reading the input, but I still need the update function to implement the actual logic.
Can't really complain though, it works. Although I'd love to know why it happens this way.
Hopefully this can be of help to someone.
You can use a timer for that purpose, in combination with events KeyUp and KeyDown.
Please look at the following link. It is pretty much similar to your problem.
Link
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
I have the a script called Timer.cs. This script is connected to some GUI Text, which displays the amount of time remaining in the game.
Also attached to this script is an Audio Source with my desired sound selected. When the clock reaches zero, the text changes to say "GAME OVER!" and the character controls lock up; however, the sound does not play.
All other instances of audio.Play() in my scene are working fine, and when I set the Audio Source to "Play On Awake", it plays without a problem. What could be the problem?
Using UnityEngine;
using System.Collections;
public class Timer : MonoBehaviour {
public float timer = 300; // set duration time in seconds in the Inspector
public static int sound = 1;
public static int go = 1;
bool isFinishedLevel = false; // while this is false, timer counts down
void Start(){
PlayerController.speed = 8;
PlayerController.jumpHeight = 12;
}
void Update (){
if (!isFinishedLevel) // has the level been completed
{
timer -= Time.deltaTime; // I need timer which from a particular time goes to zero
}
if (timer > 0)
{
guiText.text = timer.ToString();
}
else
{
guiText.text = "GAME OVER!"; // when it goes to the end-0,game ends (shows time text over...)
audio.Play();
int getspeed = PlayerController.speed;
PlayerController.speed = 0;
int getjumpHeight = PlayerController.jumpHeight;
PlayerController.jumpHeight = 0;
}
if (Input.GetKeyDown("r")) // And then i can restart game: pressing restart.
{
Application.LoadLevel(Application.loadedLevel); // reload the same level
}
}
}
Given that you are calling it as part of your Update routine, I'd have to guess that the problem is you calling it repeatedly. I.e. you're calling it every frame as long as timer <= 0.
You shouldn't call Play() more than once. Or at least not again while it is playing. A simple fix would be something along the lines of
if(!audio.isPlaying)
{
audio.Play();
}
See if that solves your problem, and then you can take it from there.
I had error using audio.Play(); and used following it fixed the error for me
GetComponent<AudioSource>().Play();
I am trying to implement a simple counter in my XNA game. Thought this would be simple enough. I have the following code:
elapsed = gameTime.ElapsedGameTime.TotalMilliseconds;
timer -= (int)elapsed;
if (timer <= 0)
{
timer = 10; //Reset Timer
}
But elapsed never changes from 0.0. Am I missing something obvious here? I suspect I am. I have gameTime declared at the top and initialised as usual.
As asked, here is a bit more code:
public class Game1 : Microsoft.Xna.Framework.Game
{
private GameTime zombieTime;
public Game1()
{
zombieTime = new GameTime();
// Other (unrelated) stuff here
}
protected void AddZombie()
{
elapsed = zombieTime.ElapsedGameTime.TotalMilliseconds;
timer -= (int)elapsed;
if (timer <= 0)
{
timer = 10; //Reset Timer
Zombie zombie = new Zombie(ScreenWidth, ScreenHeight, random);
zombie.LoadContent(this.Content, "ZombieSprites/ZombieLeft1");
zombies.Insert(0, zombie);
}
}
protected void Update()
{
AddZombie();
// Other game update stuff here
}
}
I am sorry, I believed the original code snippet would have been enough. I read some pages online where people posted examples of a timer and used the method I have used above. I understand some of the comments made here about the update going fast enough so that elapsed time will always be 0.
You're not using the correct GameTime. zombieTime is never updated by anything so it will always be zero'd out. The GameTime you want to use is passed into the Update() function already for you.
The correct way to do it would be like this:
protected void AddZombie(GameTime gameTime)
{
float elapsed = gameTime.ElapsedGameTime.TotalMilliseconds;
timer -= (int)elapsed;
if (timer <= 0)
{
timer = 10; //Reset Timer
// Rest of stuff goes here
}
}
protected void Update(GameTime gameTime)
{
AddZombie(gameTime);
}
http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.gametime_members.aspx
Elapsed game time is the time since the LAST update. TOTAL game time in the cumulative game time...
So, unless you're doing a lot of work you're not showing, you're gonna be taking no time at all to update, so a value of 0 is quite sensible
try shoving a sleep statement in there and see if elapsed time goes up.