Не знаю, как использовать сопрограммы в Unity3D

В Unity3D это мой код:

void ActivateBuff1(){
    gun.equippedGun.msPerShot /= 2;
    gun.equippedGun.shotsLeftInMag += 10;
    StartCoroutine (WaitRage ());
}

void ActivateBuff2(){
    player.speedModifier *= 1.5f;
    StartCoroutine (WaitSpeed ());
}

IEnumerator WaitRage(){
    yield return new WaitForSeconds(powerUpDuration);
    gun.equippedGun.msPerShot *= 2;
}

IEnumerator WaitSpeed(){
    yield return new WaitForSeconds(powerUpDuration);
    player.speedModifier /= 1.5f;
}

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

Ответ 1

При нормальных обстоятельствах код, который вы предоставили, должен работать нормально. Однако, как определено в комментариях, существует оговорка - , если Gameobject, вызывающий сопрограмму, отключен/уничтожен до завершения задержки от WaitForSeconds(), сопроцессор будет остановлен, а оставшийся код вообще не будет вызываться. Вам нужно либо дождаться завершения сопрограммы, чтобы уничтожить Gameobject, либо вызвать другой вызов Gameobject в сопрограмме.

Вы упомянули, что искали альтернативы, которые могли бы упростить ваш код - вы можете рассмотреть Invoke(), который позволяет вам позвонить после указанной задержки. (Пока вы не запускаете это очень часто, накладные расходы от отражения не окажут заметного влияния на вашу производительность.) Таким образом, ваш код можно было бы переписать несколько короче:

void ActivateBuff1(){
    gun.equippedGun.msPerShot /= 2;
    gun.equippedGun.shotsLeftInMag += 10;
    Invoke("ResetPlayerRage", powerUpDuration);
}

void ActivateBuff2(){
    player.speedModifier *= 1.5f;
    Invoke("ResetPlayerSpeed", powerUpDuration);
}

void ResetPlayerRage(){
    gun.equippedGun.msPerShot *= 2;
}

void ResetPlayerSpeed(){
    player.speedModifier /= 1.5f;
}

К сожалению, Invoke() также будет отменен, если Gameobject будет уничтожен, но в отличие от сопрограммы, он не будет отменен, если Gameobject будет отключен. Таким образом, вы можете сначала отключить Gameobject (чтобы он стал невидимым и не взаимодействовал ни с чем), а затем уничтожить его только после запуска метода delayed:

void ActivateBuff1(){
    gun.equippedGun.msPerShot /= 2;
    gun.equippedGun.shotsLeftInMag += 10;
    gameObject.SetActive(false);
    Invoke("ResetPlayerRage", powerUpDuration);
}

void ResetPlayerRage(){
    gun.equippedGun.msPerShot *= 2;
    Destroy(gameObject);
}

Здесь приведено краткое описание того, будут ли останавливаться Invoke() и сопрограммы в зависимости от того, как вы манипулируете компонентом script или целым Gameobject:

..........................................................................
:                                  :                     :               :
:          Does it stop?           :   InvokeRepeating   :   Coroutine   :
:                                  :                     :               :
:..................................:.....................:...............:
:                                  :                     :               :
:   Disable the script component   :         No          :      No       :
:                                  :                     :               :
:..................................:.....................:...............:
:                                  :                     :               :
:   Destroy the script component   :         Yes         :      Yes      :
:                                  :                     :               :
:..................................:.....................:...............:
:                                  :                     :               :
:   Disable the game object        :         No          :      Yes      :
:                                  :                     :               :
:..................................:.....................:...............:
:                                  :                     :               :
:   Destroy the game object        :         Yes         :      Yes      :
:                                  :                     :               :
:..................................:.....................:...............:

Ответ 2

Человек, Серлите сказал все. Однако я бы поступил иначе с такой ситуацией.

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

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

Ответ 3

Взгляните на мой подход. Он использует метод FixedUpdate для обработки таймингов и не требует использования Coroutines. Также я использовал Singleton Pattern в своих буферах, чтобы обеспечить легкий доступ.

У меня есть BufferBase script, где я обрабатываю запуск/конец буферов. У меня может быть столько буферов, сколько мне нравится, и вывести их из этого класса.

BufferBase имеет

  • два члена: _isBufferActive и _bufferRemainingTime.

  • Метод с именем FixedUpdateBuffer, который я должен вызывать в FixedUpdate моих буферов. он отвечает за синхронизацию буфера и вызывает EndBuffer(), когда время завершено.

  • И 3 виртуальных метода, которые я могу переопределить в своих классах буферов.

Вот код:

public class BufferBase : MonoBehaviour
{
    /// <summary>
    /// Indicates whether the buffer is activated
    /// </summary>
    protected bool _isBufferActive = false;
    /// <summary>
    /// Time until buffer ends
    /// </summary>
    protected float _bufferRemainingTime = 0f;


    protected void FixedUpdateBuffer()
    {
        if (_isBufferActive)
        {
            _bufferRemainingTime -= Time.fixedDeltaTime;
            if (_bufferRemainingTime <= 0)
            {
                EndBuffer();
            }
        }
    }

    /// <summary>
    /// Resets buffer
    /// </summary>
    protected void ResetBuffer()
    {
        _isBufferActive = false;
        _bufferRemainingTime = 0;
    }

    /// <summary>
    /// Marks the start of the buffer
    /// </summary>
    /// <param name="value"></param>
    protected virtual void StartOrExtendBuffer(float value)
    {
        //set buffer values
        _isBufferActive = true;
        _bufferRemainingTime = value;

        gameObject.SetActive(true);
    }

    /// <summary>
    /// Marks the end of buffer
    /// </summary>
    protected virtual void EndBuffer()
    {
        _bufferRemainingTime = 0;
        _isBufferActive = false;

        gameObject.SetActive(false);
    }
}

Теперь для фактического буфера. У меня есть несколько сценариев, полученных из BufferBase. Все они имеют эти виртуальные методы, реализованные в них.

Я могу легко:

  • проверьте, активен ли определенный тип буфера или нет через RageController.IsActive

  • активировать буфер, используя RageController.AddRage(t), где t указывает продолжительность. (его продолжительность будет reset to t каждый раз, когда вызывается AddRage)

  • выключить буфер, используя RageController.Reset()

Вот пример буфера script:

public class RageController : BufferBase
{
    public static RageController instance;

    public static bool IsActive { get { return instance._isBufferActive; } }

    #region Static Methods
    internal static void AddRage(float value)
    {
        instance.StartOrExtendBuffer(value);
    }

    internal static void Reset()
    {
        instance.ResetBuffer();
    }
    #endregion

    #region Overriden Methods
    protected override void StartOrExtendBuffer(float value)
    {
        base.StartOrExtendBuffer(value);

        //----
        //add speed etc..
        //----
    }

    protected override void EndBuffer()
    {
        base.EndBuffer();

        //----
        //remove speed etc..
        //----
    }
    #endregion   

    #region Unity Methods
    void Awake()
    {
        instance = this;
    }
    void FixedUpdate()
    {
        FixedUpdateBuffer();

        if (_isBufferActive)
        {
            //----
            //anything that changes by time
            //----
        }
    }
    #endregion
}

Обратите внимание, что в конце RageController в методе FixedUpdate вы можете плавно изменять желаемые значения или активировать буфер с задержкой или так, прочитав значение _bufferRemainingTime