I'm working on a game and I have a my player with the tag "Player". I have a cyclist spawning system, and each cyclist has the tag "cyclist", they are all prefabs. I have a script on the cyclists prefab that makes it move forwards, so each time it spawns, it instantly begins to move in a fixed direction.
I want the cyclists to be able to detect 2 things, one being if the player is in front of it, and the other if another cyclist is in-front of it. If so, I want the cyclist to stop cyclist. I have a script called cyclistStoping.cs that I'm using to do this. The script has been placed onto the cyclist prefab for multiple instances to be spawned.
Bug
I've noticed that sometimes even once a cyclist(a) is no longer in front of another cyclist(b), cyclist(b) will still remain still, and it will only move off again if the player walks in front of it and then walks off again. I feel like my code is just buggy and would really appreciate some help on this.
I've tried to do 2 simple checks inside each trigger function but some cyclists still remain idle when once the cyclist is no longer in front of them.
I have also tried using a delay function so whenever the cyclist or player are no longer in range, the cyclist doesn't move off until 2-3 seconds have passed. However with this, the cyclist doesn't move off again.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class cyclistStoping : MonoBehaviour
{
public VehicleMove cyclistMovement;
public bool playerInside = false;
public bool cyclistInside = false;
private bool timePassed = false;
void Start()
{
}
void Update()
{
}
// implement delay
// player or cyclist (INSIDE)
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Player" ) {
playerInside = true;
cyclistInside = false;
}
else if (other.gameObject.tag == "Cyclist")
{
playerInside = false;
cyclistInside = true;
}
if (playerInside == true || cyclistInside == true) {
Debug.Log("Player inside: " + playerInside);
cyclistMovement.vehicleMovement = 0.0f;
}
}
// player or cyclist (EXIT)
private void OnTriggerExit(Collider other)
{
if (other.gameObject.tag == "Player")
{
playerInside = false;
cyclistInside = false;
}
else if (other.gameObject.tag == "Cyclist")
{
playerInside = false;
cyclistInside = false;
}
if (playerInside == false || cyclistInside == false)
{
if (timePassed == true) {
Debug.Log("Player inside: " + playerInside);
cyclistMovement.vehicleMovement = 0.1f;
// delay, then move off
timePassed = false;
}
}
}
IEnumerator timeDelay()
{
// wait before moving off
yield return new WaitForSeconds(3);
timePassed = true;
}
}
I expect the cyclist to stop if a player is in front of it, and once the next cyclist spawn, I expect THAT cyclist to stop once it detects the first cyclist. Once the player moves away from the first cyclist, it should take 2 seconds before moving off, and the second cyclist will do exactly the same.
The main problem in your code is that the logic to make the bike move again should be in the IEnumerator, not the OnTriggerExit. Start the Coroutine in OnTriggerExit:
private void OnTriggerExit(Collider other)
{
if (other.gameObject.tag == "Player")
{
playerInside = false;
}
else if (other.gameObject.tag == "Cyclist")
{
cyclistInside = false;
}
if (playerInside == false && cyclistInside == false)
{
StartCoroutine(timeDelay());
}
}
And check again in the IEnumerator:
IEnumerator timeDelay()
{
// wait before moving off
yield return new WaitForSeconds(3);
if (playerInside == false && cyclistInside == false) {
cyclistMovement.vehicleMovement = 0.1f;
}
}
Related
Using c#, im trying to fire a bullet every 3 seconds, so heres my workflow:
fire button is pressed
only fire if bool fireAgain is true
set bool fireAgain = false
start timer
timer finished = bool fireAgain = true
When debugging it seems to all work properly, but when I test it, Im still able to shoot like 10 bullets a second. So somehow it just doesnt care about the bool FireAgain being false and shoots anyway even if according to debug bool fireAgain is false at that moment.
public void Update()
{
//If LEFT MouseButton is pressed, cast yer spells.
if (fireAgain == true && Input.GetMouseButtonDown(0))
{
StartCoroutine("LoopRotation");
fireAgain = false;
Debug.Log(fireAgain);
Debug.Log("1 should be FALSE");
}
while (fireAgain == false && timer < bulletTime)
{
fireAgain = false;
timer += Time.deltaTime;
Debug.Log(timer);
Debug.Log(bulletTime);
Debug.Log(fireAgain);
Debug.Log("2");
} if (timer >= bulletTime)
{
fireAgain = true;
timer = 0;
//Debug.Log("Timer is finished");
//Debug.Log(timer);
Debug.Log(fireAgain);
Debug.Log("3 should be true");
And here is the code for the Coroutine:
IEnumerator LoopRotation()
{
pivot.transform.Rotate(triggerAngle,0,0);
GameObject bullet = ObjectPooler.SharedInstance.GetPooledObject();
if (bullet != null) {
bullet.transform.position = SSpawn.transform.position;
bullet.transform.rotation = SSpawn.transform.rotation;
bullet.SetActive(true);
}
yield return new WaitForSeconds(.1f);
pivot.transform.rotation = Quaternion.Slerp(transform.rotation, originalRotationValue, Time.deltaTime * rotationResetSpeed);
StopCoroutine("LoopRotation");
}
Enumerator LoopRotation was originally just to pivot the weapon a few degrees forwards and then backwards so it looks like a wack when you cast a spell, but now its also the shoot function, as it creates bullets.
You have a while loop within Update => this loop will completely run in one single frame => "immediately" will increase the timer until it is big enough => "immediately" will set your bool flag to true again!
What you rather would do is e.g.
public void Update()
{
//If LEFT MouseButton is pressed, cast yer spells.
if (fireAgain && Input.GetMouseButtonDown(0))
{
StartCoroutine(LoopRotation());
fireAgain = false;
timer = 0;
Debug.Log(fireAgain);
Debug.Log("1 should be FALSE");
}
else if(timer < bulletTime)
{
// Only increase this ONCE per frame
timer += Time.deltaTime;
Debug.Log(timer);
Debug.Log(bulletTime);
Debug.Log(fireAgain);
Debug.Log("2");
if(timer >= bulletTime)
{
fireAgain = true;
timer = 0;
//Debug.Log("Timer is finished");
//Debug.Log(timer);
Debug.Log(fireAgain);
Debug.Log("3 should be true");
}
}
}
As alternative you could use Invoke and skip the timer in Update completely:
public void Update()
{
//If LEFT MouseButton is pressed, cast yer spells.
if (fireAgain && Input.GetMouseButtonDown(0))
{
StartCoroutine(LoopRotation());
fireAgain = false;
Invoke (nameof(AllowFireAgain), bulletTme);
}
}
private void AllowFireAgain()
{
fireAgain = true;
}
Note that your Coroutine doesn't make much sense to me. You are only rotating exactly once and only a really small amount.
The StopCoroutine in the end is unnecessary.
Also note: for debugging fine but later you should avoid to have Debug.Log running every frame in a built application. Even though the user doesn't see it the log is still created into the player log file and causes quite an amount of overhead.
I have some code that makes a player move up, down, left, and right.
public class playerMovement : MonoBehaviour
{
public float moveSpeed = 5f;
public Rigidbody2D rb;
public Animator animator;
public SpriteRenderer sr;
Vector2 movement;
bool walking = false;
private void Update()
{
//Inputs
movement.x = Input.GetAxisRaw("Horizontal");
movement.y = Input.GetAxisRaw("Vertical");
if (Input.GetKey("left") || Input.GetKey("right") || Input.GetKey("up") || Input.GetKey("down")
{
walking = true;
}
else
{
walking = false;
}
Animate();
}
private void Animate()
{
if (Input.GetKey("down") && walking == false)
{
sr.flipX = false;
animator.Play("idle_front");
}
else if (Input.GetKey("down") && walking)
{
sr.flipX = false;
animator.Play("walk_front");
}
else if (Input.GetKey("up") && walking == false)
{
sr.flipX = false;
animator.Play("idle_back");
}
else if (Input.GetKey("down") && walking)
{
sr.flipX = false;
animator.Play("walk_back");
}
else if (Input.GetKey("left") && walking == false)
{
sr.flipX = false;
animator.Play("idle_side");
}
else if (Input.GetKey("down") && walking)
{
sr.flipX = false;
animator.Play("walk_side");
}
else if (Input.GetKey("right") && walking == false)
{
sr.flipX = true;
animator.Play("idle_side");
}
else if (Input.GetKey("right") && walking)
{
sr.flipX = true;
animator.Play("walk_side");
}
}
public void FixedUpdate()
{
//Movement
rb.MovePosition(rb.position + movement * moveSpeed * Time.fixedDeltaTime);
}
}
The movement is working perfectly fine, but the character just stays in it's idle state.
I have tried switching the get keys in Input.GetKey("left") || Input.GetKey("right") || Input.GetKey("up") || Input.GetKey("down") to Input.GetKeyDown() and Input.GetKeyUp(), but none of that works.
I have also tried using Blend Trees, but I cannot figure out how to do it.
I'm fairly new to Unity so a simple answer would be great.
open animator window
create 'State'
set animation clip into 'motion'
on top left click 'parameters' tab
click on plus sign next to parameters
click on 'Trigger'
name your trigger
right click on 'Any State' and make transition to drag a line to the new 'State' you created
click on the line between 'Any State' and the 'State' you created
under 'conditions' press the plus sign and add the trigger you just created
in code write
animator.SetTrigger("triggername");
when you want to transition to the before-mentioned animation state
repeat for all your animations, link them all from 'any state'
I'm making a Mario replica in unity for my homework, and I'm trying to make the "Invisible" block, it starts off invisible, then when hit, it turns visible. I'm trying to use SpriteRenderer.enable to make it work, it works for turning it off in the start, but not when trying to make it visible.
I've tried to create a separate script for this particular block, but results are the same. All the tags are set correctly, I've tried using Debug.log to see if I enter the "if" where the sprite should be enabled, but the result is negative.
This is the start method turning off the sprite renderer for the particular block (it works):
private void Start()
{
//rendObject = this.gameObject.GetComponent<SpriteRenderer>();
if (gameObject.tag == "Invisible")
{
gameObject.GetComponent<SpriteRenderer>().enabled = false;
}
}
This is all the blocks script:
private void OnCollisionEnter2D(Collision2D collision)
{
if (timesToBeHit > 0)
{
if (collision.gameObject.tag == "Player" && IsPlayerBelow(collision.gameObject))
{
if (gameObject.tag == "Invisible")
{
gameObject.GetComponent<SpriteRenderer>().enabled = true;
}
collision.gameObject.GetComponent<PlayerController>().isJumping = false; //Mario can't jump higher
Instantiate(prefabToAppear, transform.parent.transform.position, Quaternion.identity); //instantiate other obj
timesToBeHit--;
anim.SetTrigger("GotHit"); //hit animation
}
}
if (timesToBeHit == 0)
{
anim.SetBool("EmptyBlock", true); //change sprite in animator
}
}
We've found a solution in chat, but for all people who may run or have run on this kind of problem will need to check for the next things:
Must have 2 Colliders of any type, 1 per gameObject.
At least 1 Rigidbogy.
Appropriately Collider setup.
Appropriate Tags.
Appropriate Layer Collision Matrix.
The code below will work.
public SpriteRenderer render;
void Start()
{
render.enabled = false;
}
private void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.tag == "Player")
{
render.enabled = true;
}
}
public class InvisibleBlock : MonoBehaviour
{
public SpriteRenderer rendObject;
private void Start()
{
if (gameObject.tag == "Invisible")
{
rendObject.enabled = false;
}
}
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "Player")
{
rendObject.enabled = true;
}
}
}
Separate script, sprite is attached in inspector, same results.
Edit - After testing, I discovered that only one elevator will animate. So if I walk into an elevator, the animation might not play on it, but it plays on another elevator. I noticed this by accident. I thought the animator would control the animation based upon which OnTriggerEnter2D I approached. Apparently, this caused the bug I'm experiencing.
How do I properly label the elevators so that only that elevator I'm at will run an animation?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Elevator : MonoBehaviour
{
private GameObject door;
[SerializeField]
private Text uiTxt; // Elevator UI text element
[SerializeField]
private Image uiBg; // Elevator Background image
[SerializeField]
private Image uiFg; // Elevator Foreground image
private void Start()
{
door = GameObject.FindWithTag("Elevator");
door.GetComponent<Animator>().SetBool("doorOpen", false);// Starts closed has to be TRUE to open
door.GetComponent<Animator>().SetBool("doorClose", false); // Starts closed has to be TRUE to re-close
uiBg.enabled = false;
uiFg.enabled = false;
uiTxt.enabled = false;
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "Player")
{
door.GetComponent<Animator>().SetBool("doorOpen", true);
door.GetComponent<Animator>().SetBool("doorClose", false);
uiBg.enabled = true;
uiFg.enabled = true;
uiTxt.enabled = true;
}
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.gameObject.tag == "Player")
{
door.GetComponent<Animator>().SetBool("doorOpen", false);
door.GetComponent<Animator>().SetBool("doorClose", true);
uiBg.enabled = false;
uiFg.enabled = false;
uiTxt.enabled = false;
}
else {
door.GetComponent<Animator>().SetBool("doorOpen", false);
door.GetComponent<Animator>().SetBool("doorClose", false);
}
}
}
Here's a peek at the animator I have set up if it's helpful:
I have it set up to where the parameters are bool values. The door opens when doorOpen is true and doorClose is false. It will close when doorOpen is false and doorClose is true. However, it will do nothing if both values are false.
NOTE - I'm using Unity 2019.1 Beta
First, the issue was door = GameObject.FindWithTag("Elevator"); this will return the first GameObject it finds with that tag, this is why when you hit the 2 different triggers the same door was animating for both.
After finding out this script was already on your elevator, there is no need for the GameObject door, since every MonoBehavior has a reference to its own GameObject, you can just create an Animator variable, and call GetComponent() like this:
public class Elevator : MonoBehavior
{
Animator myAnim;
void Start()
{
myAnim = GetComponent<Animator>();
myAnim.SetBool("doorOpen", true);
myAnim.SetBool("doorClose", false);
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "Player")
{
myAnim.SetBool("doorOpen", true);
myAnim.SetBool("doorClose", false);
uiBg.enabled = true;
uiFg.enabled = true;
uiTxt.enabled = true;
}
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.gameObject.tag == "Player")
{
myAnim.SetBool("doorOpen", false);
myAnim.SetBool("doorClose", true);
uiBg.enabled = false;
uiFg.enabled = false;
uiTxt.enabled = false;
}
else
{
myAnim.SetBool("doorOpen", false);
myAnim.SetBool("doorClose", false);
}
}
}
I have a canvas that is initialized when I launch the game and inside it I have a menu with the play and quit button. The game is a endless runner and everything is working fine, but when I hit the Play button to actually start the game, the Player object automatically gives me one jump, after that, it's all normal, it's like when I hit Play button it hits the game and the player too at the same time. I need this working in a Android device.
Player.cs script
...
void Start () {
Highscore = PlayerPrefs.GetInt ("Highscore", 0);
jumpOne = false;
jumpTwo = false;
canDoubleJump = false;
}
void Update() {
for (int i = 0; i < Input.touchCount; i++){
if (Input.GetTouch(i).phase == TouchPhase.Began && isFalling == false) {
jumpOne = true;
canDoubleJump = true;
isFalling = true;
}else if (Input.GetTouch(i).phase == TouchPhase.Began && isFalling == true && canDoubleJump == true) {
jumpTwo = true;
canDoubleJump = false;
}
}
}
void FixedUpdate() {
transform.Translate(Vector2.right * power * Time.deltaTime);
if (jumpOne == true) {
GetComponent<Rigidbody2D>().AddForce(Vector2.up * jumpHeight);
jumpOne = false;
}
if (jumpTwo == true) {
GetComponent<Rigidbody2D>().AddForce(Vector2.up * jumpHeight);
jumpTwo = false;
}
}
void OnCollisionStay2D(Collision2D coll) {
if (coll.gameObject.tag == "Ground") {
isFalling = false;
canDoubleJump = false;
}
}
...
//(Play Button)
public void ClickedStart() {
MainMenu.SetActive(false);
Time.timeScale = 1.0f;
Playing = true;
}
Already tried and the problem still persists
(Input.GetMouseButtonDown(0))
(Input.GetKeyDown(KeyCode.Space)) Works fine and with no bugs, but I am not able to jump running in Android.
You are using TouchPhase.Began so the moment u hide your main menu the touch is still active and hence the player jumps , I suggest you use a co-routine with waitforseconds method before you can start making the player jump again .