【问题标题】:How can I respawn pickups in an array and through a coroutine?如何通过协程重生阵列中的拾音器?
【发布时间】:2019-01-01 08:50:46
【问题描述】:

我的黄金拾音器在死亡时被摧毁后很难重生。这个想法是,如果玩家未能捡起 5 条金条,激活一个检查点并死亡,则当前的金币将被销毁,并在屏幕从黑色消失后重置。

我目前在我的健康管理器中有一个协程,如果玩家死亡并重置它们,它会正常运行。我有一个 Gold Pickup 脚本,如果它们没有被拾取,就会破坏它们。我似乎无法让他们重新实例化。我尝试在 Health Manager 的协程和 Gold Pickup 脚本中添加实例化代码。似乎没有任何效果。如果我没有收到错误提示“数组索引超出范围”,则说明“对象引用未设置为对象的实例”等。

public class GoldPickup : MonoBehaviour{
    public int value;
    public GameObject pickupEffect;
    public GameObject[] goldBarArray;
    public HealthManager healthManager;
    public Checkpoint checkpoint;

    private Vector3 goldRespawnPoint;
    private Quaternion goldStartPosition;

    void Start()
    {
        //To destroy multiple objects at once, use FindGameObjectsWithTag.
        //GetComponent is considered more efficient than FindObjectOfType, but the latter avoids any errors saying an object reference hasn't been set.
        goldBarArray = GameObject.FindGameObjectsWithTag("Gold");
        healthManager = FindObjectOfType<HealthManager>();
        //FindObjectOfType<Checkpoint>();
        checkpoint = FindObjectOfType<Checkpoint>();
        goldRespawnPoint = transform.position;
        goldStartPosition = transform.rotation;
    }

    public void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.CompareTag("Player"))
        {
            FindObjectOfType<GameManager>().AddGold(value);
            Instantiate(pickupEffect, transform.position, transform.rotation);
            Destroy(gameObject);
        }
    }

    public void DestroyGold()
    {
        //For Statics, an object reference isn't necessary. Use the FindObjectOfType to find the appropriate script and reference the Type, such as HealthManager.
        if (checkpoint.checkpoint1On == false)
        {
            foreach (GameObject Gold in goldBarArray)
            {
                Destroy(Gold);
                Instantiate(goldBarArray[5], goldRespawnPoint, goldStartPosition);
                goldRespawnPoint = transform.position;
                goldStartPosition = transform.rotation;
                //healthManager.RespawnCo();
            }
        }

    }
    /*public void GoldReset()
    {
        if (healthManager.isRespawning == true)
        {
            if (checkpoint.checkpoint1On == false)
            {
                StartCoroutine("GoldRespawnCo");
            }
        }

        else if (_respawnCoroutine != null)
        {
            StopCoroutine(_respawnCoroutine);
            _respawnCoroutine = StartCoroutine("GoldRespawnCo");
        }*/

    /*public IEnumerator GoldRespawnCo()
    {
        if (checkpoint.checkpoint1On == false)
        {
            Instantiate(goldPrefab, goldRespawnPoint, goldStartPosition);
            transform.position = goldRespawnPoint;
            transform.rotation = goldStartPosition;
        }
        else
        {
            yield return null;
        }
    }*/

    /*if (thePlayer.gameObject.activeInHierarchy == false)
    {
        Destroy(gameObject);
        Instantiate(goldBar, transform.position, transform.rotation);
    }
    else
    {
        if (thePlayer.gameObject.activeInHierarchy == true)
        {
            transform.position = respawnPoint;
            transform.rotation = startPosition;
        }
    }*/
}

    public class HealthManager : MonoBehaviour
    //The counters will count down and will keep counting down based on the length variables
    public int maxHealth;
    public int currentHealth;
    public PlayerController thePlayer;
    //public GoldPickup goldPickup;
    //public GoldPickup[] goldPickup;
    public float invincibilityLength;
    public Renderer playerRenderer;
    public float flashLength;
    public float respawnLength;
    public GameObject deathEffect;
    public Image blackScreen;
    public float fadeSpeed;
    public float waitForFade;
    public bool isRespawning;
    //public GameObject goldBar;
    //To reference another script's function, such as in the DeathTrigger script, make a public DeathTrigger, give it a reference name, and put it into the Start function. Use the reference name and assign it using GetComponent. Call another script's method by using the reference name, followed by a dot and the name of the method. Eg: deathTrigger.DestroyGold().

    private Quaternion startPosition;
    //private Quaternion goldPosition;
    private float flashCounter;
    private float invincibilityCounter;
    private Vector3 respawnPoint;
    //private Vector3 goldRespawnPoint;
    private bool isFadetoBlack;
    private bool isFadefromBlack;
    //private Coroutine _respawnCoroutine;
    //private Vector3 goldRespawnPoint;
    //private Quaternion goldStartPosition;

    void Start()
    {
        currentHealth = maxHealth;
        respawnPoint = thePlayer.transform.position;
        startPosition = thePlayer.transform.rotation;
        //goldPickup = GetComponent<GoldPickup>();
        //goldRespawnPoint = goldBar.transform.position;
        //goldStartPosition = goldBar.transform.rotation;
        //goldRespawnPoint = transform.position;
        //goldStartPosition = transform.rotation;
        //goldPickup = FindObjectOfType<GoldPickup>();
        //goldRespawnPoint = goldBar.transform.position;
        //goldPosition = goldBar.transform.rotation;
    }

    void Update()
    {
        //These functions are checked every frame until the player takes damage
        if (invincibilityCounter > 0)
        {
            invincibilityCounter -= Time.deltaTime;
            flashCounter -= Time.deltaTime;
            if (flashCounter <= 0)
            //The Flash Counter is currently set at 0.1 and will be within the 0 region as it counts down. During this period, the playerRenderer will alternate between on and off
            {
                playerRenderer.enabled = !playerRenderer.enabled;
                //The Flash Counter will keep counting down and reloop depending on the Flash Length time
                flashCounter = flashLength;
            }
            //This makes sure after the flashing and invincibility has worn off that the player renderer is always turned back on so you can see the player
            if (invincibilityCounter <= 0)
            {
                playerRenderer.enabled = true;
            }
        }

        if (isFadetoBlack)
        {
            blackScreen.color = new Color(blackScreen.color.r, blackScreen.color.g, blackScreen.color.b, Mathf.MoveTowards(blackScreen.color.a, 1f, fadeSpeed * Time.deltaTime));
            if (blackScreen.color.a == 1f)
            {
                isFadetoBlack = false;
            }
        }
        if (isFadefromBlack)
        {
            blackScreen.color = new Color(blackScreen.color.r, blackScreen.color.g, blackScreen.color.b, Mathf.MoveTowards(blackScreen.color.a, 0f, fadeSpeed * Time.deltaTime));
            if (blackScreen.color.a == 0f)
            {
                isFadefromBlack = false;
            }
        }
    }

    public void HurtPlayer(int damage, Vector3 direction)
    {
        //If the invincibility countdown reaches zero it stops, making you no longer invincible and prone to taking damage again
        if (invincibilityCounter <= 0)
        {
            currentHealth -= damage;
            if (currentHealth <= 0)
            {
                Respawn();
            }

            else
            {
                thePlayer.Knockback(direction);
                invincibilityCounter = invincibilityLength;
                playerRenderer.enabled = false;
                flashCounter = flashLength;
            }
        }
    }

    public void Respawn()
    {
        //A StartCoroutine must be set up before the IEnumerator can begin
        if (!isRespawning)
        {
            StartCoroutine("RespawnCo");
        }
    }

    //IEnumerators or Coroutines will execute the code separately at specified times while the rest of the code in a codeblock will carry on executing as normal.
    //To prevent an error appearing below the name of the Coroutine, be sure to place a yield return somewhere within the code block. Either yield return null or a new WaitForSeconds.
    public IEnumerator RespawnCo()
    {
        if (GameManager.currentGold < 5)
        {
            isRespawning = true;
            thePlayer.gameObject.SetActive(false);
            Instantiate(deathEffect, respawnPoint, startPosition);
            yield return new WaitForSeconds(respawnLength);
            isFadetoBlack = true;
            yield return new WaitForSeconds(waitForFade);
            //To reference another script's function quickly and just the once, use the FindObjectOfType function. This is considered to be slow however.
            FindObjectOfType<GoldPickup>().DestroyGold();
            //GetComponent<GoldPickup>().DestroyGold();
            //Instantiate(goldBar, goldRespawnPoint, Quaternion.identity);
            isFadefromBlack = true;
            //goldRespawnPoint = goldBar.transform.position;
            //goldStartPosition = goldBar.transform.rotation;
            isRespawning = false;
            thePlayer.gameObject.SetActive(true);
            thePlayer.transform.position = respawnPoint;
            thePlayer.transform.rotation = startPosition;
            currentHealth = maxHealth;
            invincibilityCounter = invincibilityLength;
            playerRenderer.enabled = false;
            flashCounter = flashLength;
            GameManager.currentGold = 0;
            GetComponent<GameManager>().SetCountText();
            StopCoroutine("RespawnCo");

            /*isRespawning = true;
            thePlayer.gameObject.SetActive(false);
            yield return new WaitForSeconds(respawnLength);
            isFadetoBlack = true;
            yield return new WaitForSeconds(waitForFade);
            isFadefromBlack = true;
            invincibilityCounter = invincibilityLength;
            playerRenderer.enabled = false;
            flashCounter = flashLength;
            SceneManager.LoadScene("Level 1");
            GameManager.currentGold = 0;*/
        }

        else if(GameManager.currentGold >= 5)
        {
            isRespawning = true;
            thePlayer.gameObject.SetActive(false);
            Instantiate(deathEffect, respawnPoint, startPosition);
            yield return new WaitForSeconds(respawnLength);
            isFadetoBlack = true;
            yield return new WaitForSeconds(waitForFade);
            isFadefromBlack = true;
            isRespawning = false;
            thePlayer.gameObject.SetActive(true);
            thePlayer.transform.position = respawnPoint;
            thePlayer.transform.rotation = startPosition;
            currentHealth = maxHealth;
            invincibilityCounter = invincibilityLength;
            playerRenderer.enabled = false;
            flashCounter = flashLength;
        }
    }

    /*public void HealPlayer(int healAmount)
    {
        currentHealth += healAmount;
        if(currentHealth > maxHealth)
        {
            currentHealth = maxHealth;
        }
    }*/

    public void SetSpawnPoint(Vector3 newPosition)
    {
        respawnPoint = newPosition;
    }


public class Checkpoint : MonoBehaviour

    public HealthManager theHealthManager;
    public Renderer cpRenderer;
    public Renderer postRenderer;
    public SpriteRenderer pcRenderer;
    public Material cpOff;
    public Material cpOn;
    public Material postOff;
    public Material postOn;
    public GameObject[] infoPanels;
    public bool checkpoint1On;

    //Make sure to assign a value to a bool with '=' and in an 'if' statement somewhere in the code to prevent warnings.
    //private bool checkpoint1IsActivated;
    private bool infoPanel1Activated;

    void Start()
    {
        theHealthManager = FindObjectOfType<HealthManager>();
    }

    void Update()
    //Key presses are better handled in the Update function and will recognise keys being pressed once every frame.
    {
        if (checkpoint1On == true)
        {
            if (infoPanel1Activated == false)
            {
                if (Input.GetKeyDown(KeyCode.Space))
                {
                    infoPanels[0].SetActive(true);
                    infoPanel1Activated = true;
                }
            }
            else
            {
                if (infoPanel1Activated == true)
                {
                    if (Input.GetKeyDown(KeyCode.Space))
                    {
                        infoPanels[0].SetActive(false);
                        infoPanel1Activated = false;
                    }
                }
            }
        }
    }

    public void Checkpoint1On()
    {
        cpRenderer.material = cpOn;
        postRenderer.material = postOn;
        pcRenderer.color = new Color(1f, 1f, 1f, 1f);
        checkpoint1On = true;
    }

    //[] makes a variable an Array (a list). The 'foreach' loop will check through all the Checkpoint objects

    //Checkpoint[] checkpoints = FindObjectsOfType<Checkpoint>();

    //For each Checkpoint Array called 'checkpoints', look for 'cp' and turn the others in the list off

    /*foreach (Checkpoint cp in checkpoints)
    {
        cp.CheckpointOff();
    }
    theRenderer.material = cpOn;*/

    public void Checkpoint1Off()
    {
        cpRenderer.material = cpOff;
        postRenderer.material = postOff;
        pcRenderer.color = new Color(1f, 1f, 1f, 5f);
        checkpoint1On = false;
    }

    public void OnTriggerStay(Collider other)
    {
        if (other.gameObject.CompareTag("Player"))
        {
            if (GameManager.currentGold >= 5)
            {
                if (Input.GetKeyDown(KeyCode.Return))
                {
                    theHealthManager.SetSpawnPoint(transform.position);
                    Checkpoint1On();
                    checkpoint1On = true;
                }
            }
            else if (GameManager.currentGold <= 5)
            {
                checkpoint1On = false;
            }
        }
    }

【问题讨论】:

    标签: c# unity3d


    【解决方案1】:

    在您的 DestroyGold() 函数中,您像这样实例化黄金:

    foreach (GameObject Gold in goldBarArray)
    {
            Destroy(Gold);
            Instantiate(goldBarArray[5], goldRespawnPoint, goldStartPosition);
            goldRespawnPoint = transform.position;
            goldStartPosition = transform.rotation;
            //healthManager.RespawnCo();
    }
    

    transform.positiontransform.rotation 只能获取当前对象的位置和旋转(即,您的脚本附加到的任何内容)。因此,您不仅在同一个位置生成所有金币,而且还会在持有脚本的对象的位置生成金币,而不是您真正希望它去的地方!

    在不太了解场景中的对象的情况下,我可以告诉您以下内容:尝试创建一个 Transform[] 来存储您想要重生黄金的位置。此外,请确保在 foreach 循环中调用 Instantiate() 之前分配了 goldRespawnPointgoldStartPosition。最后,只是一个一般性提示:您永远不应该在if 语句中使用variable == truevariable == false。您可以分别使用if(variable)if(!variable)。它的工作原理相同,同时更具可读性并减少了您需要编写的代码量。

    编辑 1:针对 cme​​ts,我添加了具体的代码示例来实现这些建议。

    首先,您可能会因为goldBarArray[5] 而遇到超出范围的错误。由于数组从索引 0 开始,因此您最多只能访问大小为 n 数组中的元素 n-1。有关如何在下一步中解决此问题的更多信息。

    现在是Transform 数组。在声明公共变量的区域(在脚本顶部),添加行

    public Transform[] spawnPoints;
    

    然后,回到 Unity,您将能够在 Inspector 中分​​配这些生成点。

    编辑 2: 此外,在 foreach 循环中,您尝试从场景中实例化其中一根金条,但这些金条已被 Destroy(Gold); 语句删除。相反,您应该从不会被破坏的预制件中实例化。为此,请添加

    public GameObject goldPrefab;
    

    与您的其他公共变量一起使用。然后,在编辑器中,通过将其中一根金条从 Hierarchy 拖到 Assets 文件夹中来创建一个预制件。最后,在 Inspector 中将该 prefab 设置为 goldPrefab 的值。

    现在,您实际上可以稍微清理一下 foreach 循环。您可以去掉 goldRespawnPointgoldStartPosition 行,因为重生位置将包含在我们刚刚创建的 Transform 数组中。同样,在不知道您的场景是如何构建的情况下,我只需要对什么会起作用进行有根据的猜测。试试这个循环:

    int spawnPointCounter = 0;
    foreach(GameObject Gold in goldBarArray){
        Destroy(Gold);
        Transform currentSP = spawnPoints[spawnPointCounter];
        Instantiate(goldPrefab, currentSP.position, currentSP.rotation);
        spawnPointCounter++;
    }
    

    【讨论】:

    • 感谢您的解释。我会试试看。除了 Start 函数,在调用 Instantiate 之前我应该​​在哪里分配 goldRespawnPoint 和 goldStartPosition?另外,感谢有关 if 语句的提示。知道这一点很有用。 :)
    • 另外,如果我在那个函数中实例化黄金,我会收到一个错误,说数组索引超出范围。
    • 不,我什至不确定如何设置一个变换数组来获取黄金的位置。 :-\
    • 我在回答中添加了更多内容,希望能解决您的问题。如果它有帮助,请不要忘记给我的答案投票,如果它解决了您的问题,请将其标记为已接受。
    • 看起来我们快到了。 :D 虽然当它进入实例化时,它给了我一个错误,说“'Transform' 类型的对象已被破坏,但您仍在尝试访问它。”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-04-30
    • 2015-12-11
    • 1970-01-01
    • 2017-06-04
    • 2023-02-03
    • 2020-06-18
    相关资源
    最近更新 更多