Hi there, hope everyone is having a great day.
So I’m trying to add a feature in my game, where if the player gets close to the edge of a cliff for example and hits a collider, he will get pulled back. Imagine a mom cat picking up a kitten by the neck and pulling it back a bit, back to safety.
Because I don’t want to just use blocks and have the player run into an invisible wall. It just seems more reactive.
So below is my movement script (I did achieve this following a tutorial, I won’t lie).
Basically I’m hoping to use a similar script for the pull back, involving a bit of backwards jump and animation.
If anyone could lead me in the right direction, that would be MUCH appreciated.
Thanks and have a nice day!
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Unity.VisualScripting;
using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Windows.Speech;
public class AnimationAndMovementController : MonoBehaviour
{
private PlayerInput playerInput;
private CharacterController characterController;
private Animator animator;
int isWalkingHash;
int isRunningHash;
int isJumpingHash;
float groundedGravity = -.05f;
float gravity = -9.8f;
private Vector2 currentMovementInput;
private Vector3 currentMovement;
private Vector3 currentRunMovement;
public bool isMovementPressed;
public bool isRunPressed;
public bool isJumpPressed = false;
private float rotationFactorPerFrame = 15f;
[SerializeField]private float runMultiplier = 4.0f;
private float walkMultiplier = 1.5f;
public bool isDisabled = false;
private float initialJumpVelocity;
private float maxJumpHeight = 4.0f;
private float maxJumpTime = 0.75f;
private bool isJumping = false;
bool isJumpAnimating = false;
private void Awake()
{
playerInput = new PlayerInput();
characterController = GetComponent<CharacterController>();
animator = GetComponent<Animator>();
isWalkingHash = Animator.StringToHash("isWalking");
isRunningHash = Animator.StringToHash("isRunning");
isJumpingHash = Animator.StringToHash("isJumping");
playerInput.CharacterControls.Move.started += OnMovementInput;
playerInput.CharacterControls.Move.canceled += OnMovementInput;
playerInput.CharacterControls.Move.performed += OnMovementInput;
playerInput.CharacterControls.Run.started += OnRun;
playerInput.CharacterControls.Run.canceled += OnRun;
playerInput.CharacterControls.Jump.started += OnJump;
playerInput.CharacterControls.Jump.canceled += OnJump;
setupJumpVariables();
}
private void setupJumpVariables()
{
float timeToApex = maxJumpTime / 2;
gravity = (-2 * maxJumpHeight) / Mathf.Pow(timeToApex, 2);
initialJumpVelocity = (2 * maxJumpHeight) / timeToApex;
}
private void handleJump()
{
if (!isJumping && characterController.isGrounded && isJumpPressed)
{
animator.SetBool(isJumpingHash, true);
isJumpAnimating = true;
isJumping = true;
currentMovement.y = initialJumpVelocity * .5f;
currentRunMovement.y = initialJumpVelocity * .5f;
}
else if ((!isJumpPressed && isJumping && characterController.isGrounded)){
isJumping = false;
}
}
private void OnJump(InputAction.CallbackContext context)
{
isJumpPressed = context.ReadValueAsButton();
}
private void HandleRotation()
{
Vector3 positionToLookAt;
positionToLookAt.x = currentMovement.x;
positionToLookAt.y = 0.0f;
positionToLookAt.z = currentMovement.z;
Quaternion currentRotation = transform.rotation;
if (isMovementPressed)
{
Quaternion targetRotation = Quaternion.LookRotation(positionToLookAt);
transform.rotation = Quaternion.Slerp(currentRotation, targetRotation, rotationFactorPerFrame * Time.deltaTime);
}
}
private void OnMovementInput(InputAction.CallbackContext context)
{
currentMovementInput = context.ReadValue<Vector2>();
currentMovement.x = currentMovementInput.x * walkMultiplier;
currentMovement.z = currentMovementInput.y * walkMultiplier;
currentRunMovement.x = currentMovementInput.x * runMultiplier;
currentRunMovement.z = currentMovementInput.y * runMultiplier;
isMovementPressed = currentMovementInput.x != 0 || currentMovementInput.y != 0;
}
private void OnRun(InputAction.CallbackContext context)
{
isRunPressed = context.ReadValueAsButton();
}
private void HandleAnimation()
{
bool isWalking = animator.GetBool(isWalkingHash);
bool isRunning = animator.GetBool(isRunningHash);
if (isMovementPressed && !isWalking)
{
animator.SetBool(isWalkingHash, true);
}
else if(!isMovementPressed && isWalking)
{
animator.SetBool(isWalkingHash, false);
}
if((isMovementPressed && isRunPressed) && !isRunning)
{
animator.SetBool(isRunningHash, true);
}
else if ((!isMovementPressed || !isRunPressed) && isRunning)
{
animator.SetBool(isRunningHash, false);
}
}
private void HandleGravity()
{
bool isFalling = currentMovement.y <= 0.0f || !isJumpPressed;
float fallMultiplier = 2.0f;
if (characterController.isGrounded)
{
if (isJumpAnimating)
{
animator.SetBool(isJumpingHash, false);
isJumpAnimating = false;
}
currentMovement.y = groundedGravity;
currentRunMovement.y = groundedGravity;
}
else if (isFalling)
{
float previousYVelocity = currentMovement.y;
float newYVelocity = (currentMovement.y + (gravity * fallMultiplier * Time.deltaTime));
float nextYVelocity = Mathf.Max((previousYVelocity + newYVelocity) * .5f, -20.0f);
currentMovement.y = nextYVelocity;
currentRunMovement.y = nextYVelocity;
}
else
{
float previousYVelocity = currentMovement.y;
float newYVelocity = (currentMovement.y + (gravity * Time.deltaTime));
float nextYVelocity = (previousYVelocity + newYVelocity) * .5f;
currentMovement.y = nextYVelocity;
currentRunMovement.y = nextYVelocity;
}
}
// Update is called once per frame
private void Update()
{
if (!isDisabled)
{
HandleRotation();
HandleAnimation();
if (isRunPressed)
{
characterController.Move(currentRunMovement * Time.deltaTime);
}
else
{
characterController.Move(currentMovement * Time.deltaTime);
}
HandleGravity();
handleJump();
}
}
private void OnEnable()
{
playerInput.CharacterControls.Enable();
}
private void OnDisable()
{
playerInput.CharacterControls.Disable();
}
}