Почему IEnumerator не работает? Что мы можем сделать, чтобы сделать задержку в функции пожарного ружья?

Наша проблема в том, что мы пытаемся сделать задержку в функции FireGun с помощью IEnumerator, но это, похоже, не работает. Вот наш PlayerControllerScript

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class PlayerController2 : MonoBehaviour
{
    private Rigidbody2D playerrb;
    private Vector2 moveDirection;
    public float movespeed = 5;
    private Vector2 mousePosition;
    public Camera sceneCamera;
    public WeaponScript weaponscript;
    public int exp = 0;
    public int maxexp = 50;
    public bool newlevel = false;
    public bool gamepause = false;
    public bool doubleshot = false;
    public int life = 1;
    public Image heart;
    public int firegun = 1;
    // Start is called before the first frame update
    void Start()
    {
        playerrb = GetComponent<Rigidbody2D>();
        

    }
    public IEnumerator WaitASecond()
    {
        yield return new WaitForSeconds(1.0f);
        
        // Code to execute after the delay
    }
    // Update is called once per frame
    void Update()
    {
        //if (Input.GetMouseButtonDown(0) && gamepause == false && doubleshot == false) 
        //{
        //    weaponscript.fireGun();
        //}
        //if (Input.GetMouseButtonDown(0) && gamepause == false && doubleshot == true)
        //{
        //    weaponscript.fireDoubleGun();
        //}

        if (Input.GetButton("Fire1"))
        {
              StartCoroutine(WaitASecond());
              weaponscript.fireGun();
              StartCoroutine(WaitASecond());



        }

        if (exp == maxexp)
        {
            newlevel = true;
        }
        if (life == 0)
        {
            SceneManager.LoadScene(2);

        }
        if (life == 2) {
            heart.gameObject.SetActive(true);   
        }
        if (life == 1) {
            heart.gameObject.SetActive(false);

        }
    }
    private void FixedUpdate()
    {
        // Finds if they're pressing any of the moving buttons
        if (gamepause == false) {
            float moveX = Input.GetAxisRaw("Horizontal");
            float moveY = Input.GetAxisRaw("Vertical");
            moveDirection = new Vector2(moveX, moveY).normalized;
            // The mouse position allows the camera to find the mouse and it gives us where it is
            mousePosition = sceneCamera.ScreenToWorldPoint(Input.mousePosition);
            // This causes it to move. The normalized makes it so people won't go faster if they're doing diagonal
            playerrb.velocity = new Vector2(moveDirection.x * movespeed, moveDirection.y * movespeed);
            Vector2 aimDirection = mousePosition - playerrb.position;
            //This makes it so the mouse's angle, and has  the player rotate towards that.
            float aimAngle = Mathf.Atan2(aimDirection.y, aimDirection.x) * Mathf.Rad2Deg;
            playerrb.rotation = aimAngle;

        }

    }
    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (gameObject.CompareTag("Enemy")) {
            Destroy(gameObject);
               

        }
    }
   
}

Вот наш сценарий оружия.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class WeaponScript : MonoBehaviour
{
    public GameObject bullet;
    public Transform firepoint;
    public float fireForce = 10.0f;
    public GameObject player;
    public PlayerController2 playercontroller;
    public void Start()
    {
        player = GameObject.FindGameObjectWithTag("Player");
        playercontroller = player.GetComponent<PlayerController2>();
    }

    public IEnumerator ExecuteAfterTime()
    {
        yield return new WaitForSeconds(1.0f);
        GameObject projectile = Instantiate(bullet, firepoint.position, firepoint.rotation);
       projectile.GetComponent<Rigidbody2D>().AddForce(firepoint.up * fireForce, ForceMode2D.Impulse);
        
    }

    public void fireGun()
    {

        
        StartCoroutine(ExecuteAfterTime());

    }

    public void fireDoubleGun()
    {

        GameObject projectile = Instantiate(bullet, firepoint.position, firepoint.rotation);
        projectile.GetComponent<Rigidbody2D>().AddForce(firepoint.up * fireForce, ForceMode2D.Impulse);
        GameObject projectile2 = Instantiate(bullet, firepoint.position, firepoint.rotation);
        projectile2.GetComponent<Rigidbody2D>().AddForce(firepoint.up * fireForce, ForceMode2D.Impulse);
    }
}

Думаю, основная проблема с кодом где-то здесь, в нашем WeaponScript.

       public IEnumerator ExecuteAfterTime()
    {
        yield return new WaitForSeconds(1.0f);
        GameObject projectile = Instantiate(bullet, firepoint.position, firepoint.rotation);
       projectile.GetComponent<Rigidbody2D>().AddForce(firepoint.up * fireForce, ForceMode2D.Impulse);
        
    }

    public void fireGun()
    {

        
        StartCoroutine(ExecuteAfterTime());

    }

Мы пробовали другие способы задержки срабатывания FireGun, но они тоже не сработали. Проблема сейчас в том, что он продолжает порождать бесконечное количество. Наша цель — создавать пулю каждую секунду.

Что вы имеете в виду под doesn't seem to work? Задержки нет или снаряд не спаунится? или что-нибудь еще?

Максим Фисман 31.03.2023 16:42

@МаксимФисман Задержки нет, она продолжает их порождать. Я добавлю это в свой пост.

GeniusCoder 31.03.2023 16:42

О, теперь я вижу. Проблема в том, что IEnumerator не задерживает всю программу, а только добавляет паузу перед кодом, который идет сам по себе. выложу полный ответ

Максим Фисман 31.03.2023 16:44

Вы также не хотели бы, чтобы цикл обновления выполнялся 4 с+.

BugFinder 31.03.2023 18:30

@BugFinder Я не думаю, что задержка работает, потому что она продолжает стрелять мгновенно.

GeniusCoder 31.03.2023 18:34

Нет, потому что это не то, как работают сопрограммы, они не приостанавливают этот код. Однако, если бы они сделали вашу программу, они бы зависли на 4 с+, что тоже никому не нужно.

BugFinder 31.03.2023 20:36
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
51
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

public IEnumerator FireShot()
{
    yield return new WaitForSeconds(1.0f); // this wait only work in local IEnumerator method
    
    weaponscript.fireGun(); // ...for example solution 1
}

Но вам нужна функция, зависящая от задержки, поэтому создайте в параметрах таймер с плавающей запятой и выполните действие во втором параметре.

using System;

...

public IEnumerator DoDelay(Action action, float delay = 0f) // This work with delay
{
    yield return new WaitForSeconds(delay);
    action.Invoke();
}
if (Input.GetButton("Fire1"))
{
    StartCoroutine(DoDelay(() => weaponscript.fireGun(), 1f)); // so FireGun or any method can invokes after defined delay time
}

Для действия Action это создает сообщение об ошибке в моем коде.

GeniusCoder 31.03.2023 16:57

@GeniusCoder using System; напишите в поле ссылки. (см. обновление ответа)

KiynL 31.03.2023 17:02

Извините, что снова беспокою вас, но в строке запуска сопрограммы DoDelay написано Cannot Convert From Systems.Collection.Ienumerator to string

GeniusCoder 31.03.2023 17:09

@GeniusCoder Отправьте исходный код по адресу [email protected].

KiynL 31.03.2023 17:13

я отправил это тебе

GeniusCoder 31.03.2023 18:31

@GeniusCoder Скобка была не в том месте. Еще раз взгляните на раздел StartCoroutine. (обновленный ответ)

KiynL 31.03.2023 18:36

Однако стреляет постоянно.

GeniusCoder 31.03.2023 18:44
Ответ принят как подходящий

Проблема вашего кода не там, где вы ожидаете.

Это здесь:

        if (Input.GetButton("Fire1"))
        {
              StartCoroutine(WaitASecond());
              weaponscript.fireGun();
              StartCoroutine(WaitASecond());
        }

Кажется, вы думаете, что IEnumerators и Coroutines делают задержку во всей программе.
Поскольку ваша функция WaitASecond() включает только одну строку с WaitForSeconds, очевидно, что вы ожидаете, что этот фрагмент кода будет работать следующим образом:

  1. Задержка в 1 секунду
  2. Огонь
  3. Задержка в 1 секунду

Однако это НЕ то, как работают корутины. Корутина не задерживает весь поток или программу. Он только задерживает свой собственный код. Итак, объясните это, давайте возьмем вашу функцию ExecuteAfterTime() в качестве примера:

       public IEnumerator ExecuteAfterTime()
    {
        yield return new WaitForSeconds(1.0f);
        GameObject projectile = Instantiate(bullet, firepoint.position, firepoint.rotation);
       projectile.GetComponent<Rigidbody2D>().AddForce(firepoint.up * fireForce, ForceMode2D.Impulse);
        
    }

Здесь БУДЕТ задержка перед созданием снаряда, потому что этот фрагмент кода идет ВНУТРИ сопрограммы и после задержки (WaitForSeconds). Однако внешний код будет продолжать работать без задержек.

Итак, вернемся к участку кода, где кроется ошибка: на самом деле это будет работать так:

  1. Запуск сопрограммы с задержкой в ​​1 секунду. Теперь сопрограмма ожидает, но внешний код продолжает работать.
  2. Огонь
  3. Запускаем еще одну сопрограмму с задержкой в ​​1 секунду. Теперь сопрограмма ожидает, но внешний код продолжает работать. --- через 1 секунду---
  4. Первая сопрограмма перестает ждать и завершается, так как она закончилась.
  5. Вторая сопрограмма перестает ждать и завершается, так как она закончилась.

Я надеюсь, что теперь вы понимаете, чем Coroutines лучше.


Что касается решения, могу предложить следующее:

    public IEnumerator FireCooldown()
    {
        yield return new WaitForSeconds(1.0f); // Wait 
        weaponsscripts.FireGun(); // we put shooting inside the coroutine, so it is subject to a delay now
        yield return new WaitForSeconds(1.0f); // You can add one more delay. Although I don't see any point in it, you do so in your code.
    }

    void Update()
    {
        if (Input.GetButton("Fire1"))
        {
             StartCoroutine(FireCooldown()); // Starting a coroutine
        }

Функция FireCooldown() здесь идет вместо вашего ExecuteAfterTime()


UPD: Из комментариев понял, что на самом деле не нужно стрелять снарядом каждый кадр. В этом случае пусть Coroutine сделает всю работу:

// This function starts the fire loop. Probably, it would be logical to call this function when a gun trigger gets pulled.
void StartFire() {
    StartCoroutine(FireLoop());
}

// This function shoots a projectile every n seconds
IEnumerator FireLoop() {
    while (true) { // Here you can put any condition
        weaponsscripts.FireGun(); // Shooting
        yield return new WaitForSeconds(n); // where n is the delay
    }
}

В качестве альтернативы у вас может быть переменная bool doShoot, и сопрограмма будет постоянно либо стрелять, либо ждать:

IEnumerator FireLoop() {
    while(true) {
        if (doShoot) { // when a gun trigger is pulled, for example
            weaponsscripts.FireGun(); // Shooting
            yield return new WaitForSeconds(n); // where n is the delay
        }
        else yield return new WaitUntil(() => doShoot); // waiting until doShoot turns true
    }
}

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

Опять же, в качестве альтернативы вы можете обойтись без сопрограмм, используя переменную кулдауна. Каждый кадр вы бы уменьшали его: cooldown -= Time.deltaTime и проверяли, должен ли персонаж стрелять, т.е. спусковой крючок нажат И время восстановления <= 0. После выстрела время восстановления возвращается к исходному значению

Для меня, даже после того, как я изменил свой код на решение, он по-прежнему стреляет тоннами пуль каждую секунду. Нужно ли мне также менять функцию огнестрельного оружия? [ public void fireGun () {. Снаряд GameObject = Instantiate(bullet, firepoint.position, firepoint.rotation); снаряд.GetComponent<Rigidbody2D>().AddForce(firepoint.up * fireForce, ForceMode2D.Impulse); }]

GeniusCoder 31.03.2023 17:03

Ну, это потому, что вы буквально вызываете функцию Coroutine каждый кадр в Update()! Я думал, что это было специально. Если вам не нужна пуля в рамке, что тогда? Стрелять пулей каждые n секунд?

Максим Фисман 31.03.2023 17:06

Ага. Извините за путаницу. Я решил поместить его в обновление, потому что хотел, чтобы ввод был мгновенным, и я не знал, как еще это сделать.

GeniusCoder 31.03.2023 17:10

@GeniusCoder Я обновил свой ответ. Пожалуйста, посмотрите, соответствует ли это вашим целям?

Максим Фисман 31.03.2023 17:20

Для первого решения, с огневой петлей, нужно ли установить стрельбу на false после выстрела одной пулей в оружейном скрипте?

GeniusCoder 31.03.2023 18:30

@GeniusCoder Если вы установите для него значение false, огонь прекратится, а если вы этого не сделаете, он будет продолжать выкрикивать пулю каждые n секунд. Итак, мой ответ будет таким: это зависит от того, что вам нужно.

Максим Фисман 31.03.2023 18:55

Другие вопросы по теме