'How to get Direction after cueBall collide with any ball in unity3d? Like in 8 ball pool
Solution 1:[1]
If you really want to go manual
Instead of a Raycast
you would rather use a Physics.SphereCast
(according to your balls shape) or alternatively you could also use Rigidbody.SweepTest
in order to get hit information for if you object would move.
Both provide you with the information
whether something is hit or not (e.g. check via tag if the first thing hit is a wall or another ball)
both give you a
RaycastHit
which contains detailed information such aspoint
the exact contact pointdistance
how far the ball traveled before hitting something (e.g. for calculating the already applied damping/table friction)normal
of the surface you hit so you can calculate your two balls new direction from that- and of course most importantly which object was hit so you can start a new calculation for that ball as well
The rest is The Math and Physics of Billard or Physics of Billard etc and you will find that a complete answer is way to complex for this page ;)
There are way to many things to consider when calculating physics manually and first of all you will need to decide how realistic you want to go actually. There are spins, frictions, jumps and you know in the real world there doesn't exist any fully elastic collision at all ... So boy if you are going to pre-calculate all this by hand you can as well just write your own physics engine ;)
Use the existing physics without calculating yourself at all
Now as a complete alternative approach to all that, which doesn't require you to calculate anything at all:
You could simulate the whole physics!
You could
- Store all balls current positions (in order to restore them later)
- Use
Physics.Simulate
in a loop, as condition checking if any sphere has moved between the two calls -> If not (or after certain preview time) then break. - Every step track the positions of all balls
- After breaking of the loop reset all positions and velocities
=> You already get all the tracked points for each balls LineRenderer ;)
You could even use the positions in order to move the balls yourself instead of use the physics again to show the actual movement ;)
For large systems this would of course cause immense lag (depends also on your target preview time range). But I'd say for only a limited amount of balls this should be fine.
Here a little code I cluched together in a few minutes (way not perfect of course)
// little helper component for storing some references and identify clearly as a ball
// (rather then allowing to reference just any GameObject)
public class Ball : MonoBehaviour
{
[SerializeField]
private Rigidbody _rigidbody;
public Rigidbody Rigidbody => _rigidbody;
[SerializeField]
private LineRenderer _line;
public LineRenderer Line => _line;
private void Awake()
{
if (!_rigidbody) _rigidbody = GetComponent<Rigidbody>();
if (_line) _line = GetComponentInChildren<LineRenderer>(true);
}
}
and
public class Example : MonoBehaviour
{
// A simple data container for transform values
private class TransformData
{
public Vector3 Position;
public Quaternion Rotation;
private readonly Rigidbody _rigidbody;
public TransformData(Rigidbody rigidbody)
{
_rigidbody = rigidbody;
Update();
}
public void Update()
{
Position = _rigidbody.position;
Rotation = _rigidbody.rotation;
}
}
// all balls
public Ball[] balls;
// the white ball in particular
public Ball whiteBall;
// direction to shoot in
public Vector3 direction;
// force to apply
public float force;
// How far to predict into the future
public float maxPreviewTime = 10;
// shall this be done every frame (careful with performance!)
public bool continousUpdates;
// stores all initial transform values before starting the prediction in order to restore the state later
private readonly Dictionary<Ball, TransformData> initialPositions = new Dictionary<Ball, TransformData>();
// stores all the predicted positions in order - but for now without the information about the exact frame
// for simplicity and performance we only store WHERE the balls move, not exactly WHEN (could change that though if you wish)
private readonly Dictionary<Ball, List<Vector3>> simulatedPositions = new Dictionary<Ball, List<Vector3>>();
private void Awake()
{
// Initialize empty data sets for the existing balls
foreach (var ball in balls)
{
initialPositions.Add(ball, new TransformData(ball.Rigidbody));
simulatedPositions.Add(ball, new List<Vector3>());
}
}
private void Update()
{
// you could call this every frame e.g. in order to see prediction lines while the player changes force and direction
// have performance in mind though!
if (continousUpdates)
{
UpdateLines();
}
// for demo we shoot on space key
if (Input.GetKeyDown(KeyCode.Space))
{
ShootWhiteBall();
}
}
private void ShootWhiteBall()
{
whiteBall.Rigidbody.AddForce(direction.normalized * force, ForceMode.Impulse);
}
[ContextMenu("Update Preview Lines")]
public void UpdateLines()
{
// update all balls initial transform values to the current ones
foreach (var transformData in initialPositions.Values)
{
transformData.Update();
}
// clear all prediction values
foreach (var list in this.simulatedPositions.Values)
{
list.Clear();
}
// disable autosimulation - the API is a bit unclear whether this is required when manually calling "Physics.Simulate"
// so just to be sure
Physics.autoSimulation = false;
// we will track if any transform values changed during the prediction
// if not we break out of the loop immediately to avoid unnecessary overhead
var somethingChanged = true;
// as second break condition we use time - we don't want o get stuck in prediction forever
var simulatedTime = 0f;
// Do the same thing as you would later for shooting the white ball
// preferably even actually use the exact same method to avoid double maintenance
ShootWhiteBall();
while (somethingChanged && simulatedTime < maxPreviewTime)
{
// Simulate a physics step
Physics.Simulate(Time.fixedDeltaTime);
// always assume there was no change (-> would break out of prediction loop)
somethingChanged = false;
foreach (var kvp in simulatedPositions)
{
var ball = kvp.Key;
var positions = kvp.Value;
var currentPosition = ball.Rigidbody.position;
// either this is the first frame or the current position is different from the previous one
var hasChanged = positions.Count == 0 || currentPosition != positions[positions.Count - 1];
if (hasChanged)
{
positions.Add(currentPosition);
}
// it is enough for only one ball to be moving to keep running the prediction loop
somethingChanged = somethingChanged || hasChanged;
}
// increase the counter by one physics step
simulatedTime += Time.fixedDeltaTime;
}
// Reset all balls to the initial state
foreach (var kvp in initialPositions)
{
kvp.Key.Rigidbody.velocity = Vector3.zero;
kvp.Key.Rigidbody.angularVelocity = Vector3.zero;
kvp.Key.Rigidbody.position = kvp.Value.Position;
kvp.Key.Rigidbody.rotation = kvp.Value.Rotation;
}
// apply the line renderers
foreach (var kvp in simulatedPositions)
{
var ball = kvp.Key;
var positions = kvp.Value;
ball.Line.positionCount = positions.Count;
ball.Line.SetPositions(positions.ToArray());
}
// re-enable the physics
Physics.autoSimulation = true;
}
}
as you can see it is not exactly 100% accurate, tbh no sure why but it is probably something that can be tweaked out.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |