英文:
Why does the IEnumerator not work? What can we do to make a delay in the firegun function?
问题
我们的问题是我们尝试在FireGun函数中使用IEnumerator来制造延迟,但似乎不起作用。以下是我们的PlayerControllerScript
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class PlayerController2 : MonoBehaviour
{
// 省略其他代码
public IEnumerator WaitASecond()
{
yield return new WaitForSeconds(1.0f);
// 在延迟后执行的代码
}
void Update()
{
// 省略其他代码
if (Input.GetButton("Fire1"))
{
StartCoroutine(WaitASecond());
weaponscript.fireGun();
StartCoroutine(WaitASecond());
}
// 省略其他代码
}
// 省略其他代码
}
这是我们的Weapon Script。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WeaponScript : MonoBehaviour
{
// 省略其他代码
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()
{
// 省略其他代码
}
}
我认为代码的主要问题可能在WeaponScript中,特别是在ExecuteAfterTime函数中。你尝试延迟FireGun函数的方法似乎没问题。目前的问题是它不断生成无限数量的子弹。你的目标是每秒生成一颗子弹。
英文:
Our problem is that we are trying to make a delay in the FireGun function using IEnumerator, but it doesn't seem to work. Here is our 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);
}
}
}
Here is our Weapon Script.
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);
}
}
I think the main problem with the code is somewhere in here, in our 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());
}
We have tried other ways of delaying the FireGun function, but they didn't work either.
The problem right now is that it keeps spawning endless amounts. Our goal is it to spawn a bullet every second.
答案1
得分: 0
IEnumerator
本身是一个时间相关的方法,它可以等待,因此你不能指望在 Update
函数中使用 Wait。要解决这个问题,注意下面的代码。
public IEnumerator FireShot()
{
yield return new WaitForSeconds(1.0f); // 这个等待只在本地 IEnumerator 方法中起作用
weaponscript.fireGun(); // ...例如解决方案 1
}
但你需要一个依赖延迟的函数,所以在参数中创建一个浮点计时器,并在第二个参数中执行操作。
using System;
...
public IEnumerator DoDelay(Action action, float delay = 0f) // 这个可以用于延迟
{
yield return new WaitForSeconds(delay);
action.Invoke();
}
if (Input.GetButton("Fire1"))
{
StartCoroutine(DoDelay(() => weaponscript.fireGun(), 1f)); // 这样 FireGun 或任何方法都可以在定义的延迟时间之后调用
}
英文:
IEnumerator
itself is a time-dependent method that can wait, so you can't expect the Wait working in the Update
function. To solve the problem, pay attention to the code below.
public IEnumerator FireShot()
{
yield return new WaitForSeconds(1.0f); // this wait only work in local IEnumerator method
weaponscript.fireGun(); // ...for example solution 1
}
But you need a delay dependent function, so create a float timer in the parameters and take the action in the second parameter.
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
}
答案2
得分: 0
以下是已翻译的内容:
The problem of your code is not where you expect it to be.
你代码的问题并不在你期望的地方。
It is here:
问题出在这里:
if (Input.GetButton("Fire1"))
如果 (Input.GetButton("Fire1")) 这个条件满足,
You seem to think that the IEnumerators and Coroutines make a delay in the whole program. Since your WaitASecond()
function only includes one string with WaitForSeconds
, it is obvious that you expect this piece of code function like the following:
你似乎认为 IEnumerators 和 Coroutines 会让整个程序延迟。因为你的 WaitASecond()
函数只包含一个字符串 WaitForSeconds
,很明显你希望这段代码像下面这样运行:
- A 1 second's delay
- 1秒的延迟
- Fire
- 开火
- A 1 second's delay
- 1秒的延迟
However, this is NOT how Coroutines work. A Coroutine does not delay the whole thread or program. It only delays its own code. So explain it, let's take your function ExecuteAfterTime()
as an example:
然而,这并不是 Coroutines 的工作原理。Coroutine 不会延迟整个线程或程序,它只会延迟自己的代码。所以来解释一下,让我们以你的 ExecuteAfterTime()
函数为例:
public IEnumerator ExecuteAfterTime()
public IEnumerator ExecuteAfterTime()
Here there WILL be a delay before instantiating a projectile, because this piece of code goes INSIDE the Coroutine and after the delay (WaitForSeconds
). However, the outer code will keep working with no delays.
在这里,在实例化投射物之前会有一段延迟,因为这段代码在 Coroutine 内部,并且在延迟之后 (WaitForSeconds
) 才执行。然而,外部代码将继续工作,没有延迟。
So, back to the piece of code where the mistake lies: in fact, it would work like this:
所以,回到错误所在的代码段:实际上,它会按照以下方式运行:
- Starting a coroutine of 1 second's delay. The coroutine is now waiting, but the outer code keeps working.
- 启动一个1秒的 Coroutine 延迟。Coroutine 现在正在等待,但外部代码继续工作。
- Fire
- 开火
- Starting one more coroutine of 1 second's delay. The coroutine is now waiting, but the outer code keeps working.
- 再启动一个1秒的 Coroutine 延迟。Coroutine 现在正在等待,但外部代码继续工作。
---1 second later---
---1秒后--- - The first coroutine stops waiting and finishes since it's over.
- 第一个 Coroutine 停止等待并完成,因为它结束了。
- The second coroutine stops waiting and finishes since it's over.
- 第二个 Coroutine 停止等待并完成,因为它结束了。
I hope that you now understand what Coroutines are better.
我希望现在你更好地理解了 Coroutines 是如何工作的。
As for the solution, I can suggest this:
至于解决方案,我可以提出以下建议:
public IEnumerator FireCooldown()
public IEnumerator FireCooldown()
void Update()
{
if (Input.GetButton("Fire1"))
{
StartCoroutine(FireCooldown());
}
}
<details>
<summary>英文:</summary>
The problem of your code is not where you expect it to be.
It is here:
if (Input.GetButton("Fire1"))
{
StartCoroutine(WaitASecond());
weaponscript.fireGun();
StartCoroutine(WaitASecond());
}
You seem to think that the IEnumerators and Coroutines make a delay in the whole program.\
Since your `WaitASecond()` function only includes one string with `WaitForSeconds`, it is obvious that you expect this piece of code function like the following:
1) A 1 second's delay
2) Fire
3) A 1 second's delay
However, this is NOT how Coroutines work. A Coroutine does not delay the whole thread or program. It only delays its own code. So explain it, let's take your function `ExecuteAfterTime()` as an example:
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);
}
Here there WILL be a delay before instantiating a projectile, because this piece of code goes INSIDE the Coroutine and after the delay (`WaitForSeconds`). However, the outer code will keep working with no delays.
So, *back to the piece of code where the mistake lies*: in fact, it would work like this:
1) Starting a coroutine of 1 second's delay. The coroutine is now waiting, but the outer code keeps working.
2) Fire
3) Starting one more coroutine of 1 second's delay. The coroutine is now waiting, but the outer code keeps working.
---1 second later---
4) The first coroutine stops waiting and finishes, since it's over.
5) The second coroutine stops waiting and finished, since it's over.
I hope that you now understand what Coroutines are better.
---
As for the solution, I can suggest this:
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
}
The function `FireCooldown()` here goes instead of your `ExecuteAfterTime()`
---
UPD: From the comments I understand that actually you do not need to shoot a projectile every frame. In this case let a Coroutine do all the work:
// 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
}
}
Alternatively you may have a variable `bool doShoot` and the Coroutine will be constantly either shooting or waiting:
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
}
}
In this case the Coroutine should be called somewhere in the beginning of the program, although I can't sure in this solution, since it's been long time I used Unity the last time...
Again, alternatively, you can get by without coroutines, using a cooldown variable. Every frame you'd decrease it: `cooldown -= Time.deltaTime` and check if a character should shoot, i.e. a gun trigger is pulled AND the cooldown <= 0. After the shot, the cooldown returns to its initial value
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论