Один из способов сделать это - использовать сопрограммы.MonoGame не имеет встроенной поддержки для них, как другие игровые движки, но они не слишком сложны для реализации самостоятельно.Чтобы понять их, вам нужны некоторые знания о ключевом слове yield
и перечислителях, но как только они отвлечутся, они упростят написание и понимание вашего игрового кода.
Вот пример того, как ваша логика геймплея будет выглядеть при использованииСистема сопрограмм, подобная описанной ниже:
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 вычислить время запуска следующего шага метода.Каждый шаг выполняет код в подпрограмме, пока не будет найден выход ИЛИ, пока метод не завершится естественным образом.Как только сопрограмма закончена, вы очищаете их.Эта логика выглядит примерно так:
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);
}
Класс Coroutine отвечает за отслеживание оставшегося времени между шагами процедуры и отслеживание ее завершения.Это выглядит примерно так:
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?;
}
}
}
Это может показаться значительно более сложным, чем другие решения, представленные здесь, и это может быть излишним, но сопрограммы позволяют вам отказаться от отслеживания группы состояний в отслеживании переменных, что усложняет задачу.Сценарии легче следовать и чище читать.Например, вот стратегия порождения стрел, для которой я использовал Coroutines в Ludum Dare 37. Она порождает 3 стрелы с интервалом в 600 милсекунд с интервалом в 3 секунды между ними: https://github.com/srakowski/LD37/blob/477cf515d599eba7c4b55c3f57952865d894f741/src/LD37/GameObjects/BurstArrowSpawnBehavior.cs
Если вы хотите больше социального доказательстваценность сопрограмм взглянуть на Unity.Unity - один из самых популярных игровых движков, и он имеет поддержку Coroutine.Они описывают сценарий, где это полезно в их документации: https://docs.unity3d.com/Manual/Coroutines.html.