I am making a 2D platformer movement using Rigidbody2D, and I noticed a strange thing:
If my FPS is normal, then the jumping works as expected. But if my FPS is really low (Maybe 5 fps, maybe less) then the player starts to jump extremely high. It seems like it's something to do with Rigidbody2D removing velocity much slower when it lags.
Code:
using UnityEngine.InputSystem;
using UnityEngine;
[RequireComponent(typeof(Rigidbody2D))]
public class PlayerMove : MonoBehaviour
{
private PlayerMovement _playerMovement;
private InputAction _moveAction;
private Vector2 _moveVec;
private Rigidbody2D _rb;
[SerializeField] private LayerMask groundLayer;
[SerializeField] private float speed = 5.0f;
[SerializeField] private float jumpHeight = 8.0f;
private bool _jump;
private bool _jumpHold;
private bool _isJumping;
private int _jumpTime = 0;
private int _maxJumpTime = 40;
private void Awake() {
_playerMovement = new PlayerMovement();
}
private void Start() {
_rb = GetComponent<Rigidbody2D>();
}
private void OnEnable() {
_playerMovement.Enable();
}
private void OnDisable() {
_playerMovement.Disable();
_playerMovement.Dispose();
}
private bool isGrounded() => Physics2D.Raycast(transform.position, Vector3.down, 0.6f, groundLayer)
|| Physics2D.Raycast(new Vector2(transform.position.x - 0.5f, transform.position.y), Vector3.down, 0.6f, groundLayer)
|| Physics2D.Raycast(new Vector2(transform.position.x + 0.5f, transform.position.y), Vector3.down, 0.6f, groundLayer);
private void Update() {
if(_playerMovement.Movement.Jumping.WasPerformedThisFrame() && isGrounded())
{
_isJumping = true;
}
if (_playerMovement.Movement.Jumping.WasReleasedThisFrame()) _isJumping = false;
if(_playerMovement.Movement.Jumping.IsPressed() && _isJumping) {
if(_jumpTime>0) {
_rb.velocity = Vector2.up * jumpHeight;
_jumpTime--;
}
else {
_isJumping = false;
}
}
if (isGrounded()) _jumpTime = _maxJumpTime;
if (!_playerMovement.Movement.Jumping.IsPressed()) _isJumping = false;
}
private void FixedUpdate() {
Vector2 inp = _playerMovement.Movement.Move.ReadValue<Vector2>();
_rb.velocity = new Vector2(inp.x * speed , _rb.velocity.y);
}
}
Is there any way to fix this, or do I just have to accept that that's how physics are gonna work on low FPS.
I think the problem depends on the updates per second in FixedUpdate when your FPS are low. This causes the speed to drop slower than intended, increasing the jump height.
Try using the following function to adjust the physics update interval to the frame rate:
private void FixedUpdate() {
Vector2 inp = _playerMovement.Movement.Move.ReadValue < Vector2 > ();
_rb.velocity = new Vector2(inp.x * speed * Time.deltaTime, _rb.velocity.y);
}
Related
I assigned the spacebar as jump key in my game but it doesn't seem to work 100% of the time. Also there is a slight delay when the character jumps and button is pressed. The jump key is like a gamble in the game. Here's the script that i attached to the character.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MonsterScript : MonoBehaviour
{
[SerializeField] private float walkStrength = 10f;
[SerializeField] private float jumpStrength = 11f;
private float movementX;
private bool onCloud = true;
private string CLOUD_TAG = "Cloud";
private string WALK_ANIMATION = "walk";
private Rigidbody2D mybody;
private SpriteRenderer sr;
private Animator anim;
private void Awake()
{
mybody = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
sr = GetComponent<SpriteRenderer>();
}
// Update is called once per frame
void Update()
{
monsterWalk();
}
private void FixedUpdate()
{
monsterJump();
}
void monsterWalk()
{
movementX = Input.GetAxisRaw("Horizontal");
transform.position += walkStrength * Time.deltaTime * new Vector3(movementX, 0f, 0f);
}
void monsterJump()
{
if (Input.GetKeyUp(KeyCode.Space) && onCloud == true)
{
mybody.AddForce(new Vector3(0, jumpStrength,0), ForceMode2D.Impulse);
onCloud = false;
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag(CLOUD_TAG))
{
onCloud = true;
Debug.Log("Landed!");
}
}
}
I tried changing the collider of my player thinking it might have got stuck with other collider. Again I trying changing the key thinking it was a problem of my keyboard but wasn't.
In general: get single event input in Update!
If you check GetKeyUp within FixedUpdate - which is not called every frame - you might miss an Input.
So you should do e.g.
private bool isJump;
private void Update ()
{
...
if(Input.GetKeyUp(KeyCode.Space) && onCloud)
{
isJump = true;
}
}
private void FixedUpdate ()
{
...
if(isJump)
{
mybody.AddForce(new Vector3(0, jumpStrength,0), ForceMode2D.Impulse);
onCloud = false;
isJump = false;
}
}
However, further
transform.position += walkStrength * Time.deltaTime * new Vector3(movementX, 0f, 0f);
is a problem as well when using a Rigidbody/Rigidbody2D since you completely overwrite the position here => the physics has no effect whatsoever so your adding force is good for nothing.
You also want to shift this into FixedUpdate and only go through the myBody here as well
private void FixedUpdate ()
{
var movementX = Input.GetAxisRaw("Horizontal") * walkStrength * Time.deltaTime;
// Keep Y velocity untouched and only overwrite X
var vel = mybody.velocity;
vel.x = movementX;
mybody.velocity = vel:
}
as soon as physics is involved you do not want to get or set anything through the Transform component.
I have a script that detects when the player is nearby and triggers an attack animation for the enemy to deal damage to the player. However, I am running into issues getting damage dealt. I am using an event on the frame that the attack lands to call a pre-existing method called takeDamage from the player's script to reduce health but this doesn't seem to work and no change happens to the player's health. Below is the code for the enemy and the method for taking damage from the player.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyAI : MonoBehaviour
{
[SerializeField] private float attackCooldown;
[SerializeField] private int damage;
private float cooldownTimer = Mathf.Infinity;
[SerializeField] private BoxCollider2D boxCollider;
[SerializeField] private LayerMask playerLayer;
[SerializeField] private float range;
[SerializeField] private float ColliderDistance;
private Animator anim;
public PlayerHealth playerHealth;
private void Awake()
{
anim = GetComponent<Animator>();
}
private void Update()
{
cooldownTimer += Time.deltaTime;
if (playerInSight())
{
if (cooldownTimer >= attackCooldown)
{
cooldownTimer = 0;
anim.SetTrigger("meleeAttack");
}
}
}
private bool playerInSight()
{
RaycastHit2D hit =
Physics2D.BoxCast(boxCollider.bounds.center + transform.right * range * transform.localScale.x * ColliderDistance,
new Vector3(boxCollider.bounds.size.x * range, boxCollider.bounds.size.y, boxCollider.bounds.size.z)
, 0, Vector2.left, 0, playerLayer);
return hit.collider != null;
}
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireCube(boxCollider.bounds.center + transform.right * range * transform.localScale.x * ColliderDistance,
new Vector3(boxCollider.bounds.size.x * range, boxCollider.bounds.size.y, boxCollider.bounds.size.z));
}
private void damagePlayer()
{
if (playerInSight())
{
playerHealth.takeDamage(damage);
}
}
}
Below is the method for taking damage:
public void takeDamage(int damage)
{
currentHealth -= damage;
if (currentHealth < maxHealth && !_isPlayingAnimation)
{
maxHealth = currentHealth;
anim.SetTrigger("attacked");
_isPlayingAnimation = true;
}
if (currentHealth <= 0)
{
Debug.Log("Player has died!");
anim.SetBool("isDead", true);
_isPlayingAnimation = true;
}
}
You're reducing the players maxHealth to match the new health after taking damage, therefore the player's new health ist still 100%
if (currentHealth < maxHealth && !_isPlayingAnimation)
{
maxHealth = currentHealth;
anim.SetTrigger("attacked");
_isPlayingAnimation = true;
}
currentHealth < maxHealth is always gonna be true (as long as your currentHealth cannot exceed maxHealth) and will reduce your maxHP everytime _isPlayingAnimation is false while taking damage.
To me it looks like you didn't meant to actually modify the players maxHealth.
no matter what I do with my code, the Player doesn't seem to want to jump. Everything is referenced correctly in Unity and the code is hopefully error free. I've also checked to see if the Input System is corresponding to what I've got pressed. The only thing I can think of is the force isn't strong enough for the player to lift off of the ground, due to the high gravity (7f). But I lowered the gravity and nothing changed. Thanks for the help in advance.
using System.Collections;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
private float horizontal;
private float speed = 8f;
private float jumpingPower = 16f;
private bool isFacingRight = true;
private bool isJumping;
private float coyoteTime = 0.2f;
private float coyoteTimeCounter;
private float jumpBufferTime = 0.2f;
private float jumpBufferCounter;
[SerializeField] private Rigidbody2D rb;
[SerializeField] private Transform groundCheck;
[SerializeField] private LayerMask groundLayer;
private void Update()
{
horizontal = Input.GetAxisRaw("Horizontal");
if (IsGrounded())
{
coyoteTimeCounter = coyoteTime;
}
else
{
coyoteTimeCounter -= Time.deltaTime;
}
if (Input.GetButtonDown("Jump"))
{
jumpBufferCounter = jumpBufferTime;
}
else
{
jumpBufferCounter -= Time.deltaTime;
}
if (coyoteTimeCounter > 0f && jumpBufferCounter > 0f && !isJumping)
{
rb.velocity = new Vector2(rb.velocity.x, jumpingPower);
jumpBufferCounter = 0f;
StartCoroutine(JumpCooldown());
}
if (Input.GetButtonUp("Jump") && rb.velocity.y > 0f)
{
rb.velocity = new Vector2(rb.velocity.x, rb.velocity.y * 0.5f);
coyoteTimeCounter = 0f;
}
Flip();
}
private void FixedUpdate()
{
rb.velocity = new Vector2(horizontal * speed, rb.velocity.y);
}
private bool IsGrounded()
{
return Physics2D.OverlapBox(groundCheck.position, groundCheck.GetComponent<BoxCollider2D>().size, 0f, groundLayer);
//return Physics2D.OverlapCircle(groundCheck.position, 0.2f, groundLayer);
}
private void Flip()
{
if (isFacingRight && horizontal < 0f || !isFacingRight && horizontal > 0f)
{
Vector3 localScale = transform.localScale;
isFacingRight = !isFacingRight;
localScale.x *= -1f;
transform.localScale = localScale;
}
}
private IEnumerator JumpCooldown()
{
isJumping = true;
yield return new WaitForSeconds(0.4f);
isJumping = false;
}
}
The code works fine and the player jumps and moves without problems
Make sure to perform these steps inside Unity Engine
Inside the player you must add
Rigidbody2D
Box Collider2D
the code
Inside the code you must specify
Ground Layer : "Ignore Raycast"
Ground Check : "The ground the player is walking on"
And inside the ground must be added
Layer : "Ignore Raycast"
Component: "Box Collider2D"
As in these pictures
Player
enter image description here
Ground
enter image description here
The code worked. The ground check had a collider on it, when it wasn't necessary. The ground check in reality was inside the player.
I'm making a 2D platformer with Unity. I used this tutorial to write this code but changed it a little bit. I want to support both keyboard and gamepad, so I use the new Input System. I've defined three variables called GroundedRememeber, JumpPressedRemember and JumpPressedRememberTime and basically they work like timers and check if the player leaves the ground and then the player can jump when he is near the ground without need to touch it and I want to use it instead of famous "groundCheck". But the problem is that these timers are not working and the player can jump forever even in the air when I press jump button rapidly. Also, as you can see, I added a LayerMask named "groundLayers" for the player to jump only on this type of objects but when I choose "Ground" in the "groundLayers" slot in the Inspector, the player can't jump anymore.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerController : MonoBehaviour, PlayerInputActions.IPlayerActions
{
private PlayerInputActions controls;
[SerializeField] LayerMask groundLayers;
private Rigidbody2D rb;
private Animator anim;
private bool facingRight = true;
private Vector2 moveInput;
[SerializeField] private float jumpForce;
float JumpPressedRemember = 0;
[SerializeField] float JumpPressedRememberTime = 0.2f;
float GroundedRemember = 0;
[SerializeField] float GroundedRememberTime = 0.25f;
[SerializeField] float HorizontalAcceleration = 1;
[SerializeField] [Range(0, 1)] float HorizontalDampingBasic = 0.5f;
[SerializeField] [Range(0, 1)] float HorizontalDampingWhenStopping = 0.5f;
[SerializeField] [Range(0, 1)] float HorizontalDampingWhenTurning = 0.5f;
[SerializeField] [Range(0, 1)] float JumpHeight = 0.5f;
private void Awake()
{
controls = new PlayerInputActions();
controls.Player.SetCallbacks(this);
}
void Start()
{
rb = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
}
void PlayerInputActions.IPlayerActions.OnMove(InputAction.CallbackContext context)
{
moveInput = context.ReadValue<Vector2>();
}
void Jump() {
rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Force);
GroundedRemember = 0;
JumpPressedRemember = 0;
}
bool TryJump() {
if (GroundedRemember > 0) {
Jump();
return true;
} else {
JumpPressedRemember = JumpPressedRememberTime;
return false;
}
}
void PlayerInputActions.IPlayerActions.OnJump(InputAction.CallbackContext context)
{
jumpForce = context.ReadValue<float>();
switch (context.phase) {
case InputActionPhase.Performed:
TryJump();
break;
}
}
void FixedUpdate()
{
if(facingRight == false && moveInput.x > 0){
Flip();
}else if (facingRight == true && moveInput.x < 0){
Flip();
}
}
void Flip(){
facingRight = !facingRight;
Vector3 Scaler = transform.localScale;
Scaler.x *= -1;
transform.localScale = Scaler;
}
void OnEnable()
{
controls.Enable();
}
void OnDisable()
{
controls.Disable();
}
void Update()
{
Vector2 GroundedBoxCheckPosition = (Vector2)transform.position + new Vector2(0, -0.01f);
Vector2 GroundedBoxCheckScale = (Vector2)transform.localScale + new Vector2(-0.02f, 0);
bool Grounded = Physics2D.OverlapBox(GroundedBoxCheckPosition, transform.localScale, 0, groundLayers);
GroundedRemember -= Time.deltaTime;
if (Grounded)
{
GroundedRemember = GroundedRememberTime;
}
JumpPressedRemember -= Time.deltaTime;
if ((JumpPressedRemember > 0)) {
TryJump();
}
rb.velocity = new Vector2(rb.velocity.x, jumpForce);
float HorizontalVelocity = rb.velocity.x;
HorizontalVelocity += moveInput.x;
if (Mathf.Abs(moveInput.x) < 0.01f)
HorizontalVelocity *= Mathf.Pow(1f - HorizontalDampingWhenStopping, Time.deltaTime * 10f);
else if (Mathf.Sign(moveInput.x) != Mathf.Sign(HorizontalVelocity))
HorizontalVelocity *= Mathf.Pow(1f - HorizontalDampingWhenTurning, Time.deltaTime * 10f);
else
HorizontalVelocity *= Mathf.Pow(1f - HorizontalDampingBasic, Time.deltaTime * 10f);
rb.velocity = new Vector2(HorizontalVelocity, rb.velocity.y);
}
}
If you know how much the player jumps for, then try maybe adding a delay to the next jump
Maybe using the Invoke() function ur just using Coroutines if you know how to use them.
But i would still recommend using a Ground Check since it's practical and just easier and i don't see a reason why you wouldn't use it.
Here is the code I currently have however I want to be able to move mid-jump.
as of now if I jump while moving I will carry on in that direction however I can't change the direction I am heading while in the air. This code was made using unity and the player that this code is attached to is a capsule.
private CharacterController charController;
[SerializeField] private AnimationCurve jumpFallOff;
[SerializeField] private float jumpMultipliyer;
[SerializeField] private KeyCode jumpKey;
private bool isJumping;
private void Awake()
{
charController = GetComponent<CharacterController>();
}
private void Update()
{
PlayerMovement();
}
private void PlayerMovement()
{
float horziInput = Input.GetAxis(horizontalInputName) * movementSpeed;
float vertInput = Input.GetAxis(verticalInputName) * movementSpeed;
Vector3 forwardMovement = transform.forward * vertInput;
Vector3 rightMovement = transform.right * horziInput;
charController.SimpleMove(forwardMovement + rightMovement);
JumpInput();
}
private void JumpInput()
{
if(Input.GetKeyDown(jumpKey) && !isJumping)
{
isJumping = true;
StartCoroutine(JumpEvent());
}
}
private IEnumerator JumpEvent()
{
charController.slopeLimit = 90.0f;
float timeInAir = 0.0f;
do
{
float jumpForce = jumpFallOff.Evaluate(timeInAir);
charController.Move(Vector3.up * jumpForce * jumpMultipliyer * Time.deltaTime);
timeInAir += Time.deltaTime;
yield return null;
} while (!charController.isGrounded && charController.collisionFlags != CollisionFlags.Above);
charController.slopeLimit = 45.0f;
isJumping = false;
}