Reputation: 313
I'm using unity3d 2022.3.10f and I am trying to work out how accurate someone hits an object with the controller in a VR game I am doing for the oculus.
To simplify this so I can test repeatedly with various scenarios, I have a static object (sphere) that has a collider on it. I have a 2nd object (another sphere) that also has a collider on it.
They are directly inline with each other e.g. Sphere 1: is at 0,1,1 Sphere 2: is at 0,1,0
So they are 1 distance on the z axis.
I then impart a velocity to sphere 2 to send it exactly towards Sphere 1 (0,0,1). When the colliders trigger - I want a % rating of how accurate they collided - basically projecting the center of sphere 2 on its path to see how close it would have come to hitting the center of sphere 1.
In my above example I would expect a % of 100% as they were on exact collision course.
If I then change the velocity imparted on Sphere 2 to make it still collide but very slightly offline i.e. velocity of (0,0.04,1) - I would expect a lower % value e.g. 75% and so on to a % of near 0 if they just about scrape when then hit.
I have tried many different ways but unable to come up with anything.
Has anyone got code to do this they could share?
Thank you for your time. R
EDIT: The 2 sphere scenario is just for testing as a rig so I can accurately change slightly the angles/speed etc to see how the percentage hit calcs are working.
I do have a script attached to each hand controller that gives me the Average Velocity (int) and Average Direction (Vector3) for each controller all the time i.e. it is stored currently in a singleton value for each controller and updated per frame so I can easily access it.
So if this can be used to calculate the accuracy of the collider hit maybe?
Here is the Velocity and Direction script:
public class AverageDirectionCalculator : MonoBehaviour
{
private Vector3[] recentPositions = new Vector3[10];
private int currentIndex = 0;
void Update()
{
// Store the current position in the array
recentPositions[currentIndex] = transform.position;
// Increment the index and wrap around if necessary
currentIndex = (currentIndex + 1) % recentPositions.Length;
}
public Vector3 GetAverageDirection()
{
Vector3 averageDirection = Vector3.zero;
// Calculate the average direction based on the stored positions
for (int i = 0; i < recentPositions.Length - 1; i++)
{
averageDirection += (recentPositions[i + 1] - recentPositions[i]).normalized;
}
// Normalize the result to get the average direction
averageDirection.Normalize();
return averageDirection;
}
public int GetVelocity()
{
// Calculate the velocity based on the magnitude of the most recent position change
int previousIndex = (currentIndex - 1 + recentPositions.Length) % recentPositions.Length;
float velocity = (recentPositions[currentIndex] - recentPositions[previousIndex]).magnitude / Time.deltaTime;
float maxPossibleVelocity = 100f;
float scaledVelocity = (velocity / maxPossibleVelocity) * 100f;
// Cast the scaled velocity to an integer
int scaledVelocityInt = Mathf.RoundToInt(scaledVelocity);
return scaledVelocityInt;
}
}
EDIT: ANSWER
I think I have worked out a workable solution with the help of derHugo help and advice.
Step 1: I get the Vector3 of the controller from my AverageDirection script at the point of impact so I know the rough trajectory the controller was moving in on impact
Step 2: I work out a step movement - in my case 1/250'th of the Vector3 - this will give me a slice to move along the trajectory
Step 3: Loop - move along the trajectory path in increments of the step (1/250th) and calculate the distance from the new Vector3 to the target position
Step 4: Keep doing this until I find the smallest distance
This then gives me how close along that trajectory the controller would have hit the target.
Seems to work. Below is the process (in test form): Note: I add a sphere to each step just to visualise the trajectory was indeed on the correct path - just for testing.
float _distanceToTarget = 0f;
float _minDistanceFound = 1000f;
Vector3 _stepAmount = Vector3.zero;
Vector3 _refPoint = Vector3.zero;
private void OnTriggerEnter(Collider collider)
{
_distanceToTarget = Vector3.Distance(collider.transform.position, transform.position);
_minDistanceFound = _distanceToTarget;
_stepAmount = controllerDirection / StepDiv;
_refPoint = transform.position;
var target = collider.transform.position;
while (_distanceToTarget <= _minDistanceFound)
{
_refPoint = _refPoint + _stepAmount;
_distanceToTarget = Vector3.Distance(target, _refPoint);
if (_distanceToTarget < _minDistanceFound)
{
_minDistanceFound = _distanceToTarget;
}
GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
sphere.transform.position = _refPoint;
sphere.transform.localScale = new Vector3(0.06f, 0.06f, 0.06f);
}
}
There is bound to be a math way of doing this rather than small step slices moving forward, but I don't know how so this seems to workout.
Thank you hope this helps someone else if needed.
Upvotes: 1
Views: 67
Reputation: 90813
You could probably use angles for that.
If your sphere is at a certain distance
and has a certain radius
and your projectile also has a projectileRadius
you can basically calculate a range of angle around the direction
Shoot origin -> target center
within which you will just miss the target.
This you can basically do using a right scalene triangle math like e.g.
// distance between shoot origin and target center
// = adjacent of the triangle
var distance = Vector3.Distance(shootOrigin.position, target.position);
// maximum distance between projectile and target so you just miss it - just sum both radius
// = opposite of the triangle
var maxDistance = projectileRadius + targetRadius;
// maximum angle using arcsin
// = angle of the triangle
var maxAngle = Mathf.Asin(maxDistance / distance) * Mathf.Rad2Deg;
So you can simply check the angle between
// the actual angle between the two directions when hitting the target
var hitAngle = Vector3.Angle(target.position - shootOrigin.position, projectile.position - shootOrigin.position);
And now all that's left is basically mapping this angle onto the percentage using the before calculated maxAngle
// clamp the derail to maximum 1 so accuracy will be directly inverted
// 0 miss, 1 full hit
var accuracy = 1f - Mathf.Min(hitAngle / maxAngle, 1f);
Upvotes: 1