I have previously asked about this question, and whilst doing some debugging, I have finally found the issue with my code, however I'm not sure how to go about fixing it.
I have created a spawn system that spawns cyclists and instantly begin to cycle them forwards, I have 1 script that makes each cyclist move forwards, and another that makes them stop and continue off when another player or cyclist is in front of it, THIS is the script that has the issue.
To quickly summarise, whenever a cyclist(a) is stopped by a player, and another cyclist(b) get stopped by the cyclist in front, a boolean triggers which turns playerInside and cyclistInside to true. This is fine. However once the player moves out the way, the first cyclist(a) has playerInside set to false, which is correct, but cyclistInside is still set to true. This is because the cyclist behind him is still detecting the one in front. So it doesn't seem like the scripts are working independently for each cyclist.
I've spent some time debugging to really find the issue here, and this is what I've found.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class cyclistStoping : MonoBehaviour
{
public VehicleMove cyclistMovement;
public Animator CycleAnims;
// bools
public bool playerInside;
public bool cyclistInside;
private void Start()
{
playerInside = false;
cyclistInside = false;
}
// implement delay
private void Update()
{
if (playerInside == false && cyclistInside == false)
{
StartCoroutine(timeDelay());
}
}
// player or cyclist (INSIDE)
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Player" ) {
playerInside = true;
cyclistMovement.vehicleMovement = 0.0f;
}
else if (other.gameObject.tag == "Cyclist")
{
cyclistInside = true;
cyclistMovement.vehicleMovement = 0.0f;
}
}
private void OnTriggerExit(Collider other)
{
if (other.gameObject.tag == "Player")
{
playerInside = false;
}
else if (other.gameObject.tag == "Cyclist")
{
cyclistInside = false;
}
}
IEnumerator timeDelay()
{
// wait before moving off
yield return new WaitForSeconds(2);
if (playerInside == false && cyclistInside == false)
{
cyclistMovement.vehicleMovement = 0.1f;
StopAllCoroutines();
}
}
}
At this point, once the player moves away from the cyclist, the first cyclist (and the ones waiting behind) won't move off, because they are waiting for the first cyclist to move, which won't move due to the cyclistInside being set to true even though there's no cyclist infront.
I expect the cyclist to move off once the player is no longer in front of it, and so fourth with the rest of them.
Related
In my game I have a shooting enemy that I want to attack the player only when he's in range, and to attack him once every 3 seconds. This is the code I need repeated
Whatever I try doing just results in the enemy shooting a metric ton of bullets at the player to the point when it sometimes crashes my project(tried for loops, while loops, played with booleans and timing with Time.deltaTime). Can someone smarter than me give me some pointers how I could do this?
You'd probably want to do the check not every frame but whenever the state changes. So you cache the player.
Transform target;
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
target = other.transform;
}
private void OnTriggerExit2D(Collider2D other)
{
if (other.CompareTag("Player"))
target = null;
}
Whenever target is not null, you may shoot to it.
private void Update()
{
if (target != null)
ShootAt(target.position);
}
private void ShootAt(Vector3 position)
{
// Spawn your stuff here to shoot at the given location
}
Now how about the timing? I am not a fan of coroutines in Unity. They tend to get messy, are overused and the "WaitForSeconds" is not very precise. It just waits until the time has passed, but not exactly. Maybe you want more precision, who knows?
So let's extend the Update call to have a small timer.
private float shootCooldown;
private void Update()
{
if (target != null)
{
shootCooldown -= Time.deltaTime;
if (shootCooldown <= 0.0f)
{
shootCooldown += 3.0f;
ShootAt(target.position);
}
}
}
One last thing: If you want the timer to reset at some point, just set it to 0.0f. A good location for that would be when the player leaves the trigger. Also, maybe you want the timer to continue to go down, even if the target is null, so move the shootCooldown -= Time.deltaTime line out of the if-scope but make sure it does not count down forever, but rests at 0.
I would avoid using OnTriggerStay2D() and use OnTriggerEnter2D and OnTriggerExit2D in this situation. You need to call the 'yield return' statement within a loop as shown below. The below code should do what you are asking.
private void OnTriggerEnter2D(Collider2D other) {
if (other.tag == "Player") {
StartCoroutine(ShootAtPlayer());
}
}
private void OnTriggerExit2D(Collider2D other) {
if (other.tag == "Player") {
StopAllCoroutines();
}
}
private IEnumerator ShootAtPlayer() {
while (true) {
Instantiate(enemyBullet, enemy.transform.localPosition, transform.LocalRotation);
yield return new WaitForSeconds(3.0f);
}
}
I'm making a game with unity 2022 and was struggling to make this animator [bool turn true] after entering a trigger and pressing "E". and then go false after still holding "E" still or not pressing it. also am struggling with Get Key stuff. it isn't working mate and I have been trying for hours at a time with struggles so yeah. this is my code. it's basically someone pressing a button (the object in unity I'm using), making it click until you click it again, please help this is for a school summer project!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
class Fixedpress : MonoBehaviour
{
public Animator Tributton;
bool YUIp = false;
// Start is called before the first frame update
void OnTriggerEnter(Collider other)
{
if(other.tag == "player")
{
YUIp = true;
}
}
void OnTriggerStay(Collider other)
{
if (other.tag == "Player" && YUIp == true)
{
if (Input.GetKeyDown(KeyCode.E))
{
Tributton.SetBool("Fiveyon", true);
Debug.Log("THY");
}
else if (Input.GetKey(KeyCode.E))
{
Tributton.SetBool("Fiveyon", false);
Debug.Log("THe");
}
else if (Input.GetKeyUp(KeyCode.Return))
{
Tributton.SetBool("Fiveyon", false);
Debug.Log("THane");
}
}
}
void OnTriggerExit(Collider other)
{
if(other.tag == "Player")
{
YUIp = false;
}
}
}
I mostly need help with the press e code stuff to make work so yeah :D
thank you
sorry for the bad grammar its 3 am rn
I think I got it now.
You want
Player has to be in the trigger in order to start the animation
Pressing E should trigger the animation only ONCE
I would no use a Bool parameter in that case but rather a Trigger and then Animator.SetTrigger.
Your transitions should be somewhat like e.g.
Idle State --condition-Fiveyon--> Your Animation
Your Animation --exit-time-no-further-condition--> Idle State
Then you could probably do something like e.g.
public class Fixedpress : MonoBehaviour
{
public Animator Tributton;
private Coroutine routine;
private void OnTriggerEnter(Collider other)
{
// not sure if needed right now, some of the events are triggered even on disabled components
if(!enabled) return;
// in general rather use CompareTag instead of ==
// it is slightly faster and also shows an error if the tag doesn't exist instead of failing silent
if(!other.CompareTag("player")) return;
// just in case to prevent concurrent routines
if(routine != null) StopCoroutine(routine);
// start a new Coroutine
routine = StartCoroutine(WaitForKeyPress());
}
private IEnumerator WaitForKeyPress()
{
// check each FRAME if the key goes down
// This is way more reliable as OnTriggerStay which is called
// in the physics loop and might skip some frames
// This also prevents from holding E while entering the trigger, it needs to go newly down
yield return new WaitUntil(() => Input.GetKeyDown(KeyCode.E));
// set the trigger once and finish the routine
// There is no way to trigger twice except exit the trigger and enter again now
Tributton.SetTrigger("Fiveyon");
Debug.Log("Fiveyon!");
// If you even want to prevent this from getting triggered ever again simply add
enabled = false;
// Now this can only be triggered ONCE for the entire lifecycle of this component
// (except you enable it from the outside again of course)
}
void OnTriggerExit(Collider other)
{
if(!other.CompareTag("player")) return;
// when exiting the trigger stop the routine so later button press is not handled
if(routine != null) StopCoroutine(routine);
}
}
I'm writing an Asteroid clone and I am running up against an issue with re-spawning my player object after it is deactivated on collision with an asteroid. I run the SetActive(false) function to turn off the object from within the player object and when I try to use SetActive(true) from the GameManager to reactivate the Player object it doesn't work. Any help would be appreciated.
Here is the section of code in the Player script where I turn off the Player object:
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "Asteroid")
{
_rigidbody.velocity = Vector3.zero;
_rigidbody.angularVelocity = 0.0f;
this.gameObject.SetActive(false);
GameManager.Instance.PlayerDeath();
}
and here is the section of the GameManager where I try to reactivate the Player object:
public void PlayerDeath()
{
this.lives--;
if (this.lives <= 0)
{
GameOver();
}
else
{
//Invoke(nameof(Respawn), this.respawnTimer);
StartCoroutine(Respawn(respawnTimer));
}
}
public IEnumerator Respawn(float f)
{
yield return new WaitForSeconds(f);
this.player.transform.position = Vector3.zero;
this.player.gameObject.SetActive(true);
}
Is your GameOver method will call IEnumerator Respawn?
if not then I think that's your problem, cause what I know so far is that
an if statement will executed and if it's True if not True then it will be an Else statement, in your case your IEnumerator was inside Else statement try to move it above.
Overview
Using Unity2D 2019.3.5, I am making a platformer game using C#. I implemented raycast to detect when my player is touching the ground and attempted to make it only so the player can jump only once.
Problem
Although I thought I programmed my character to jump once, after the first jump, the Unity engine still shows a checkmark to my "isGrounded" variable and only turns to false (unchecked) after a second jump before hitting the ground.
My Code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player_Controller : MonoBehaviour
{
public int playerSpeed = 10;
public int playerJumpPower = 1250;
private float moveX;
public bool isGrounded;
public float distanceToBottomOfPlayer = .7f;
// Update is called once per frame
void Update()
{
PlayerMove();
PlayerRaycast();
}
void PlayerMove()
{
// CONTROLS
moveX = Input.GetAxis("Horizontal");
if (Input.GetButtonDown("Jump") && isGrounded == true)
{
Jump();
}
// ANIMATIONS
// PLAYER DIRECTION
if (moveX < 0.0f)
{
GetComponent<SpriteRenderer>().flipX = true;
}
else if (moveX > 0.0f)
{
GetComponent<SpriteRenderer>().flipX = false;
}
// PHYSICS
gameObject.GetComponent<Rigidbody2D>().velocity = new Vector2(moveX * playerSpeed,
gameObject.GetComponent<Rigidbody2D>().velocity.y);
}
void Jump()
{
GetComponent<Rigidbody2D>().AddForce(Vector2.up * playerJumpPower);
isGrounded = false;
}
void PlayerRaycast()
{
// Ray Down
RaycastHit2D rayDown = Physics2D.Raycast(transform.position, Vector2.down);
if (rayDown.collider != null && rayDown.distance < distanceToBottomOfPlayer &&
rayDown.collider.tag == "ground")
{
isGrounded = true;
}
}
}
Extra Info
I did have to change a Unity setting in Edit > Project Settings > Physics 2D > Queries Start In Colliders. I had to turn this setting off (uncheck) in order to get my player to jump using the code I wrote above. I know there are other ways of making my player jump, however, this seemed to be the most efficient while maintaining the readability of the code.
Solutions Tried
What I believe the problem is that I have a raycast issue that I don't know how to fix. I looked at other Stack Overflow posts including the ones recommended after writing this post, but none of them applied to my problem.
Final Notes
As I said before, I know there are other ways to make my player jump only once using different code, however, I would like to stick with this code for my own learning purposes and for future reference.
Because you can't be sure that isGrounded is false when you call Jump().
I think issue lies there.
Try not setting isGrounded flag when calling Jump(). Setting isGrounded is purely PlayerRaycast()'s job.
void Update()
{
// Raycast before moving
PlayerRaycast();
PlayerMove();
}
void PlayerRaycast()
{
// Ray Down
RaycastHit2D rayDown = Physics2D.Raycast(transform.position, Vector2.down);
if (rayDown.collider != null && rayDown.collider.tag == "ground")
{
if( rayDown.distance < distanceToBottomOfPlayer )
{
isGrounded = true;
}
else
{
isGrounded = false;
}
}
}
void Jump()
{
GetComponent<Rigidbody2D>().AddForce(Vector2.up * playerJumpPower);
//isGrounded = false;
}
The first problem is that you add the force and check for the ground at the same frame.
Applied Force is calculated in FixedUpdate or by explicitly calling
the Physics.Simulate method.
So after you press the "Jump" button, the object is still on the ground until the next frame comes.
To fix this, you can simply exchange the order of "move" and "raycast"
void Update()
{
PlayerRaycast();
PlayerMove();
}
The second problem is if the jump power is not large enough, the object can still be close to the ground in the next frame, you should avoid checking the landing when the jump is in ascending state.
void PlayerRaycast()
{
if(GetComponent<Rigidbody2D>().velocity.y > 0)
return;
...
}
I am using unity 5 c# and I have a gameobject with 2 trigger colliders one of them is in a different location.
I need to be able to use OnTriggerStay2D and OnTriggerEnter2D for them but I need to find what trigger is being entered. Right now if I enter the 1st(polygon) trigger the OnTriggerEnter activates for the 2nd(box).
How can I Tell the two colliders apart???
public void OnTriggerEnter2D(Collider2D other) //2nd collider trigger
{
if (other.tag == "Player") {
Found = true; //if the player is in shooting range
Idle = false;
}
}
public void OnTriggerStay2D(Collider2D other) //1st collider trigger
{
if (Found != true) {
if (other.tag == "Player") {
Shield = true;
Idle = false;
}
}
}
public void OnTriggerExit2D(Collider2D other) //2nd collider trigger
{
if (other.tag == "Player") {
Found = false;
Shield = false;
Shooting = false;
Idle = true;
}
}
I have tried making the 1st trigger public void OnTriggerStay2D(PolygonCollider2D other) but it says "This message parameter has to be of type: Collider2D
The message will be ignored."
What I am trying to do is have a polygon trigger in front of the gameobject and a different box trigger closer to the gameobject so when you go near the gameobject you enter the 1st trigger and it puts its shield up but when you get close to it (within shooting range of it) it will put its shield down and start shooting you.
Well collider2d detects all types of 2d colliders. It doesn't matter if it's polygon or just a box. As the documentation suggestions it doesn't need to be public or private. It only takes a collider2d as it's argument however.
For debugging purposes why not use print?
Print("you've entered the trigger function");
Also I wouldn't use 2 different trigger colliders on the same GameObject. Why not just make 2 separate gameobjects so you can have more thorough detection. Each GameObject with its own trigger collider can have different tags.
If you have to use 2 trigger colliders on one object. Which isn't the best idea. You could use shapeCount to determine which one it's hitting. Although like I said I would warrant against doing 2 trigger colliders on the same object when whatever you're trying to do can be easier on two separate objects.
However links aren't usually prohibited I think. I would watch and study these videos. They're very useful for explaining the engine and they really aren't even that long.
https://unity3d.com/learn/tutorials/modules/beginner/2d
They even have a video explaining 2d colliders.
This is my fix. In one of my games I have a boulder, I have a trigger which will delete a block below it so it falls, I then have another trigger which tells the boulder to start moving left or right I then also have another trigger which will delete the boulder once the boulder comes in contact.
So what you can do is create 2 new game objects, create a new CS file and name them appropriately, then with those two new classes allow them to take in the gameobject you are referring to in your question.
Then when they are triggered you can use code from their class.
So your first class would become something like this
public void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player") {
Enemy.Found = true; //if the player is in shooting range
Enemy.Idle = false;
}
}
public void OnTriggerExit2D(Collider2D other)
{
if (other.tag == "Player") {
Enemy.Exit2DTrigger();
}
}
Then the other class would be something like this
public void OnTriggerStay2D(Collider2D other)
{
if (Enemy.Found != true) {
if (other.tag == "Player") {
Enemy.Shield = true;
IEnemy.dle = false;
}
}
}
public void OnTriggerExit2D(Collider2D other)
{
if (other.tag == "Player") {
Enemy.Exit2DTrigger();
}
}
Then in your Enemy class you would have
public void Exit2DTrigger()
{
Found = false;
Shield = false;
Shooting = false;
Idle = true;
}
P.S. also don't you need to use other.gameObject.tag == "Player" ?