I have a waypoint system in a Unity 2D project where the GameObject will follow each waypoint in order and then repeat the process, i am wanting the GameObject to stop at each point for a fixed amount of time and i thought i could achieve this using a coroutine but not entirely sure of how to achieve this, what i have done so far is create a coroutine called WaitAtPoint and then call this on each waypoint movement but to no avail, not sure what i am doing wrong.
public class BrainBoss : MonoBehaviour {
[SerializeField]
Transform[] waypoints;
[SerializeField]
float moveSpeed = 2f;
int waypointIndex = 0;
// Start is called before the first frame update
void Start()
{
transform.position = waypoints[waypointIndex].transform.position;
}
// Update is called once per frame
void Update()
{
Move();
}
void Move()
{
transform.position = Vector2.MoveTowards(transform.position,
waypoints[waypointIndex].transform.position, moveSpeed * Time.deltaTime);
if(transform.position == waypoints[waypointIndex].transform.position)
{
StartCoroutine(WaitAtPoint());
waypointIndex += 1;
}
if(waypointIndex == waypoints.Length)
{
waypointIndex = 1;
}
}
IEnumerator WaitAtPoint()
{
yield return new WaitForSeconds(3f);
}
}
You are calling Move every update and you could also be calling that StartCoroutine multiple times so i suggest using a variable to see if you should even update the movement
public class BrainBoss : MonoBehaviour
{
[SerializeField]
Transform[] waypoints;
[SerializeField]
float moveSpeed = 2f;
int waypointIndex = 0;
private bool shouldMove = true;
// Start is called before the first frame update
void Start() {
transform.position = waypoints[waypointIndex].transform.position;
}
// Update is called once per frame
void Update() {
if (this.shouldMove) {
Move();
}
}
void Move() {
transform.position = Vector2.MoveTowards(transform.position,
waypoints[waypointIndex].transform.position, moveSpeed * Time.deltaTime);
if (transform.position == waypoints[waypointIndex].transform.position) {
StartCoroutine(WaitAtPoint(3));
waypointIndex += 1;
}
if (waypointIndex == waypoints.Length) {
waypointIndex = 1;
}
}
IEnumerator WaitAtPoint(int seconds) {
this.shouldMove = false;
int counter = seconds;
while (counter > 0) {
yield return new WaitForSeconds(1);
counter--;
}
this.shouldMove = true;
}
}
You can use a simble bool flag to know if you should move or not. In Move (or Update), check for that bool to know if you should move or not.
In WaitAtPoint, set the bool (like shouldWait) to true, then back to false after the WaitForSecond !
Well, your WaitAtPoint is not doing an awful lot at the moment. This is because it is waiting inside the IEnumerator, not where you are calling it.
There are various ways to tackle this, but I would suggest using a callback on your IEnumerator which is executed after the waiting time.
Like this:
private bool isWaiting;
void Update() {
if (!isWaiting) {
Move();
}
}
void Move()
{
transform.position = Vector2.MoveTowards(transform.position,
waypoints[waypointIndex].transform.position, moveSpeed * Time.deltaTime);
if(transform.position == waypoints[waypointIndex].transform.position)
{
StartCoroutine(WaitAtPoint(() =>
{
// All code that should be executed after waiting here.
waypointIndex += 1;
}));
}
if(waypointIndex == waypoints.Length)
{
waypointIndex = 1;
}
}
IEnumerator WaitAtPoint(Action callback)
{
isWaiting = true;
yield return new WaitForSeconds(3f);
callback.Invoke();
isWaiting = false;
}
Related
I'm making an endless runner game and i have a question about my player colliding with so,e obstacles, I used Raycast but when i try to debug this collision doesn't occur.
Here my player Code.
public class Player : MonoBehaviour
{
private CharacterController controller;
public float speed;
public float jumpHeight;
private float jumpVelocity;
public float gravity;
public float rayRadius;
public LayerMask layer;
public float horizontalSpeed;
private bool isMovingLeft;
private bool isMovingRight;
private bool isDead;
// Start is called before the first frame update
void Start()
{
controller = GetComponent<CharacterController>();
}
// Update is called once per frame
void Update()
{
Vector3 direction = Vector3.forward * speed;
if(controller.isGrounded)
{
if(Input.GetKeyDown(KeyCode.Space))
{
jumpVelocity = jumpHeight;
}
if(Input.GetKeyDown(KeyCode.RightArrow)&& transform.position.x < 3.58f && !isMovingRight)
{
isMovingRight = true;
StartCoroutine(RightMove());
}
if(Input.GetKeyDown(KeyCode.LeftArrow)&& transform.position.x > -3.58f && !isMovingLeft)
{
isMovingLeft = true;
StartCoroutine(LeftMove());
}
}
else
{
jumpVelocity -= gravity;
}
OnCollision();
direction.y = jumpVelocity;
controller.Move(direction * Time.deltaTime);
}
IEnumerator LeftMove()
{
for(float i = 0; i < 10; i += 0.1f)
{
controller.Move(Vector3.left * Time.deltaTime * horizontalSpeed);
yield return null;
}
isMovingLeft = false;
}
IEnumerator RightMove()
{
for (float i = 0; i < 10; i += 0.1f)
{
controller.Move(Vector3.right * Time.deltaTime * horizontalSpeed);
yield return null;
}
isMovingRight = false;
}
void OnCollision()
//The player will collide with obstacles that have a specific type of layer and dead.
{
RaycastHit hit;
if(Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit, rayRadius, layer) && !isDead)
{
Debug.Log("GameOver!");
speed = 0;
jumpHeight = 0;
isDead = true;
}
}
}
Instead of using raycast inside OnCollision function. You can do this with very much by following these steps:
Add a tag to your obstacles let say its "Obstacle"
Add collider to them
Check when you collide with someone that if it is obstacle or not if yes then you can call your dead function.
void OnCollisionEnter(Collision collision)
{
if(collision.transform.CompareTag("Obstacle")){
Debug.Log("GameOver!");
speed = 0;
jumpHeight = 0;
isDead = true;
}
}
In this way you can detect collision with super ease instead of using raycast.
I just started learning Unity and wanna try to use events/delegates in FlappyBird game.
Flappy bird screenshot
As on this pic, I need to replace text with current score + 1, when the bird triggers collider between pipes.
public class BirdController : MonoBehaviour
{
private SpriteRenderer sr;
private Rigidbody2D rigidBody;
private Animator anim;
[SerializeField]
private float movementXForce, movementYForce, rotationSpeedUp,
rotationSpeedDown;
private float lastPosY;
float rotation;
private string FLY_ANIMATION = "fly";
private void Awake() {
sr = GetComponent<SpriteRenderer>();
rigidBody = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
lastPosY = transform.position.y;
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
fly();
moveRight();
animFly();
}
void fly()
{
if (Input.GetButtonDown("Jump")) {
rigidBody.AddForce(new Vector2(0f, movementYForce),
ForceMode2D.Impulse);
lastPosY = transform.position.y;
}
if (rigidBody.velocity.y < 0 && rigidBody.rotation > -30) {
rotation = -1f * rotationSpeedDown;
transform.Rotate(Vector3.forward * rotation);
} else if (rigidBody.velocity.y >= 0 && rigidBody.rotation < 30) {
rotation = 1f * rotationSpeedUp;
transform.Rotate(Vector3.forward * rotation);
}
}
void moveRight()
{
transform.position += new Vector3(1f, 0f, 0f) * Time.deltaTime *
movementXForce;
}
void animFly()
{
if (lastPosY < transform.position.y) {
anim.SetBool(FLY_ANIMATION, true);
} else {
anim.SetBool(FLY_ANIMATION, false);
}
}
private void OnTriggerEnter2D(Collider2D other) {
if (other.CompareTag("Ground") || other.CompareTag("Trap")) {
Destroy(gameObject);
}
}
private void OnTriggerExit2D(Collider2D other) {
if (other.CompareTag("Score")) {
}
}
}
I am about to add extra field in BirdController, something like
public delegate void OnTriggerScoreLine();
public static event OnTriggerScoreLine onTriggerScoreLine;
Then in OnTriggerExit2D I will
if (onTriggerScoreLine != null) {
OnTriggerScoreLine();
}
After that I will create new script ScoreController and there I will subscribe onTriggerScoreLine on method that will change the score text on score + 1 and also static scoreValue variable
And I just wanted to ask if I correctly understood delegates and events. Is it a good example of its using? Thanks:)
This question already has answers here:
How to make the script wait/sleep in a simple way in unity
(7 answers)
Closed 1 year ago.
Whenever lives get removed by an enemy in my game in unity, the game temporarily freezes. I think it has to do with the line "Thread.Sleep(3000);" but I am not sure of an alternative wait that will not freeze the entire game temporarily.
I would greatly appreciate if you could help me!
Thanks.
using System.Collections;
using System.Collections.Generic;
using System;
using System.Threading;
using UnityEngine;
public class Enemy : MonoBehaviour
{
public Transform attackPoint;
public float attackrange = 0.5f;
public LayerMask playerlayers;
public float speed = 3f;
private Transform target;
bool hitID = false;
private void Update()
{
if (target != null)
{
float step = speed * Time.deltaTime;
transform.position = Vector2.MoveTowards(transform.position, target.position, step);
Collider2D[] hitplayers = Physics2D.OverlapCircleAll(attackPoint.position, attackrange, playerlayers);
foreach (Collider2D player in hitplayers)
{
if (hitID == false)
{
hitID = true;
player.GetComponent<HeartSystem>().TakeDamage(1);
Thread.Sleep(3000);
hitID = false;
}
}
}
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.tag == "Player")
{
target = other.transform;
}
}
private void OnTriggerExit2D(Collider2D other)
{
if (other.gameObject.tag == "Player")
{
target = null;
}
}
void OnDrawGizmosSelected()
{
if (attackPoint == null)
return;
Gizmos.DrawWireSphere(attackPoint.position, attackrange);
}
}
Enemy.cs
using System.Collections;
using UnityEngine;
public class Enemy : MonoBehaviour
{
public Transform attackPoint;
public float attackrange = 0.5f;
public LayerMask playerlayers;
public float speed = 3f;
private Transform _target;
private bool _hitID;
private void Update()
{
if (_target != null)
{
var step = speed * Time.deltaTime;
transform.position = Vector2.MoveTowards(transform.position, _target.position, step);
var hitPlayers = new Collider2D[10];
Physics2D.OverlapCircleNonAlloc(attackPoint.position, attackrange, hitPlayers, playerlayers);
foreach (var player in hitPlayers)
{
if (_hitID == false)
{
StartCoroutine(HitCoroutine(player));
}
}
}
}
private IEnumerator HitCoroutine(Collider2D player)
{
_hitID = true;
player.GetComponent<HeartSystem>().TakeDamage(1);
yield return new WaitForSeconds(3);
_hitID = false;
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("Player"))
{
_target = other.transform;
}
}
private void OnTriggerExit2D(Collider2D other)
{
if (other.gameObject.CompareTag("Player"))
{
_target = null;
}
}
private void OnDrawGizmosSelected()
{
if (attackPoint == null)
return;
Gizmos.DrawWireSphere(attackPoint.position, attackrange);
}
}
Try this.
In a case like this you want to use a coroutine.
You can read about coroutines here:
https://docs.unity3d.com/Manual/Coroutines.html
Unity is single threaded, so you are actually suspending the entire thread the game is running on. Since the frames are updated on this thread, you're even freezing the visual frame updates.
You have a few ways to manage "waiting" in game.
Coroutines:
A process that can be suspended until a later condition is met (including time)
void Update()
{
//Your other code goes here
if (hitID == false)
{
hitID = true;
player.GetComponent<HeartSystem>().TakeDamage(1);
StartCoroutine(ResetHitIDAfterSeconds(3))
}
}
IEnumerator ResetHitIDAfterSeconds(float seconds)
{
yield return new WaitForSeconds(seconds);
hitID = false;
}
The above code allows you to have the same logic, and will kick off a coroutine that waits for 3 seconds before setting hitID back to false.
Time.deltaTime:
You can also track the elapsed time in your Update method and manage your variables actively based on the elapsed time. Time.deltaTime returns the amount of time that elapsed since the previous frame.
public class ConstantRotation : MonoBehaviour
{
public float timeElapsed = 0f;
void Update()
{
//Your other code goes here
if (hitID == false)
{
hitID = true;
player.GetComponent<HeartSystem>().TakeDamage(1);
timeElapsed = 0f;
}
else
{
timeElapsed += Time.deltaTime
if(timeElapsed >= 3.0f)
{
hitId = false;
}
}
}
}
You would likely want to modify the above code a bit to avoid having the logic run on every frame, and only run it on hit.
So im making a clicker game and here is my code. What i want to ask is how to limiting button to click so it can't be clicked by multiple time, because if i clicked it multiple time the speed became too fast
public float downForce;
public float speed;
public int playerHp;
public Text healthText;
Rigidbody2D rb;
CharacterController controller;
void Awake()
{
rb = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
healthText.text = playerHp.ToString();
if (Input.GetMouseButtonDown(0))
{
Jump();
}
if (playerHp < 0)
{
Destroy(this.gameObject);
SceneManager.LoadScene("GameOver");
}
}
public void Jump()
{
rb.AddForce(Vector2.up * downForce + Vector2.right * speed, ForceMode2D.Impulse);
rb.isKinematic = false;
}
public Button BTN;
public float btnDelay = .5f;
this to get the Button reference and specify the duration
coroutine = ButtonDelayed(btnDelay);
StartCoroutine(coroutine);
this after you call Jump(); in your update or in Jump()
IEnumerator ButtonDelayed(float delay)
{
BTN.interactable = false;
yield return new WaitForSeconds(delay);
BTN.interactable = !BTN.interactable;
}
this somewhere.
Just a quick mockup. Not sure if you will get an exception. If you have a problem just hit me up.
EDIT: I forgot to tell you to change the color of the disabled state in the inspector to the color you have when the Button is interactable. Otherwise you will see the Button change colors.
EDIT2: Full script updated
public float downForce;
public float speed;
public int playerHp;
public Text healthText;
Rigidbody2D rb;
CharacterController controller;
public Button BTN;
public float btnDelay = .5f;
void Awake()
{
rb = GetComponent<Rigidbody2D>();
}
void Update()
{
healthText.text = playerHp.ToString();
if (Input.GetMouseButtonDown(0))
{
Jump();
}
if (playerHp < 0)
{
Destroy(this.gameObject);
SceneManager.LoadScene("GameOver");
}
}
public void Jump()
{
coroutine = ButtonDelayed(btnDelay);
StartCoroutine(coroutine);
rb.AddForce(Vector2.up * downForce + Vector2.right * speed,
ForceMode2D.Impulse);
rb.isKinematic = false;
}
IEnumerator ButtonDelayed(float delay)
{
BTN.interactable = false;
yield return new WaitForSeconds(delay);
BTN.interactable = !BTN.interactable;
}
I still can't reply to comments so I'll post it here
To use IEnumerator is just like any other function
you put it somewhere in your code and then call it
the difference is when you call it you add
StartCoroutine(MethodName()) ;
and it will run the first part of the code - then Wait For the amount of time you specified, then it will run the second part of the code
I have created an IEnumerator so that the enemy doesn't kill the player immediately on contact. the code worked fine yesterday, but now the IEnumerator seems to be completely ignored. The Unity 3d console is not showing any errors either.
I have tried to lower the damage amount to check if maybe it was too high but it wasn't the case.
the following is the code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class enemyAI : MonoBehaviour {
[SerializeField]
float chaseDistance = 5.0f;
public float damageAmount = 1.0f;
void Update () {
dist = Vector3.Distance(target.position, transform.position);
float distance = Vector3.Distance(transform.position, target.position);
if (distance < chaseDistance )
{
AttackPlayer();
}
}
void AttackPlayer()
{
agent.updateRotation = false;
Vector3 direction = target.position - transform.position;
direction.y = 0;
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), turnSpeed * Time.deltaTime);
agent.updatePosition = false;
anim.SetBool("isWalking", false);
anim.SetBool("isAttacking", true);
StartCoroutine(AttackTime());
}
IEnumerator AttackTime()
{
canAttack = false;
yield return new WaitForSeconds(0.5f);
Player.singleton.Damage(damageAmount);
yield return new WaitForSeconds(2.0f);
canAttack = true;
}
}
//Player Script {
public class Player : MonoBehaviour {
public static Player singleton;
public float currentHealth;
public static float maxHealth = 100f;
public bool isDead = false;
private void Awake()
{
singleton = this;
}
// Use this for initialization
void Start () {
currentHealth = maxHealth;
}
// Update is called once per frame
void Update () {
if (currentHealth < 0)
{
currentHealth = 0;
}
}
public void Damage(float damage)
{
if(currentHealth > 0)
{
currentHealth -= damage;
}
else
{
currentHealth = 0;
}
}
void Dead()
{
currentHealth = 0;
isDead = true;
}
}
You are starting the "AttackPlayer" Coroutine in "Update()" - so when the enemy is in range, you will start ~60 Coroutines per second. While you want a single one.
You are already setting "canAttack" to "false" - maybe add "&& canAttack" to your range-condition in Update?
Like
if (distance < chaseDistance && canAttack)
{
AttackPlayer();
}
Try putting "canAttack = false;" beneath yield command