I'm having this problem with my enemy shooting, you see I'm using raycasting to detected where my player is and once detected I want the enemy to shoot, so far I have accomplished that but however there's not delay between each Instantiated bullet!So the bullet is being constantly spawn rather than a delay in between each spawn. I've tried a whole lot of different solutions to fix this problem but nothing worked! I've tried IEnumerator, object pooling, creating a count down timer and invoke & invokerepeating but still my bullets are still being instantiated instantly without no delay. Does any one knows how to have a delay between each instantiated bullet?? Thank you!
This is my script:
public GameObject bulletPrefab;
public Transform bulletSpawn;
public Transform sightStart, sightEnd;
public bool spotted = false;
void Update()
{
RayCasting ();
Behaviours ();
}
void RayCasting()
{
Debug.DrawLine (sightStart.position, sightEnd.position, Color.red);
spotted = Physics2D.Linecast (sightStart.position, sightEnd.position, 1 << LayerMask.NameToLayer("Player"));
}
void Behaviours()
{
if (spotted == true) {
//Invoke("Fire", cooldownTimer);
Fire ();
} else if (spotted == false) {
//CancelInvoke ();
}
}
void Fire()
{
GameObject bullet = (GameObject)Instantiate (bulletPrefab);
//GameObject bullet = objectPool.GetPooledObject ();
bullet.transform.position = transform.position;
bullet.GetComponent<Rigidbody2D> ().velocity = bullet.transform.up * 14;
Destroy (bullet, 2.0f);
}
You need to throttle the rate at which bullets are spawned. You could base it on number of frames but that's a bad approach as fire rate will vary with game performance.
A better approach is to use a variable to track how much time must elapse before we can fire again. I'll call this "Time To Next Shot" (TTNS)
Decrement TTNS by the time that has elasped.
Check if TTNS is 0 or less
If so:
Set TTNS as appropriate for our desired rate of fire.
Fire a shot
Something like..
private float fireRate = 3f; // Bullets/second
private float timeToNextShot; // How much longer we have to wait.
// [Starts at zero so our first shot is instant]
void Fire() {
// Subtract the time elapsed (since the last frame) from TTNS .
timeToNextShot -= time.deltaTime;
if(timeToNextShot <= 0) {
// Reset the timer to next shot
timeToNextShot = 1/fireRate;
//.... Your code
GameObject bullet = (GameObject)Instantiate (bulletPrefab);
//GameObject bullet = objectPool.GetPooledObject ();
bullet.transform.position = transform.position;
bullet.GetComponent<Rigidbody2D> ().velocity = bullet.transform.up * 14;
Destroy (bullet, 2.0f);
}
}
This does assume that you only call Fire() once per frame (Otherwise the elapsed time will be subtracted from nextShot multiple times).
I opted to do it this way (subtracting slices from a time span) rather than defining an absolute time (wait until time.elapsedTime > x) as the resolution of elapsedTime decreases over time and that approach will result in glitches after a few hours of gameplay.
Related
So I'm currently working on a unity boss fight game where lasers are meant to fire from above on a certain time interval (let's say like 10 seconds) after the player shoots the boss for the first time. What happens is when the boss is hit multiple times, it spawns multiple different laser projectiles. I'm using InvokeRepeating to do this, which I don't think is the most efficient way, so if anyone has any ideas on this, help would be greatly appreciated. Here's the code (it's probably messy I'm pretty new to game development)
using UnityEngine;
public class startboss : MonoBehaviour
{
public bool fight = false;
public Sprite awake;
public bool player_dead = false;
public float spawntime = 10f;
public float spawndelay = 10f;
public bool spawn = false;
// Start is called before the first frame update
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.collider.tag == "Bullet")
{
spawn = true;
fight = true;
if (fight == true)
{
gameObject.GetComponent<SpriteRenderer>().sprite = awake;
if (spawn == true)
{
InvokeRepeating("Shoot", spawntime, spawndelay);
}
}
}
}
public GameObject laserprefab;
public Transform laserpoint;
public Transform laserpoint2;
public float seconds = 10;
public Rigidbody2D rblaser;
// Start is called before the first frame update
// Update is called once per frame
// Start is called before the first frame update
void Shoot()
{
Instantiate(laserprefab, laserpoint.position, laserpoint.rotation);
Instantiate(laserprefab, laserpoint2.position, laserpoint2.rotation);
}
}
It looks like you are creating a new InvokeRepeating("Shoot", spawntime, spawndelay); every time you shoot the boss so it will start another instance of Shoot repeating. So if you shoot the boss 3 times you will have 3 repeating Shoots going on.
You could move the shooting outside of the collision and use a timer to count down instead of InvokeRepeating to have more control. If you need you can set the timer to 0 or stop / start the timer on collision.
//Declair timer and time
float fireTimer = 0.0f;
float fireTime = spawnTime;
void Update(){
//Add time to the timer.
fireTimer += Time.deltaTime;
//If timer = time then fire and reset the timer.
if(fireTimer == fireTime){
Shoot();
fireTimer = 0.0f;
}
}
I'm Making a movement script with vector 2 and Rigidbody.velocity. I want my gameobject to move until a certain x pos and then return to the initial pos, all of that while moving continiously. I achieved the first part, it goes backwards when it reaches a certain point but then the backwards movement never stops:
public float spawnPos;
public float currentPos;
public float constantSpeed = 3;
public float speed = 0;
KeyInput scriptBeholderKI;
Rigidbody2D squadRigidBody;
//Comprovation of spawnposition and KeyInput
//Script adquisition
void Start () {
spawnPos = transform.position.x;
scriptBeholderKI = gameObject.GetComponent <KeyInput> ();
squadRigidBody = gameObject.GetComponent <Rigidbody2D> ();
}
void FixedUpdate () {
//Movement
squadRigidBody.velocity = new Vector2 (constantSpeed + speed , 0);
//key inputs
if (Input.GetKeyDown (scriptBeholderKI.forward)) {
StopAllCoroutines ();
StartCoroutine (RightMovement(0f));
}
if (Input.GetKeyDown (scriptBeholderKI.backwards)) {
StopAllCoroutines ();
StartCoroutine (LeftMovement (0f));
}
}
//Speed values (Right, Left)
IEnumerator RightMovement (float Rloop) {
while (transform.position.x < constantSpeed * Time.time + spawnPos + 14) {
speed = 10f;
yield return new WaitForSeconds (Rloop);
}
if (transform.position.x> constantSpeed * Time.time + spawnPos + 14) {
StopAllCoroutines ();
StartCoroutine (LeftMovement (0f));
}
}
IEnumerator LeftMovement (float Lloop) {
while (transform.position.x > constantSpeed * Time.time + spawnPos) {
speed = -7f;
yield return new WaitForSeconds (Lloop);
}
}
If you also see anything that could be improved pls make me note c:
edit:
when I press the forward input my gameobject moves until it reaches a point, when I press the bacwards one it moves to the left nonstop.
also the thing is that I want my target to move at the constantspeed of the gameobject, thats why I use Time.time. the movements I do with the inputs are more properly said accelerations. I want my target to be always at the same relative distance from the gameobject, since the game starts. At least the maxium reach to the right is the one I want...
And I also tried to remove the stop all coroutines block and nothing, I but my intention was to be able to cancel the movement whenever I want (I removed the one in the RightMovement coroutine, however. It wasn't neecesary after all)
one last thing: I changed "FixedUpdate" for "Update" because it gave me problems with my inputs response, I don't know if it's relevent but just in case.
I think you are mistaking the Time.time variable. Just like yes mentions in his comment
Time.time is the time in seconds since the start of the game.
This means, as time moves by, so will your target, which will slowly but surely move away.
Instead of Time.time you could however use Time.deltaTime
Time.deltaTime is the time in seconds it took to complete the last frame
This is most often used for movement.
When you multiply with Time.deltaTime you essentially express: I want to move this object 10 meters per second instead of 10 meters per frame, if the multiplication is 10.
Unity docs: Time.deltaTime
I'm working on a script, so I could detect when player is not moving for x seconds and load another scene accordingly.
If player started moving again after x seconds, then loading another scene should't be called.
I've tried using isSleeping function and delay it by including Coroutine with WaitForSeconds but it is still checking Rigidbody2D every frame. Is there any other way to check if Rigidbody2D hasn't moved for x seconds and only then load game over level, otherwise continue moving as before?
using UnityEngine;
using System.Collections;
public class PlayerStop : MonoBehaviour {
void Update() {
if (GetComponent<Rigidbody2D>().IsSleeping()) {
Application.LoadLevel(2);
}
}
}
In addition I have a script, which enables me to draw lines (with mouse) and stop player's movement, however lines disappear after x seconds. So for example if I'd set lines to disappear after 1 second, I would like to check if Rigidbody2D stopped moving for 2 seconds and only then load game over scene. Otherwise do nothing as Rigidbody2D will continue moving again after line disappears.
Try this
using UnityEngine;
using System.Collections;
public class PlayerStop : MonoBehaviour {
float delay = 3f;
float threshold = .01f;
void Update() {
if (GetComponent<Rigidbody2D>().velocity.magnitude < threshold * threshold)
StartCoRoutine("LoadTheLevel");
}
IEnumerator LoadTheLevel()
{
float elapsed = 0f;
while (GetComponent<Rigidbody2D>().velocity.magnitude < threshold * threshold)
{
elapsed += Time.deltaTime;
if(elapsed >= delay)
{
Application.LoadLevel(2);
yield break;
}
yield return null;
}
yield break;
}
}
You could try this...I'm not able to test this right now, so may need a little tweaking...
First some private variables:
private float _loadSceneTime;
private Vector3 _lastPlayerPosition;
private float _playerIdleDelay;
And in the Update method check if the player has moved:
private void Update()
{
// Is it time to load the next scene?
if(Time.time >= _loadSceneTime)
{
// Load Scene
}
else
{
// NOTE: GET PLAYERS POSITION...THIS ASSUMES THIS
// SCRIPT IS ON THE GAME OBJECT REPRESENTING THE PLAYER
Vector3 playerPosition = this.transform.position;
// Has the player moved?
if(playerPosition != _lastPlayerPosition)
{
// If the player has moved we will attempt to load
// the scene in x-seconds
_loadSceneTime = Time.time + _playerIdleDelay;
}
_lastPlayerPosition = playerPosition;
}
}
I've been working on this script for the past day. For some reason my character will not jump as long as it's animator is active. I've got into the animation (there is only one) and removed all references to the animation placing a position anywhere and still the issue presides.
I have discovered that I can make my player jump if I use Co-routine which I'm using. However, I'm still new to using them and I can't work out why my player won't fall to the ground once a force has been added to it. And my player only moves up when the button is clicked. Could someone please take a look at my script and tell me what I'm doing wrong?
public float jumpSpeed = 100.0f;
public float jumpHeight = 2.0f;
public AudioClip jumpSound;
private GameObject pos;
private bool moving;
private bool isJumping;
void Start()
{
}
// Update is called once per frame
void Update ()
{
if(Input.GetMouseButtonDown(0))// && !moving)
{
isJumping = true;
StartCoroutine(JumpPlayer(gameObject.transform.localPosition));
}
else
{
isJumping = false;
}
}
IEnumerator JumpPlayer(Vector3 startPos)
{
Vector3 jump = new Vector3(transform.localPosition.x, jumpHeight, transform.localPosition.z);
float t = 0f;
t += Time.deltaTime / jumpSpeed;
rigidbody.AddForce(Vector3.up * jumpSpeed);
//gameObject.transform.localPosition = Vector3.Lerp(startPos, jump, 0.5f);
//isJumping = false;
yield return null;
}
Firstly, your use of coroutine isn't doing anything in particular - because it only does yield return null at the end, it'll run in a single frame and then exit. You could make it a regular void function and you shouldn't see any change in behaviour.
Removing other redundant code and you have just this:
if(Input.GetMouseButtonDown(0))
{
rigidbody.AddForce(Vector3.up * jumpSpeed);
}
This force is added for only a single frame: the frame where the mouse button is pressed down (if you used Input.GetMouseButton instead, you'd see the force applied for multiple frames).
You say "my player only moves up when the button is clicked" but I'm not clear why that's a problem - perhaps you mean that the player should continue to move up for as long as the button is held, in which case you should refer to my previous paragraph.
The most obvious reasons for the player not falling again are related to the RigidBody component: do you have weight & drag set to suitable values? An easy way to test this would be to position your player some distance from the ground at the start of the scene, and ensure that they fall to the ground when you start the scene.
Another reason might be that you're using the default override of .AddForce in an Update cycle. The default behaviour of this method applies force during the FixedUpdate calls, and you might find that using ForceMode.Impulse or ForceMode.VelocityChange gives you the result you're looking for.
So, I've set up a basic script in Unity to move around a 2D sprite, and it works pretty well, except for the fact that occasionally the player-character will not jump when told to. It seems to only happen while or shortly after the character moves horizontally. I really have no idea why this is happening. Hopefully someone else can shed some light on this. Here is the controller script. Any feedback is helpful, even if it's unrelated to the question, I'm doing this as a learning exercise.
using UnityEngine;
using System.Collections;
public class PlayerControlsCs : MonoBehaviour {
public KeyCode walkLeft;
public KeyCode walkRight;
public KeyCode jumpUp;
public float speed = 5;
public float jumpForce = 750;
public int jumpCapacity = 1;
public int extraJumps = 0;
public bool facingRight = true;
public bool grounded = false;
private Transform groundCheck;
private Animator anim;
void Awake () {
groundCheck = transform.Find("GroundCheck");
anim = GetComponent<Animator>();
}
void Update () {
grounded = Physics2D.Linecast(transform.position, groundCheck.position, 1 << LayerMask.NameToLayer("Terrain"));
if(grounded){
anim.SetTrigger("Grounded");
anim.ResetTrigger("Falling");
extraJumps = jumpCapacity;
}
else {
anim.ResetTrigger("Grounded");
anim.SetTrigger("Falling");
}
}
void FixedUpdate () {
anim.SetFloat("Speed", Mathf.Abs(rigidbody2D.velocity.x));
anim.SetFloat("Ascent", rigidbody2D.velocity.y);
if(Input.GetKey(walkLeft))
{
if(facingRight){
Flip();
}
rigidbody2D.velocity = new Vector2(-speed, rigidbody2D.velocity.y);
}
else if(Input.GetKey(walkRight))
{
if(!facingRight){
Flip();
}
rigidbody2D.velocity = new Vector2(speed, rigidbody2D.velocity.y);
}
else
{
rigidbody2D.velocity = new Vector2(0, rigidbody2D.velocity.y);
}
if(Input.GetKeyDown(jumpUp) && grounded)
{
anim.SetTrigger("Jump");
rigidbody2D.velocity = new Vector2(rigidbody2D.velocity.x, 0);
rigidbody2D.AddForce(new Vector2(0f, jumpForce));
}
else if(Input.GetKeyDown(jumpUp) && extraJumps > 0)
{
anim.SetTrigger("Jump");
rigidbody2D.velocity = new Vector2(rigidbody2D.velocity.x, 0);
rigidbody2D.AddForce(new Vector2(0f, jumpForce));
extraJumps -= 1;
}
}
void Flip ()
{
// Switch the way the player is labelled as facing.
facingRight = !facingRight;
// Multiply the player's x local scale by -1.
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
}
If it helps at all, here is what I have made:
https://www.dropbox.com/s/ka4vgc0s0205sbd/test.html
https://www.dropbox.com/s/40i8kltwfz1jgyu/test.unity3d
Building on Max's answer...
You should use FixedUpdate() for physics stuff like applying a force to a RigidBody as it runs 50 times a second regardless of how fast the game is running. This makes it frame rate independent.
See the documentation.
Update() runs once per frame, so is frame rate dependent. In here is where most of your non-physics stuff should go, checking for inputs for example.
This video is a good explanation of the difference.
The link in the comment is also correct:
You need to call this function from the Update function, since the
state gets reset each frame
So check if is grounded only when the player presses jump as ray/linecasts are computationally expensive, apply the physics in FixedUpdate(), and check for input in Update().
Update and FixedUpdate aren't guaranteed to happen every time one after another. I haven't ran into this kind of bugs, so I can't say for sure, but you may experience a situation where your grounded state is incorrect. Instead of saving this value as a field, try checking for it every time you need it — at least a separate check in Update and FixedUpdate.
Input should be handeled in Update, because update runs every frame, while fixed update isn't like update and it doesn't run every frame so when input is handeled in fixed update it might miss the input and it won't jump !
I suggest you cut and paste all the input code from fixed update to update !