How to make Smooth Gravity Transitions
I am trying to make a game where the player can manipulate gravity with button presses. W makes the players gravity pull upwards, D to the right, etc. The issue I have been running into for a while is that if the player is falling downwards and I press the button to make gravity pull right, instead of a smooth acceleration curve, the player stops in midair and then starts moving right. My goal is for the player to smoothly transition from falling straight down to falling sideways. i.e. I want a curve and not a right angle. I have tried using Vector2.Lerp but instead of smoothly transitioning between the current gravity vector and the desired gravity vector, it sets the gravity to zero and then moves towards the desired gravity vector. How do I avoid this? using System; using System.Collections; using System.Collections.Generic; using UnityEngine;
public class PhysicsObject : MonoBehaviour
{
public float minGroundNormalY = .65f;
public float minGroundNormalX = .65f;
public float gravityModifier = 1f;
public float gravityChangeTime = .5f;
protected Vector2 targetVelocity;
protected bool grounded;
protected Vector2 groundNormal;
protected Rigidbody2D rb2d;
protected Vector2 velocity;
protected ContactFilter2D contactFilter;
protected RaycastHit2D[] hitBuffer = new RaycastHit2D[16];
protected List<RaycastHit2D> hitBufferList = new List<RaycastHit2D>(16);
//Information regarding the current gravitational orientation of the object.
protected bool onFloor = true;
protected bool rightWall = false;
protected bool leftWall = false;
protected bool ceiling = false;
protected Dictionary<string, bool> orientations = new Dictionary<string, bool>();
protected const string FLOOR = "onFloor";
protected const string CEILING = "ceiling";
protected const string LEFTWALL = "leftWall";
protected const string RIGHTWALL = "rightWall";
protected string currentOrientation = FLOOR;
//Variables to store gravitational orientation
protected Vector2 downwardGravity = new Vector2(0f, -10f);
protected Vector2 upwardGravity = new Vector2(0f, 10f);
protected Vector2 rightGravity = new Vector2(10f, 0f);
protected Vector2 leftGravity = new Vector2(-10f, 0f);
protected Vector2 gravity = new Vector2(0f, -10f);
protected Dictionary<string, Vector2> directionalGravity = new Dictionary<string, Vector2>();
protected Vector2 oldGravity;
protected bool gravityJustChanged = false;
protected string DESIRED_GRAVITY = FLOOR;
float gravFrac = .1f;
float gravFrac2 = .1f;
private Vector2 gravitySum;
int onlyRunAFewTimes = 0;
bool gravEqualsSum = false;
protected const float minMoveDistance = 0.001f;
protected const float shellRadius = 0.01f;
void OnEnable()
{
rb2d = GetComponent<Rigidbody2D>();
}
void Start()
{
contactFilter.useTriggers = false;
contactFilter.SetLayerMask(Physics2D.GetLayerCollisionMask(gameObject.layer));
contactFilter.useLayerMask = true;
orientations.Add(FLOOR, onFloor);
orientations.Add(RIGHTWALL, rightWall);
orientations.Add(LEFTWALL, leftWall);
orientations.Add(CEILING, ceiling);
directionalGravity.Add(FLOOR, downwardGravity);
directionalGravity.Add(CEILING, upwardGravity);
directionalGravity.Add(RIGHTWALL, rightGravity);
directionalGravity.Add(LEFTWALL, leftGravity);
}
void Update()
{
targetVelocity = Vector2.zero;
ComputeVelocity();
}
protected void UpdateGravity(Vector2 startingGrav, Vector2 targetGrav)
{
Debug.Log(Vector2.Lerp(startingGrav, targetGrav, 0f));
gravity = Vector2.Lerp(startingGrav, targetGrav, 1f);
}
protected virtual void ComputeVelocity()
{
}
protected virtual void ChangeGravity()
{
//Should call ChangeOrientationBools in the derived class
}
protected void ChangeOreintationBools(string currentOrientation, string newOrientation)
{
oldGravity = directionalGravity[GetCurrentOrientation()];
orientations[currentOrientation] = false;
orientations[newOrientation] = true;
DESIRED_GRAVITY = newOrientation;
gravitySum = directionalGravity[DESIRED_GRAVITY] + gravity;
}
protected string GetCurrentOrientation()
{
foreach (var orientation in orientations)
{
if(orientation.Value == true)
{
//Debug.Log(orientation.Key);
return orientation.Key;
}
}
//This code should never run
return "failed to find a true value";
}
void FixedUpdate()
{
if (gravity != directionalGravity[DESIRED_GRAVITY])
{
Debug.Log("We want the gravity to be" + directionalGravity[DESIRED_GRAVITY]);
Debug.Log(gravity == directionalGravity[DESIRED_GRAVITY]);
if (onlyRunAFewTimes < 10)
{
Debug.Log("the gravity sum is: " + gravitySum);
Debug.Log("Moving to Sum");
UpdateGravity(directionalGravity[GetCurrentOrientation()], gravitySum * gravFrac2);
onlyRunAFewTimes++;
Debug.Log("gravity is: " + gravity);
gravFrac2 += .1f;
}
else if (gravitySum == gravity || gravEqualsSum)
{
gravEqualsSum = true;
Debug.Log("We tryna update gravity mofo");
UpdateGravity(gravitySum, directionalGravity[DESIRED_GRAVITY] * gravFrac);
Debug.Log("gravity is: " + gravity);
gravFrac += .1f;
}
}
else
{
onlyRunAFewTimes = 0;
gravFrac = .1f;
gravFrac2 = .1f;
gravEqualsSum = true;
}
ChangeGravity();
if (orientations[FLOOR])
{
HandleFloorMovement();
}
else if (orientations[CEILING])
{
HandleCeilingMovement();
}
else if (orientations[RIGHTWALL])
{
HandleRightWallMovement();
}
else if (orientations[LEFTWALL])
{
HandleLeftWallMovement();
}
}
//The following functions are for if gravity is pulling the object down (towards the floor)
private void HandleFloorMovement()
{
velocity += gravityModifier * gravity * Time.deltaTime;
velocity.x = targetVelocity.x;
grounded = false;
Vector2 deltaPosition = velocity * Time.deltaTime;
Vector2 moveAlongGround = new Vector2(groundNormal.y, -groundNormal.x);
Vector2 move = moveAlongGround * deltaPosition.x;
FloorMovement(move, false);
move = Vector2.up * deltaPosition.y;
FloorMovement(move, true);
}
void FloorMovement(Vector2 move, bool yMovement)
{
float distance = move.magnitude;
if (distance > minMoveDistance)
{
int count = rb2d.Cast(move, contactFilter, hitBuffer, distance + shellRadius);
hitBufferList.Clear();
for (int i = 0; i < count; i++)
{
hitBufferList.Add(hitBuffer[i]);
}
for (int i = 0; i < hitBufferList.Count; i++)
{
Vector2 currentNormal = hitBufferList[i].normal;
if (currentNormal.y > minGroundNormalY)
{
grounded = true;
if (yMovement)
{
groundNormal = currentNormal;
currentNormal.x = 0;
}
}
float projection = Vector2.Dot(velocity, currentNormal);
if (projection < 0)
{
velocity = velocity - projection * currentNormal;
}
float modifiedDistance = hitBufferList[i].distance - shellRadius;
distance = modifiedDistance < distance ? modifiedDistance : distance;
}
}
rb2d.position = rb2d.position + move.normalized * distance;
}
private void HandleCeilingMovement()
{
velocity += gravityModifier * gravity * Time.deltaTime;
velocity.x = targetVelocity.x;
grounded = false;
Vector2 deltaPosition = velocity * Time.deltaTime;
Vector2 moveAlongGround = new Vector2(groundNormal.y, -groundNormal.x);
Vector2 move = moveAlongGround * deltaPosition.x;
CeilingMovement(move, false);
move = Vector2.up * deltaPosition.y;
CeilingMovement(move, true);
}
private void CeilingMovement(Vector2 move, bool yMovement)
{
float distance = move.magnitude;
if (distance > minMoveDistance)
{
int count = rb2d.Cast(move, contactFilter, hitBuffer, distance + shellRadius);
hitBufferList.Clear();
for (int i = 0; i < count; i++)
{
hitBufferList.Add(hitBuffer[i]);
}
for (int i = 0; i < hitBufferList.Count; i++)
{
Vector2 currentNormal = hitBufferList[i].normal;
//Debug.Log(currentNormal.y);
//Debug.Log(minGroundNormalY);
if (currentNormal.y < -minGroundNormalY) //This may need to be a < because if we are on the ceiling wont the y component of the normal vector be negative?
{
grounded = true;
if (yMovement)
{
groundNormal = currentNormal;
currentNormal.x = 0;
}
}
float projection = Vector2.Dot(velocity, currentNormal);
if (projection < 0)
{
velocity = velocity - projection * currentNormal;
}
float modifiedDistance = hitBufferList[i].distance - shellRadius;
distance = modifiedDistance < distance ? modifiedDistance : distance;
}
}
rb2d.position = rb2d.position + move.normalized * distance;
}
private void HandleLeftWallMovement()
{
velocity += gravityModifier * gravity * Time.deltaTime;
velocity.y = targetVelocity.y;
grounded = false;
Vector2 deltaPosition = velocity * Time.deltaTime;
Vector2 moveAlongWall = new Vector2(groundNormal.y, groundNormal.x);
Vector2 move = moveAlongWall * deltaPosition.y;
LeftWallMovement(move, false);
move = Vector2.right * deltaPosition.x;
LeftWallMovement(move, true);
}
private void LeftWallMovement(Vector2 move, bool xMovement)
{
float distance = move.magnitude;
if (distance > minMoveDistance)
{
int count = rb2d.Cast(move, contactFilter, hitBuffer, distance + shellRadius);
hitBufferList.Clear();
for (int i = 0; i < count; i++)
{
hitBufferList.Add(hitBuffer[i]);
}
for (int i = 0; i < hitBufferList.Count; i++)
{
Vector2 currentNormal = hitBufferList[i].normal;
if (currentNormal.x > minGroundNormalX)
{
grounded = true;
if (xMovement)
{
groundNormal = currentNormal;
currentNormal.y = 0;
}
}
float projection = Vector2.Dot(velocity, currentNormal);
if (projection < 0)
{
velocity = velocity - projection * currentNormal;
}
float modifiedDistance = hitBufferList[i].distance - shellRadius;
distance = modifiedDistance < distance ? modifiedDistance : distance;
}
}
rb2d.position = rb2d.position + move.normalized * distance;
}
private void HandleRightWallMovement()
{
velocity += gravityModifier * gravity * Time.deltaTime;
velocity.y = targetVelocity.y;
grounded = false;
Vector2 deltaPosition = velocity * Time.deltaTime;
Vector2 moveAlongWall = new Vector2(groundNormal.y, -groundNormal.x);
Vector2 move = moveAlongWall * deltaPosition.y;
RightWallMovement(move, false);
move = Vector2.right * deltaPosition.x;
RightWallMovement(move, true);
}
private void RightWallMovement(Vector2 move, bool xMovement)
{
float distance = move.magnitude;
if (distance > minMoveDistance)
{
int count = rb2d.Cast(move, contactFilter, hitBuffer, distance + shellRadius);
hitBufferList.Clear();
for (int i = 0; i < count; i++)
{
hitBufferList.Add(hitBuffer[i]);
}
for (int i = 0; i < hitBufferList.Count; i++)
{
Vector2 currentNormal = hitBufferList[i].normal;
//Debug.Log(currentNormal.y);
// Debug.Log(minGroundNormalY);
if (currentNormal.x < -minGroundNormalX) //May need to be a <
{
grounded = true;
if (xMovement)
{
groundNormal = currentNormal;
currentNormal.y = 0;
}
}
float projection = Vector2.Dot(velocity, currentNormal);
if (projection < 0)
{
velocity = velocity - projection * currentNormal;
}
float modifiedDistance = hitBufferList[i].distance - shellRadius;
distance = modifiedDistance < distance ? modifiedDistance : distance;
}
}
rb2d.position = rb2d.position + move.normalized * distance;
}
}
I have been having this same problem for a few months now and I'm curious if such a thing is even possible in Unity.
Your answer
Follow this Question
Related Questions
Had to turn off gravity scale, but now player goes through walls 0 Answers
How to make instantiated rigidbodies continue moving the in the same direction as destroyed object? 1 Answer
Rigidbody2D.Cast() against mutiple colliders with a single Rigidbody2D 0 Answers
RigidBody.MovePosition won't stop even after reaching the destination 0 Answers
Uneven Jump Heights 1 Answer