I am really new to the programming scene, and as such I am trying to build a small project to figure out what aspects I need to work on.
What I have so far is a ball and a “cup” of sorts. I want the sphere to be dropped from a location and have the “cup” magnetized enough to pull the ball into itself when it is close enough. Having the “cup” constantly face towards the ball would be preferred.
The ball has rigid body already, so gravitational effects are applying. What I am having issues with is vector3 transform data and making a variable to determine the distance between the ball and any magnetic field. I created a public variable to apply different magnetic strengths to different magnets, and I want this variable (MagnetStrength) to be used to determine the field of influence a magnet has. (I plan on using more than just the “cup”) Once the ball gets within the field of influence, I want it to be pulled towards the magnet as normal.
Put this component on your cup gameobject.
Then drag your marble (that has a rigidbody component) on to the “MarbleRigidBody” field.
It will start applying force pulling the marble towards it when it comes within the MaxDistance variable. You can set the strength with the MaxStrength variable. The 2 Lerps make it so that it starts out at 0 strength when the marble is at max distance and smoothly increases to the maxstrength as the marble gets closer.
If you’ve only got 1 marble, then you can put this on as many things as you like and have them affect the marble as it comes within range of each of them. You could do things like make ‘reverse’ magnets that push away by just reversing the direction.
If you want multiple balls at once and multiple magnets then you’ll be looking at methods to determine what objects are within an area around the magnet such as physics.overlap
using System;
using System.Collections.Generic;
using UnityEngine;
internal class MagneticCup : MonoBehaviour
{
[SerializeField]
Rigidbody MarbleRigidBody;
float MaxDistance = 10f; // Maximum range at which the marble will start being pulled to the cup
float MaxStrength = 5f; // Strength with which the marble will be pulled when it is right next to the cup (reduces with distance)
void Update()
{
Update_Magnetism();
Update_CupDirection();
}
void Update_Magnetism()
{
float Distance = Vector3.Distance(MarbleRigidBody.transform.position, this.transform.position);
if (Distance < MaxDistance) // Marble is in range of the magnet
{
float TDistance = Mathf.InverseLerp(MaxDistance, 0f, Distance); // Give a decimal representing how far between 0 distance and max distance.
float strength = Mathf.Lerp(0f, MaxStrength, TDistance); // Use that decimal to work out how much strength the magnet should apple
Vector3 DirectionToCup = (this.transform.position - MarbleRigidBody.transform.position).normalized; // Get the direction from the marble to the cup
MarbleRigidBody.AddForce(DirectionToCup * strength, ForceMode.Force);// apply force to the marble
}
}
void Update_CupDirection()
{
Vector3 DirectionToMarble = (MarbleRigidBody.transform.position - this.transform.position).normalized; // Direction from the cup to the marble
this.transform.forward = DirectionToMarble; // Make the cap face that direction
}
}
Thank you for the reply. Though I am still having one issue with the script. It refuses to run with the following error:
The referenced script on this Behavior (Game Object ‘Cup’) is missing!
I altered the names of my objects to reflect the names you used in your code.
The “Cup” is now labeled Cup, and the ball is now labeled “Marble”
I don’t believe that’s an error with the script (it never asks for anything called cup, the names of your game objects don’t matter to it).
I believe that indicates that a script you have placed on a gameobject cannot be found. Which usually happens if you delete, move or rename a script outside of unity; gameobjects that already had it on it won’t be able to find it anymore and give you that error.
Simply removing the script from the game object and putting it back again should fix it.
Also make sure the name of the class and the name of the .cs file its in match, as i believe that can effect it being found as well.
It’s not assigned to because it’s marked serialise field so you can assign it in the editor. its not letting you build it? that’s quite strange, visual studio takes no issue with it and just gives a warnin. are you using monodevelope or something, maybe that handles things differently and considers it an error. are you sure it’s not just a warning or that you have “treat all warnings as errors” turned on.
You can always try changing it to
[SeralizeField]
Rigidbody MarbleRigidBody = null;
Adding =null usually takes away the not assigned warning in visual studio, which seems silly since the warning is that it will always be null and your just making it null, but I guess it’s just happier if it thinks your doing it on purpose.
Well, I just added = null and am still getting the same error. I’m using monodevelop. It’s only showing up as a warning, but not an error, but it refuses to attach with the message: Can’t add script component ‘Magnetism’ because the script class cannot be found. Make sure that there are no compile errors and that the file name and class name match.
Ah I thought it would be a warning, didn’t make sense that that would be an error. A warning wont affect anything and isn’t causing the problem.
Now. It says it can’t find the script class magnetism. If you copied my script as is the class isn’t called magnetism it’s called magneticcup. Again please recheck that the name of the .cs file matches the name of the class.
Ok, fixed that. Was unaware that the actual script name had to match internally like that. I’m more impressed that the game objects don’t need a direct reference within this script. (Used to other types of coding I suppose. Dabbled in basic and whatever Scratch uses)
Now I have another problem though. The script attaches, but doesn’t do anything at all. I get this error at the bottom of the screen: UnassignedReferenceException: The variable MarbleRigidBody of MagneticCup has not been assigned.
They do. that’s why the serialise field tag is on the marble rigid body variable. look at the component on the object in the editor. The marble rigid body variable should be visible. drag your ball that had a rgidbody component on to that to assign it
That makes loads more sense now. Thank you. The cup is turning to face the ball as it should, and the ball is getting pulled towards the magnet but dropping through it. I’ll have to mess with some numbers. I do want it to actually stick to the cup if it manages to get within the sphere of influence and doesn’t have the velocity to escape. The same goes for all magnets
What do you mean dropping through it, you mean that it passes through the cup without actually colliding with it?
When i run it with just a sphere for a ball and a cube for a cup it pulls the ball to the cube, collides with it and then sits pressed up against it.
Make sure that you have a collider component on both the cup and the ball. If you want it to get pulled more strongly to the magnet then just increase the MaxStrength variable.
Funny enough, I actually did that before refreshing the page again. Was going to edit out some of the comment. Now I’m having problems with the directional facing of the Cup object. It seems to want to face 90 degrees to the left or right of the ball at all times. (Tested from above, and to the sides of the cup. It spazzes if the ball lands on the cup. But at no point will it face straight towards the ball. The end goal is to have it actually drop inside and “end the level”.) There’s a small collision sphere inside the cup that has a trigger attached to it.
The script simply points the ‘forward’ direction of the game object at the ball. If the front of the cup seems to always be at a 90º angle to where it should be then the front of the cup model isn’t facing the front of the gameobject.
Select your cup object, make sure that the front of the cup that you want to be facing the ball is facing along the Z axis (the blue arrow).
Changing the way the model faces may be easier if you put the script on a seperate gameobject and make it the parent of the gameobject containing the modelThen you can rotate the model gameobject separately and align it the way you want.
Thank you so much. It’s working as intended. I’ll have to adjust the collision detection a minor amount to make the ball actually sit well inside the cup. But it is getting pulled in and sticking like intended. I’m glad I only had to rotate the model itself and not the whole object, due to the fact that most of the time I want it to point straight up anyways.
Anyways, you’ve been a big help. Consider this problem solved.
Hey Mordus, this script is fantastic, thanks :). I modified it a bit to make it attract some objects while repulsing others and it’s working great. But so far i managed to make it work for single objects only (it attracts object A and repel object B). I s there a way to make it work for multiple rigidbodies (attract object A and reperls ALL objects named “B” or tagged “B”)?
Yes you just need to get a list of the objects you want to effect and then do pretty much the same code in a for each loop. Checking each object for whether you want it attracted or repulsed (i.e: check tags), and applying the force.
You could use Physics.OverlapSphere to find all objects within range of the magnet, and iterate over the result to apply attraction/repulsion as needed.
that’s what i tried to do, using lists or arrays etc.… but i’m not a 100% programmer so i encountered problems making it work. The layer mask is also a thing i considered but i wasn’t able to write a code.
Let me be more specific. I-m lost because, even I I manage to create a list and check the tag or put all the rigidbodies in a new list, I don-t know how to apply the force and all transformations to the new items in the list.
You use a loop to iterate (look at each item in turn) over the collection and call the function on each item the same way you do for an individual item. I.e:
foreach(Collider Item in ListOfColliders)
{
Item.DoSomething();
}
Below is a working way of doing it, assumes you’ve tagged your objects “Attract” or “Repulse”. The code for applying the force is exactly the same, there’s just a physics.overlapsphere to find all objects in range and a foreach loop to check each object found for a tag and a rigidbody. Everything commented so try to have a look through it and see what it’s doing and how it works.
using UnityEngine;
class Magnet : MonoBehaviour
{
// Name of the tags you're using to mark objects to be affected by the magnet
const string AttractTagName = "Attract";
const string RepulseTagName = "Repulse";
float MaxRange = 10f; // maximum distance the magnet will begin pulling an object from
float MaxStrength = 5f; // Maximum strength the magnet will pull something right next to it. Goes down as the object gets further away
// We use FixedUpdate() instead of Update() because we're working entirely with rigidbodies and physics
// FixedUpdate() occurs at regular intervals in time with the physics system, not once per frame like Update() does.
// For almost anything else you do you should use Update()
private void FixedUpdate()
{
// Find all colliders in range of the magnet
Collider[] Colliders = Physics.OverlapSphere(transform.position, MaxRange);
foreach(Collider Collider in Colliders)
{
if (Collider.tag == AttractTagName) // The object has a tag saying it should be attracted by the magnet
{
Rigidbody rb = Collider.GetComponent<Rigidbody>(); // get the rigidbody on the object
if (rb != null)
{ // object had a rigidbody, apply the force
Attract(rb);
}
else
{ // object did not have a rigidbody, log a warning to show that's why it's not being affected by the magnet
Debug.LogWarning("Object had an attract tag, but did not have a rigidbody component");
}
}
else if (Collider.tag == RepulseTagName) // The object has a tag saying it should be repulsed by the magnet
{
Rigidbody rb = Collider.GetComponent<Rigidbody>(); // get the rigidbody on the object
if (rb != null)
{ // object had a rigidbody, apply the force
Repulse(rb);
}
else
{ // object did not have a rigidbody, log a warning to show that's why it's not being affected by the magnet
Debug.LogWarning("Object had an attract tag, but did not have a rigidbody component");
}
}
}
}
private void Attract(Rigidbody rb)
{
float Distance = Vector3.Distance(rb.transform.position, this.transform.position);
float TDistance = Mathf.InverseLerp(MaxRange, 0f, Distance); // Give a decimal representing how far between 0 distance and max distance the object is.
float strength = Mathf.Lerp(0f, MaxStrength, TDistance); // Use that decimal to work out how much strength the magnet should apply
Vector3 FromObjectToMagnet = (this.transform.position - rb.transform.position).normalized; // Get the direction from the object to the magnet
rb.AddForce(FromObjectToMagnet * strength, ForceMode.Force);// apply force to the object
}
private void Repulse(Rigidbody rb)
{ // This is exactly the same as Attract(), the direction is just reversed
float Distance = Vector3.Distance(rb.transform.position, this.transform.position);
float TDistance = Mathf.InverseLerp(MaxRange, 0f, Distance); // Give a decimal representing how far between 0 distance and max distance the object is.
float strength = Mathf.Lerp(0f, MaxStrength, TDistance); // Use that decimal to work out how much strength the magnet should apply
Vector3 FromMagnetToObject = (rb.transform.position - this.transform.position).normalized; // Get the direction from the object to the magnet
rb.AddForce(FromMagnetToObject * strength, ForceMode.Force);// apply force to the object
}
}