Calculate bounce velocity on a line - c#

I'm trying to implement bounce physics on a ball in my game using MonoGame c#. I've googled plenty but I'm unable to understand how to do this.
The circle should be able to hit any of the red lines and bounce realistically (not just invert the velocity).
I'm using this code to detect collision:
public bool IntersectCircle(Vector2 pos, float radius, out Vector2 circleWhenHit)
{
circleWhenHit = default;
// find the closest point on the line segment to the center of the circle
var line = End - Start;
var lineLength = line.Length();
var lineNorm = (1 / lineLength) * line;
var segmentToCircle = pos - Start;
var closestPointOnSegment = Vector2.Dot(segmentToCircle, line) / lineLength;
// Special cases where the closest point happens to be the end points
Vector2 closest;
if (closestPointOnSegment < 0) closest = Start;
else if (closestPointOnSegment > lineLength) closest = End;
else closest = Start + closestPointOnSegment * lineNorm;
// Find that distance. If it is less than the radius, then we
// are within the circle
var distanceFromClosest = pos - closest;
var distanceFromClosestLength = distanceFromClosest.Length();
if (distanceFromClosestLength > radius)
return false;
// So find the distance that places the intersection point right at
// the radius. This is the center of the circle at the time of collision
// and is different than the result from Doswa
var offset = (radius - distanceFromClosestLength) * ((1 / distanceFromClosestLength) * distanceFromClosest);
circleWhenHit = pos - offset;
return true;
}
And this code when the ball wants to change position:
private void GameBall_OnPositionChange(object sender, GameBallPositionChangedEventArgs e)
{
foreach(var boundary in mapBounds)
{
if (boundary.IntersectCircle(e.TargetPosition, gameBall.Radius, out Vector2 colVector))
{
var normalizedVelocity = Vector2.Normalize(e.Velocity);
var velo = e.Velocity.Length();
var surfaceNormal = Vector2.Normalize(colVector - e.CurrentPosition);
e.Velocity = Vector2.Reflect(normalizedVelocity, surfaceNormal) * velo;
e.TargetPosition = e.CurrentPosition;
break;
}
}
}
This code gives a decent result but I'm not using my boundary positions to calculate an angle.
How do I proceed to take those into account?
EDIT:
I've removed the event based update. I've added collision between players and the ball. This is now my map-update method:
foreach (var entity in circleGameEntities)
{
for (int i = 0; i < interpolatePos; i++)
{
entity.UpdatePosition(gameTime, interpolatePos);
var intersectingBoundaries = mapBounds
.Where(b =>
{
var intersects = b.IntersectCircle(entity.Position, entity.Radius, 0f, out _);
if (intersects)
averageNormal += b.Normal;
return intersects;
}).ToList();
if (intersectingBoundaries.Count > 0)
{
averageNormal.Normalize();
var normalizedVelocity = Vector2.Normalize(entity.Velocity); // Normalisera hastigheten
var velo = entity.Velocity.Length();
entity.Velocity = Vector2.Reflect(normalizedVelocity, averageNormal) * velo * entity.Bounciness;
entity.UpdatePosition(gameTime, interpolatePos);
}
foreach (var otherEntity in circleGameEntities.Where(e => e != entity))
{
if (entity.CollidesWithCircle(otherEntity, out Vector2 d))
{
Vector2 CMVelocity = (otherEntity.Mass * otherEntity.Velocity + entity.Mass * entity.Velocity) / (otherEntity.Mass + entity.Mass);
var otherEntityNorm = otherEntity.Position - entity.Position;
otherEntityNorm.Normalize();
var entityNorm = -otherEntityNorm;
var myVelocity = entity.Velocity;
myVelocity -= CMVelocity;
myVelocity = Vector2.Reflect(myVelocity, otherEntityNorm);
myVelocity += CMVelocity;
entity.Velocity = myVelocity;
entity.UpdatePosition(gameTime, interpolatePos);
var otherEntityVelocity = otherEntity.Velocity;
otherEntityVelocity -= CMVelocity;
otherEntityVelocity = Vector2.Reflect(otherEntityVelocity, entityNorm);
otherEntityVelocity += CMVelocity;
otherEntity.Velocity = otherEntityVelocity;
otherEntity.UpdatePosition(gameTime, interpolatePos);
}
}
}
entity.UpdateDrag(gameTime);
entity.Update(gameTime);
}
This code works quite well but sometimes the objects get stuck inside the walls and eachother.
CircleGameEntity class:
class CircleGameEntity : GameEntity
{
internal float Drag { get; set; } = .9999f;
internal float Radius => Scale * (Texture.Width + Texture.Height) / 4;
internal float Bounciness { get; set; } = 1f;
internal float Mass => BaseMass * Scale;
internal float BaseMass { get; set; }
internal Vector2 Velocity { get; set; }
internal float MaxVelocity { get; set; } = 10;
internal void UpdatePosition(GameTime gameTime, int interpolate)
{
var velocity = Velocity;
if (velocity.X < 0 && velocity.X < -MaxVelocity)
velocity.X = -MaxVelocity;
else if (velocity.X > 0 && velocity.X > MaxVelocity)
velocity.X = MaxVelocity;
if (velocity.Y < 0 && velocity.Y < -MaxVelocity)
velocity.Y = -MaxVelocity;
else if (velocity.Y > 0 && velocity.Y > MaxVelocity)
velocity.Y = MaxVelocity;
Velocity = velocity;
Position += Velocity / interpolate;
}
internal void UpdateDrag(GameTime gameTime)
{
Velocity *= Drag;
}
internal bool CollidesWithCircle(CircleGameEntity otherCircle, out Vector2 depth)
{
var a = Position;
var b = otherCircle.Position;
depth = Vector2.Zero;
float distance = Vector2.Distance(a, b);
if (Radius + otherCircle.Radius > distance)
{
float result = (Radius + otherCircle.Radius) - distance;
depth.X = (float)Math.Cos(result);
depth.Y = (float)Math.Sin(result);
}
return depth != Vector2.Zero;
}
}

The surfaceNormal is not the boundary normal, but the angle between the collision point and the center of the circle. This vector takes the roundness of the ball into account and is the negative of the ball's direction(if normalized) as if it hit the surface head-on, which is not needed unless the other surface is curved.
In the Boundary class calculate the angle and one of the normals in the constructor and store them as public readonly:
public readonly Vector2 Angle; // replaces lineNorm for disabiguity
public readonly Vector2 Normal;
public readonly Vector2 Length;
public Boundary(... , bool inside) // inside determines which normal faces the center
{
// ... existing constructor code
var line = End - Start;
Length = line.Length();
Angle = (1 / Length) * line;
Normal = new Vector2(-Angle.Y,Angle.X);
if (inside) Normal *= -1;
}
public bool IntersectCircle(Vector2 pos, float radius)
{
// find the closest point on the line segment to the center of the circle
var segmentToCircle = pos - Start;
var closestPointOnSegment = Vector2.Dot(segmentToCircle, End - Start) / Length;
// Special cases where the closest point happens to be the end points
Vector2 closest;
if (closestPointOnSegment < 0) closest = Start;
else if (closestPointOnSegment > Length) closest = End;
else closest = Start + closestPointOnSegment * Angle;
// Find that distance. If it is less than the radius, then we
// are within the circle
var distanceFromClosest = pos - closest;
return (distanceFromClosest.LengthSquared() > radius * radius); //the multiply is faster than square root
}
The change position code subset:
// ...
var normalizedVelocity = Vector2.Normalize(e.Velocity);
var velo = e.Velocity.Length();
e.Velocity = Vector2.Reflect(normalizedVelocity, boundary.Normal) * velo;
//Depending on timing and movement code, you may need add the next line to resolve the collision during the current step.
e.CurrentPosition += e.Velocity;
//...
This updated code assumes a single-sided non-moving boundary line as prescribed by the inside variable.
I am not a big fan of C# events in games, since it adds layers of delay (both, internally to C# and during proper use the cast of sender.)
I would be remiss not to mention your abuse of the e variable. e should always be treated as a value type: i.e. read-only. The sender variable should be cast(slow) and used for writing purposes.

Related

How to move a projectile through positions in a given time regardless of the positions count

I want to lerp a projectile through a list of positions in a certain time.
My code works when the positions count is not very high, but when the list contains a thousand points, the time that projectile takes is not the same, it clearly increases.
My code to move the projectile is the following:
public float timeToMove = 3;
public GameObject projectile;
public ParabolaDrawer parabolaDrawer; //this class generates the list of the points
private int currentPosIndex = 0;
private Vector3 currentPos = new Vector3();
private List<Vector3> parabolaPointsPositions = new List<Vector3>();
private bool hasArrivedToPos = false;
private bool hasArrivedToLastPos = false;
[ContextMenu("LaunchProjectile")]
private void LaunchProjectile()
{
currentPosIndex = 0;
hasArrivedToPos = false;
hasArrivedToLastPos = false;
parabolaPointsPositions = parabolaDrawer.parabolaPoints; //returns a List<Vector3>
projectile.transform.position = parabolaDrawer.parabolaPoints.ElementAt(0);
currentPos = projectile.transform.position;
StartCoroutine(MoveProjectileThroughTheParabola(timeToMove));
}
private IEnumerator MoveProjectileThroughTheParabola(float timeToMove)
{
float timeToMoveToNextPosition = timeToMove / (parabolaPointsPositions.Count - 1);
float step = 0f;
Vector3 initialPos = parabolaDrawer.initialPosition;
while (step < 1 && !hasArrivedToLastPos)
{
// Move our position a step closer to the target.
step += Time.smoothDeltaTime / timeToMoveToNextPosition; // calculate step distance to move in stablished time
projectile.transform.position = Vector3.Lerp(initialPos, currentPos, step);
//Check if arrived to the point
if (Vector3.Distance(projectile.transform.position, currentPos) <= 0.001f)
hasArrivedToPos = true;
if (hasArrivedToPos)
{
//Check which is the next point or if has arrived to the last one
if (currentPosIndex == (parabolaPointsPositions.Count - 1))
hasArrivedToLastPos = true;
else
{
initialPos = currentPos;
step = 0f;
currentPosIndex++;
currentPos = parabolaPointsPositions.ElementAt(currentPosIndex);
hasArrivedToPos = false;
}
}
yield return null;
}
}
My test was to set timeToMove to 3, and try to move the object through 1000 points in this 3 seconds, it takes 10.
I guess the problem lies on deltaTime being greater than timeToMoveToNextPosition, but I'm not sure how to fix it, should I calculate currentPosIndex on a different way?
Edit: Providing info on #NSJacob1 answer
If I'm understanding correctly, this is the final code using your method?
private IEnumerator MoveProjectileThroughTheParabola(float timeToMove)
{
Vector3[] points = parabolaDrawer.GetParabolaPoints().ToArray();
float elapsedTime = 0;
float duration = timeToMove;
while(elapsedTime < duration)
{
elapsedTime += Time.deltaTime;
float pathPosition = Time.deltaTime / duration;
float indexPosition = pathPosition * points.Length;
int left = Mathf.FloorToInt(indexPosition);
int right = Mathf.CeilToInt(indexPosition);
float t = indexPosition - left;
Vector3 position = Vector3.Lerp(points[left], points[right], t);
projectile.transform.position = position;
yield return new WaitForEndOfFrame();
}
}
Edit2: Keep improving with info from #NSJacob1 comments
private IEnumerator MoveProjectileThroughTheParabola(float timeToMove)
{
Vector3[] points = parabolaDrawer.GetParabolaPoints().ToArray();
elapsedTime = 0;
duration = timeToMove;
indexPosition = 0;
pathPosition = 0;
right = 0;
left = 0;
while (right < points.Length)
{
pathPosition = elapsedTime / duration;
indexPosition = pathPosition * points.Length;
left = Mathf.FloorToInt(indexPosition);
right = Mathf.CeilToInt(indexPosition);
if(right < points.Length)
{
t = indexPosition - left;
position = Vector3.LerpUnclamped(points[left], points[right], t);
projectile.transform.position = position;
}
else
{
projectile.transform.position = points.Last();
}
elapsedTime += Time.smoothDeltaTime;
yield return null;
}
}
If you move from point to point, you will get a lag based on the number of times you need to change sets of points.Try thinking instead about the whole set of points as a path and decide where, as a float from [0, 1], you are along that path.
If the whole path is traversed in 3 seconds, and there are 1000 points, 1.5 seconds is 0.5f along our path, or point 500.
Here is an example:
private IEnumerator MoveProjectileAlongPath(float timeToMove)
{
Vector3[] points = path;
float duration = timeToMove;
float start = Time.time;
float elapsed = 0;
// Time.deltaTime is the duration of a single frame,
// so this will set position to "after one frame of movement"
while (elapsed < duration )
{
elapsed = Time.time - start;
float pathPosition = elapsed / duration; // our [0, 1] value
float indexPosition = pathPosition * points.Length; // position in path
int left = Mathf.FloorToInt(indexPosition);
int right = Mathf.CeilToInt(indexPosition);
if (right >= segments)
{
transform.position = path[points.Length - 1];
break;
}
float t = indexPosition - left; // percent between left and right position
transform.position = Vector3.Lerp(points[left], points[right], t);
yield return null;
}
}
If you track your previous path position or duration of time seen so far, you can build on each frame. Not by stepping toward a goal, but by figuring out where in the whole traversal you are and going there directly.

Create just one sphere that travels between two points in a loop within a half circle?

In relation to the question asked here (How to place spheres in a half circle shape between 2 points) that generates spheres between two points A and B.
How do I create just one sphere that moves from Point A to Point B and then back from Point B to Point A in a loop cycle? How do I use Lerp in this context?
I have tried making the sphere move in the angle (half circle) described in the below code but it always moves in a straight line.
The below code generates spheres between two points.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GetCurves : MonoBehaviour
{
public GameObject A;
public GameObject B;
public int amount;
[ContextMenu("PlaceSpheres()")]
public void Start()
{
PlaceSpheres(A.transform.position, B.transform.position, amount);
}
public void PlaceSpheres(Vector3 posA, Vector3 posB, int numberOfObjects)
{
// get circle center and radius
var radius = Vector3.Distance(posA, posB) / 2f;
var centerPos = (posA + posB) / 2f;
// get a rotation that looks in the direction
// posA -> posB
var centerDirection = Quaternion.LookRotation((posB - posA).normalized);
for (var i = 0; i < numberOfObjects; i++)
{
var angle = Mathf.PI * (i+1) / (numberOfObjects + 1); //180 degrees
var x = Mathf.Sin(angle) * radius;
var z = Mathf.Cos(angle) * radius;
var pos = new Vector3(x, 0, z);
// Rotate the pos vector according to the centerDirection
pos = centerDirection * pos;
var sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
sphere.transform.position = centerPos + pos;
sphere.transform.localScale = new Vector3(0.05f, 0.05f, 0.05f);
}
}
}
The below script I had created that makes an object move between two points in a loop but only in a straight line. How do I make it move in a curve (180 degrees)?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RunInLoop : MonoBehaviour
{
public float speed = 0.25f;
public Transform PointA;
public Transform PointB;
private Vector3 origin;
private bool backToOrigin;
void Start()
{
transform.position = PointA.transform.position;
origin = transform.position;
}
void Update()
{
transform.position = Vector3.MoveTowards(transform.position, backToOrigin ? origin : PointB.transform.position, speed * Time.deltaTime);
// if one of the two positions is reached invert the flag
if (transform.position == PointB.transform.position || transform.position == origin)
{
backToOrigin = !backToOrigin;
}
}
}
Solution using your code
As I told you in my last answer that provided your first code you should store them in a list and then make the object move between them:
public class GetCurves : MonoBehaviour
{
public GameObject A;
public GameObject B;
public int amount;
public float moveSpeed;
private List<Vector3> positions = new List<Vector3>();
private Transform sphere;
private int currentIndex = 0;
private bool movingForward = true;
private void Start()
{
sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere).transform;
sphere.transform.localScale = new Vector3(0.05f, 0.05f, 0.05f);
GeneratePositions(A.transform.position, B.transform.position, amount);
sphere.position = positions[0];
}
private void GeneratePositions(Vector3 posA, Vector3 posB, int numberOfObjects)
{
// get circle center and radius
var radius = Vector3.Distance(posA, posB) / 2f;
var centerPos = (posA + posB) / 2f;
// get a rotation that looks in the direction
// posA -> posB
var centerDirection = Quaternion.LookRotation((posB - posA).normalized);
for (var i = 0; i < numberOfObjects; i++)
{
var angle = Mathf.PI * (i + 1) / (numberOfObjects + 1); //180 degrees
var x = Mathf.Sin(angle) * radius;
var z = Mathf.Cos(angle) * radius;
var pos = new Vector3(x, 0, z);
// Rotate the pos vector according to the centerDirection
pos = centerDirection * pos;
// store them in a list this time
positions.Add(centerPos + pos);
}
}
private void Update()
{
if (positions == null || positions.Count == 0) return;
// == for Vectors works with precision of 0.00001
// if you need a better precision instead use
//if(!Mathf.Approximately(Vector3.Distance(sphere.position, positions[currentIndex]), 0f))
if (sphere.position != positions[currentIndex])
{
sphere.position = Vector3.MoveTowards(sphere.transform.position, positions[currentIndex], moveSpeed * Time.deltaTime);
return;
}
// once the position is reached select the next index
if (movingForward)
{
if (currentIndex + 1 < positions.Count)
{
currentIndex++;
}
else if (currentIndex + 1 >= positions.Count)
{
currentIndex--;
movingForward = false;
}
}
else
{
if (currentIndex - 1 >= 0)
{
currentIndex--;
}
else
{
currentIndex++;
movingForward = true;
}
}
}
}
If you want to stick to Single-Responsibility-Principles you could also seperate the movement from the list generation like e.g.
public class GetCurves : MonoBehaviour
{
public GameObject A;
public GameObject B;
public int amount;
public float moveSpeed;
private void Start()
{
GeneratePositions(A.transform.position, B.transform.position, amount);
}
private void GeneratePositions(Vector3 posA, Vector3 posB, int numberOfObjects)
{
// get circle center and radius
var radius = Vector3.Distance(posA, posB) / 2f;
var centerPos = (posA + posB) / 2f;
// get a rotation that looks in the direction
// posA -> posB
var centerDirection = Quaternion.LookRotation((posB - posA).normalized);
List<Vector3> positions = new List<Vector3>();
for (var i = 0; i < numberOfObjects; i++)
{
var angle = Mathf.PI * (i + 1) / (numberOfObjects + 1); //180 degrees
var x = Mathf.Sin(angle) * radius;
var z = Mathf.Cos(angle) * radius;
var pos = new Vector3(x, 0, z);
// Rotate the pos vector according to the centerDirection
pos = centerDirection * pos;
// store them in a list this time
positions.Add(centerPos + pos);
}
var sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
sphere.transform.localScale = new Vector3(0.05f, 0.05f, 0.05f);
var movement = sphere.AddComponent<MoveBetweenPoints>();
movement.positions = positions;
movement.moveSpeed = moveSpeed;
}
and in a seperate script
public class MoveBetweenPoints : MonoBehaviour
{
public List<Vector3> positions = new List<Vector3>();
public float moveSpeed;
privtae bool movingForward = true;
private int currentIndex = 0;
private void Update()
{
if (positions == null || positions.Count == 0) return;
// == for Vectors works with precision of 0.00001
// if you need a better precision instead use
//if(!Mathf.Approximately(Vector3.Distance(sphere.position, positions[currentIndex]), 0f))
if (sphere.position != positions[currentIndex])
{
transform.position = Vector3.MoveTowards(transform.position, positions[currentIndex], moveSpeed * Time.deltaTime);
return;
}
// once the position is reached select the next index
if (movingForward)
{
if (currentIndex + 1 < positions.Count)
{
currentIndex++;
}
else if (currentIndex + 1 >= positions.Count)
{
currentIndex--;
movingForward = false;
}
}
else
{
if (currentIndex - 1 >= 0)
{
currentIndex--;
}
else
{
currentIndex++;
movingForward = true;
}
}
}
}
Actual Solution
However, if you want a smooth movement on a circle curve ... why even reduce that circle curcve to a certain amount of positions? You could directly moove according to the angle between 0° and 180° like this:
public class GetCurves : MonoBehaviour
{
public GameObject A;
public GameObject B;
// now in Angles per second
public float moveSpeed;
private Transform sphere;
private bool movingForward = true;
private float angle;
private void Start()
{
sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere).transform;
sphere.transform.localScale = new Vector3(0.05f, 0.05f, 0.05f);
}
private void Update()
{
if (movingForward)
{
angle += moveSpeed * Time.deltaTime;
}
else
{
angle -= moveSpeed * Time.deltaTime;
}
if (angle < 0)
{
angle = 0;
movingForward = true;
}
else if (angle > 180)
{
angle = 180;
movingForward = false;
}
// get circle center and radius
var radius = Vector3.Distance(A.transform.position, B.transform.position) / 2f;
var centerPos = (A.transform.position + B.transform.position) / 2f;
// get a rotation that looks in the direction
// posA -> posB
var centerDirection = Quaternion.LookRotation((B.transform.position - A.transform.position).normalized);
var x = Mathf.Sin(angle * Mathf.Deg2Rad) * radius;
var z = Mathf.Cos(angle * Mathf.Deg2Rad) * radius;
var pos = new Vector3(x, 0, z);
// Rotate the pos vector according to the centerDirection
pos = centerDirection * pos;
sphere.position = centerPos + pos;
}
}

Check if two lines intersect

I need to check if two lines intersect. These are currently wrapped in edge colliders.
In my minimal example i using Collider2D.OverlapsCollider
public class EdgeColliderChecker : MonoBehaviour
{
public EdgeCollider2D e1;
public EdgeCollider2D e2;
void Update () {
Collider2D[] results1 = new Collider2D[1];
e1.OverlapCollider(new ContactFilter2D(), results1);
if (results1[0] != null)
{
Debug.Log(results1[0].name);
}
Collider2D[] results2 = new Collider2D[1];
e1.OverlapCollider(new ContactFilter2D(), results2);
if (results2[0] != null) {
Debug.Log(results2[0].name);
}
}
}
This is how i have set up my scene:
As you can see in the picture above the two lines clearly intersect.
The issue is that nothing is outputed to the console.
I am not 100% sure about how ContactFilter should be configured but looking at the documentation it is used for filtering out results. So leaving it blank should include everything.
I really only need to do the check between two lines. So a function that takes them as arguments and returns a bool indicating intersection would be most convenient. Unfortunetaly I could not find such function in Unity.
It should not be overly complicated to construct the function myself but i would prefer to use the functions unity provide as much as possible. So consider this more of a unity related question than a math related one.
EDIT:
Using Collider2D.IsTouching(Collider2D) does not seem to work either. I use the same setup as before with this code instead:
public class EdgeColliderChecker : MonoBehaviour
{
public EdgeCollider2D e1;
public EdgeCollider2D e2;
void Update () {
if (e1.IsTouching(e2)) {
Debug.Log("INTERSECTION");
}
}
}
Edit 2:
I tried creating my own method for this:
public static class EdgeColliderExtentions {
public static List<Collider2D> GetInterSections(this EdgeCollider2D collider)
{
List<Collider2D> intersections = new List<Collider2D>();
Vector2[] points = collider.points;
for (int i = 0; i < points.Length - 1; i++)
{
Vector2 curr = collider.transform.TransformPoint(points[i]);
Vector2 next = collider.transform.TransformPoint(points[i + 1]);
Vector2 diff = next - curr;
Vector2 dir = diff.normalized;
float distance = diff.magnitude;
RaycastHit2D[] results = new RaycastHit2D[30];
ContactFilter2D filter = new ContactFilter2D();
Debug.DrawLine(curr, curr + dir * distance, Color.red, 1 / 60f);
int hits = Physics2D.Raycast(curr, dir, filter, results, distance);
for (int j = 0; i < hits; i++)
{
Collider2D intersection = results[j].collider;
if (intersection != collider)
{
intersections.Add(intersection);
}
}
}
return intersections;
}
}
EdgeColliderChecker:
public class EdgeColliderChecker : MonoBehaviour
{
public EdgeCollider2D e1;
void Update ()
{
List<Collider2D> hits = e1.GetInterSections();
if (hits.Count > 0) {
Debug.Log(hits.Count);
}
}
}
Still nothing. Even though the points i calculate align perfectly with the collider:
I did the math for it and it seems to work ok, not tested very thorougly. The intersection check is a bit choppy if it is run while the colliders are moving around:
public class Line {
private Vector2 start;
private Vector2 end;
public Line(Vector2 start, Vector2 end)
{
this.start = start;
this.end = end;
}
public static Vector2 GetIntersectionPoint(Line a, Line b)
{
//y = kx + m;
//k = (y2 - y1) / (x2 - x1)
float kA = (a.end.y - a.start.y) / (a.end.x - a.start.x);
float kB = (b.end.y - b.start.y) / (b.end.x - b.start.x);
//m = y - k * x
float mA = a.start.y - kA * a.start.x;
float mB = b.start.y - kB * b.start.x;
float x = (mB - mA) / (kA - kB);
float y = kA * x + mA;
return new Vector2(x,y);
}
public static bool Intersects(Line a, Line b)
{
Vector2 intersect = GetIntersectionPoint(a, b);
if (Vector2.Distance(a.start, intersect) < Vector2.Distance(a.start, a.end) &&
Vector2.Distance(a.end, intersect) < Vector2.Distance(a.start, a.end))
{
return true;
}
return false;
}
}
public static class EdgeColliderExtentions
{
public static bool Intersects(this EdgeCollider2D collider, EdgeCollider2D other)
{
Vector2[] points = collider.points;
Vector2[] otherPoints = other.points;
for (int i = 0; i < points.Length - 1; i++)
{
Vector2 start = collider.transform.TransformPoint(points[i]);
Vector2 end = collider.transform.TransformPoint(points[i + 1]);
Line line = new Line(start, end);
for (int j = 0; j < otherPoints.Length - 1; j++)
{
Vector2 otherStart = other.transform.TransformPoint(otherPoints[i]);
Vector2 otherEnd = other.transform.TransformPoint(otherPoints[i + 1]);
Line otherLine = new Line(otherStart, otherEnd);
if (Line.Intersects(line, otherLine))
{
return true;
}
}
}
return false;
}
}
But I'd really like to use something provided by unity instead.
Use Collider.bounds.Intersects(Collider.bounds) to determine if two bounds are intersecting:
void Update () {
if (e1.bounds.Intersects(e2.bounds)) {
Debug.Log("Bounds intersecting");
}
}
This unfortunately won't let you know if the edges are intersecting. However, if this tests false, you can skip testing the edges.
I realized that i could exhange one of the edge colliders for a polygon collider for my use case.
Using a polygon and an edge collider with Collider2D.OverlapsCollider() works as expected.
I don't know if i should accept this as an answer or not because it does not solve the original question about finding a funciton in unity for line intersection.
You could get a relatively close approximation with the following:
You could create a script that adds several small box colliders evenly along the line, the more the better. And then just do normal collision detection. But the more boxes (higher precision), the more costly computation-wise.

XNA Negative Rotation?

I'm programming a starship-game and have just finished the part which manages the rotating. I used a simple if/else-statement to check if the ship must rotate positive or negative in order to face the target as fast as possible. But I saw that the Rotation-Value can get negative, and then the ship does rotate to the wrong direction (It still faces the target-point in the end, but it takes longer). Please tell me what I did wrong :(
The function:
public bool RotateOrMove(Vector2 position)
{
if (IsRotationg == null) //todo
{
Vector2 direction = Position - position;
direction.Normalize();
FinalRotation = (float)Math.Atan2(-direction.X, direction.Y);
IsRotationg = true;
}
if (Equals(FinalRotation, Rotation))
IsRotationg = false;
if (IsRotationg == false)
{
Position = position;
return true;
}
else
{
if (FinalRotation >= Rotation)
{
Rotation += RotationVelocity;
if (FinalRotation - Rotation < RotationVelocity)
{
Rotation = FinalRotation;
IsRotationg = false;
}
}
if (FinalRotation < Rotation)
{
Rotation -= RotationVelocity;
if (FinalRotation - Rotation > -RotationVelocity)
{
Rotation = FinalRotation;
IsRotationg = false;
}
}
return false;
}
}
The Player-Class owns the Ship. When the player press the right mouse-button, this method will be called once per frame until the ship reaches the position where the cursor is pointing at.
if (!Ship.RotateOrMove(Position))
Position -= Velocity;
So if the ship had to rotate and couldn't move, it will remove the velocity it added just before to ensure that the ship won't move.
Hope you understand my problem^^
Math.Atan2 return values from -pi to pi.
to get a smooth rotation you can use this code got from here
private float CurveAngle(float from, float to, float step)
{
if (step == 0) return from;
if (from == to || step == 1) return to;
Vector2 fromVector = new Vector2((float)Math.Cos(from), (float)Math.Sin(from));
Vector2 toVector = new Vector2((float)Math.Cos(to), (float)Math.Sin(to));
Vector2 currentVector = Slerp(fromVector, toVector, step);
return (float)Math.Atan2(currentVector.Y, currentVector.X);
}
private Vector2 Slerp(Vector2 from, Vector2 to, float step)
{
if (step == 0) return from;
if (from == to || step == 1) return to;
double theta = Math.Acos(Vector2.Dot(from, to));
if (theta == 0) return to;
double sinTheta = Math.Sin(theta);
return (float)(Math.Sin((1 - step) * theta) / sinTheta) * from + (float)(Math.Sin(step * theta) / sinTheta) * to;
}

Collision Detection between player and tiles

I am trying to implement basic (for now) collision detection into my platformer. I have tiles that are each 16 x 16 in size. The character is 32 x 32 pixels in size and has its own bounding box. Now, in my Tile class, I have a bool, isSolid. Each of these tiles in my array also have a rect for their respective bounding boxes.
I am checking to see if there's an intersection between the player and tiles by doing:
if (player.GetBoundingBox().Intersects(map.tiles[(int)player.position.Y / 16,
(int)player.position.X / 16].bounds) && map.tiles[(int)player.position.Y / 16,
(int)player.position.X / 16].isSolid)
{
...
}
Now, my problem is that this is extremely inaccurate as I'm rounding off the position. I'm tired as heck right now and for the life of me I can't figure out how to properly do this. What is the best way to approach this issue?
Well this might not be exactly "basic", It works very nicely and dosen't have any problems because it calculates the X axis and Y axis seperatley, this collision structure will help you later on. (I switched to this from the old Platformer Starter kit code, which was very glitchy)
Assuming you already have methods for gravity, lets get started.
This should be after your falling and velocity logic, It will see what axises need to be checked.
float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds; //If you havent already, get the elapsed time
if (velocity.X != 0f)
{
Position += velocity.X * Vector2.UnitX * elapsed;
HandleCollisions(CollisionDirection.Horizontal);
}
if (velocity.Y != 0f)
{
Position += velocity.Y * Vector2.UnitY * elapsed;
HandleCollisions(CollisionDirection.Vertical);
}
Now for the very important HandleCollisons method
private void HandleCollisions(CollisionDirection direction)
{
// Get the player's bounding rectangle and find neighboring tiles.
Rectangle bounds = player.GetBoundingBox();
int leftTile = (int)Math.Floor((float)bounds.Left / Tile.Width);
int rightTile = (int)Math.Ceiling(((float)bounds.Right / Tile.Width)) - 1;
int topTile = (int)Math.Floor((float)bounds.Top / Tile.Height);
int bottomTile = (int)Math.Ceiling(((float)bounds.Bottom / Tile.Height)) - 1;
// Reset flag to search for ground collision.
isOnGround = false;
// For each potentially colliding tile,
for (int y = topTile; y <= bottomTile; ++y)
{
for (int x = leftTile; x <= rightTile; ++x)
{
Rectangle tileBounds = Level.GetBounds(x, y);
// If this tile is collidable,
bool IsSolid = map.tiles[x,y].IsSolid;
Vector2 depth;
if (isSolid && TileIntersectsPlayer(BoundingRectangle, tileBounds, direction, out depth))
{
if ((collision == ItemCollision.Platform && movement.Y > 0))
continue;
isOnGround = true;
if (isSolid || isOnGround)
{
if (direction == CollisionDirection.Horizontal)
{
position.X += depth.X;
}
else
{
isOnGround = true;
position.Y += depth.Y;
}
}
}
}
}
// Save the new bounds bottom.
previousBottom = bounds.Bottom;
}
public static bool TileIntersectsPlayer(Rectangle player, Rectangle block, CollisionDirection direction, out Vector2 depth)
{
depth = direction == CollisionDirection.Vertical ? new Vector2(0, player.GetVerticalIntersectionDepth(block)) : new Vector2(player.GetHorizontalIntersectionDepth(block), 0);
return depth.Y != 0 || depth.X != 0;
}
Thats it for that! It will detect collisons, but we need to allow it to figure out how much to push the player back up once it collides! You will need these two extension methods.
public static float GetHorizontalIntersectionDepth(this Rectangle rectA, Rectangle rectB)
{
// Calculate half sizes.
float halfWidthA = rectA.Width / 2.0f;
float halfWidthB = rectB.Width / 2.0f;
// Calculate centers.
float centerA = rectA.Left + halfWidthA;
float centerB = rectB.Left + halfWidthB;
// Calculate current and minimum-non-intersecting distances between centers.
float distanceX = centerA - centerB;
float minDistanceX = halfWidthA + halfWidthB;
// If we are not intersecting at all, return (0, 0).
if (Math.Abs(distanceX) >= minDistanceX)
return 0f;
// Calculate and return intersection depths.
return distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
}
public static float GetVerticalIntersectionDepth(this Rectangle rectA, Rectangle rectB)
{
// Calculate half sizes.
float halfHeightA = rectA.Height / 2.0f;
float halfHeightB = rectB.Height / 2.0f;
// Calculate centers.
float centerA = rectA.Top + halfHeightA;
float centerB = rectB.Top + halfHeightB;
// Calculate current and minimum-non-intersecting distances between centers.
float distanceY = centerA - centerB;
float minDistanceY = halfHeightA + halfHeightB;
// If we are not intersecting at all, return (0, 0).
if (Math.Abs(distanceY) >= minDistanceY)
return 0f;
// Calculate and return intersection depths.
return distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
}
Note you may need to modify this a bit, as the players position is the BOTTOM left. Also a collision enum is needed, for vertical and horizontal. Please tell me if anything seems missing in this.

Categories

Resources