How to fix Character Controller jumping up steep slopes? - unity3d

I'm using a 3d character controller for my player but I can't figure out how to stop it from being able to jump up steep slopes. I've looked around and could only find one video that fixes the issue but after the character slides down the slope it stays locked in the slide movement function and I'm not really sure why. I've edited out the code that doesn't matter for clarity, pls lmk if u know how to fix this!
Heres the code:
public class PlayerMovement : MonoBehaviour
{
public CharacterController controller;
public float speed = 12f;
public float gravity = -9.81f;
public float jumpHeight = 3f;
public Transform groundCheck;
public float groundDistance = 0.4f;
public LayerMask groundMask;
//SLOPES
private float groundRayDistance = 1;
private RaycastHit slopeHit;
public float slopeSlideSpeed;
Vector3 velocity;
bool isGrounded;
// Update is called once per frame
void Update()
{
HandleMovement();
HandleAnimations();
if(OnSteepSlope())
{
SteepSlopeMovement();
}
}
private void HandleMovement()
{
isGrounded = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask);
if (isGrounded && velocity.y < 0)
{
velocity.y = -2f;
}
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
move = transform.right * x + transform.forward * z;
controller.Move(move * speed * Time.deltaTime);
if (Input.GetButtonDown("Jump") && isGrounded)
{
velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
}
velocity = AdjustVelocityToSlope(velocity);
velocity.y += gravity * Time.deltaTime;
controller.Move(velocity * Time.deltaTime);
}
private Vector3 AdjustVelocityToSlope(Vector3 velocity)
{
var ray = new Ray(transform.position, Vector3.down);
if(Physics.Raycast(ray, out RaycastHit hitInfo, 0.2f))
{
var slopeRotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
var adjustedVelocity = slopeRotation * velocity;
if(adjustedVelocity.y < 0)
{
return adjustedVelocity;
}
}
return velocity;
}
private bool OnSteepSlope()
{
if (!isGrounded) return false;
if (Physics.Raycast(transform.position, Vector3.down, out slopeHit, (controller.height / 2) + groundRayDistance))
{
float slopeAngle = Vector3.Angle(slopeHit.normal, Vector3.up);
if (slopeAngle > controller.slopeLimit) return true;
}
return false;
}
private void SteepSlopeMovement()
{
Vector3 slopeDirection = Vector3.up - slopeHit.normal * Vector3.Dot(Vector3.up, slopeHit.normal);
float slideSpeed = speed + slopeSlideSpeed + Time.deltaTime;
velocity = slopeDirection * -slideSpeed;
velocity.y = velocity.y - slopeHit.point.y;
}

OnSteepSlope() wasnt returning false so I put the return false statement in an else loop, I also changed how I was moving the character in SteepSlopeMovement by using controller.Move() instead of directly changing the velocity. It works great now

I see you've got dot product check inside of SteepSlopeMovement might be useful to draw some rays with gizmos of the player's down and the normal of the surface. Then you can adjust the threshold value for when you can jump.

Related

Is there a way to have an object with rigidbody tilt but not rotate (cube)?

I am working on a project where a player can control a cube and move it around. I love having physics on the cube, but when manuevering, it is very difficult to time a jump when the cube is spinning all over the place. I would like the player to be able to move the cube along the ground, and the cube would gradually tilt towards the direction they are moving it, but not flip entirely. I don't know if it would even work conceptually, but if someone could help me figure out how to alter my code to keep it grounded but still tilt. Thank you.
{
public GameObject Camera;
Collider coll;
private bool isGrounded;
Rigidbody rb;
Vector3 velocity;
public float speed = 12f;
public float gravity = -9.8f;
public float jumpStrength = 1000f;
// Start is called before the first frame update
void Start()
{
Camera = GameObject.Find("Main Camera");
rb = GetComponent<Rigidbody>();
}
void OnCollisionEnter(UnityEngine.Collision collision)
{
if (collision.gameObject.tag == "Floor")
{
isGrounded = true;
Debug.Log("Is Grounded");
}
}
void OnCollisionExit(UnityEngine.Collision collision)
{
if (collision.gameObject.tag == "Floor")
{
isGrounded = false;
Debug.Log("Is Not Grounded");
}
}
// Update is called once per frame
void Update()
{
Camera.transform.position = new Vector3(transform.position.x, transform.position.y + 2.38f, transform.position.z - 3.45f);
if (isGrounded && velocity.y < 0)
{
velocity.y = -2f;
}
float mH = Input.GetAxis("Horizontal");
float mV = Input.GetAxis("Vertical");
rb.velocity = new Vector3(mH * speed, rb.velocity.y, mV * speed);
if(isGrounded && Input.GetButtonDown("Jump"))
{
velocity.y = Mathf.Sqrt(3 * -2 * gravity);
}
velocity.y += gravity * Time.deltaTime;
if(Input.GetButtonDown("Jump") && isGrounded)
{
rb.AddForce(0f, jumpStrength, 0f);
Debug.Log("Jump");
}
}
}

Parameter values are not being sent from script to blend tree (Unity 2020)

(Parameter values are not being sent from script to blend tree resulting in not following animation with movement.) I have animations set up in a blend tree that has a 1d type blend tree. I have it set to custom thresholds with an idle animation as the base state then the blend tree has three motions: a strafe left, right, and a walk forward. When I move forward the player does not do the animations told in the tree and I am almost certain it is the script.
I am also using Unity 1212.1.2f1 and for my scripts I have been using Microsoft Visual Studio if it matters.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
public CharacterController controller;
public float speed = 12f;
public float gravity = -9.81f;
public float jumpHeight = 3f;
public float rotationSpeed = 75.0f;
public Animator anim;
public Transform groundCheck;
public float groundDistance = 0.4f;
public LayerMask groundMask;
Vector3 velocity;
bool isGrounded;
// Start is called before the first frame update
void Start()
{
anim = GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
isGrounded = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask);
float translation = Input.GetAxis("Vertical") * speed;
float rotation = Input.GetAxis("Horizontal") * rotationSpeed;
translation *= Time.deltaTime;
rotation *= Time.deltaTime;
if (isGrounded && velocity.y < 0)
{
velocity.y = -2f;
}
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
if ((Input.GetButtonDown("Jump")) && isGrounded)
{
velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
}
Vector3 move = transform.right * x + transform.forward * z;
controller.Move(move * speed * Time.deltaTime);
velocity.y += gravity * Time.deltaTime;
controller.Move(velocity * Time.deltaTime);
{
anim.SetFloat("Vertical", Input.GetAxis("Vertical"));
anim.SetFloat("Horizontal", Input.GetAxis("Horizontal"));
}
}
}
first i think you could try to make a new var :
private Rigidbody2D rg;
then add this in Start ()
rg = Player.GetComponent<Rigidbody2D>();
and Update () with this
anim.SetFloat("Vertical", rg.velocity.x);
anim.SetFloat("Horizontal", rgvelocity.x);
and sure made 2 Vertical and Horizontal in Animator parameters

how do i make something which moves with Velocity jump in Unity?

I was hoping I could get some help! I've been trying to make a platformer using velocity to move but I can't find a good way to do jumping using the system. Every frame the velocity's y just resets itself and I don't know how to create a jump. I have tried using ForceMode.VelocityChange and I have tried to write out equations. The player falls extremely slowly even with gravity turned on.
playerBody.AddForce(Vector3.up * jumpForce, ForceMode.VelocityChange);
I have the same issues when I try to set the y velocity to change with gravity
float MoveDirectionY = jumpForce * Physics.gravity.y;
enter image description here
Nothing seems to be working here. When i play the game gravity still pulls the object down slowly but if i turn off gravity it doesnt pull the object down at all.
The game does log the statement letting me know that it does know the space button was pressed.alt text
I want to also provide my code here:
using System.Collections;
using System.Collections.Generic;
using System.Transactions;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
[SerializeField] private Rigidbody playerBody;
[SerializeField] private Vector3 inputVector;
[SerializeField] public float speed = 0.01f;
[SerializeField] public bool jump;
[SerializeField] private float turnSpeed = 45;
[SerializeField] public float jumpForce = 35000f;
[SerializeField] private bool isOnGround = true;
[SerializeField] float enemyPushForce = 100;
public int ingredient;
public GameManager gameManager;
public camSwitch cs;
public float horizontalInput;
public float verticalInput;
float playerFacingAngleY;
private GameObject FocalPoint;
// Start is called before the first frame update
void Start()
{
//Just making sure we have the rigid body of the game object the script is attached to so we can move it later
playerBody = gameObject.GetComponent<Rigidbody>();
FocalPoint = GameObject.Find("Focal Point");
}
// Update is called once per frame
//This is where the player script should be realizing we are using inputs
void Update()
{
horizontalInput = Input.GetAxis("Horizontal");
verticalInput = Input.GetAxis("Vertical");
playerFacingAngleY += horizontalInput * turnSpeed;
Vector3 playerFacingDirection = new Vector3(0, playerFacingAngleY, 0);
playerBody.rotation = Quaternion.Euler(playerFacingDirection);
float moveDirectionX = (FocalPoint.transform.position.x - gameObject.transform.position.x) *speed * verticalInput * Time.deltaTime;
float MoveDirectionY = jumpForce * Physics.gravity.y;
float moveDirectionZ = (FocalPoint.transform.position.z - gameObject.transform.position.z) * speed * verticalInput * Time.deltaTime;
Vector3 moveDirection = new Vector3(moveDirectionX, MoveDirectionY, moveDirectionZ);
playerBody.velocity = moveDirection;
if (Input.GetKeyDown(KeyCode.Space) && isOnGround == true)
{
playerBody.AddForce(Vector3.up * jumpForce, ForceMode.VelocityChange);
isOnGround = false;
print("player has jumped");
}
}
private void OnCollisionEnter(Collision collision)
{
isOnGround = true;
if (collision.gameObject.tag == "Enemy")
{
Debug.Log("Player ran into an enemy");
if (cs.inSky == true)
{
speed = 0;
}
else
{
speed = 10;
}
}
else if (collision.gameObject.tag == "Ingredient")
{
Debug.Log("Player collided with an ingredient");
collision.gameObject.SetActive(false);
ingredient++;
}
else if (collision.gameObject.tag == "Ground") {
isOnGround = true;
print("player has hit the ground");
}
}
}
Do not play with rigidbody in Update method. Use FixedUpdate() instead.
In addition, do not change velocity using rb.velocity = ... but use rigibody.AddForce() method. Try something like this:
void FixedUpdate() //using rigidbody? => ONLY FIXEDUPDATE
{
horizontalInput = Input.GetAxis("Horizontal");
verticalInput = Input.GetAxis("Vertical");
playerFacingAngleY += horizontalInput * turnSpeed;
Vector3 playerFacingDirection = new Vector3(0, playerFacingAngleY, 0);
playerBody.rotation = Quaternion.Euler(playerFacingDirection);
float moveDirectionX = (FocalPoint.transform.position.x - gameObject.transform.position.x) *speed * verticalInput * Time.deltaTime;
float moveDirectionZ = (FocalPoint.transform.position.z - gameObject.transform.position.z) * speed * verticalInput * Time.deltaTime;
Vector3 moveDirection = new Vector3(moveDirectionX, 0.0f, moveDirectionZ); //0.0f - just turn on gravity in rigidbody component or you can change it if you want some additional Vertical force
playerBody.AddForce(moveDirection, ForceMode.VelocityChange); //force mode change to whatever you want
if (Input.GetKeyDown(KeyCode.Space) && isOnGround == true)
{
playerBody.AddForce(Vector3.up * jumpForce, ForceMode.VelocityChange);
isOnGround = false;
print("player has jumped");
}
}

Unity camera problem - Moving FPS/TPS camera to look at a target and resuming player control

I am having trouble with aligning my player controlled camera (I use the same class for FPS and TPS) to look at a target (when ResetCamera() is called by another script) and then getting the player to resume control. The main reason I am doing this is so I can switch between FPS and TPS cameras and remain looking at the same target.
I can look at the target fine, but ONLY if I stop setting the rotation based on yaw and pitch (from "Mouse X" and "Mouse Y" inputs) in LateUpdate() after I set the lookAtTarget in ResetCamera(), but that means the player can no longer look around.
However, I cannot figure out how to get the correct yaw and pitch values after this so the player can continue looking around from the new look at target. How could I do this so the player could continue looking around?
public class PlayerCamera : MonoBehaviour {
public float mouseSensitivity = 10f;
public Transform target;
public float dstFromTarget = 2f;
public Vector2 pitchConstraints = new Vector2(-20f, 85f);
public float rotSmoothTime = .12f;
Vector3 rotSmoothVel;
Vector3 currRot;
float yaw;
float pitch;
void LateUpdate() {
yaw += Input.GetAxis("Mouse X") * mouseSensitivity;
pitch -= Input.GetAxis("Mouse Y") * mouseSensitivity;
pitch = Mathf.Clamp(pitch, pitchConstraints.x, pitchConstraints.y);
currRot = Vector3.SmoothDamp(currRot, new Vector3(pitch, yaw), ref rotSmoothVel, rotSmoothTime);
transform.eulerAngles = currRot;
transform.position = target.position - transform.forward * dstFromTarget;
}
public void ResetCamera(Transform lookAtTarget) {
transform.LookAt(lookAtTarget);
// below gets yaw and pitch values that move the camera to look at the
// wrong location
// yaw = transform.eulerAngles.x;
// pitch = transform.eulerAngles.y;
// pitch = Mathf.Clamp(pitch, pitchConstraints.x, pitchConstraints.y);
// currRot = new Vector3(pitch, yaw);
}
}
So, I figured out how to do what I wanted. Below is a script for a third or first person (just adjust the dstFromTarget variable to zero) camera that can be rotated to look at a target by another script with ResetCamera() and the player can resume moving around from that point.
public class PlayerCamera : MonoBehaviour {
public bool isFirstPerson = false;
public float mouseSensitivity = 10f;
public Transform target;
public float dstFromTarget = 2f;
public bool invertedPitch = false;
public float smoothTime = .12f;
public float minimumX = -85f;
public float maximumX = 85f;
// LateUpdate, so the Camera Target will definitely have been set
void LateUpdate () {
// first person updating is handled by Inverse kinematics stuff in my case
if (isFirstPerson) return;
UpdateStuff();
}
public void UpdateStuff() {
float yaw = Input.GetAxisRaw("Mouse X");
float pitch = -Input.GetAxisRaw("Mouse Y");
if (invertedPitch) {
pitch *= -1f;
}
Vector3 yRot = new Vector3(0f, yaw, 0f) * mouseSensitivity;
Vector3 xRot = new Vector3(pitch, 0f, 0f) * mouseSensitivity;
xRot = ClampRotationAroundXAxis(transform.rotation, xRot);
Transform newTrans = transform;
newTrans.Rotate(xRot);
newTrans.Rotate(yRot, Space.World);
transform.rotation = Quaternion.Slerp(transform.rotation, newTrans.rotation, smoothTime * Time.deltaTime);
transform.position = target.position - transform.forward * dstFromTarget;
}
public void ResetCamera(Transform lookAtTarget) {
transform.LookAt(lookAtTarget);
}
Vector3 ClampRotationAroundXAxis(Quaternion q, Vector3 xrot) {
q.x /= q.w;
q.y /= q.w;
q.z /= q.w;
q.w = 1.0f;
float angleX = 2.0f * Mathf.Rad2Deg * Mathf.Atan (q.x);
if (angleX < minimumX && xrot.x < 0f) {
xrot = Vector3.zero;
}
if (angleX > maximumX && xrot.x > 0f) {
xrot = Vector3.zero;
}
return xrot;
}
}

How to jump smoothly in Unity3D

I add a CharacterController to Player.But when I test the jump function,I find that the Player will move upwards immediately.
if (Player.isGrounded)
{
if (jump)
{
Move.y = JumpSpeed;
jump = false;
Player.Move (Move * Time.deltaTime);
}
}
Move += Physics.gravity * Time.deltaTime * 4f;
Player.Move (Move * Time.fixedDeltaTime);`
You are calling Player.Move() twice in one frame. This might be an issue.
You are adding gravity to Move vector, which means it will always go upward when you call this code.
naming a variable like Move is not a good convention. It creates confusion while reading because there is already a method of same name. change it to moveDirection.
Here is sample code:
public class ExampleClass : MonoBehaviour {
public float speed = 6.0F;
public float jumpSpeed = 8.0F;
public float gravity = 20.0F;
private Vector3 moveDirection = Vector3.zero;
CharacterController controller;
void Start()
{
controller = GetComponent<CharacterController>();
}
void Update() {
if (controller.isGrounded) {
moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
moveDirection = transform.TransformDirection(moveDirection);
moveDirection *= speed;
if (Input.GetButton("Jump"))
moveDirection.y = jumpSpeed;
}
moveDirection.y -= gravity * Time.deltaTime;
controller.Move(moveDirection * Time.deltaTime);
}
}
hope this helps.