Magnetism: attracth or repulse rigidboies by tag or name

Hi guys, I’m using this amazing script for add magnetic force to ridigbodies. It works perfectly for an object A and an object B, but not with multiple objects. What i’m trying to achieve is: the object containing the script (object C) should attract object A and push away ALL objects B type. I tried several approches such find objects by Tag or name, lists, arrays and OverlapSphere but i got stuck in the process. Any suggestion?

here’s my code

using System;
using System.Collections.Generic;
using UnityEngine;


internal class MagnetAll : MonoBehaviour
{
    [SerializeField]
    public Rigidbody A;
    public Rigidbody B;
    public float Range;
    public float Strength;


    void Update()
    {
        MagnetA();
        DirectionA();
        MagnetB();
        DirectionB();
    }

    void MagnetA()
    {
        float Distance = Vector3.Distance(A.transform.position, this.transform.position);

        if (Distance < Range)
        {
            float TempDistance = Mathf.InverseLerp(Range, 0f, Distance);
            float strength = Mathf.Lerp(0f, Strength, TempDistance);
            Vector3 DirectionToMagnet = (this.transform.position - A.transform.position).normalized;

            A.AddForce(DirectionToMagnet * strength, ForceMode.Force);

        }
    }

    void DirectionA()
    {
        Vector3 DirectionToObj = (A.transform.position - this.transform.position).normalized;
        this.transform.forward = DirectionToObj;
    }



    void MagnetB()
    {
        float Distance = Vector3.Distance(B.transform.position, this.transform.position);


        if (Distance < Range)
        {
            float TempDistance = Mathf.InverseLerp(Range, 0f, Distance);
            float strength = Mathf.Lerp(0f, Strength, TempDistance);
            Vector3 DirectionToMagnet = (this.transform.position - B.transform.position).normalized;

            B.AddForce(DirectionToMagnet * -strength, ForceMode.Force);

        }
    }

    void DirectionB()
    {
        Vector3 DirectionToObj = (B.transform.position - this.transform.position).normalized;
        this.transform.forward = DirectionToObj;
    }

}

This is a neat script idea, but it could use a few efficiency tweaks in the process of enabling it to affect an infinite number of potential objects.

First off, your “main” object attracting and repelling objects can have a [Sphere Collider][1] (flagged as a “[trigger][2]”) attached to it.

Then, rather than performing any complex checks on a per-frame basis, calculate them once when the object is in range, adding valid objects to a list of objects to be attracted/repelled. Here’s an example to get you started:

// C#
using System.Collections.Generic;
using UnityEngine;

internal class MagnetAll : MonoBehaviour
{
	List<MagnetizedObject> mo;
	public float range;
	public float strength;

	public void Start()
	{
		mo = new List<MagnetizedObject>();
		gameObject.GetComponent<SphereCollider>().radius = range;
	}

	public void FixedUpdate()
	{
		for(int i = 0; i < mo.Count; i++)
		{
			ApplyMagneticForce(mo*);*
  •  }*
    
  • }*

  • public void OnTriggerEnter(Collider other)*

  • {*

  •  if(other.gameObject.CompareTag("Attract"))*
    
  •  {*
    
  •  	MagnetizedObject newMag = new MagnetizedObject();*
    
  •  	newMag.col = other;*
    
  •  	newMag.rb = other.GetComponent<Rigidbody>();*
    
  •  	newMag.t = other.transform;*
    
  •  	newMag.polarity = 1;*
    
  •  	mo.Add(newMag);*
    
  •  }*
    
  •  else if(other.gameObject.CompareTag("Repel"))*
    
  •  {*
    
  •  	MagnetizedObject newMag = new MagnetizedObject();*
    
  •  	newMag.col = other;*
    
  •  	newMag.rb = other.GetComponent<Rigidbody>();*
    
  •  	newMag.t = other.transform;*
    
  •  	newMag.polarity = -1;*
    
  •  	mo.Add(newMag);*
    
  •  }*
    
  • }*

  • public void OnTriggerExit(Collider other)*

  • {*

  •  if(other.CompareTag("Attract") || other.CompareTag("Repel"))*
    
  •  {*
    
  •  	for(int i = 0; i < mo.Count; i++)*
    
  •  	{*
    

_ if(mo*.col == other)_
_
{_
_
mo.RemoveAt(i);_
_
break;_
_
}_
_
}_
_
}_
_
}*_

* private void ApplyMagneticForce(MagnetizedObject obj)*
* {*
* Vector3 rawDirection = transform.position - obj.t.position;*

* float distance = rawDirection.magnitude;*
* float distanceScale = Mathf.InverseLerp(range, 0f, distance);*
* float attractionStrength = Mathf.Lerp(0f, strength, distanceScale);*

_ obj.rb.AddForce(rawDirection.normalized * attractionStrength * obj.polarity, ForceMode.Force);_
* }*
}

public class MagnetizedObject
{
* public Collider col;*
* public Rigidbody rb;*
* public Transform t;*
* public int polarity;*
}
Now, before we just say “Hey, thanks for writing that for me!” let’s look at what I changed and why.

As I mentioned above, this script is based on using a Trigger Collider to determine the baseline for when an object is within range. Once it is, I assemble a MagnetizedObject class to associate with it, fill it with related data, and put it in a List (but only if the GameObject has a correct tag. I used “Attact” and “Repel” as my examples, but anything can be suitable, really). By setting “polarity” at this time to a +/- 1 multiplier, it only takes a single formula to apply force.

When an object leaves the radius (OnTriggerExit()), unfortunately, is one of the more expensive checks to make in this situation. Because I’m keeping track of a custom class for a collection rather than the collider alone, I can’t simply use mo.Remove(other);. It would have been convenient, but I still tried to keep the check as short as possible by breaking out of the loop once the collider is found. To further shorten the process, however, I make sure the object leaving has the necessary tag(s) before running through the list. After all, any object with a Rigidbody, whether it’s in the list or not, will be running through that function.

In FixedUpdate(), it runs through each object in the list and sends them through the ApplyMagneticForce() function, where they each use the their polarity as a multiplier determining whether they’re attracted or repelled.

Edit: Whoops, forgot to use FixedUpdate() instead of Update(). This will help Rigidbody forces considerably.
_[1]: https://docs.unity3d.com/Manual/CollidersOverview.html*_
_
[2]: https://docs.unity3d.com/ScriptReference/Collider-isTrigger.html*_

Hey thanks for the detailed answers. It looks promising. I’ll test it in a month because I’m actually on vacation in Europe. meanwhile I have a question. Isn’t it really possible to just use a distance check on the objects instead the trigger enter (as the original script did)?

Hint: You can notify a user about this post by typing @username

I got your point. I don’t think in my game there will be more than 10 obj to detect, unless I’ll decide to attract all the particles of some emitters put on the enemies (which is a possible scenario I considered). Also if the check will be performed in a certain range (distance < range) so I guess this will affect only the closest items isn’t it?

Yes, sure. I can’t wait to test it. I’ll give you a feedback. Thanks man you’re the best.