【问题标题】:Do not know how to use coroutines in Unity3D不知道如何在 Unity3D 中使用协程
【发布时间】:2016-12-04 00:38:13
【问题描述】:

在 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 中的代码永远不会被调用。请帮助...(请提出另一种编码方式,也许我知道它不是很干净)

【问题讨论】:

  • 您是否通过日志记录/调试确定所有导致启动协程的代码都被调用了?并在WaitForSeconds 之前记录以确定是否正在输入协程?
  • 我想你的 powerUpDuration 设置在你的代码的其他地方?您已经发现在调试器中调用了 ActivateBuff1 和 ActivateBuff2?
  • 您能否在您的代码中添加 cmets 来提示哪些部分正在被击中,哪些没有被击中,哪些应该被击中?我有点想不通,我有点累了哈哈..
  • 是的,一切都被称为 Serlite,除了 IENnumerator 中的代码似乎没有运行......
  • 嗯......这是一个不同的故事 - 它非常固执己见。对于更简单的代码,你可以使用Invoke()来延迟重置播放器属性,这样powerUpDuration和reset方法之间的关系就更清楚了。还可以为您节省几行代码。

标签: c# unity3d


【解决方案1】:

在正常情况下,您提供的代码应该可以正常工作。但是,根据 cmets 的规定,有一个警告 - 如果调用协程的 Gameobject 在 WaitForSeconds() 的延迟完成之前被禁用/销毁协程将被停止 并且根本不会调用剩余的代码。您要么需要等待协程完成,然后再销毁游戏对象,要么让其他游戏对象调用协程。

您提到您还在寻找可能简化代码的替代方案 - 您可以考虑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;
}

不幸的是,如果 Gameobject 被销毁,Invoke() 也会被取消 - 但与协程不同的是,如果 Gameobject 被禁用,它不会被取消。所以你可以先禁用游戏对象(让它变得不可见并且不与任何东西交互),然后只有在运行延迟方法后才销毁它:

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() 和协程是否会停止,具体取决于您如何操作脚本组件或整个 Gameobject:

..................................................... ……………………………………………………………………………… : : : : : 停了吗? : InvokeRepeating : 协程 : : : : : ………………………………………………………………………………………………………………………… ........:......: : : : : :禁用脚本组件:否:否: : : : : ………………………………………………………………………………………………………………………… ........:......: : : : : :销毁脚本组件:是:是: : : : : ………………………………………………………………………………………………………………………… ........:......: : : : : :禁用游戏对象:否:是: : : : : ………………………………………………………………………………………………………………………… ........:......: : : : : :销毁游戏对象:是:是: : : : : …………………………………………………………………………………………………… …………………………:

【讨论】:

  • 我发现 Unity 中最难记住的一件事!!!这就像一张你必须记住的东西的 3D 表格。
  • 我冒昧地在你的问题上启动了一个 ascii-art-table,最终解决了这个问题!也许你知道这些价值观。
  • 谢谢,@JoeBlow!我用我知道的大部分内容填写了其中的大部分内容,但稍后我需要设置一个测试用例来验证最后两个条目 - 我怀疑它们都是“是”,但最好确定一下!
  • 我们已经在@serlite dude 网站上为 ascii 艺术设定了标准! :)
  • 嘿,@JoeBlow 确实很荣幸。感谢您对答案的帮助!
【解决方案2】:

伙计,Serlite 什么都说了。但是我会以不同的方式处理这种情况。

如果我没记错的话,你在脚本中设置了这个 ActivateBuff 函数,该函数附加到某种物品上,该物品在装备的枪中设置了一个修饰符,然后被禁用。我不会那样做,而是在装备的枪脚本中创建一个 Buff 函数(将修饰符和时间作为参数传递)并让枪本身来处理。

由于装备的枪不会消失,它会解决你的问题,理想情况下甚至可以是更通用的解决方案。一旦你可以通过另一个最近可能会产生一些意外行为的加电(因为会有很多脚本对枪进行抛光和取消抛光)。

【讨论】:

【解决方案3】:

看看我的方法。它使用 FixedUpdate 方法来处理时间并且不需要协程。我还在缓冲区中使用了单例模式以方便访问。

我有一个 BufferBase 脚本,用于处理缓冲区的开始/结束。我可以拥有任意数量的缓冲区,并从此类中派生它们。

BufferBase

  • 两个成员:_isBufferActive_bufferRemainingTime

  • 我必须在缓冲区的 FixedUpdate 中调用一个名为 FixedUpdateBuffer 的方法。它负责缓冲区的计时,并在时间结束时调用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 派生的脚本。它们都实现了这些虚拟方法。

我可以轻松:

  1. 通过RageController.IsActive检查特定类型的缓冲区是否处于活动状态

  2. 使用RageController.AddRage(t) 激活缓冲区,其中 t 指定持续时间。 (每次调用AddRage 时,其持续时间将重置为 t)

  3. 使用RageController.Reset()关闭缓冲区

这是一个示例缓冲区脚本:

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
}

请注意,在 FixedUpdate 方法中 RageController 的末尾,您可以通过读取 _bufferRemainingTime 的值来平滑地更改所需的值或延迟启用缓冲区

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-10
    • 1970-01-01
    • 2022-10-11
    • 2014-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多