
Although Unity has a wonderful set of physics solutions out of the box, it misses a key physics phenomenon that is absolutely essential to many sports: spin. Golf, soccer (football), bowling, billiards and many other sports rely on spins to create unexpected and exciting game plays. Given this circumstance, it would be almost tragic if such a feature is not implemented in virtual ball games.
Fortunately, the solution to ball spin effect was derived rather easily and it takes only one line of code:
rb.AddForce(Vector3.Cross(rb.angularVelocity, rb.velocity)*0.01f);
When a ball is spinning clockwise or counter-clockwise relative to a flat ground surface, the angular velocity vector is directed either up or down (majority of the code below is for visualizing that). When we take a cross product between this vertical vector and the forward movement vector, the resulting vector when a ball has a side spin would be perpendicular to the ball’s trajectory. By adding a small force in that perpendicular direction, we could finally simulate spins. The constant at the end could be adjusted according to needs.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PoolBallSpin : MonoBehaviour
{
Rigidbody rb;
GameObject spinIndicator;
GameObject EndIndicator;
void Awake()
{
rb = GetComponent<Rigidbody>();
// For visualization of angular velocity vector
spinIndicator = GameObject.CreatePrimitive(PrimitiveType.Cube);
spinIndicator.name = "SpinIndicator";
spinIndicator.layer = LayerMask.NameToLayer("YourNonPhysicsLayer");
spinIndicator.transform.localScale = new Vector3(0.01f,0.01f,0f);
EndIndicator = GameObject.CreatePrimitive(PrimitiveType.Sphere);
EndIndicator.name = "EndIndicator";
EndIndicator.layer = LayerMask.NameToLayer("YourNonPhysicsLayer");
EndIndicator.transform.localScale = new Vector3(0.02f,0.02f,0.02f);
}
void Update()
{
// Scale angular velocity visualizer based on magnitude
ScaledPosition(spinIndicator, transform.position, transform.position + rb.angularVelocity*0.25f);
Quaternion rotation = Quaternion.LookRotation(rb.angularVelocity, Vector3.up);
spinIndicator.transform.rotation = rotation;
// "spin force" or curve force. A.K.A Magnus effect: https://en.wikipedia.org/wiki/Magnus_effect
rb.AddForce(Vector3.Cross(rb.angularVelocity, rb.velocity)*0.01f);
}
// Assuming mesh spans -0.5 to 0.5 along z-axis at size 1f
void ScaledPosition(GameObject go, Vector3 Start, Vector3 End)
{
float Length = (End - Start).magnitude;
go.transform.localScale = new Vector3(go.transform.localScale.x, go.transform.localScale.y, Length);
go.transform.position = (Start + End) / 2;
EndIndicator.transform.position = End;
}
}
Jay Oh – Edited on 09/14/2025

Leave a comment