2d Billiard power physics - c#

I have what I think is a mathematical problem with my power engine and I've spent the past 2 days trying to solve this. I am relatively new to programming and am in the process of developing a 2D top-down billiard game in Unity.
My code is attached below, but essentially the player will click on the ball and drag back to produce power. Once they reach the desired power and let go the ball will launch in the desired direction at the desired power. For the sake of this example let's say that I set maxPower to 100, and the player object's linear drag and the tile drag remains constant. In the following example scenarios, all shots are at full power, and I'm shooting from the bottom left of my table:
I shoot a purely x-axis shot - the ball is shot at 100 power to the right
I shoot a purely y-axis shot - the ball is shot at 100 power upwards
I shoot at a 45 degree angle - how can I calculate what happens here?
If you look at the code below, I am using AddForce to push the ball on the x and y axis once I release the mouse. I'm tracking the distance of each shot made at a variety of angles and I've tried the following power management:
Splitting the power between x and y axis - this ends up produces a much weaker shot than a pure x/y axis shot (50 power per axis)
Giving each axis the full power - this ends up producing a much more powerful shot (100 power per axis)
Feeling like Goldilocks, I tried to make this juuuuust right and compute this using the Pythagorean theorem, and this is where I am struggling.
Any help would be much appreciated.
Code below - all variables are defined, I've just removed irrelevant code.
public float maxForce = 100;
public float forceMultiplier = 2
void Awake() {
startPosition = transform.position;
}
void Update () {
/// Tracks distance (start vs current)
distance = Vector3.Distance(transform.position,startPosition);
//On Click
if (Input.GetMouseButtonDown (0)) {
startPosX = Input.mousePosition.x;
startPosY = Input.mousePosition.y;
}
/// While being clicked
if (Input.GetMouseButton (0)) {
/// Determines force to be applied based on mouse drag
current_xForce = (startPosX - Input.mousePosition.x);
current_yForce = (startPosY - Input.mousePosition.y);
// Stores the original drag angle
yCheck = current_yForce;
xCheck = current_xForce;
/// if current x/y force is greater than maxForce, set to maxForce
if (current_yForce > 0 && current_yForce > maxForce)
current_yForce = maxForce;
if (current_yForce < 0 && current_yForce < -maxForce)
current_yForce = -maxForce;
if (current_xForce > 0 && current_xForce > maxForce)
current_xForce = maxForce;
if (current_xForce < 0 && current_xForce < -maxForce)
current_xForce = -maxForce;
// Determines the % of x/y while aiming
current_xPercentage = Mathf.Abs (current_xForce) / (Mathf.Abs (current_xForce) + Mathf.Abs (current_yForce));
current_yPercentage = Mathf.Abs (current_yForce) / (Mathf.Abs (current_xForce) + Mathf.Abs (current_yForce));
// Decides the Power bar% - power determined by highest powered axis (may need improvement)
current_powerPercent = Mathf.Max (Mathf.Abs (current_xForce)/maxForce, Mathf.Abs (current_yForce)/maxForce);
// get angle from start position to relative mouse position... add relative mouse position NOT WORKING... need degrees
shotAngle = Vector2.Angle(startPosition,Input.mousePosition);
}
// Mouse button released
if (Input.GetMouseButtonUp (0)) {
/// Only shots with greater than % of total power count - allows you to cancel shots
if (current_powerPercent >= 0.10) {
/// Increase force by public multiplier
xForce = current_xForce * forceMultiplier ;
yForce = current_yForce * forceMultiplier ;
/// Use % of absolute xCheck to determine shot angle
float xForcePercent = Mathf.Abs (xCheck) / (Mathf.Abs (yCheck) + Mathf.Abs (xCheck));
float yForcePercent = Mathf.Abs (yCheck) / (Mathf.Abs (yCheck) + Mathf.Abs (xCheck));
/// Adds force to x/y based off xForce and the shot angle
float x_addForce = xForce * xForcePercent;
float y_addForce = yForce * yForcePercent;
/// Attempt at a-squared + b-squared = c-squared.... this is supposed to return c
powerVar = Mathf.Sqrt(Mathf.Pow (x_addForce*xForcePercent, 2) + Mathf.Pow (y_addForce*yForcePercent, 2));
/// Applies the force to the player object.. .for each axis I take powerVar * angle * sign (to adjust for negative values)
GetComponent<Rigidbody2D>().AddForce(new Vector2(powerVar*xForcePercent*Mathf.Sign(x_addForce), powerVar*yForcePercent*Mathf.Sign(y_addForce)));
yForce = 0;
xForce = 0;
startPosX = 0;
startPosY = 0;
distance = 0;
startPosition = transform.position; /// resets Distance counter
}
}
}

The Pythagorean theorem dictates that in a right triangle the following is true:
c * c = a * a + b * b
Where c is the hypotenuse. Now, in your case c is 100 and the angle between c and a or b is 45°. This means you are equally "splitting" c between a and b. Ok, let's do that:
a = b, therefore:
c * c = a * a + a * a, which means
c * c = 2 * a * a
If we take the square root on both sides:
c = sqrt(2) * a, and therefore
a = c / sqrt(2)
You will see this same relation written may times in the following form:
a = sqrt(2)/2 * c
And there you go. In your case if c is 100 then a and b will be 70,711.
You can generalize this for any angle if you use trigonometric functions (based on the Pythagorean theorem). For any angle d between c and a, the following is true:
a = c * cos(d)
b = c * sin(d)
Make sure you get your units right if you are going to use trigonometric functions. Angles are normally given in radians, not degrees, the relation between the two is:
180 degrees = pi radians

Related

Accelerate rotation from 0 to x angle in x seconds in Unity

This is more of a math question than a coding question. I would like to reach for example an rotation angle of 90 in 1 second while speed is accelerating at constant value. My current version takes 1.4 seconds to reach the desired rotation angle, and it should reach it in 1 second. I believe that the reason for that is that it currently accelerates to speed of 90 in 1 second and not to rotation angle of 90. Since I am not that good in math, I have no idea how I need to adjust the acceleration calculation. I am unable to find any solution to this.
NOTE: I need to adjust the rotation angles manually, I am not able to use any existing functions, like for example transform.Rotate(), since in my complete version the rotation direction can change at any time and the rotation also has deceleration value.
This is a very simplified version of what I have (it only rotates the z axis to one direction and runs once on start):
private float accelerationInSeconds = 1;
private float targetAngle = 90f;
private float speed = 0;
private float axis = 1;
private bool rotate = true;
private float acceleration;
void Start() {
// Calculate acceleration (this calculation should be changed)
acceleration = targetAngle / accelerationInSeconds;
}
void Update() {
if (rotate) {
// Accelerate
speed += axis * (acceleration * Time.deltaTime);
// Calculate next rotation position
Vector3 rotationVector = transform.rotation.eulerAngles;
rotationVector.z += speed * Time.deltaTime;
// Rotate object
transform.rotation = Quaternion.Euler(rotationVector);
// Check if rotation has gone over the target angle
if (rotationVector.z >= targetAngle) {
rotationVector.z = targetAngle;
speed = 0;
rotate = false;
}
}
}
Thanks in advance for anyone who can help!
EDIT: Modified code to be more efficient. I can't use RotateTowards() since in my complete code I need to clamp the rotation between targetAngle and negative targetAngle. Hopefully this code is more efficient and performance friendly. But I still have not found a solution for my original math related question, which was the whole point of this question.
private float accelerationInSeconds = 1;
private float targetAngle = 90f;
private float speed = 0;
private float angle = 0;
private float axis = 1;
private bool rotate = true;
private float acceleration;
void Start() {
// Calculate acceleration (this calculation should be changed)
acceleration = targetAngle / accelerationInSeconds;
}
void Update() {
if (rotate) {
// Accelerate
speed += axis * (acceleration * Time.deltaTime);
// Calculate next rotation position
angle += speed * Time.deltaTime;
// Check if rotation has gone over the target angle
if (angle >= targetAngle) {
angle = targetAngle;
speed = 0;
rotate = false;
}
// Rotate object
transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
}
}
I finally figured it out, thanks to Math section in StackExchange.
So the simple answer is this:
acceleration = 2 * targetAngle / Mathf.Pow(accelerationInSeconds, 2);
As was suggested before I would use a Coroutine. Coroutines are like temporary Update methods and often easier to control and maintain than doing stuff directly in Update.
// Flag to avoid concurrent routines
private bool isRotating;
public void Rotate(float targetAngle, float duration)
{
if(! isRotating) StartCoroutine (RotateRoutine(targetAngle, duration));
}
private IEnumerator RotateRoutine (float targetAngle, float duration)
{
// Just to be sure
if(isRotating) yield break;
// block concurrent routines
isRotating = true;
// Pre-calculate the start and end rotation
var start = transform.rotation;
var end = Quaternion.Euler(0, 0, targetAngle);
var timePassed = 0f;
while(timePassed < duration)
{
// This value will grow linear from 0 to 1 in exactly "duration" seconds
var x = timePassed / duration;
// TODO!
var y = MAGIC;
// Interpolate between the start and end rotation using given factor "y"
transform.rotation = Quaternion.Lerp(start, end, y);
// "pause" the routine here, render this frame
// and continue from here in the next frame
yield return null;
// Increase by the time passed since last frame
timePassed += Time.deltaTime;
}
// To be sure to end with clean values
transform.rotation = end;
// Allow next routine
isRotating = false;
}
So what do we have to fill in for MAGIC?
Basically it can be any mathematical function that maps given input 0 to 1 to 0 to 1.
There are multiple possibilities.
What you currently ask for is a linear growing speed. That means the resulting movement shall be quadratic! So we already know the Formular
var y = a * x * x + b;
We further know from your code that speed always starts from 0 -> b = 0. And the last step is pretty straight forward:
What value do we have to fill in so y goes from 0 to 1 at the same time that x goes from 0 to 1?
1 = a * 1 * 1 + 0;
=> a = 1!
So in your case it is simply
var y = x * x;
If you also want ease-out you could also simply use Mathf.Smoothstep which automatically adds ease-in and ease-out
var y = Mathf.SmoothStep(0, 1, x);
To make it even easier to control you could use an AnimationCurve and adjust the movement curve exactly to your needs in the Inspector
[SerializeField] private AnimationCurve curve;
The curve editor already comes with some preset curves like e.g. linear, logarithmic, exponential and eased-in/-out grow from 0 to 1!
And then use AnimationCurve.Evaluate to get the value (y) in the routine for a given input time (x).
var y = curve.Evaluate(x);

2D Projectile Trajectory Prediction(unity 2D)

using (unity 2019.3.7f1) 2d.
I have a player that moves around using a pullback mechanic and has a max power(like in Angry Birds).
I'm trying to draw a line(using a line renderer) that shows the exact path the player will go. I'm trying to make the line curve just like the player's path will. so far I've only managed to make a straight line in a pretty scuffed way.
The known variables are the Jump Power and the player's position, there is no friction. and I believe gravity is a constant(-9.81). Also, I would like to have a variable that allows me to control the line's length. And, if possible, the line will not go through objects and would act as if it has a collider.
// Edit
This is my current code. I changed The function so it would return the list's points because I wanted to be able to access it in Update() so it would only draw while I hold my mouse button.
My problem is that the trajectory line doesn't seem to curve, it goes in the right angle but it's straight. the line draws in the right direction and angle, but my initial issue of the line not curving remains unchanged. If you could please come back to me with an answer I would appreciate it.
enter code here
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TrajectoryShower : MonoBehaviour
{
LineRenderer lr;
public int Points;
public GameObject Player;
private float collisionCheckRadius = 0.1f;
public float TimeOfSimulation;
private void Awake()
{
lr = GetComponent<LineRenderer>();
lr.startColor = Color.white;
}
// Update is called once per frame
void Update()
{
if (Input.GetButton("Fire1"))
{
lr.positionCount = SimulateArc().Count;
for (int a = 0; a < lr.positionCount;a++)
{
lr.SetPosition(a, SimulateArc()[a]);
}
}
if (Input.GetButtonUp("Fire1"))
{
lr.positionCount = 0;
}
}
private List<Vector2> SimulateArc()
{
float simulateForDuration = TimeOfSimulation;
float simulationStep = 0.1f;//Will add a point every 0.1 secs.
int steps = (int)(simulateForDuration / simulationStep);
List<Vector2> lineRendererPoints = new List<Vector2>();
Vector2 calculatedPosition;
Vector2 directionVector = Player.GetComponent<DragAndShoot>().Direction;// The direction it should go
Vector2 launchPosition = transform.position;//Position where you launch from
float launchSpeed = 5f;//The initial power applied on the player
for (int i = 0; i < steps; ++i)
{
calculatedPosition = launchPosition + (directionVector * ( launchSpeed * i * simulationStep));
//Calculate gravity
calculatedPosition.y += Physics2D.gravity.y * (i * simulationStep);
lineRendererPoints.Add(calculatedPosition);
if (CheckForCollision(calculatedPosition))//if you hit something
{
break;//stop adding positions
}
}
return lineRendererPoints;
}
private bool CheckForCollision(Vector2 position)
{
Collider2D[] hits = Physics2D.OverlapCircleAll(position, collisionCheckRadius);
if (hits.Length > 0)
{
for (int x = 0;x < hits.Length;x++)
{
if (hits[x].tag != "Player" && hits[x].tag != "Floor")
{
return true;
}
}
}
return false;
}
}
Here's a simple way to visualize this.
To create your line you want a bunch of points.
The points represents the player's positions after being fired after X amount of time.
The position of each point is going to be : DirectionVector * (launch speed * time elapse) + (GravityDirection * time elapse^2)
You can decide in advance how far you pre calculate the points by simulating X duration and choosing the simulation step(calculate a point every X amount of time)
To detect collision each time you calculate a point you can do a small circle cast at that location. If it hits something you can stop add new points.
private float collisionCheckRadius = 0.1f;
private void SimulateArc()
{
float simulateForDuration = 5f;//simulate for 5 secs in the furture
float simulationStep = 0.1f;//Will add a point every 0.1 secs.
int steps = (int)(simulateForDuration/simulationStep);//50 in this example
List<Vector2> lineRendererPoints = new List<Vector2>();
Vector2 calculatedPosition;
Vector2 directionVector = new Vector2(0.5f,0.5f);//You plug you own direction here this is just an example
Vector2 launchPosition = Vector2.zero;//Position where you launch from
float launchSpeed = 10f;//Example speed per secs.
for(int i = 0; i < steps; ++i)
{
calculatedPosition = launchPosition + ( directionVector * (launchSpeed * i * simulationStep));
//Calculate gravity
calculatedPosition.y += Physics2D.gravity.y * ( i * simulationStep) * ( i * simulationStep);
lineRendererPoints.Add(calculatedPosition);
if(CheckForCollision(calculatedPosition))//if you hit something
{
break;//stop adding positions
}
}
//Assign all the positions to the line renderer.
}
private bool CheckForCollision(Vector2 position)
{
Collider2D[] hits = Physics2D.OverlapCircleAll(position, collisionCheckRadius);
if(hits.Length > 0)
{
//We hit something
//check if its a wall or seomthing
//if its a valid hit then return true
return true;
}
return false;
}
This is basically a sum of 2 vectors along the time.
You have your initial position (x0, y0), initial speed vector (x, y) and gravity vector (0, -9.81) being added along the time. You can build a function that gives you the position over time:
f(t) = (x0 + x*t, y0 + y*t - 9.81t²/2)
translating to Unity:
Vector2 positionInTime(float time, Vector2 initialPosition, Vector2 initialSpeed){
return initialPosition +
new Vector2(initialSpeed.x * t, initialSpeed.y * time - 4.905 * (time * time);
}
Now, choose a little delta time, say dt = 0.25.
Time | Position
0) 0.00 | f(0.00) = (x0, y0)
1) 0.25 | f(0.25) = (x1, y1)
2) 0.50 | f(0.50) = (x2, y2)
3) 0.75 | f(0.75) = (x3, y3)
4) 1.00 | f(1.00) = (x4, y4)
... | ...
Over time, you have a lot of points where the line will cross. Choose a time interval (say 3 seconds), evaluate all the points between 0 and 3 seconds (using f) and put your line renderer to cover one by one.
The line renderer have properties like width, width over time, color, etc. This is up to you.

Equalize over slope and flat surface velocity in Unity

I've posted a similar question but it is not exactly the same problem so here I go.
I'm not using physics in my project, so every force it's calculated and applied by me. The point is that the character covers the same area in the same time regardless the inclination of the surface is running on.
Being applied the same movement into the CharacterController.Move() function, the velocity of the controller increases exponentially to try to move along the same surface than being in a flat area.
For example. If a prints the movemement calculated that will be applied in the "Move()" function, it is a normalize one, being (0,0,1) in perpendicular and (0.7,0,0.7) in diagonal. However, if I retrieve the velocity via CharacterController.velocity.magnitude I get a different one, being 8 on flat surface and 11.47 in a 45º slope.
I've made a formula to calculate what is the value that should be retrieved with that "velocity.magnitude" function.
groundAngle = Mathf.Abs(Mathf.Round(Vector3.Angle(hit.normal, transform.forward)) - 90);
groundMovementMagnitude = characterController.velocity.magnitude;
slopeMovementIdeal = ((((groundAngle/2) - groundAngle) + 100) * (groundMovementMagnitude / 100));
With this formula, I get in fact a value of "8" in a flat surface and instead of "11.47", the value of the velocity retrieved in a 45º inclination slope is "6.4"
Nevertheless, this value is simply informative, because the velocity of the character controller can not be set. Instead of that, I need a way to modify the movement Vector3 that will be used to trigger the movement, so, instead of moving (0,0,1) on a slope, or (0.7,0,0.7) in diagonal on a slope, apply a reducer to deduct this Vector3 depending on the angle of the slope.
What I've finally done is using the velocity magnitude to calculate the difference between on flat movement and on slope. Then, I just converted that value into a normalized vector and deduct it to the characters movement.
float minMovement = 0.01f;
float difference = 0.0f;
float x = 0.0f;
float z = 0.0f;
if (OnSlope() && characterController.velocity.magnitude - moveSpeed > minMovement) {
difference = characterController.velocity.magnitude - moveSpeed;
//Diagonal
if ((movement.x > minMovement || movement.x < -minMovement) && (movement.z > minMovement || movement.z < -minMovement)) {
x = z = (difference / 2) / characterController.velocity.magnitude;
if (movement.x < 0)
x *= -1;
if (movement.z < 0)
z *= -1;
}
//Perpendicular
else {
if (movement.x > minMovement || movement.x < -minMovement) {
x = difference / moveSpeed;
if (movement.x < 0)
x *= -1;
}
if (movement.z > minMovement || movement.z < -minMovement) {
z = difference / moveSpeed;
if (movement.x < 0)
z *= -1;
}
}
movement.x -= x;
movement.z -= z;
}
It is working fine.

How can I get a 1 to -1 float value from the localEulerAngels of a third person camera in Unity3D?

I have a have a ball with a rigidbody attached and I am adding torque to the ball based on the angle of a third person camera (always looking at the ball) witch rotates around the ball. I am trying to change the direction of the ball based on the position of the camera in its rotation around the ball. I am using Vector3.right for forward and reverse torque and Vector3.back for right and left. I have the forward/reverse value, rightVal working but I have been working on the left/right value, backVal for three days and am at a loss. Here is how I am trying to accomplish this.
void CalcRightVal()
{
float degrees = Camera.main.transform.localEulerAngles.y;
if (degrees > 0 && degrees < 180.0f)
{
rightVal = (-degrees / 360) * 4 + 1;
}
if (degrees > 180.0f && degrees < 360.0f) //
{
rightVal = (-degrees / 360) * 4 + 1;
if (rightVal < -1.0f)
{
rightVal = (degrees / 360) * 4 - 3;
}
}
}
void ApplyTorque()
{
Vector3 dir = new Vector3(rightVal, 0, backVal);
ball.AddTorque(dir * ballAcceleration);
}
In ApplyTorque() I need the backVal to go from 0 to -1 i.e. Vector3.back when degrees == 90. Then backVal should increase in value to 0 when degrees == 180 then up to 1 when degrees == 270 then back to 0 when degrees == 360. This will apply torque to the right when the camera is at 90 degrees and left when the camera is at 270 degrees. The Idea is that the user will add input to forward i.e. KeyCode.W to add acceleration and reverse or KeyCode.S for breaks. The camera will be responsible for changing the direction of the force. I am not terrible at math but this one has me beat.
While rightVal is at 1 or -1, backVal needs to be at 0 and vice versa. That way when the camera is directly behind the ball or 0 degrees the torque applied will be Vector3.right or (1, 0, 0) and when the camera is at 90 degrees or on the left of the ball, torque applied will be Vector3.back or (0, 0, -1). This will apply torque to make the ball turn right. Basically wherever the camera is the ball rolls forward from the point of view of the user. Thank you for the help, and all help is always appreciated.
Here is a graphic to help out.
Thank You.
If you are looking for a smoothed increase/decrease of the backval you can simply do:
backVal = -Mathf.Sin(degrees * Mathf.Deg2Rad);
If you want a linear relation however, it will be slightly more complicated:
private void CalcBackValue()
{
float degrees = Camera.main.transform.localEulerAngles.z;
if (degrees >= 0 && degrees < 90)
backVal = -degrees / 90f; // 0 to -1
else if (degrees >= 90 && degrees < 270)
backVal = -2 + degrees / 90; // -1 to 0 to 1
else if (degrees >= 270 && degrees < 360)
backVal = 4 - degrees / 90; // 1 to 0
}
You will probably need to expand on this to include the negative euler angles, but essentially you are finding the values along the following curves in the second example:

enemy ship rotates towards the player but does not lock into position

when my enemy ship moves towards the player i find the distance between them with
private float FindDistance(Vector2 heroCenter, Vector2 spritePos)
{
var deltaX = Math.Pow((spritePos.X - heroCenter.X), 2); // the 2 is the power value (into the power of 2)
var deltaY = Math.Pow((spritePos.Y - heroCenter.Y), 2);
float distance = (float)Math.Sqrt(deltaX + deltaY);
return distance; // returns the distance between the two objects
}
then I calculate the enemy ship angle of rotation with this code
distanceFromHero = FindDistance(Constants.heroCenter, spritePos);
if (distanceFromHero < Constants.HeroWithinRange)
{
heroClose = true;
VelocityX *= .985f; // enemy reduces vel X
VelocityY *= .985f; // enemy reduces vel Y
//atan2 for angle
var radians = Math.Atan2((spritePos.Y - Constants.heroCenter.Y), (spritePos.X - Constants.heroCenter.X));
//radians into degrees
rotationAngle = (float)(radians * (180 / Math.PI));
}
else
{
heroClose = false;
}
but strangely the enemy though moving towards the player does not lock on and stay steady but does a pendulum like movement and when they at the same point the enemy ship rotate endlessly. Some help with code would help.
The perfect answer to my question i found in an XBox forum which couldn't loaded into Visual Studio 2017 so I made a new solution. Hope others can benefit from this excellent tutorial made by Microsoft. The Solution is here.

Categories