XNA: предотвращение возврата метода - PullRequest
1 голос
/ 17 марта 2009

Я хочу, чтобы метод в DrawableGameComponent классе не возвращался, пока не будет выполнено определенное условие

Скажем, у меня есть этот класс (фрагмент из DrawableGameComponent класса):

public override void Update(GameTime gameTime)
        {
            if (moving && pixFromLastMove <= distanceToMove)
            {
                position += velocity;
                pixFromLastMove += velocity.Length();
            }
            else
            {
                moving = false;
            }
            if (rotating)
            {
                rotation += 0.1f;
                var cRotation = MathHelper.Clamp(rotation, -MathHelper.PiOver2, angleBeforeRotation + degToRotate);
                if (cRotation != rotation)
                {
                    rotation = cRotation;
                    angleBeforeRotation = rotation;
                    rotating = false;
                }
            }
            base.Update(gameTime);
        }

public void Ahead(int pix)
        {
            moving = true;
            distanceToMove = pix;
            pixFromLastMove = 0;
            velocity = new Vector2((float) Math.Cos(rotation), (float) Math.Sin(rotation))*5.0f;

            //DO NOT RETURN UNTIL THIS ROBOT HAS MOVED TO ITS DESTINATION
            }

public void TurnLeft(int deg)
        {
            rotating = true;
            degToRotate = MathHelper.ToRadians(deg);
            angleBeforeRotation = rotation;

            //DO NOT RETURN UNTIL THIS ROBOT HAS BEEN FULLY ROTATED
        }

Этот класс рисуется (Draw()) в основном потоке (потому что этот drawablegamecomponent выполняется в отдельном потоке), а также в основном потоке у меня есть список команд, которые я хочу выполнить по порядку .. .но в настоящее время, поскольку метод Ahead возвращается сразу после присвоения значения velocity, методы будут выполняться почти одновременно, что, в свою очередь, просто выполняет все анимации одновременно.

Так что, по вашему мнению, мне следует сделать, чтобы методы, являющиеся командами (Ahead, TurnLeft и т. Д.), Не возвращались до выполнения определенного условия?

Ответы [ 4 ]

3 голосов
/ 17 марта 2009

Вам необходимо создать какой-то конечный автомат для вашего метода Update (). например,

public override void Update() { 
    if (movingRobot) {
        OnlyUpdateRobotPosition();
    }
    else {
        DoStuffPerhapsIncludingStartingRobotMove();
    }
}

Или я пропускаю вопрос?

2 голосов
/ 27 марта 2009

Я согласен с Pop Catalin: вероятно, лучше не блокировать эти командные функции. Я думаю, вы могли бы улучшить свою игру, немного подумав о дизайне. Позвольте мне высказать некоторые соображения о том, как вы могли бы улучшить свой дизайн.

Во-первых, похоже, что проблема, которую вы описываете, заключается в том, что вы хотите отправить множество команд перемещения в определенном порядке игровому компоненту и заставить его выполнять эти команды в определенном порядке. Как вы заметили, существует разница во времени, которое требуется компьютеру для выполнения вычислений (для скорости или вращения), и времени, которое требуется компоненту для фактического выполнения действия (перемещения или вращения).

Проблема с блокировкой во время вычислений (Ahead, TurnLeft и т. Д.) Заключается в том, что цикл обновления, вызывающий эту функцию, не может обновлять любые другие компоненты. Это может работать нормально, если беспокоиться только об одном компоненте, но обычно это не так в большинстве игр.

Теперь по хорошей части: как мы можем решить эту проблему? Я думаю, что у Эриккалена правильная идея, но я бы пошел дальше. Похоже, что игровой компонент - это некая сущность, которая будет перемещаться, так почему бы не дать ему очередь действий? Простая реализация состоит в том, чтобы ваша вызывающая функция вызывала что-то вроде:

gameComponent.queueAction( (MethodInvoker)delegate() 
                           { gameComponent.Ahead(10); });

Ваша функция queueAction может выглядеть следующим образом:

public void queueAction(MethodInvoker action)
{
    queue.Enqueue(action);
}

В верхней части функции обновления вы можете добавить:

if(noCurrentAction && queue.Count > 0)
{
    ((MethodInvoker)queue.Dequeue()).Invoke();
    noCurrentAction = false;
}

И вам нужно добавить строку в конце функции обновления, например:

if(!moving && !rotating)
    noCurrentAction = true;

Теперь я бы точно не назвал это лучшим решением, но для его реализации не требуется много кода. Конечно, если вам нужно двигаться и вращаться одновременно, вам придется немного подправить его. Кроме того, будет сложнее, если вы добавите различные типы действий.

Для более общего решения я бы подумал о создании базового класса Action и извлечении из него определенных классов действий. Затем вы можете просто отправить действия в очередь, и ваша функция обновления может вызвать функцию обновления действия, которая будет выполнять ту работу, которую сейчас выполняют две части ваших игровых компонентов, которые выполняет функция обновления.

Это всего лишь некоторые идеи для размышления, надеюсь, что-то здесь поможет вам начать.

Последнее, что я хотел упомянуть, это то, что я не вижу, чтобы вы использовали переменную gameTime, которая передается в Update. Количество, которое ваш компонент перемещает и вращает, может зависеть от времени, прошедшего с момента последнего вызова Update. Это означает, что функция Update будет перемещать и вращать ваш игровой компонент на основе прошедшего времени, а не только того, сколько раз была вызвана функция Update. Я не очень хорошо объясняю это, и это зависит от того, как вы хотите, чтобы ваша игра функционировала. Вот пара различных сообщений от Шона Харгривза (эксперт XNA). Кроме того, XNA Forum post обсуждает вопрос, который я пытался сделать.

2 голосов
/ 18 марта 2009

Ааа, два слова: совместная многозадачность. С радостью Fibers (или вашего предпочтительного строительного блока для совместной многозадачности) вы можете (после некоторых земляных работ, таких как , включить волокна в C # ), сделать что-то вроде этого:

public void Ahead(int pix)
{
  moving = true;
  distanceToMove = pix;
  pixFromLastMove = 0;
  velocity = new Vector2((float) Math.Cos(rotation), (float) Math.Sin(rotation))*5.0f;

  //DO NOT RETURN UNTIL THIS ROBOT HAS MOVED TO ITS DESTINATION
  while(!HasReachedDestination())
  {
    Yield(); // let another fiber run
  }
}

Однако, чтобы сделать это, вам нужно реализовать простой циклический планировщик. C # на самом деле не моя лодка, но я бы сделал так, чтобы он был простым и создавал какой-то базовый класс, который я бы назвал Cooperative (или что-то в этом роде). Этот класс будет иметь статический список всех созданных волокон, а также статические методы Create () и Yield (). Create () создаст новое волокно (или что-то еще), а Yield () просто запланирует выполнение следующего волокна (стиль циклического перебора) в мире волокон, который будет включать вызов SwitchToFiber (). Он также будет иметь виртуальный метод Start () (или любой другой), в котором волокно начнет работать.

Чтобы сделать его более вычурным, вы могли бы позже сохранить отдельные списки волокон, которые либо работают, либо не работают (т.е. ожидают, что что-то произойдет). В этом случае вы можете упростить цикл в Ahead до:

WaitFor(HasReachedDestination);

Но я предлагаю вам сначала промокнуть от концепции совместной многозадачности.

Наконец, некоторые мысли о том, что должно быть сделано волокнами, как правило, ваш основной цикл обновления - это одно волокно, обновляющее и рисующее все объекты, а затем вызывающее Yield (). Все игровые объекты также будут волокнами (это может быть невозможно, если у вас много игровых объектов). Для ваших игровых объектов вы должны сделать что-то вроде:

public override Start()
{
  do
  {
    if(EnemyToTheLeft())
    {
      TurnLeft(90); // this will call Yield and return when we have finished turning
      Shoot();
    }
    Yield(); // always yield
  }while(!dead);
}
1 голос
/ 17 марта 2009

Хотя я нахожу ваш дизайн несколько странным, лучший способ выполнить то, что вы хотите, - это использовать EventWaitHandle и сигнализировать об этом из другого потока.

Скажем, у вас есть экземпляр ручки ожидания в вашем классе

вы можете вызвать waithadle.WaitOne () в вашем методе и сообщить четное значение из другого потока, используя waithandle.Set (), когда условие выполнено, и в этот момент ваш метод возобновит ожидание.

...