Unity Jumping Issue - Character won't jump if it walks to the platform - unity3d

(This is a 2D project)
I have a jumping issue where if my character WALKS into an X platform, she won't jump, but when she JUMPS ONTO the X platform, she can perform a jump.
For the platforms I am currently using 2 Box Collider 2Ds (one with "is trigger" checked)
For the character I am currently using 2 Box Collider 2Ds (one with "is trigger" checked) and Rigidbody 2D.
Below is the code for jumping and grounded I am currently trying to use.
{
public float Speed;
public float Jump;
bool grounded = false;
void Start()
{
}
void Update()
{
if (Input.GetKeyDown(KeyCode.UpArrow))
{
if (grounded)
{
GetComponent<Rigidbody2D>().velocity = new Vector2(GetComponent<Rigidbody2D>().velocity.x, Jump);
}
}
}
void OnTriggerEnter2D()
{
grounded = true;
}
void OnTriggerExit2D()
{
grounded = false;
}
}
Issue arises on the same part of every platform. (Each square represents a single platform sprite and they have all the same exact characteristics, since I copy pasted each one of them). Please check the photo on this link: https://imgur.com/a/vTmHw

It happens because your squares have seperate colliders. Imagine this:
There are two blocks : A and B. You are standing on block A. Now you try to walk on block B. As soon as your Rigidbody2D collider touches block B, your character gets an event OnTriggerEnter2D(...). Now you claim, that you are grounded.
However, at this moment you are still colliding with block A. As soon as your Rigidbody2D no longer collides with block A, your character receives OnTriggerExit2D(...). Now you claim, that you are no longer grounded. But in fact, you are still colliding with block B.
Solution
Instead of having bool variable for checking if grounded, you could have byte type variable, called collisionsCounter:
Once you enter a trigger - increase the counter.
Once you exit a trigger - decrease the counter.
Do some checking to make sure you are actually above the collider!
Now, once you need to check if your character is grounded, you can just use
if (collisionsCounter > 0)
{
// I am grounded, allow me to jump
}
EDIT
Actually, after investingating question further, I've realized that you have totally unnecessary colliders (I'm talking about the trigger ones). Remove those. Now you have only one collider per object. But to get the calls for collision, you need to change:
OnTriggerEnter2D(...) to OnCollisionEnter2D(Collision2D)
OnTriggerExit2D(...) to OnCollisionExit2D(Collision2D)
Final code
[RequireComponent(typeof(Rigidbody2D))]
public sealed class Character : MonoBehaviour
{
// A constant with tag name to prevent typos in code
private const string TagName_Platform = "Platform";
public float Speed;
public float Jump;
private Rigidbody2D myRigidbody;
private byte platformCollisions;
// Check if the player can jump
private bool CanJump
{
get { return platformCollisions > 0; }
}
// Called once the script is started
private void Start()
{
myRigidbody = GetComponent<Rigidbody2D>();
platformCollisions = 0;
}
// Called every frame
private void Update()
{
// // // // // // // // // // // // // //
// Need to check for horizontal movement
// // // // // // // // // // // // // //
// Trying to jump
if (Input.GetKeyDown(KeyDode.UpArrow) && CanJump == true)
Jump();
}
// Called once Rigidbody2D starts colliding with something
private void OnCollisionEnter2D(Collision2D collision)
{
if(collision.collider.tag == TagName_Platform)
platformCollisions++;
}
// Called once Rigidbody2D finishes colliding with something
private void OnCollisionExit2D(Collision2D collision)
{
if(collision.collider.tag == TagName_Platform)
platformCollisions--;
}
// Makes Character jump
private void Jump()
{
Vector2 velocity = myRigidbody.velocity;
velocity.y = Jump;
myRigidbody.velocity = velocity;
}
}
Here can be minor typos as all the code was typed inside Notepad...

I think there are a couple of issues here.
Firstly, using Triggers to check this type of collision is probably not the best way forward. I would suggested not using triggers, and instead using OnCollisionEnter2D(). Triggers just detect if the collision space of two objects has overlapped each other, whereas normal collisions collide against each otehr as if they were two solid objects. Seen as though you are detecting to see if you have landed on the floor, you don't want to fall through the floor like Triggers behave.
Second, I would suggest using AddForce instead of GetComponent<Rigidbody2D>().velocity.
Your final script could look like something like this:
public class PlayerController : MonoBehaviour
{
public float jumpForce = 10.0f;
public bool isGrounded;
Rigidbody2D rb;
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
void OnCollisionEnter2D(Collision2D other)
{
// If we have collided with the platform
if (other.gameObject.tag == "YourPlatformTag")
{
// Then we must be on the ground
isGrounded = true;
}
}
void Update()
{
// If we press space and we are on the ground
if(Input.GetKeyDown(KeyCode.Space) && isGrounded)
{
// Add some force to our Rigidbody
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
// We have jumped, we are no longer on the ground
isGrounded = false;
}
}
}

Related

Jump Animation Not Displaying in Unity 2D

I'm trying to "animate" (yes, I'm using the animate capabilities of Unity but I'm only changing the sprite from one still frame to another still frame) my character jumping in Unity. According to my code, everything should be running properly but for some reason, nothing happens in-game when I hit the Jump button (player is jumping but the sprite isn't changing). I'm following along with this tutorial and I've tried several others explaining how to use Unity's Animator and Animation windows but nothing seems to be working to change the sprite.
My code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
[SerializeField]
private float moveForce = 10f;
[SerializeField]
private float jumpForce = 11f;
private float movementX;
private float movementY;
private Rigidbody2D myBody;
private SpriteRenderer sr;
private Animator anim;
private string WALK_ANIMATION = "Walk";
private bool isGrounded = true;
private string GROUND_TAG = "Ground";
private string JUMP_ANIMATION = "Jump";
private void Awake()
{
myBody = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
sr = GetComponent<SpriteRenderer>();
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
PlayerMoveKeyboard();
AnimatePlayer();
PlayerJump();
}
void FixedUpdate()
{
}
void PlayerMoveKeyboard()
{
movementX = Input.GetAxisRaw("Horizontal");
movementY = Input.GetAxisRaw("Vertical");
transform.position += new Vector3(movementX, 0f, 0f) * Time.deltaTime * moveForce;
}
void AnimatePlayer()
{
if (movementX > 0)
{
anim.SetBool(WALK_ANIMATION, true);
sr.flipX = false;
}
else if (movementX < 0)
{
anim.SetBool(WALK_ANIMATION, true);
sr.flipX = true;
}
else
{
anim.SetBool(WALK_ANIMATION, false);
}
}
void PlayerJump()
{
if (Input.GetButtonDown("Jump") && isGrounded)
{
isGrounded = false;
myBody.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse);
anim.SetBool(JUMP_ANIMATION, true);
Debug.Log("You should be jumping.");
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
if(collision.gameObject.CompareTag(GROUND_TAG))
{
isGrounded = true;
anim.SetBool(JUMP_ANIMATION, false);
}
}
}
My animator states currently:
I'm not sure if this is enough information or if I've included screenshots of everything so please feel free to ask for more input.
I have tried:
Changing the Transitions from (currently) Walking to Jumping to (previously) Idle to Jumping.
Removing the Walking animation entirely from the code
Removing the Transitions from Walking to Idle so that there were only transitions from Walking to Jumping
Checking "Has Exit Time" and unchecking "Has Exit Time"
Extending the length of the Jumping animation
Changing the speed of the jump from 1 to .5 and from 1 to 2
Using an if/else statement in the Animate Player function where the bool of JUMPING_ANIMATION only becomes true if the Y value of the player is higher than the base position when they're standing on the ground
(Trying to; not sure if I coded this correctly because I'm shaky on using triggers instead of bools) Use a trigger instead of a bool for initializing the jump animation
I'm absolutely positive I've just missed something in the tutorial or something else fairly simple and dumb but I cannot for the life of me figure out what it is I'm missing. I've searched the other jump animation issues here on SO, too, and none of them seem to be quite what I'm missing. As it stands, my code is telling me that the Jump parameter is becoming true properly based on the Console Log but nothing about the visual sprite changes for the character.
As I can see in your Animator, you need to set your "StateMachine Default State" to one of your used states (I suppose for your case, the Idle State).
You can do that with a right click on the Entry State.

Why is this Projectile Script Continuing to Hit Targets After I've Destroyed It?

I am new to Unity2D. I am trying to make castle defense game. When the spawners start to inheritance the enemies overlap (they should), but when the archer arrow collides whit the enemies it kills them all. I searched everywhere for the answer of this but nothing...
My questions is:
Is there a way to only hit one target at time?
Arrow script:
void Start()
{
target = GameObject.FindGameObjectWithTag("Enemy").GetComponent<Transform>();
}
// Update is called once per frame
void Update()
{
transform.position = Vector2.MoveTowards(transform.position, target.position, speedProjectile * Time.deltaTime);
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.CompareTag("Enemy"))
{
Destroy(gameObject);
}
}
Enemy script:
void Start()
{
target = GameObject.FindGameObjectWithTag("target3").GetComponent<Transform>();
}
// Update is called once per frame
void Update()
{
transform.position = Vector2.MoveTowards(transform.position, target.position, speed * Time.deltaTime);
}
private void OnTriggerEnter2D(Collider2D col)
{
if (col.gameObject.CompareTag("arrow"))
{
EHealth -= HDamage;
}
if (EHealth <= 0)
{
Destroy(gameObject);
}
Your enemies are all taking damage because destroying a GameObject isn't something that happens immediately (for good reason). Because of this, in a single frame, any number of enemies can be hit by the same arrow.
If you'd like to rely on these collision methods, I'd suggest controlling the damage from the arrow instead of from the enemy, so you can be sure it only hits something once:
private bool dealtDamage = false;
private void OnTriggerEnter2D(Collider2D col) {
// Only deal damage once
if (dealtDamage) {
return;
}
// Does the thing I hit have this "EnemyScript" ?
var enemy = col.gameObject.GetComponentInChildren<EnemyScript>();
if (enemy == null) {
return;
}
// Make this "DealDamage()" method public on your EnemyScript
enemy.DealDamage();
dealtDamage = true;
Destroy(gameObject);
}
Then get rid of the Enemy Script's OnTriggerEnter2D because the arrow is handling it all. I don't know what the name of the script is on your enemies so I just called it EnemyScript. This is also calling a DealDamage() method that you'd have to make (which would probably look a lot like the one you current have listed in your "Enemy Script.")
its hard to say with the information given, based on what you have said it seems that all the entity's are just 1 entity(so when you kill 1 enemy you kill the only enemy which is all of them). you can have them run independently from each other.
Maybe you can use collision enter function to check bullet is hitting enemy's body.
You can use this code below...
private bool isEntered = false;
void OnCollisionEnter(Collision collision)
{
if(isEntered) return;
if(collision.gameObject.tag == "enemy") isEntered = true;
....
....
}
I hope it will work for you.
Finally figured it out. First of all the script is dealing damage so I should hit the target specific number of times, so I can't stop the collision of the arrow without turning it back on. So I made a method, that is being Invoked (InvokeRepeating()) after the arrow collides, that turns a bool back to false. Which method (function) of course should be in Update();
I am not sure if I am saying it right bc I am a beginner but I hope this helps somebody. :D
And here is the code:
Arrow Script:
private void DealDamage()
{
if (hit == true)
return;
eHealth.EHealth -= hDamage.HDamage;
hit = true;
}
void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.CompareTag("enemy"))
{
DealDamage();
Destroy(gameObject); //Destroying arrow on collision
}
}
private void HitSetter()
{
hit = false;
}
void Update()
{
InvokeRepeating("HitSetter", 0f, 1.1f);
}
Enemy Script:
void Update()
{
if (EHealth <= 0)
{
anim.SetBool("EnemyDie", true);
Destroy(gameObject, 0.833f);
}
Also thanks to #Foggzie

How to show a trigger only when pointing to it directly?

I created a transparent cube trigger and I placed it in front of closed doors, so whenever the player walks near the door a message appears saying "this door is locked".
However, I want the message to be gone whenever the player is Not pointing to the door. currently, it shows even when I turn around, the player needs to walk away from the door to make the message disappear.
Here is my code:
public class DoorsTrigger : MonoBehaviour
{
public GameObject partNameText;
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
partNameText.SetActive(true);
}
}
private void OnTriggerExit(Collider other)
{
if (other.CompareTag("Player"))
{
partNameText.SetActive(false);
}
}
}
How can I modify it to achieve my goal?
Here is a simple example using Vector3.Angle() to get the direction the player is facing relative to that trigger.
public class DoorsTrigger : MonoBehaviour
{
public GameObject partNameText;
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
//Assuming 'other' is the top level gameobject of the player,
// or a child of the player and facing the same direction
Vector3 dir = (this.transform.position - other.gameObject.transform.position);
//Angle will be how far to the left OR right you can look before it doesn't register
float angle = 40f;
if (Vector3.Angle(other.gameObject.transform.forward, dir) < angle) {
partNameText.SetActive(true);
}
}
}
private void OnTriggerExit(Collider other)
{
//Make sure the text is actually showing before trying to disable it again
if (other.CompareTag("Player") && partNameText.activeSelf)
{
partNameText.SetActive(false);
}
}
}
Alternatively, you can keep your trigger mostly as is, and do a ray cast from the player to see if they are looking at the door.
//Put this snippet in a function and call it inside the player's Update() loop
RaycastHit hit;
Ray ray = new Ray(this.transform.position, this.transform.forward);
if(Physics.Raycast(ray, out hit))
{
//Tag the gameObject the trigger is on with "Door"
if(hit.collider.isTrigger && hit.collider.CompareTag("Door"))
{
//Here is where you would call a function on the door trigger to activate the text.
// Or better would be to just have that code be on the player.
// That way you can avoid unnecessary calls to another gameObject just for a UI element!
}
}

Double Jump turns into Triple Jump sometimes

I'm using a counter to implement double jump, but it only works sometimes. The character is able to triple jump from time to time.
{
// Start is called before the first frame update
public Transform grounded;
public LayerMask playerMask;
public Rigidbody rb;
bool isGrounded;
float distToGround;
int counter;
void Start()
{
}
// Update is called once per frame
void Update()
{
if(Physics.OverlapSphere(grounded.position, 0.2f, playerMask).Length > 0)
{
isGrounded = true;
counter = 0;
}
else
{
isGrounded = false;
}
if(Input.GetButtonDown("Jump") && counter < 2)
{
counter++;
rb.AddForce(0, 1000, 0);
}
}
private void FixedUpdate()
{
if (!isGrounded)
{
rb.AddForce(0, -50, 0);
}
}
}
The Player Mask is set to everything except for the character itself.
Thank you for your time and help!
Problem solved. I think it was the problem of FixedUpdate() and Update(). I tried to move the ground check and jump code to FixedUpdate(), and Update() only checks for if the space button is pressed. So I initialized a bool variable jumped in Updated(), whenever space is pressed, jumped is set to true. And in FixedUpdate(), before applying force to the character, check if jumped is true. I am new to Unity and I'm not sure why this happens, I'm guessing it's because Update() and FixedUpdate() run on different speed, so they need to have an extra condition to align them.
Thanks for all the helps!

How to move a gameobject in Unity with the click on UI button when otherwise I would need the Update function to check for new inputs?

I am having a little trouble understanding the following. I am working on a simple game where the player can move a game object around by clicking left or right on the keyboard. Simple enough:
void Update()
{
if (Input.GetKey(KeyCode.LeftArrow))
{
transform.position += Vector3.left * movementSpeed * Time.deltaTime;
}
if (Input.GetKey(KeyCode.RightArrow))
{
transform.position += Vector3.right * movementSpeed * Time.deltaTime;
}
if (Input.GetKey(KeyCode.UpArrow))
{
transform.position += Vector3.up * jumpHeight * Time.deltaTime;
}
if (Input.GetKey(KeyCode.DownArrow))
{
transform.position += Vector3.down * movementSpeed * Time.deltaTime;
}
}
However, I also have two buttons on screen, that are also supposed to move the player just as the keyevents do. What I dont understand is, that I need an update function to check every frame if a new button is pressed, right? So now I have my button and assign a script to it, where I can attach the button to a certain function of that script. But I cannot simply assign the button to a new "update" function that checks for different inputs of that button. How do I get my Ui Button to also controll the player JUST like the Update function does here?
Thank you!
Check out the Screenshot of the Scene. Just create an UI with an EventSystem. Create a button within your UI. Attach an EventTrigger to your button. Now create two Events on your EventTrigger:
(1) Pointer Down (will be called, when the button get's pressed down.)
(2) Pointer Up (will be called when the button is released.)
Attach the script at the end of this answer as a component to your player or cube or whatever you wanna move.
Now you can see on the screenshot, that the public method MoveCube.MoveLeft get's called whenever the MoveLeftButton is pressed. Whenever it is released, the MoveCube.StopMovingLeft gets called. The joke is about the bool, that will be switched from false to true and back. The actual movement happens in the Update, that essentially follows the same logic as the script you have provided. Hope this helps.
private bool moveLeft, moveRight;
void Start ()
{
// Get Rigidbody or CC or whatever you use to move your object.
}
void Update ()
{
if (moveLeft)
{
// Move Left
Debug.Log("Moving Left");
}
}
public void MoveLeft()
{
moveLeft = true;
}
public void StopMovingLeft()
{
moveLeft = false;
}
EDIT: Very important - I just saw that you are using transform.position += Vector3 to move the object. You will have big troubles with accurate collision, as your function is literally teleporting your gameobject to the new position. To avoid bad colliding you should use Rigidbody.AddForce or if you want to use transform you can easily use transform.Translate:
From Unity Docs:
transform.Translate(Vector3.forward * Time.deltaTime);
EDIT 2: Here is the code you have requested in the comment to this answer. Just copy the script, should work fine.
private bool moveLeft, moveRight;
// Create the rigidbody and the collider
private Rigidbody rb;
private Collider col;
// Create a variable you can change in inspector window for speed
[SerializeField]
private float speed;
void Start ()
{
// You can either use a CC (Character Controller) or a Rigidbody. Doesn't make that much of a difference.
// Important thing is, if you use a rigidbody, you will need a collider as well. Collider detect collision
// but rigidbodies do the actual work with regards to physics. In this case we use rigidbody/collider
// Get the rigidbody
// If you are making a 2D game, you should use Rigidbody2D
// The rigidbody will simulate actual physics. I tested this script, the
// rigibody will accelerate and will need time to slow down upon breaking
// You can change it's mass and stuff
rb = GetComponent<Rigidbody>();
// Now in this case we just get any collider. You can be more specific, if you know which collider your gameobjects has
// e.g. BoxCollider or SphereCollider
// If you are making a 2D game, you should use Collider2D
col = GetComponent<Collider>();
}
void Update ()
{
if (moveLeft)
{
// If you make a 2D game, use Vector2
rb.AddForce(Vector3.left * speed);
}
}
public void MoveLeft()
{
moveLeft = true;
}
public void StopMovingLeft()
{
moveLeft = false;
}
You could do something like this. In your script, attached to a button:
using UnityEngine.EventSystems; // Required when using Event data.
public class ExampleClass : MonoBehaviour, IPointerDownHandler // required interface when using the OnPointerDown method.
{
bool moveLeft = false;
// Do this when the mouse is clicked over the selectable object this script is attached to.
public void OnPointerDown(PointerEventData eventData)
{
moveLeft = true;
Debug.Log(this.gameObject.name + " was clicked.");
}
public void OnPointerUp(PointerEventData eventData)
{
moveLeft = false;
Debug.Log(this.gameObject.name + " was released.");
}
void Update()
{
if (moveLeft)
// Move left code
}
}
This is a fairly dumb example, but should be at least something you can expand on.