I have a problem, I am trying to make the enemy shoot at the player every 5 seconds and right now it is just shooting lots of bullets constantly in a line.
This is my code, I would really appreciate some help!
void ShootAtPlayer()
{
StartCoroutine(bulletshooting());
IEnumerator bulletshooting()
{
shooting = true;
if (shooting == true)
{
GameObject tempBullet = Instantiate(enemyBullet, eyes.gameObject.transform.position, eyes.gameObject.transform.rotation) as GameObject; //shoots from enemies eyes
Rigidbody tempRigidBodyBullet = tempBullet.GetComponent<Rigidbody>();
tempRigidBodyBullet.AddForce(tempRigidBodyBullet.transform.forward * enemyBulletSpeed);
Destroy(tempBullet, 0.1f);
yield return new WaitForSeconds(5f);
}
shooting = false;
}
From your comment
shootAtPlayer is called in the update when the enemy is close enough to the player
The issue is: You are starting a new Coroutine every frame!
In your case I wouldn't use a Coroutine at all since you already have a code that is executed every frame. (Because otherwise you would need some code to also stop the running routine)
Rather use a simple timer:
[SerializeField] private float cooldown = 5;
private float cooldownTimer;
void ShootAtPlayer()
{
cooldownTimer -= Time.deltaTime;
if(cooldownTimer > 0) return;
cooldownTimer = cooldown;
GameObject tempBullet = Instantiate(enemyBullet, eyes.gameObject.transform.position, eyes.gameObject.transform.rotation) as GameObject; //shoots from enemies eyes
Rigidbody tempRigidBodyBullet = tempBullet.GetComponent<Rigidbody>();
tempRigidBodyBullet.AddForce(tempRigidBodyBullet.transform.forward * enemyBulletSpeed);
Destroy(tempBullet, 0.1f);
}
If you really want to use a Coroutine for that it would probably look like
private bool isShooting;
void ShootAtPlayer()
{
// Only allow a new routine if there is none running already
if(isShooting) return;
StartCoroutine (shootRoutine());
}
private IEnumerator shootRoutine()
{
// Just in case avoid concurrent routines
if(isShooting) yield break;
isShooting = true;
GameObject tempBullet = Instantiate(enemyBullet, eyes.gameObject.transform.position, eyes.gameObject.transform.rotation) as GameObject; //shoots from enemies eyes
Rigidbody tempRigidBodyBullet = tempBullet.GetComponent<Rigidbody>();
tempRigidBodyBullet.AddForce(tempRigidBodyBullet.transform.forward * enemyBulletSpeed);
Destroy(tempBullet, 0.1f);
yield return new WaitForSeconds (5f);
isShooting = false;
}
Related
I'm new at Unity and C#. I started this 2D game few weeks ago.
So basically what I want to do is when the player is hit by "FrostBullet", it should deal the damage and slow that player over time. In my script the player is hit by FrostBullet, it deals the damage and the slow is constant, not over time.
I want it to be like, when the player is hit by FrostBullet, the player should be slowed for 2 sec and than his movement speed is back to normal.
So this is how my code look like:
public class FrostHit : MonoBehaviour
{
float maxS = 40f;
float slowSpeed = 20f;
public Rigidbody2D rb;
int damage = -5;
int timeOfReducedSpeed = 2;
bool isHit = false;
void Start()
{
rb.velocity = transform.right * slowSpeed;
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.tag == "Player_Njinja")
{
HealthNjinja healthNjinja = other.gameObject.GetComponent<HealthNjinja>();
healthNjinja.ModifyHealth(damage);
PlayerMovementNjinja reduceMoveSpeed = other.gameObject.GetComponent<PlayerMovementNjinja>();
reduceMoveSpeed.runSpeed = slowSpeed;
Destroy(gameObject);
}
PlayerMovementKnight maxSpeed = other.gameObject.GetComponent<PlayerMovementKnight>();
maxSpeed.runSpeed = maxS;
HealthKnight healthKnight = other.gameObject.GetComponent<HealthKnight>();
if (other.gameObject.tag == "Player_Knight")
{
isHit = true;
healthKnight.ModifyHealth(damage);
StartCoroutine(speedTime());
Destroy(gameObject);
}
IEnumerator speedTime()
{
while (isHit == true)
{
slowPlayer();
yield return new WaitForSeconds(timeOfReducedSpeed);
revertSpeed();
}
}
void slowPlayer()
{
maxSpeed.runSpeed = slowSpeed;
}
void revertSpeed()
{
maxSpeed.runSpeed = maxS;
}
}
}
Also my PlayerMovement code:
public class PlayerMovementKnight : MonoBehaviour
{
public CharacterController2D controller;
public Animator animator;
public float runSpeed = 40f;
float horizontalMove = 0f;
bool jump = false;
bool crouch = false;
public WeaponKnight Bullet;
public bool grounded;
void Update()
{
horizontalMove = Input.GetAxisRaw("Horizontal") * runSpeed;
animator.SetFloat("Speed", Mathf.Abs(horizontalMove));
if (Input.GetButtonDown("Jump"))
{
jump = true;
animator.SetBool("IsJumping", true);
}
if (Input.GetButtonDown("Crouch"))
{
crouch = true;
}
else if (Input.GetButtonUp("Crouch"))
{
crouch = false;
}
if (grounded && GetComponent<Bullet>().knockBack == false)
{
GetComponent<Rigidbody2D>().velocity = new Vector2(0, 0);
}
}
public void OnLanding()
{
animator.SetBool("IsJumping", false);
}
public void OnCrouching(bool isCrouching)
{
animator.SetBool("IsCrouching", isCrouching);
}
void FixedUpdate()
{
controller.Move(horizontalMove * Time.fixedDeltaTime, crouch, jump);
jump = false;
}
}
The issue seems to be that your bool isHit, that is set to true before calling the Coroutine, is never reset to false.
So the loop:
while (isHit == true)
{
slowPlayer();
yield return new WaitForSeconds(timeOfReducedSpeed);
revertSpeed();
}
is really an infinite loop.
You might want to reset the bool to false somewhere.
Edit:
Following to comments, I see now that your script is attached to the bullet GameObject. After starting the Coroutine, and slowing down the player, you Destroy(gameObject);
After that, the script gets destroyed too, and the Coroutine is therefore stopped...and nothing remains to restore player’s speed back to normal.
You should move the Coroutine to the player’s script, and start it when needed. This way, it would still exist after you destroy the bullet.
Your bool isHit is never set to false. Resulting in the Player being slowed and then being reset forever after he get's hit once.
As well as creating isHit in the first place isn't needed. Why would you need to check that the Player has been hit when you enter the IEnumerator only when the Player_Knight has been hit anyway.
Code Example:
public class FrostHit : MonoBehaviour
{
float maxS = 40f;
float slowSpeed = 20f;
public Rigidbody2D rb;
int damage = -5;
int timeOfReducedSpeed = 2;
void Start()
{
rb.velocity = transform.right * slowSpeed;
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.tag == "Player_Njinja")
{
HealthNjinja healthNjinja = other.gameObject.GetComponent<HealthNjinja>();
healthNjinja.ModifyHealth(damage);
PlayerMovementNjinja mSpeedNinja = other.gameObject.GetComponent<PlayerMovementNjinja>();
StartCoroutine(speedTimeNinja());
}
else if (other.gameObject.tag == "Player_Knight")
{
HealthKnight healthKnight = other.gameObject.GetComponent<HealthKnight>();
healthKnight.ModifyHealth(damage);
PlayerMovementKnight mSpeedKnight = other.gameObject.GetComponent<PlayerMovementKnight>();
StartCoroutine(speedTimeKnight());
}
// Destroy Bullet
Destroy(gameObject);
}
IEnumerator speedTimeKnight()
{
mSpeedKnight.runSpeed = slowSpeed;
yield return new WaitForSeconds(timeOfReducedSpeed);
mSpeedKnight.runSpeed = maxS;
}
IEnumerator speedTimeNinja()
{
mSpeedNinja.runSpeed = slowSpeed;
yield return new WaitForSeconds(timeOfReducedSpeed);
mSpeedNinja.runSpeed = maxS;
}
}
Things I've changed and the reason:
Removed bool as well as while loop, didn't solve any real purpose
Seperated IEnumerator and 2 Functions from inside OnTriggerEnter2D()
Added Destroy(gameObject); to the end, because the bullets always gets destroyed anyway
Removed maxSpeed.runSpeed = maxS;, because the speed get's set/reset already anyway. There is no reason to reset the speed after we collided with the bullet.
Put GetComponent for Player_Knight into the else if, there is no reason to execute the GetComponent even if the Player_Njinja gets hit.
Removed reduceMoveSpeed.runSpeed = slowSpeed; the Player_Njinja only gets slowed and never regains his speed. Instead use the IEnumerator again
Added another IEnumerator so that both Player_Njinja and Player_Knight get slowed and unslowed correctly (it would now be even possible to create different slow times depending on which player gets hit)
Removed functions, it's not really worth it to write a function for one line of code. Especially when you can just set it in IEnumerator without needing more lines of code
I am making a small game that is based on the Roll-A-Ball tutorial from Unity, though I haven't used the actual tutorial. I incorporated a respawn mechanic, but if you are moving around when you die, then after you respawn, you still have that momentum after you land. I have tried to fix this, but I am not sure how since I am still pretty new at using Unity. I have a video that shows this: https://drive.google.com/open?id=1752bPBDVOe2emN_hmnlPaD4uaJQITpsP
Here is the C# script that handles respawn:
public class PlayerBehavior : MonoBehaviour
{
Rigidbody PlayerRB;
public bool Dead;
private int timer;
public GameObject Particles;
public bool InRespawn;
void Update()
{
PlayerRB = GetComponent<Rigidbody>();
if (Dead)
{
StartCoroutine("Respawn");
}
}
IEnumerator Respawn()
{
InRespawn = true; //Used to prevent movement during respawn.
PlayerRB.useGravity = false;
transform.position = new Vector3(0, 4, 0);
transform.rotation = new Quaternion(-80, 0, 0, 0); // Resets position.
Dead = false;
Instantiate(Particles, transform); // Adds respawn particle effect.
yield return new WaitForSeconds(2);
Destroy(this.gameObject.transform.GetChild(0).gameObject);
PlayerRB.useGravity = true;
PlayerRB.AddForce(0, 400, 0); // Does a little hop.
InRespawn = false; // Tells the game that respawn is finished.
}
}
Zero out the rigidbody's velocity when the respawn occurs:
IEnumerator Respawn()
{
PlayerRB.velocity = Vector3.zero;
// ... rest of method
}
As a sidenote, you probably don't need to run GetComponent on every frame. It's an expensive operation so it's best to do it as infrequently as you can get away with:
void Start()
{
PlayerRB = GetComponent<Rigidbody>();
}
void Update()
{
if (Dead)
{
StartCoroutine("Respawn");
}
}
If instead you would like to disable all physics interactions with the player while it is dead, you can set it to be kinematic during that time. Just be sure to unset isKinematic before adding force to it.
IEnumerator Respawn()
{
PlayerRB.isKinematic = true;
// ... rest of method
PlayerRB.isKinematic = false;
PlayerRB.useGravity = true;
PlayerRB.AddForce(0, 400, 0); // Does a little hop.
InRespawn = false; // Tells the game that respawn is finished.
}
make a bool in your code like this
bool isDead=false;
then make it true when you die
the add this into you update
if(isDead){
rb.velocity=vector3.zero;
}
this will stop you objects movements if it is dead
I'm coding my games boss behaviour and in the final stage of the battle the boss is supposed to charge towards the player and then move back to its original position. Wait for 5 seconds and then do the same.
I tried to achieve this using coroutines and Vector2.MoveTowards() but am not getting the desired effect, first off the boss does not "Move Towards" the player but instantly appears at the targetPosition and then just stays there, does not move back.
Below is my code:
private Vector2 chargeTarget;
private Vector2 tankStartPosition;
void Start()
{
chargeTarget = new Vector2(-5.0f, transform.position.y);
tankStartPosition = transform.position;
}
void Update()
{
if (Time.time > nextCharge)
{
StartCoroutine(TankCharge());
nextCharge = Time.time + chargeRate;
}
}
IEnumerator TankCharge()
{
transform.position = Vector2.MoveTowards(tankStartPosition, chargeTarget, Time.deltaTime * chargeSpeed);
transform.position = Vector2.MoveTowards(chargeTarget, tankStartPosition, Time.deltaTime * returnSpeed);
}
Any idea what I am doing wrong here? And how to get my desired action?
Thank you
Calling MoveTowards once only moves the game object once during that iteration of the game loop. Calling MoveTowards once doesn't move the game object all the way to its target (unless the maxDistanceDelta parameter is big enough to move the game object to its target in one iteration).
If the boss is instantly appearing at the target, I'm guessing your chargeSpeed is too big.
What you want to do is call MoveTowards once per Update cycle. However, the way you're doing your coroutine, the coroutine will only move the game object once and then exit. Normally coroutines will have a loop within them (otherwise the coroutine will exit after running once). Something like this:
IEnumerator TankCharge()
{
while (Vector3.Distance(transform.position, chargeTarget.position) > Mathf.Epsilon)
{
// Adjust this so this game object doesn't move the entire
// distance in one iteration
float distanceToMove = Time.deltaTime * chargeSpeed;
transform.position = Vector3.MoveTowards(transform.position, chargeTarget.position, distanceToMove)
yield return null;
}
}
However, for your situation, you don't really need a coroutine. You can just do this directly in Update()
private bool returnToStart = false;
private float timer;
void Update
{
float distanceToMove = Time.deltaTime * chargeSpeed;
if (timer <= 0)
{
if (!returnToStart)
{
transform.position = Vector3.MoveTowards(transform.position, chargeTarget.position, distanceToMove)
// Target reached? If so, start moving back to the original position
if (Vector3.Distance(transform.position, chargeTarget.position) <= Mathf.Epsilon)
{
returnToStart = true;
this.timer = this.chargeRate;
}
}
else
{
transform.position = Vector3.MoveTowards(transform.position, tankStartPosition.position, distanceToMove)
// Original position reached? If so, start moving to the target
if (Vector3.Distance(transform.position, tankStartPosition.position) <= Mathf.Epsilon)
{
returnToStart = false;
this.timer = this.chargeRate;
}
}
}
else
{
this.timer -= Time.time;
}
}
I make effect for my enemy if the player is near to the enemy decrease the player speed its work fine but if there's one of the same enemies in the filed has the same script or work if there's more all of them are near to the player not work if there's more then one of the same enemy one near to the the player and the other not here's the question
how to make enemy does the effect if there's more then one of the same enemy that has the same effect and only one near the player or more ?
here is my effect script
void Update () {
if (Vector3.Distance (target.position, transform.position) < 20) {
// if the enemy near effect
player.speed = 5f;
} else {
player.speed = 10f;
}
Here is a solution that doesn't require you to add or change the location of scripts and/or add any more components to the object.
By using a short buffer of time and only removing the slow if the "enemy" was previously slowing, you can do without the extra steps.
This is not the BEST way of doing this, some great suggestions in the comments, but it is A way of doing this.
Tested and confirmed to work in Unity.
private float timeUntilSlowEnds = 0;
private bool isSlowing = false;
void Update ()
{
if (Vector3.Distance (target.position, transform.position) < 20)
{
//1/4 second of slow time
timeUntilSlowEnds = 0.25f;
isSlowing = true;
}
if(timeUntilSlowEnds <= 0)
{
if(isSlowing)
{
//only reset player speed if ending slow
isSlowing = false;
player.speed = 10.0f;
}
}
else
{
//slow the player each frame and decrease the timer
player.speed = 5.0f;
timeUntilSlowEnds -= Time.deltaTime;
}
}
Basically I want to make a player that can transform into demon at will whenever the user press the power-up button however I want the transformation to end after 60 seconds (when the transformation ends I want the player revert back to his original state). I also want the transformation to end if the player gets hit by an enemy. So far I've made this code and it works but I'm having trouble resetting the yield wait for seconds back to 60 seconds when if the player gets hit by an enemy and if the user decided to press the button to transform the player back into a demon. Can anyone help me with this problem?
In my hierarchy I have my player as the parent and my demon player as the child. A playermovement script attached to the player as well as the transformation script below:
public GameObject demon;
public BoxCollider2D col;
public Renderer rend;
public ParticleSystem par1;
public static Vector3 target;
void Start () {
target = transform.position;
}
void Update () {
target.z = transform.position.z;
}
public void DemonCharacter() {
StartCoroutine (PowerUpCoroutine ());
}
private IEnumerator PowerUpCoroutine() {
yield return new WaitForSeconds (0.3f);
par1.Play (); // particle system animation to cover transformation happening
par1.transform.position = target;
yield return new WaitForSeconds (0.2f);
demon.SetActive (true); // activates demon gameobject
rend.enabled = false; // deactivate players spriterenderer
col.enabled = false;
yield return new WaitForSeconds (60f);
demon.SetActive (false); // deactivates demon gameobject
rend.enabled = true; // activate players spriterenderer
col.enabled = true;
par1.Stop ();
}
And on my demon player, I attached this script;
I works but when the user clicks on the button to transform into a demon the yield waitforseconds doesn't stop, so when the player transform into a demon seconds later the demon player transforms back into the player rather than resetting the yield wait for seconds.
public BoxCollider2D Playercol;
public Renderer PlayerRend;
void Start()
{
}
void Update ()
{
}
void OnTriggerEnter2D(Collider2D col) {
if (col.tag == "enemy") {
demon.SetActive (false);
PlayerRend.enabled = true;
Playercol.enabled = true;
}
}
Another way than what #m.rogalski suggested would be to use a simple float variable as timer:
public GameObject demon;
public BoxCollider2D col;
public Renderer rend;
public ParticleSystem par1;
public static Vector3 target;
private float demonTimer;
void Start()
{
target = transform.position;
demonTimer = 0.0f;
}
void Update()
{
target.z = transform.position.z;
if (demonTimer > 0.0f)
{
demonTimer -= Time.deltaTime;
if (demonTimer <= 0.0f)
{
demon.SetActive(false);
rend.enabled = true;
col.enabled = true;
}
}
}
public void DemonCharacter()
{
par1.Play();
par1.transform.position = target;
demon.SetActive(true);
rend.enabled = false;
col.enabled = false;
demonTimer = 60.0f;
}
public void CancelDemon()
{
demonTimer = 0.0f;
}
Hope this helps,
My suggestion would be for you to modify your Coroutine to not use WaitForSeconds but use it's own timing calculations.
// create the flag indicating interruption
bool _interrupt = false;
// create your coroutine
IEnumerator PowerUpCoroutine()
{
// set the time you want to hold transformation
const float TRANSFORMATION_INTERVAL = 60.0f;
// currently elapsed time
float currentlyElapsed = 0.0f;
// add your logic for pre-transformation
while ( currentlyElapsed < TRANSFORMATION_INTERVAL && !_interrupt )
{
yield return null;
currentlyElapsed += Time.deltaTime;
}
// add post-transformation logic
// revert transformation process
_interrupt = false;
}
Now if you run this coroutine calling StartCoroutine(PowerUpCoroutine()); you can interrupt it setting _interrupt flag to true. eg :
public void Interrupt()
{
_interrupt = true;
}
// in some update :
if ( gotHitThisFrame == true )
Interrupt();