Наша проблема в том, что мы пытаемся сделать задержку в функции 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, но они тоже не сработали. Проблема сейчас в том, что он продолжает порождать бесконечное количество. Наша цель — создавать пулю каждую секунду.
@МаксимФисман Задержки нет, она продолжает их порождать. Я добавлю это в свой пост.
О, теперь я вижу. Проблема в том, что IEnumerator не задерживает всю программу, а только добавляет паузу перед кодом, который идет сам по себе. выложу полный ответ
Вы также не хотели бы, чтобы цикл обновления выполнялся 4 с+.
@BugFinder Я не думаю, что задержка работает, потому что она продолжает стрелять мгновенно.
Нет, потому что это не то, как работают сопрограммы, они не приостанавливают этот код. Однако, если бы они сделали вашу программу, они бы зависли на 4 с+, что тоже никому не нужно.
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 using System;
напишите в поле ссылки. (см. обновление ответа)
Извините, что снова беспокою вас, но в строке запуска сопрограммы DoDelay написано Cannot Convert From Systems.Collection.Ienumerator to string
@GeniusCoder Отправьте исходный код по адресу [email protected].
я отправил это тебе
@GeniusCoder Скобка была не в том месте. Еще раз взгляните на раздел StartCoroutine. (обновленный ответ)
Однако стреляет постоянно.
Проблема вашего кода не там, где вы ожидаете.
Это здесь:
if (Input.GetButton("Fire1"))
{
StartCoroutine(WaitASecond());
weaponscript.fireGun();
StartCoroutine(WaitASecond());
}
Кажется, вы думаете, что IEnumerators и Coroutines делают задержку во всей программе.
Поскольку ваша функция WaitASecond()
включает только одну строку с WaitForSeconds
, очевидно, что вы ожидаете, что этот фрагмент кода будет работать следующим образом:
Однако это НЕ то, как работают корутины. Корутина не задерживает весь поток или программу. Он только задерживает свой собственный код. Итак, объясните это, давайте возьмем вашу функцию 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
). Однако внешний код будет продолжать работать без задержек.
Итак, вернемся к участку кода, где кроется ошибка: на самом деле это будет работать так:
Я надеюсь, что теперь вы понимаете, чем 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); }]
Ну, это потому, что вы буквально вызываете функцию Coroutine каждый кадр в Update()! Я думал, что это было специально. Если вам не нужна пуля в рамке, что тогда? Стрелять пулей каждые n секунд?
Ага. Извините за путаницу. Я решил поместить его в обновление, потому что хотел, чтобы ввод был мгновенным, и я не знал, как еще это сделать.
@GeniusCoder Я обновил свой ответ. Пожалуйста, посмотрите, соответствует ли это вашим целям?
Для первого решения, с огневой петлей, нужно ли установить стрельбу на false после выстрела одной пулей в оружейном скрипте?
@GeniusCoder Если вы установите для него значение false, огонь прекратится, а если вы этого не сделаете, он будет продолжать выкрикивать пулю каждые n секунд. Итак, мой ответ будет таким: это зависит от того, что вам нужно.
Что вы имеете в виду под
doesn't seem to work
? Задержки нет или снаряд не спаунится? или что-нибудь еще?