I am making a ball-rolling game. I want to implement an interaction where the ball can move on a specific area of a vertical wall like the game that the images show, but I don't know how to implement it.
1 Answer
First, we'll create invisible trigger colliders covering the volume of your map where you want the gravity to point in a new direction. Rotate those colliders so that their "forward" direction (the blue arrow on the transform gizmo in local translation mode) points in the direction you want gravity to point.
Create a physics layer called "Gravity Zone" and ensure these objects are on that layer. You can filter collisions with this layer so only objects that need to use this altered gravity detect overlaps with these objects - good for performance since you have fewer collisions to check, and it saves you some filtering code to manually ignore them in unrelated scripts.
Then we'll make a script for the rigidbody (or bodies) that should obey this altered gravity:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class VariableGravity : MonoBehaviour {
// Reference to our attached body, for modifying our physics.
[SerializeField, HideInInspector] Rigidbody _body;
// Cache ID of gravity modification physics layer.
LayerMask _gravityLayer;
// Current gravity acceleration vector - default to normal gravity.
Vector3 _gravity = Physics.gravity;
// Collection of altered gravity zone trigger volumes we're touching.
// These can overlap, so we store an ordered list of them,
// where the last entry is always the most recent one we've entered.
List<Collider> _zonesTouched = new List<Collider>();
// Change our gravity to point in the provided direction.
public void SetGravityDirection(Vector3 direction)
{
// We'll make our custom gravity exactly as strong as the default one.
_gravity = Physics.gravity.magnitude * direction;
}
void Start() {
_gravityLayer = LayerMask.NameToLayer("Gravity Zone");
}
// Apply our gravity change every physics step.
void FixedUpdate() {
// If _gravity == Physics.gravity, then this is a no-op
// (ie. we apply zero additional acceleration).
// Otherwise, it undoes the normal gravity and applies our new gravity.
_body.AddForce(_gravity - Physics.gravity, ForceMode.Acceleration);
}
// When we enter a gravity zone, apply its changes to our gravity direction.
void OnTriggerEnter(Collider other) {
// Ignore triggers that are not gravity zones.
if (other.gameObject.layer != _gravityLayer) {
return;
}
// Add this zone to the end of our list.
_zonesTouched.Add(other);
// Use this zone's gravity as our current falling direction.
SetGravityDirection(other.transform.forward);
}
// When we exit a gravity zone, remove its effect.
void OnTriggerExit(Collider other) {
// Ignore triggers that are not gravity zones.
if (other.gameObject.layer != _gravityLayer) {
return;
}
// When we leave a zone, we want to fall back on the effects
// of the next-most-recent zone we touched, if any.
int zoneCount = _zonesTouched.Count;
if (zoneCount > 1) {
// We have another zone to fall back on.
// If "this" zone we're leaving is the one currently in effect,
// fall back on the next most recent zone in the stack.
if (other == _zonesTouched[zoneCount - 1]) {
var fallback = _zonesTouched[zoneCount - 2];
SetGravityDirection(fallback.transform.forward);
}
// ...and remove the zone from our list,
// even if it wasn't the currently active one.
_zonesTouched.Remove(other);
} else {
// No other zones in our list, revert to normal gravity.
_zonesTouched.Clear();
_gravity = Physics.gravity;
}
}
// Automatically wire up the _body variable so we don't have to set it.
void OnValidate()
{
// TryGet is a good habit for minimizing editor allocations,
// plus it's less typing than out _body = GetComponent<Rigidbody>();
TryGetComponent(out _body);
}
}
Most of the complexity here is just in ensuring we handle entering/exiting multiple zones in any order - so if you have a funny-shaped volume to navigate, you can build it out of multiple colliders and this script will handle the transitions between them sensibly.