Unity - the player jumps too high when stuck to a wall - c#

I'm writing some code to create a Minecraft Quake like game but I have an issue with the jump mecanic. When I'm stuck to a wall the player jump to high (see the video).
I use a Rigidbody for the physics and I modify that velocity to move the player. There is a Physic Material on the player's Collider with no consideration for friction or bouncing.
If you have ideas to fix the bug or an alternative to work around the problem, I'm interested.
How it looks like
Here is my code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum PlayerMovementState {
Sneak,
Walk,
Run
}
public class PlayerControls : MonoBehaviour
{
Rigidbody rb;
Vector3 velocity, desiredVelocity;
PlayerMovementState moveState;
float groundDistance;
[SerializeField]
bool forwardAir, backAir, rightAir, leftAir;
[SerializeField]
LayerMask groundLayer;
[SerializeField]
bool onGround;
bool desiredJump;
float jumpHeight = 1.0f;
private void Awake() {
rb = GetComponent<Rigidbody>();
moveState = PlayerMovementState.Walk;
groundDistance = GetComponentInChildren<Collider>().bounds.extents.y;
}
private void Update() {
Vector2 playerInputs = Vector2.ClampMagnitude(
new Vector2(
Input.GetAxis("Horizontal"),
Input.GetAxis("Vertical")
), 1.0f
);
if (Input.GetKey(KeyCode.LeftShift)) moveState = PlayerMovementState.Sneak;
else if (Input.GetKey(KeyCode.LeftControl)) moveState = PlayerMovementState.Run;
else moveState = PlayerMovementState.Walk;
float speed = moveState == PlayerMovementState.Run ? 10f : (
moveState == PlayerMovementState.Sneak ? 2f : 5f
);
RaycastGround();
onGround = !forwardAir && !backAir && !rightAir && !leftAir;
if (Input.GetButtonDown("Jump")) desiredJump = true;
if (moveState == PlayerMovementState.Sneak)
{
if (forwardAir && playerInputs.y > 0) playerInputs.y = 0f;
if (backAir && playerInputs.y < 0) playerInputs.y = 0f;
if (rightAir && playerInputs.x > 0) playerInputs.x = 0f;
if (leftAir && playerInputs.x < 0) playerInputs.x = 0f;
}
desiredVelocity =
(transform.forward * playerInputs.y + transform.right * playerInputs.x) * speed;
}
private void FixedUpdate() {
velocity = rb.velocity;
float acceleration = 10;
velocity.x = Mathf.MoveTowards(velocity.x, desiredVelocity.x, acceleration);
velocity.z = Mathf.MoveTowards(velocity.z, desiredVelocity.z, acceleration);
if (desiredJump && onGround)
{
desiredJump = false;
float jumpSpeed = Mathf.Sqrt(-2f * Physics.gravity.y * jumpHeight);
velocity.y += jumpSpeed;
}
rb.velocity = velocity;
desiredJump = false;
}
void RaycastGround()
{
forwardAir = !(Physics.Raycast(
transform.position + Vector3.forward * 0.1f,
-Vector3.up,
groundDistance + 0.1f,
groundLayer
));
backAir = !(Physics.Raycast(
transform.position - Vector3.forward * 0.1f,
-Vector3.up,
groundDistance + 0.1f,
groundLayer
));
rightAir = !(Physics.Raycast(
transform.position + Vector3.right * 0.1f,
-Vector3.up,
groundDistance + 0.1f,
groundLayer
));
leftAir = !(Physics.Raycast(
transform.position - Vector3.right * 0.1f,
-Vector3.up,
groundDistance + 0.1f,
groundLayer
));
}
}

Very likely the problem is that the script thinks it's still grounded while it is jumping upwards along the wall.
Depending on what feeling you want, either fix the raycasts such that they only trigger when you are standing directly on top of an object, or you check if the y part of your velocity is <= 0 for your onGround variable.

I did not find a solution to my problem but I found a workaround anyway.
By detecting the walls around the player, I prevent him from moving in the direction of the wall which prevents him from being stuck on it and having this bug when he jumps.
(It means that my problem is not resolved and that I am still looking for a solution)
Video
...
float wallDistance;
...
[SerializeField]
bool forwardWall, backWall, rightWall, leftWall;
...
SpherecastWall();
...
if (forwardWall && playerInputs.y > 0) playerInputs.y = 0f;
if (backWall && playerInputs.y < 0) playerInputs.y = 0f;
if (rightWall && playerInputs.x > 0) playerInputs.x = 0f;
if (leftWall && playerInputs.x < 0) playerInputs.x = 0f;
void SpherecastWall() {
forwardWall = (Physics.SphereCast(
new Ray(transform.position, Vector3.forward),
wallDistance,
.2f,
groundLayer
));
backWall = (Physics.SphereCast(
new Ray(transform.position, -Vector3.forward),
wallDistance,
.2f,
groundLayer
));
rightWall = (Physics.SphereCast(
new Ray(transform.position, Vector3.right),
wallDistance,
.2f,
groundLayer
));
leftWall = (Physics.SphereCast(
new Ray(transform.position, -Vector3.right),
wallDistance,
.2f,
groundLayer
));
}

I think that's because the spheres center gets over the corner of the wall. So when you apply a force the sphere will be pushed over it.
Maybe you could replace the sphere collider of your player with a capsule or a square collider.

Related

Horizontal movement of player causes egregious numbers when checking vertical velocity

Player movement is working (at least somewhat) now, however one issue remains, and that's the insane numbers the y velocity of the Rigidbody2D on the player. Since the isGrounded check I plan to add will use velocity for the sake of stability, this needs to be fixed.
It confuses me, considering the velocity is 0 normally, but whenever moving left or right it changes to said high numbers.
Movement code:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerM : MonoBehaviour
{
private PControls control;
Rigidbody2D rb;
SpriteRenderer sp;
Transform tr;
public float speed = 0f;
public float speedC;
public float heightSpeed = 5f;
public float heightC;
public bool grounded;
void Start()
{
control = new PControls();
control.Enable();
rb = GetComponent<Rigidbody2D>();
sp = GetComponent<SpriteRenderer>();
tr = GetComponent<Transform>();
}
// Update is called once per frame
void FixedUpdate()
{
RaycastHit2D hit = Physics2D.Raycast(rb.position, -Vector2.up);
Color color = new Color(0, 0, 1.0f);
Debug.DrawRay(transform.position, Vector2.down);
speedC = rb.velocity.magnitude;
var pos = control.Movement.Move.ReadValue<float>();
float GetVerticalSpeed() => rb.velocity.y;
if(pos == -1)
{
sp.flipX = true;
}
else if(pos == 1)
{
sp.flipX = false;
}
if((pos == 0) && (speed > 0.1f))
{
speed -= 3f * Time.deltaTime;
}
else if(speed < 1.4f)
{
speed += Mathf.Abs(pos) * 8 * Time.deltaTime;
}
if(speedC < 7f)
{
rb.AddForce(new Vector3((pos * 5), 0f) * speed * Time.deltaTime, ForceMode2D.Impulse);
}
var jump = control.Movement.Jump.ReadValue<float>();
Debug.Log(GetVerticalSpeed());
Vector3 v = rb.velocity;
v.y = 10;
if(jump == 1)
{
rb.velocity = v;
}
}
}
Seems the main issue was fixed by converting the height velocity to an integer using System.Math.Round():
float vel = rb.velocity.y;
heightC = (int)System.Math.Round(vel);
Not sure it's the best solution, but it's something..

Smooth movement using joystick in Unity3D

I try to accomplish smooth movement with the Xbox Controller, however it is very snappy, even though snap is turned of and deadzone set to 0. Movement with keyboard however is very smooth, so I assume GetAxis does not work with a controller, it's always GetAxisRaw. So I switched to GetAxisRaw and tried to smooth everything out. Even with acceleration, the movement is still very jagged and snappy. Does anyone have an idea how to get a nice smooth movement with a controller, like keyboard + GetAxis?
public class PlayerController : MonoBehaviour
{
CharacterController cc;
float acceleration = 0;
[SerializeField] float speed;
[SerializeField] float acclerationRate = 1;
private void Awake()
{
cc = GetComponent<CharacterController>();
}
private void Update()
{
float deadzone = 0f;
Vector2 stickInput = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
if (stickInput.magnitude < deadzone)
stickInput = Vector2.zero;
else
stickInput = stickInput.normalized * ((stickInput.magnitude - deadzone) / (1 - deadzone));
if (stickInput.x != 0 || stickInput.y != 0)
{
acceleration += Time.deltaTime * acclerationRate;
acceleration = Mathf.Clamp01(acceleration);
}
else
{
acceleration = 0;
}
cc.Move(new Vector3(stickInput.x, 0, stickInput.y) * speed * acceleration * Time.deltaTime);
}
}
I would keep track of your player's velocity and use MoveTowards to adjust it based on the input. Don't forget to clamp your input's magnitude so your character doesn't travel diagonally faster than orthogonally in the event the player prefers keyboard input.
CharacterController cc;
[SerializeField] float speed;
[SerializeField] float acclerationRate = 1;
[SerializeField] float deadzone = 0f;
Vector3 velocity;
private void Awake()
{
cc = GetComponent<CharacterController>();
velocity = Vector3.zero;
}
private void Update()
{
Vector3 stickInput = new Vector3(Input.GetAxisRaw("Horizontal"), 0f,
Input.GetAxisRaw("Vertical"));
if (stickInput.magnitude < deadzone)
}
stickInput = Vector3.zero;
}
else
{
float clampedMag = Mathf.Min(1f, stickInput.magnitude);
stickInput = stickInput.normalized * (clampedMag - deadzone) / (1f - deadzone);
}
velocity = Vector3.MoveTowards(velocity, stickInput,
accelerationRate * Time.deltaTime);
cc.Move(velocity * Time.deltaTime);
}

Why is my player getting stuck in walls Unity 2D

I'm making a game in unity 2d and when my player goes in to a wall he gets stuck and can't move at all. Here is a video:
VIDEO
I've tried using a composite collider, physics material with friction at 0.
Here is my movement script:
public class PlayerMovement : MonoBehaviour
{
Vector3 pos;
float speed = 2.0f;
private Animator animator;
void Start()
{
pos = transform.position;
animator = gameObject.GetComponent<Animator>();
}
void FixedUpdate()
{
if (Input.GetKey(KeyCode.W) && transform.position == pos)
{ // Up
animator.SetInteger("isWalking", 1);
pos += Vector3.up;
}
if (Input.GetKey(KeyCode.S) && transform.position == pos)
{ // Down
animator.SetInteger("isWalking", 2);
pos += Vector3.down;
}
if (Input.GetKey(KeyCode.D) && transform.position == pos)
{ // Right
animator.SetInteger("isWalking", 3);
pos += Vector3.right;
}
if (Input.GetKey(KeyCode.A) && transform.position == pos)
{ // Left
animator.SetInteger("isWalking", 4);
pos += Vector3.left;
}
if (Input.anyKey == false)
animator.SetInteger("isWalking", 0);
transform.position = Vector3.MoveTowards(transform.position, pos, Time.deltaTime * speed);
}
}
The Player object in your case contains a Rigidbody component. So, it would be better to use some of Rigidbody's movement methods like MovePosition() instead of changing position of GameObject directly via transform.position
Thanks to #Nitro557 i had a new idea instead of basicly teleporting the player around i used a whole different method of moving the player here is the script:
public float runSpeed = 2.0f;
private Rigidbody2D body;
private Animator animator;
private float horizontal;
private float vertical;
private float moveLimiter = 0.7f;
void Start()
{
body = GetComponent<Rigidbody2D>();
animator = GetComponent<Animator>();
}
void Update()
{
horizontal = Input.GetAxisRaw("Horizontal");
vertical = Input.GetAxisRaw("Vertical");
if(Input.GetKeyDown(KeyCode.LeftShift))
{
runSpeed += 0.5f;
}
}
private void FixedUpdate()
{
if (horizontal != 0 && vertical != 0)
{
horizontal *= moveLimiter;
vertical *= moveLimiter;
}
body.velocity = new Vector2(horizontal * runSpeed, vertical * runSpeed);
// Up
if (Input.GetKey(KeyCode.W))
animator.SetInteger("isWalking", 1);
// Down
if (Input.GetKey(KeyCode.S))
animator.SetInteger("isWalking", 2);
// Right
if (Input.GetKey(KeyCode.D))
animator.SetInteger("isWalking", 3);
// Left
if (Input.GetKey(KeyCode.A))
animator.SetInteger("isWalking", 4);
if (Input.anyKeyDown == false)
animator.SetInteger("isWalking", 0);
body.velocity = new Vector2(horizontal * runSpeed, vertical * runSpeed);
}

Rotation smooth in editor but isn't in device

I have a 3D object that I want to rotate with mouse/finger swipe, so I made the script below.
The object's rotation looks smooth on editor, but when playing the game on real device (android), the rotation didn't follow the finger movement immediately, it takes some milliseconds to follow finger, it isn't smooth and the controls become hard and frustrating!
float sensitivity = 0.8f;
Vector2 firstPressPos;
Vector2 secondPressPos;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
//save began touch 2d point
firstPressPos = new Vector3(Input.mousePosition.x, Input.mousePosition.y);
}
if (Input.GetMouseButton(0))
{
//save ended touch 2d point
secondPressPos = new Vector3(Input.mousePosition.x, Input.mousePosition.y);
if (firstPressPos != secondPressPos)
{
float RotX = Input.GetAxis("Mouse X") * sensitivity * Time.deltaTime;
float RotY = Input.GetAxis("Mouse Y") * sensitivity * Time.deltaTime;
transform.RotateAround(Vector3.up, RotX);
transform.RotateAround(Vector3.right, -RotY);
}
}
}
try this code
// Screen Touches
Vector2?[] oldTouchPositions = {
null,
null
};
// Rotation Speed
public float rotSpeed = 0.5f;
public void Update()
{
if (Input.touchCount == 0)
{
oldTouchPositions[0] = null;
oldTouchPositions[1] = null;
}
else if (Input.touchCount == 1)
{
if (oldTouchPositions[0] == null || oldTouchPositions[1] != null)
{
oldTouchPositions[0] = Input.GetTouch(0).position;
oldTouchPositions[1] = null;
}
else
{
Vector2 newTouchPosition = Input.GetTouch(0).position;
float distanceX = (oldTouchPositions[0] - newTouchPosition).Value.x;
float distanceY = (oldTouchPositions[0] - newTouchPosition).Value.y;
float rotX = distanceX * rotSpeed * Mathf.Deg2Rad;
float rotY = distanceY * rotSpeed * Mathf.Deg2Rad;
transform.Rotate(Vector3.up, rotX * 5, Space.Self);
transform.Rotate(Vector3.left, rotY * 5, Space.Self);
oldTouchPositions[0] = newTouchPosition;
}
}
else
{
if (oldTouchPositions[1] == null)
{
oldTouchPositions[0] = Input.GetTouch(0).position;
oldTouchPositions[1] = Input.GetTouch(1).position;
}
else
{
}
}
}
Here's a script I found a few years back that manipulates X axis spin with Mouse or Touches. It seems to work well with touches, but not as good with Mouse. Try it and let me know if it behaves a bit better. This one should adjust the spinning speed dynamically:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpinDrag : MonoBehaviour {
float f_lastX = 0.0f;
float f_difX = 0.5f;
float f_steps = 0.0f;
int i_direction = 1;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
f_difX = 0.0f;
}
else if (Input.GetMouseButton(0))
{
f_difX = Mathf.Abs(f_lastX - Input.GetAxis("Mouse X"));
if (f_lastX < Input.GetAxis("Mouse X"))
{
i_direction = -1;
transform.Rotate(Vector3.up, -f_difX);
}
if (f_lastX > Input.GetAxis("Mouse X"))
{
i_direction = 1;
transform.Rotate(Vector3.up, f_difX);
}
f_lastX = -Input.GetAxis("Mouse X");
}
else
{
if (f_difX > 0.5f) f_difX -= 0.05f;
if (f_difX < 0.5f) f_difX += 0.05f;
transform.Rotate(Vector3.up, f_difX * i_direction);
}
}
}

Unity collider prefab doesn't work on one side

I have a collider prefab (a cube, with a box collider the size of the cube). In one scene, my player collides perfectly with it, but in another scene, the player glitches and phases through one side of the collider but not the other. Rotating it changes the side that it glitches out on, so it always faces the same global direction.
What could be causing this? I've made sure the prefab for both the player and collider object are exactly the same across scenes, and nothing effects the collisions besides the player controller script which moves the player and has a condition for OnCollisionExit that sets the rigidbody's velocity to 0.
Added the code below. The input is with joysticks (think the joysticks in a helicopter). Like I said, the collision works perfectly usually, but for some reason only in this scene it doesn't. I suspect it's some kind of hierarchy or rigidbody problem but I've checked seemingly everything.
https://github.com/ben-humphries/FRC-Driving-Simulation
using UnityEngine;
using UnityEngine.UI;
public class ChassisController : MonoBehaviour {
public UIController uiController;
public Canvas PauseCanvas;
public float speedLinear = 10f;
public float speedAngular = 100f;
public float joyDeadZone = 0.5f;
public float rotationOffset = 3f;
public bool squaredMovement = false;
public DriveModes driveMode = DriveModes.Tank;
[HideInInspector]
public bool paused = false;
private Rigidbody rigidbody;
Vector3 lastLinearPosition;
float lastAngularPosition;
void Start () {
rigidbody = GetComponent<Rigidbody> ();
lastLinearPosition = Vector3.zero;
lastAngularPosition = 0f;
paused = false;
}
void FixedUpdate () {
if (!paused) {
/*
* INPUT
*/
if (driveMode == DriveModes.Tank) {
if (Mathf.Abs (Input.GetAxis ("VerticalLeft")) > joyDeadZone) {
Vector3 rotatePoint = (transform.position) + transform.TransformDirection (Vector3.right) * rotationOffset;
Vector3 rotateAxis = transform.TransformDirection (Vector3.up);
Debug.DrawRay (rotatePoint, rotateAxis * 10f, Color.red);
transform.RotateAround (rotatePoint, rotateAxis, -speedAngular * Input.GetAxis ("VerticalLeft") * Time.fixedDeltaTime * (squaredMovement ? Mathf.Abs (Input.GetAxis ("VerticalLeft")) : 1));
}
if (Mathf.Abs (Input.GetAxis ("VerticalRight")) > joyDeadZone) {
Vector3 rotatePoint = (transform.position) + transform.TransformDirection (Vector3.left) * rotationOffset;
Vector3 rotateAxis = transform.TransformDirection (Vector3.up);
Debug.DrawRay (rotatePoint, rotateAxis * 10f, Color.red);
transform.RotateAround (rotatePoint, rotateAxis, speedAngular * Input.GetAxis ("VerticalRight") * Time.fixedDeltaTime * (squaredMovement ? Mathf.Abs (Input.GetAxis ("VerticalRight")) : 1));
}
} else if (driveMode == DriveModes.Mecanum) {
if (Mathf.Abs (Input.GetAxis ("VerticalRight")) > joyDeadZone) {
transform.Translate (Vector3.forward * -speedLinear * Input.GetAxis ("VerticalRight") * Time.fixedDeltaTime * (squaredMovement ? Mathf.Abs (Input.GetAxis ("VerticalRight")) : 1));
}
if (Mathf.Abs (Input.GetAxis ("HorizontalRight")) > joyDeadZone) {
transform.Translate (Vector3.right * speedLinear / 5f * Input.GetAxis ("HorizontalRight") * Time.fixedDeltaTime * (squaredMovement ? Mathf.Abs (Input.GetAxis ("HorizontalRight")) : 1));
}
if (Mathf.Abs (Input.GetAxis ("TwistRight")) > joyDeadZone) {
transform.Rotate (0, speedAngular * Input.GetAxis ("TwistRight") * Time.fixedDeltaTime * (squaredMovement ? Mathf.Abs (Input.GetAxis ("TwistRight")) : 1), 0);
}
}
float linearVelocity = Mathf.Round (((transform.position - lastLinearPosition) / Time.fixedDeltaTime).magnitude * 100f) / 100f;
lastLinearPosition = transform.position;
float angularVelocity = Mathf.Round ((transform.eulerAngles.y - lastAngularPosition) / Time.fixedDeltaTime * Mathf.Deg2Rad * 100f) / 100f;
lastAngularPosition = transform.eulerAngles.y;
uiController.UpdateVelocities (linearVelocity, angularVelocity);
}
}
void OnCollisionExit(Collision col){
rigidbody.isKinematic = true;
rigidbody.velocity = Vector3.zero;
rigidbody.angularVelocity = Vector3.zero;
rigidbody.isKinematic = false;
}
public enum DriveModes{
Tank,
Mecanum
}
public void setDriveMode(){
int index = PauseCanvas.transform.GetChild (1).GetComponent<Dropdown> ().value;
if (index == 0) {
driveMode = DriveModes.Tank;
} else if (index == 1) {
driveMode = DriveModes.Mecanum;
}
uiController.UpdateDriveMode (index == 0 ? "Tank" : "Mecanum");
}
}
When GameObject has Rigidbody and Collider and you want collision, do not move the Object by transform.Translate or rotate it with transform.Rotate or transform.RotateAround. When you do this there will be no collision.
The right way to move GameObject with a Rigidbody and Collision is with the Rigidbody.MovePosition function.
The correct way to rotate it is to use the Rigidbody.MoveRotation function.
Example of Rigidbody.MovePosition:
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
Vector3 tempVect = new Vector3(h, 0, v);
tempVect = tempVect.normalized * speed * Time.deltaTime;
rb.MovePosition(transform.position + tempVect);
Example of Rigidbody.MoveRotation:
Quaternion deltaRotation = Quaternion.Euler(eulerAngleVelocity * Time.deltaTime);
rb.MoveRotation(rb.rotation * deltaRotation);
You need to replace transform.Translate, transform.Rotate and transform.RotateAround with these.

Categories

Resources