I’m working on a game where I have a pressure plate and the player can stack objects on it. Is it possible to detect how much mass is stacked on top of the pressure plate? Any explanation of methods I could use to achieve this?
Force = Mass x Acceleration.
In this case it is Mass x Gravity.
That simple.
Maybe manipulate the OnCollisionStay functions or even perform up upwards raycast from the plate to collect all the rigidbody.mass information associated with the colliders you detect. Then you can just add the masses and multiply by gravity if you like, to get the downward Force.
I made a weight scale for my game where I used raycasts to determine this, but it was a bit iffy as it was Virtual reality and things could slide away from the ray etc - so I used [Physics.OverlapSphere][1] instead. Then I simply populated two rigidbody arrays with the correct parent object of the compound colliders that it interacts with and determined the weight on the right and left and could them compare.
I used the extension method below to find the correct parent that has the rigidbody. I dont like using the keyword [.root][2] as that way I can organise my hierarchy without worrying about breaking code.
/// <summary>
/// Finds a parent with a certain tag name, will return null
/// if none can be found
/// </summary>
/// <param name="go">Child Object from which to begin searching</param>
/// <param name="tag">String value tag to search for</param>
/// <returns></returns>
public static GameObject FindParentWithTag(this GameObject go, string tag)
{
// reference to child
Transform child = go.transform;
// go up the hierarchy and look through all parent objects
while (child.parent != null)
{
// if you find one with the given tag
if (child.parent.tag == tag)
{
// return it
return child.parent.gameObject;
}
// set the current child to be the previous parent so we can keep looking
child = child.parent.transform;
}
// return nothing if there was no parent with the given tag
return null;
}
Once I had that in Place I could attach the script below to the scale and it works quite nicely:
public class ScalesText : MonoBehaviour
{
[Tooltip("Position of the left shelf from where we will cast the overlapsphere")]
[SerializeField] Transform leftShelf;
[Tooltip("Position of the right shelf from where we will cast the overlapsphere")]
[SerializeField] Transform rightShelf;
[Tooltip("Text field where we will write the difference between the two weights")]
[SerializeField] TextMeshProUGUI textField;
[Tooltip("Text field where we will write the total weight on the right side")]
[SerializeField] TextMeshProUGUI rightTotal;
[Tooltip("Text field where we will write the total weight on the left side")]
[SerializeField] TextMeshProUGUI leftTotal;
/// <summary>
/// List of rigidbodies on the right side
/// </summary>
private List<Rigidbody> objectsOnRight;
/// <summary>
/// lidt of rigidbodies on the left side
/// </summary>
private List<Rigidbody> objectsOnLeft;
/// <summary>
/// the total weight current on the right side
/// </summary>
float totalWeightOnRight;
/// <summary>
/// the total weight currently on the left side
/// </summary>
float totalWeightOnLeft;
private void Start()
{
/// Initialize the two lists that will be needed
objectsOnRight = new List<Rigidbody>();
objectsOnLeft = new List<Rigidbody>();
}
private void Update()
{
/// Don't perform this check everyframe as it
/// is not very efficient to do so
if (Time.frameCount % 50 == 0)
{
DetermineWeight();
}
}
/// <summary>
/// Determines the weight on the right and left
/// side of the Scale and also the difference in weight.
/// Will also write the results to the UI elements
/// </summary>
private void DetermineWeight()
{
totalWeightOnRight = 0;
totalWeightOnLeft = 0;
int interactableLayer = 1 << 14;
Collider[] right = Physics.OverlapSphere(rightShelf.position, 0.15f, interactableLayer);
Collider[] left = Physics.OverlapSphere(leftShelf.position, 0.15f, interactableLayer);
for (int i = 0; i < right.Length; i++)
{
if (!objectsOnRight.Contains(right*.gameObject.FindParentWithTag("Interactable").GetComponent<Rigidbody>()))*
{
objectsOnRight.Add(right*.gameObject.FindParentWithTag(“Interactable”).GetComponent());*
}
}
for (int i = 0; i < left.Length; i++)
{
if (!objectsOnLeft.Contains(left*.gameObject.FindParentWithTag(“Interactable”).GetComponent()))*
{
objectsOnLeft.Add(left*.gameObject.FindParentWithTag(“Interactable”).GetComponent());*
}
}
for (int i = 0; i < objectsOnRight.Count; i++)
{
totalWeightOnRight += objectsOnRight*.mass;*
}
for (int i = 0; i < objectsOnLeft.Count; i++)
{
totalWeightOnLeft += objectsOnLeft*.mass;*
}
rightTotal.text = totalWeightOnRight.ToString();
leftTotal.text = totalWeightOnLeft.ToString();
textField.text = Math.Abs(totalWeightOnRight - totalWeightOnLeft).ToString();
objectsOnRight.Clear();
objectsOnLeft.Clear();
}
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(rightShelf.position, 0.15f);
Gizmos.DrawWireSphere(leftShelf.position, 0.15f);
}
Hope this helps someone trying to do the same as me =)
have fun!
[131970-scale.png|131970]
[1]: https://docs.unity3d.com/ScriptReference/Physics.OverlapSphere.html*_
*[2]: https://docs.unity3d.com/ScriptReference/Transform-root.html*_
*
You can try this: (assuming all objects and button have center anchor)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WeightedButton : MonoBehaviour
{
[SerializeField]
float massRequired = 100; //Mass required to activate button
[SerializeField]
float initialCastDistance = 0.2f; //Initial Check distance for first object on the button
float dynamicCastDistance; //This number will be used to change the check distance of the box cast
[SerializeField]
LayerMask objectLayerMask; //Objects to weigh.They must have a rigidbody & collider
bool totalMassCalculated; //Used to exit the while loop after adding all masses
RaycastHit[] raycastHits; //Array that will contain all objects placed on the button
int currentNumberOfObjectsOnButton; //Dynamic Counter
float totalMass; //Sum of masses of all placed objects
Vector3 halfExtents; //The size of the button collider used for boxcasting
float distanceFromTheTop; //highest point in the stack
private void Awake()
{
halfExtents = GetComponent<BoxCollider>().bounds.extents;
}
void OnCollisionStay(Collision collision)
{
if (collision.gameObject.tag == "object")
{
CheckTotalMass();
if (totalMass >= massRequired)
{
Debug.Log("Button is ON");
}
else
{
Debug.Log("Button is OFF");
}
}
}
void CheckTotalMass()
{
// Reset all the values
currentNumberOfObjectsOnButton = 0;
totalMass = 0;
totalMassCalculated = false;
distanceFromTheTop = 0;
dynamicCastDistance = initialCastDistance;
//While loop used for determining the number and mass of placed objects
while (!totalMassCalculated)
{
//Check for objects using the dynamic distance
raycastHits = Physics.BoxCastAll(transform.position, halfExtents, Vector3.up,
Quaternion.identity, dynamicCastDistance, objectLayerMask);
//Check if we reached the top object
if (raycastHits.Length > currentNumberOfObjectsOnButton)
{
//Calculate the distance from the top
for (int j = 0; j < raycastHits.Length; j++)
{
if (distanceFromTheTop < (raycastHits[j].distance
+ raycastHits[j].transform.GetComponent<Collider>().bounds.size.y))
{
distanceFromTheTop = raycastHits[j].distance
+ raycastHits[j].transform.GetComponent<Collider>().bounds.size.y;
}
}
//Increase the check distance
dynamicCastDistance = distanceFromTheTop + initialCastDistance;
//Increase the number of placed object to check for in the next loop cycle
currentNumberOfObjectsOnButton++;
}
else
{
//This is used to exit the while loop when we reach the top of the stack
totalMassCalculated = true;
//For loop to add the masses of the placed object
for (int i = 0; i < raycastHits.Length; i++)
{
totalMass += raycastHits*.transform.GetComponent<Rigidbody>().mass;*
}
}
}
Debug.Log("Total mass is " + totalMass);
Debug.Log("Total number of objects is " + raycastHits.Length);
Debug.Log("Approximate Distance from top is " + distanceFromTheTop);
}
}