"isGrounded" variable doesn't change to false after initial jump in Unity - c#

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;
...
}

Related

friction not working correctly in my 2d game - unity

I am developing a 2d game in unity, and I am trying to add friction to slow the player down when he is on the ground. I launch the player into the air with the rigidbody's add force. I have heard of physics materials, and am currently trying to use them. I am using the 2d version of them, I made one material which had high friction. I add the material to the floor, and then I play the game, and my character is still sliding on the floor. I thought the problem might be with the player not having a physics material, so I added one. It still didn't work.
I tried doing different combinations with the different materials.
I tried attaching a rigidbody, but kinematic to the floor.
I tried looking it up, and couldn't find an answer.
Here is the code that moves the player:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public bool isGrounded = true;
public float fireForce;
Rigidbody2D rb;
void Start()
{
rb = GetComponent<Rigidbody2D>();
camera = camObj.GetComponent<Camera>();
}
void Update()
{
RotateToPoint();
Fire();
}
void OnCollisionEnter2D(Collision2D obj)
{
if (obj.gameObject.tag == "surface")
{
isGrounded = true;
}
}
void OnCollisionExit2D(Collision2D obj)
{
if (obj.gameObject.tag == "surface")
{
isGrounded = false;
}
}
void Fire()
{
if (Input.GetKeyDown(KeyCode.Mouse0) && isGrounded)
{
rb.AddForce(transform.up * fireForce, ForceMode2D.Impulse);
}
}
}
I do not know if I am approaching the problem correctly. If I am not, does anyone know how to avoid slippery game objects. Any help would be appreciated.
Adding a higher angular drag was the solution to my problem. Angular drag is what slows the object down along its rotation. Friction may not always be the problem.
Notice the two highlighted variables. You might want to raise these values (I changed angular drag from its default) to something higher. Adding the physics material might help, you should try this if this first solution doesn't work.
The problem here is that you are not limiting the velocity of the player, so what you can do is set an if statement if the player is touching the floor and the velocity is higher than what you want it to be.
Example:
void FixedUpdate()
{
if (isGrounded == true && rb.velocity.x > 1 || isGrounded == true && rb.velocity.z >1)
{
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
}
}

OnCollisionStay not working in 2D movement script

So, I'm still not the best at this but I'm trying to use this script for 2D movement but the jumping isn't working for some reason. It keeps saying that the OnCollisionEnter function "is declared but never used". Can someone tell me what im doing wrong? Thanks
If I remove the (Collision col) part it says that "void cannot be used in this context".
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RonyWalking : MonoBehaviour
{
Rigidbody2D rigid;
SpriteRenderer sprite;
public bool isJumping;
public float spd = 2.0f;
// Start is called before the first frame update
void Start()
{
rigid = GetComponent<Rigidbody2D>();
sprite = GetComponent<SpriteRenderer>();
}
// Update is called once per frame
void Update()
{
if(Input.GetKey("d")){rigid.velocity = new Vector2(spd, 0);}
else if(Input.GetKey("a")){rigid.velocity = new Vector2(-spd, 0);}
if(Input.GetKeyDown("w") && isJumping == false)
{
rigid.velocity = new Vector2(0, 5);
isJumping = true;
}
void OnCollisionStay(Collision col)
{
isJumping = false;
}
}
}
When using 2D physics, you need to use the 2D lifecycle methods;
void OnCollisionStay2D(Collision2D col)
{
isJumping = false;
}
And you shouldn't put this method inside your Update method... It should be on class level:
public class RonyWalking
{
void Update()
{
// ...
}
void OnCollisionStay2D(Collision2D col)
{
// ...
}
}
Don't worry about "Is declared but never used", this may be because you don't have specific code referencing the method, but Unity will raise events that calls it, "automagically"
Another thing that I can see while reading your code, that may be unintentional behaviour for you, is that when clicking left/right, you set velocity UP to 0, and when clicking up you set LEFT/RIGHT velocity to 0; this will result in freezing the movement mid-air if you jump, then move while in air:
Click D; velocity = 2, 0
Click W; velocity = 0, 5
Character will now move upwards until another input is given
Click D; velocity = 2, 0 and the character will continue moving while in air because when moving sideways the up/down velocity is set to 0
To solve this, either set the other to existing velocity or make the inputs manipulate a Vector that you then apply at the end of the movement code:
Vector2 existingMovement = rigid.velocity;
if (Input.GetKey(KeyCode.D))
existningMovement.x = spd;
else if (Input.GetKey(KeyCode.A))
existningMovement.x = -spd;
if (Input.GeyKeyDown(KeyCode.W) && !isJumping)
{
existningMovement.y = 5f;
isJumping = true;
}
Furthermore, I think you may have some unexpected behaviour with OnCollisionStay; it will fire every frame that you're colliding with the ground, I assume. But I think it may also fire a frame or two AFTER you've jumped since the physics of your character will not INSTANTLY leave the collision, so isJumping will be set to false even after your jump, letting you jump while in the air one more time.
I would recommend that you use OnCollisionExit2D(Collision2D col) to set isJumping = true instead, or OnCollisionEnter2D(Collision2D col) and set it to isJumping = false, depending on the functionality you desire (if you want the ability to jump after walking out of a cliff)

Rigidbody velocity is zero sometimes while the rigidbody is moving

I'm trying to add sound to my pushable object and just have a simple if statement for checking if the pushable object is moving. The concept is quite simple, if the object is moving the sound should play and when it's not moving it shouldn't. The problem however is, that when I debug the value there is a 0 every 5 frames or so. This causes the sound to work inconsistently. The script I have is really simple, and I have tried changing to fixedupdate, but it didn't work. I had read somewhere that physics calculations are done in fixedUpdate.
public class PushableObject : MonoBehaviour
{
Rigidbody rb;
AudioSource audioS;
bool rbIsMoving = false;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody>();
audioS = GetComponent<AudioSource>();
}
// Update is called once per frame
void FixedUpdate()
{
if (rb.velocity == Vector3.zero)
{
Debug.Log("Not moving");
audioS.volume = 0f;
}
else
{
Debug.Log("Moving");
audioS.volume = 0.1f;
}
}
}
Edit: I have just figured out that if the player pushes the pushable object into the wall the sound is still playing, for this reason I think I have to change the way too determine if the object is moving or not.
This happens due to single precision floating point.
You never (should) compare two float values directly since they might logically be equal (e.g. 1 = 5*0.2) but for the computer they might be different by a small "epsilon"
So Unity decides that Vector3 simply uses only a precision of 0.00001 for equality for ==
Rather use Mathf.Approximately which uses a very small Epsilon instead.
Than easier than comparing each component of the rb.velocity against 0 what you actually want is the rb.velocity.magnitude which is actually the overall speed.
if (Mathf.Approximately(rb.velocity.magnitude, 0))
Update
Alternatively store the last position and compare it to the current one using Vector3.Distance either again with Mathf.Approximately
private Vector3 lastPosition;
private void LateUpdate()
{
if(Mathf.Approximately(Vector3.Distance(lastPosition, transform.position), 0))
{
//...
}
else
{
//...
lastPosition = transform.positiom;
}
}
Or with a custom threshold
if(Vector3.Distance(lastPosition, transform.position) <= someThreshold)
or this time you actually can use == if a threshold of 0.00001 is what you want
if(lastPosition == transform.position)
You can check if the RigidBody is sleeping:
if (!rb.IsSleeping()
{
//it's moving
}
else
{
//it's not
}
Or check if the transform position has moved since last frame:
Vector3 lastposition;
Gameobject go = somegameobject;
function Update()
{
if (lastposition == go.transform.position)
{
//not moving
}
else
{
//moving
}
lastposition = go.transform.position;
}
You can use Transform.hasChanged to check if the player position has changed on the last update
if (!this.transform.hasChanged)
{
print("Player is not moving");
}
transform.hasChanged = false;

Unity 2d game movement script issue - unable to jump

So basically, after hours of torment trying to create basic movement script for simple platformer game I succeeded, but not quite. Square character is able to move around and jump just ok, but sometimes it won't jump, usually while moving on short distances or, rarely, standing in place and trying to jump. I can't figure out how to fix that. Here is entire script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovement : MonoBehaviour {
private Rigidbody2D rgdb2;
public float movementSpeed;
public float jumpHeight;
private bool isJumping = false;
// Use this for initialization
void Start ()
{
rgdb2 = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void FixedUpdate ()
{
float moveHorizontal = Input.GetAxis("Horizontal");
HandleMovement(moveHorizontal);
if (Input.GetKeyDown(KeyCode.Space) && isJumping == false)//by typing Space player jumps, cant double-jump
{
rgdb2.AddForce(new Vector2(rgdb2.velocity.x, 1 * jumpHeight), ForceMode2D.Impulse);
isJumping = true;
Debug.Log("jumped");
}
}
private void HandleMovement(float moveHorizontal)//applying player horizontal controls and customing player's speed by movementSpeed variable
{
rgdb2.velocity = new Vector2(moveHorizontal * movementSpeed, rgdb2.velocity.y);
}
private void OnCollisionEnter2D(Collision2D coll)
{
if (coll.transform.tag == "Platform") //if player is touching object with Platform tag, he can jump
{
Debug.Log("on ground bitch");
isJumping = false;
}
}
}
It may not be that important, but I want to polish this game as much as possilble, even if I don't need to, since it's basically my first game made in Unity3d with C#.
An important thing to keep in mind: Unity3D Engine's inputs are only updated during the time the engine calls Update() methods for your GameObjects.
What this means is that you should not read any type of input in the FixedUpdate() method. Methods like GetKeyDown() and other methods from the Input class which read keyboard/mouse/axis buttons/values should not be called during FixedUpdate(), as their returned values are unreliable.
Due to this, what is probably causing your jump implementation to fail is that the GetKeyDown() method you're calling in FixedUpdate() is returning inconsistent/invalid (false) results, when the user presses the jump key.
Fixing this can be quite simple. I suggest you keeping a boolean variable which keeps track of whether the jump key has been pressed, and gets its value updated during Update(). This should fix your problem.
bool jumpKeyPressed;
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
jumpKeyPressed = true;
else if (Input.GetKeyUp(KeyCode.Space))
jumpKeyPressed = false;
}
private void FixedUpdate()
{
/* Update "moveHorizontal", call HandleMovement(...) here, as you've already done. */
if (jumpKeyPressed && isJumping == false)
{
// IMPORTANT: this prevents the "jump force" from being applied multiple times, while the user holds the Space key
jumpKeyPressed = false;
/* Remaining jumping logic goes here (AddForce, set "isJumping", etc) */
}
}
It's because your logic for jumping is inside FixedUpdate()
When you use GetKeyDown to register input make sure to use Update instead because if you press the key using FixedUpdate it may or may not run during that frame, test it with Update instead.
You already have in comments how Update works, it is called every frame but FixedUpdate according to Unity documentation: This function is called every fixed framerate frame
void Update ()
{
float moveHorizontal = Input.GetAxis("Horizontal");
HandleMovement(moveHorizontal);
if (Input.GetKeyDown(KeyCode.Space) && isJumping == false)//by typing Space player jumps, cant double-jump
{
rgdb2.AddForce(new Vector2(rgdb2.velocity.x, 1 * jumpHeight), ForceMode2D.Impulse);
isJumping = true;
Debug.Log("jumped");
}
}

My character sometimes fails to jump - Unity2D

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 !

Categories

Resources