- Home /
Efficient faux gravity script
Hello, I am trying to simulate faux gravity on a planet with circle collider. (Point effector was not working right, becouse it did not count with the mass of the object) I am trying to do it the most efficient way but this probably is not enough. In the OnTriggerEnter function, I add the object and its Rigidbody to lists (which probably needs a lot of processing time) then apply gravity force on them till they leave the collider around the planet again. Do you have any idea to make this more efficient?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class gravity : MonoBehaviour {
public float forceMagnitude;
List<GameObject> objectsIn = new List<GameObject>();
List<Rigidbody2D> rgbodiesIn = new List<Rigidbody2D>();
void OnTriggerEnter2D(Collider2D other)
{
// add object and its rigidbody into the lists
objectsIn.Add(other.gameObject);
rgbodiesIn.Add(other.gameObject.GetComponentInParent<Rigidbody2D>());
}
void OnTriggerExit2D(Collider2D other)
{
for (int i = 0; i < objectsIn.Count; i++)
{
if (objectsIn[i] != null)
{
if (objectsIn[i].GetInstanceID() == other.gameObject.GetInstanceID())
{
// object is not in anymore
objectsIn[i] = null;
break;
}
}
}
}
void Update()
{
for (int i = 0; i < objectsIn.Count; i++)
{
if (objectsIn[i] != null)
rgbodiesIn[i].AddForce(Vector3.Normalize(objectsIn[i].transform.position - transform.position) * rgbodiesIn[i].mass * forceMagnitude * Time.deltaTime);
}
}
}
Answer by hoekkii · Aug 09, 2017 at 05:20 PM
Readability is most of the time more important than efficiency, but first of all some feedback on your code:
You should initialize objects inside Awake
.
Why don't you remove items from the list? This way it will only grow in size.
Why is the collider separated from the rigidbody? (the parent has the rigidbody...?) There could occur a null reference exception in the update loop because the parent doesn't need to have a rigidbody and will result in adding null
to your rgbodiesIn list, but objectsIn does have a reference >>> Why do you have two Lists?
For efficiency you could cache the transform position at the beginning of Update()
. Which brings me to why you do physics manipulation inside Update
and not FixedUpdate
? This causes irregularities at different frame-rates.
Some style feedback:
in OnTriggerExit2D
you have an if statement inside an if statement, which as a reader can make reading quite 'chaotic', I would recommend instead of if (objectsIn[i] != null) { ...
doing if (objectsIn[i] == null) continue;
this way you relieve some of the "what objects could be passing this code...?".
Split long lines into shorter ones, the compiler is quite good at optimizing things.
Last note I know it is boring to do, but I really recommend adding comments.
Here is some code (note this is not tested in any way)
#define DEBUG_FORCES
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Collider2D))]
public class Attractor : MonoBehaviour
{
[SerializeField] float _force;
/// <summary>
/// Collection of rigidbodies that are inside the collider
/// </summary>
HashSet<Rigidbody2D> _rigidbodies;
/// <summary>
/// Initialize
/// </summary>
void Awake()
{
_rigidbodies = new HashSet<Rigidbody2D>();
}
/// <summary>
/// Called when a collider enters this collider
/// </summary>
void OnTriggerEnter2D(Collider2D other)
{
// Get the rigidbody and check if can proceed
var obj = other.gameObject;
var body = obj.GetComponent<Rigidbody2D>();
if (body == null)
return;
// Add rigidbody to the collection
_rigidbodies.Add(body);
}
/// <summary>
/// Called when a collider exits this collider
/// </summary>
void OnTriggerExit2D(Collider2D other)
{
// Get the rigidbody and check if can proceed
var obj = other.gameObject;
var body = obj.GetComponent<Rigidbody2D>();
if (body == null)
return;
// Remove rigidbody to the collection
_rigidbodies.Remove(body);
}
/// <summary>
/// Called when the object is being disabled
/// Clear the rigidbody collection
/// </summary>
void OnDisable()
{
_rigidbodies.Clear();
}
/// <summary>
/// Called at a fixed time step
/// </summary>
void FixedUpdate()
{
// Cache the position
var currentPosition = (Vector2)transform.position;
// Apply force to all rigidbodies
foreach (var body in _rigidbodies)
{
// Calculate the magnitude of the force by the rigidbody mass and the attractor force
var forceMagnitude = body.mass * _force * Time.fixedDeltaTime;
// Calculate the force by getting the delta position with the previously calculated magnitude
var force = GetDirection(body, currentPosition) * forceMagnitude;
// Apply
body.AddForce(force);
}
}
/// <summary>
/// Get the relative position of body opposing to the point
/// </summary>
/// <returns>Normalized direction</returns>
static Vector2 GetDirection(Rigidbody2D body, Vector2 point)
{
// Calculate the delta position
var delta = body.position - point;
return delta.normalized;
}
#if DEBUG_FORCES
void Update()
{
foreach (var body in _rigidbodies)
{
var force = GetDirection(body, transform.position);
Debug.DrawRay(body.transform.position, force, Color.red);
}
}
#endif
}
If efficiency is all you want, You'll probably end up something like this (Note this is not tested in any way and quickly put together, so it could not be working)
[RequireComponent(typeof(Collider2D))]
public class AttractorEfficient : MonoBehaviour
{
const int Capacity = 64;
[SerializeField] float _force;
Affectee[] _affectees;
int _firstFreeIndex;
bool _firstFreeIndexOccupied;
int _lastIndex;
/// <summary>
/// Initialize
/// </summary>
void Awake()
{
_affectees = new Affectee[Capacity];
_firstFreeIndex = 0;
_lastIndex = -1;
_firstFreeIndexOccupied = false;
}
/// <summary>
/// Called when a collider enters this collider
/// </summary>
void OnTriggerEnter2D(Collider2D other)
{
var obj = other.gameObject;
var body = obj.GetComponent<Rigidbody2D>();
if (body == null)
return;
var index = _firstFreeIndex;
if (_firstFreeIndexOccupied)
{
for (index = 0; index < Capacity; ++index)
if (!_affectees[index].Occupied)
break;
_firstFreeIndex = index;
if (index > _lastIndex)
{
if (index == Capacity)
return; // Or throw error
_lastIndex = index;
}
}
_firstFreeIndexOccupied = true;
_affectees[index].Occupied = true;
_affectees[index].Rigidbody = body;
_affectees[index].Id = body.GetInstanceID();
}
/// <summary>
/// Called when a collider exits this collider
/// </summary>
void OnTriggerExit2D(Collider2D other)
{
var obj = other.gameObject;
var body = obj.GetComponent<Rigidbody2D>();
if (body == null)
return;
var id = body.GetInstanceID();
for (var i = 0; i <= _lastIndex; ++i)
if (_affectees[i].Occupied && _affectees[i].Id == id)
{
_affectees[i].Occupied = false;
if (i < _firstFreeIndex || _firstFreeIndexOccupied)
{
_firstFreeIndex = i;
_firstFreeIndexOccupied = false;
}
if (i == _lastIndex)
for (--_lastIndex; _lastIndex >= 0; --_lastIndex)
if (_affectees[_lastIndex].Occupied)
break;
return;
}
}
/// <summary>
/// Called at a fixed time step
/// </summary>
void FixedUpdate()
{
var currentPosition = (Vector2)transform.position;
var mul = _force * Time.fixedDeltaTime;
for (var i = 0; i <= _lastIndex; ++i)
{
if (!_affectees[i].Occupied)
continue;
var body = _affectees[i].Rigidbody;
var force = body.position;
force.x -= currentPosition.x;
force.y -= currentPosition.y;
force.Normalize();
var mulNet = mul * body.mass;
force.x *= mulNet;
force.y *= mulNet;
body.AddForce(force);
}
}
struct Affectee
{
public bool Occupied;
public Rigidbody2D Rigidbody;
public int Id;
}
}
Absolutely perfect, thank you so much. As you have probably deducted I am new in Unity as well as in C#, so thank you for your feedback on my code and coding style :)
Your answer
Follow this Question
Related Questions
Edit this script so that on the second jump change the gravity to 5 2 Answers
How can I make a game object move in parabolic motion as if it were under gravity? 2 Answers
Change direction of gravity for a specific instance of a prefab 1 Answer
Setting a jump force on a rigidbody 1 Answer
Gravity and rotation control 0 Answers