Monogame C#计时器(每3秒执行15秒)

时间:2019-05-24 13:25:48

标签: c# monogame

我正在尝试创建一个计时器,例如,在15秒钟内每3秒钟执行一次操作。

我尝试使用gameTime.ElapsedGameTime.TotalSeconds和循环,但不幸的是,它不起作用。

我有一个Attack()函数,当敌人攻击它时,它会减少玩家的统计数据。我希望在有一个特定敌人的情况下,此功能在指定的时间段内会减少玩家的生命值,例如每3秒一次。我想应该在更新功能中完成此操作以访问gameTime,不幸的是,我不知道该怎么做。

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);
}

5 个答案:

答案 0 :(得分:1)

我将使其简化,因此您需要修改我的代码以实现所需的结果。

我最好的猜测是,当怪物击中玩家时,您希望具有特殊的效果。

首先,您需要检查怪物是否真的击中了玩家(如果检测到碰撞):

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
}

在您的Player类中,您需要:

public float ModifierDuration {
 get {
  return modifierDuration;
 }
 set {
  modifierDuration = value;
  modiferCurrentTime = 0;
 }
}

然后在Update类的Player方法中:

// 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
}

希望这会有所帮助!

答案 1 :(得分:0)

您需要存储开始时间或上一次执行操作的时间。然后,在每次更新期间,将经过的时间与存储的时间进行比较。如果3秒钟过去了,请执行该操作,存储当前时间并重复该过程。

答案 2 :(得分:0)

我不知道单人游戏,但是如果我在一个C#应用程序中这样做,我将使用一个计时器,并传入计时器需要修改的任何内容。

这里https://docs.microsoft.com/en-us/dotnet/api/system.timers.timer?view=netframework-4.8有很好的信息,我从这里窃取了一些代码,并对其进行了修改,以演示我的想法。我扩展了System.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();
                }
            }
        }
    }
}

您可以在此处看到如何将对象传递到计时器的示例(@JaredPar的示例)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) {
...
}

答案 3 :(得分:0)

执行此操作的一种方法是使用协程。 MonoGame不像其他游戏引擎那样对它们进行内置支持,但是它们实现起来并不太复杂。您需要了解yield关键字和枚举器的知识才能理解它们,但是一旦将它们抽象化,它们将使您的游戏代码更易于编写和理解。

这是一个使用Coroutine系统的游戏逻辑外观的示例,如下所述:

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;
    }
}

代码的读取方式与您描述要解决的实际问题的方式非常接近。现在是协程系统...

StartCouroutine方法创建一个Coroutine类实例并将其存储。在游戏循环的“更新”步骤中,您遍历协程并更新它们,并提供gameTime来计算何时应运行该方法的下一步。每个步骤都会在例程中执行代码,直到找到一个yield为止,或者直到该方法自然结束为止。一旦协程完成,您将其清除。这种逻辑看起来像这样:

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);
}

协程类负责跟踪例程步骤之间的剩余时间,并跟踪例程何时结束。看起来像这样:

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?;
        }
    }
}

这似乎比这里提供的其他解决方案复杂得多,并且可能会过大,但是协程允许您在跟踪变量时放弃跟踪一堆状态,从而使复杂的场景更易于理解和阅读。例如,这是我在Ludum Dare 37中使用协程的箭道生成策略。它生成3箭,相距600毫秒,它们之间的等待时间为3秒:https://github.com/srakowski/LD37/blob/477cf515d599eba7c4b55c3f57952865d894f741/src/LD37/GameObjects/BurstArrowSpawnBehavior.cs

如果您想获得更多有关协程价值的社会证据,请查看Unity。 Unity是最受欢迎的游戏引擎之一,它具有协程支持。他们在文档中描述了一种有用的场景:https://docs.unity3d.com/Manual/Coroutines.html

答案 4 :(得分:0)

我在游戏中使用它:

Public Async Function DelayTask(Time As Double) As Threading.Tasks.Task
    Await Threading.Tasks.Task.Delay(TimeSpan.FromSeconds(Time))
End Function

已转换为C#:

public async System.Threading.Tasks.Task DelayTask(double Time)
{
    await System.Threading.Tasks.Task.Delay(TimeSpan.FromSeconds(Time));
}

您将在异步功能中像这样使用它:

Await DelayTask(1.5);

数字以秒为单位,您可以通过更改TimeSpan.evereverformat来更改此值。

考虑到您会有各种影响统计数据的事情,也许您最好在Stats类中有一个update子例程,该子例程将检查计划在某个时间点后更新的效果列表。 对于性能而言,这比使每种效果都依赖于其自己的线程更好。