|
|
|
@ -8,7 +8,6 @@ namespace Game { |
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
* Possible optimizations: |
|
|
|
|
* - Ignore impossible balls |
|
|
|
|
* - Try to hit ball with edge |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
@ -16,6 +15,8 @@ namespace Game { |
|
|
|
|
|
|
|
|
|
public Difficulty Difficulty { get; set; } |
|
|
|
|
|
|
|
|
|
private float FutureSeconds => (float) Difficulty * 0.5f; |
|
|
|
|
|
|
|
|
|
// True if ball y velocity points towards player |
|
|
|
|
private bool BallApproaches(Ball ball) { |
|
|
|
|
var ballVy = ball.Rb.velocity.y; |
|
|
|
@ -32,8 +33,9 @@ namespace Game { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// o: Origin, e: End |
|
|
|
|
public static bool Intersect(Vector2 o1, Vector2 e1, Vector2 o2, Vector2 e2, out Vector2 point) { |
|
|
|
|
public static bool Intersect(Vector2 o1, Vector2 e1, Vector2 o2, Vector2 e2, out Vector2 point, out Vector2 rs) { |
|
|
|
|
point = Vector2.zero; |
|
|
|
|
rs = Vector2.zero; |
|
|
|
|
|
|
|
|
|
Vector2 m1 = e1 - o1; |
|
|
|
|
Vector2 m2 = e2 - o2; |
|
|
|
@ -61,64 +63,84 @@ namespace Game { |
|
|
|
|
|
|
|
|
|
if (s is > 0 and < 1 && r is > 0 and < 1) { |
|
|
|
|
point = o1 + r * m1; |
|
|
|
|
rs = new Vector2(r, s); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Vector2 Simulate(Vector2 origin, Vector2 velocity, float radius, float secondsLeft) { |
|
|
|
|
private float IdlePosition => Difficulty >= Difficulty.Medium ? 0 : X(); |
|
|
|
|
|
|
|
|
|
private bool IsPositionReachableInTime(float futurePosition, float seconds) { |
|
|
|
|
float requiredDistance = Mathf.Abs(futurePosition - X()) - Width / 2; |
|
|
|
|
if (requiredDistance < 0) |
|
|
|
|
return true; |
|
|
|
|
return Speed * seconds > requiredDistance; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private float Simulate(Vector2 origin, Vector2 velocity, float radius, float secondsLeft, float secondsUsed, out bool impossible) { |
|
|
|
|
impossible = false; |
|
|
|
|
Vector2 validAreaOrigin = new Vector2(BorderSize.Singleton.x1, BorderSize.Singleton.y1) + new Vector2(radius, radius); |
|
|
|
|
Vector2 validAreaSize = BorderSize.Singleton.Size - new Vector2(radius, radius) * 2; |
|
|
|
|
Rect area = new Rect(validAreaOrigin, validAreaSize); |
|
|
|
|
|
|
|
|
|
// Try to follow this line from origin -> end |
|
|
|
|
Vector2 end = origin + velocity * secondsLeft; |
|
|
|
|
|
|
|
|
|
// Line ends in playground |
|
|
|
|
if (area.Contains(end)) |
|
|
|
|
return end; |
|
|
|
|
return end.x; |
|
|
|
|
|
|
|
|
|
float playerY = Side == ESide.Bottom ? area.yMin : area.yMax; |
|
|
|
|
Vector2 playerLeft = new Vector2(area.xMin, playerY); |
|
|
|
|
Vector2 playerRight = new Vector2(area.xMax, playerY); |
|
|
|
|
|
|
|
|
|
// Horizontal line (player line) -> stop simulating |
|
|
|
|
if (Intersect(origin, end, playerLeft, playerRight, out Vector2 point)) |
|
|
|
|
return point; |
|
|
|
|
if (Intersect(origin, end, playerLeft, playerRight, out Vector2 point, out Vector2 rs)) { |
|
|
|
|
secondsUsed += secondsLeft * rs.x; |
|
|
|
|
if (!IsPositionReachableInTime(point.x, secondsUsed)) |
|
|
|
|
impossible = true; |
|
|
|
|
return point.x; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool borderHit = false; |
|
|
|
|
Vector2 borderHitPoint = Vector2.zero; |
|
|
|
|
Vector2 borderRs = Vector2.zero; |
|
|
|
|
|
|
|
|
|
// Left vertical border |
|
|
|
|
if (Intersect(origin, end, new Vector2(area.xMin, area.yMin), new Vector2(area.xMin, area.yMax), out point)) { |
|
|
|
|
if (Intersect(origin, end, new Vector2(area.xMin, area.yMin), new Vector2(area.xMin, area.yMax), out point, out rs)) { |
|
|
|
|
borderHit = true; |
|
|
|
|
borderHitPoint = point; |
|
|
|
|
borderRs = rs; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Right vertical border |
|
|
|
|
if (Intersect(origin, end, new Vector2(area.xMax, area.yMin), new Vector2(area.xMax, area.yMax), out point)) { |
|
|
|
|
if (Intersect(origin, end, new Vector2(area.xMax, area.yMin), new Vector2(area.xMax, area.yMax), out point, out rs)) { |
|
|
|
|
borderHit = true; |
|
|
|
|
borderHitPoint = point; |
|
|
|
|
borderRs = rs; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Any border -> invert x velocity and simulate all seconds left |
|
|
|
|
// Any border -> invert x velocity and simulate again from there |
|
|
|
|
if (borderHit) { |
|
|
|
|
secondsLeft -= (borderHitPoint - origin).magnitude / Speed; |
|
|
|
|
float secondsUsedHere = borderRs.x * secondsLeft; |
|
|
|
|
secondsLeft -= secondsUsedHere; |
|
|
|
|
secondsUsed += secondsUsedHere; |
|
|
|
|
velocity = new Vector2(-velocity.x, velocity.y); |
|
|
|
|
origin = borderHitPoint; |
|
|
|
|
return Simulate(origin, velocity, radius, secondsLeft); |
|
|
|
|
return Simulate(origin, velocity, radius, secondsLeft, secondsUsed, out impossible); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// No intersection -> Ball outside of field -> dont simulate further |
|
|
|
|
return origin; |
|
|
|
|
return origin.x; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private float FutureSeconds => (float) Difficulty * 0.5f; |
|
|
|
|
|
|
|
|
|
private float GetTargetPosition() { |
|
|
|
|
var balls = GameManager.Singleton.Balls.Where(b => b.IsAlive); |
|
|
|
|
var approaching = balls.Where(BallApproaches).ToList(); |
|
|
|
|
|
|
|
|
|
if (approaching.Count == 0) |
|
|
|
|
return Difficulty >= Difficulty.Medium ? 0 : X(); |
|
|
|
|
while (approaching.Count > 0) { |
|
|
|
|
|
|
|
|
|
// Nearest by Y-Distance |
|
|
|
|
Ball ball = approaching.Aggregate(approaching[0], (prev, current) => YDistanceToBall(current) < YDistanceToBall(prev) ? current : prev); |
|
|
|
@ -127,9 +149,19 @@ namespace Game { |
|
|
|
|
Vector2 velocity = ball.Rb.velocity; |
|
|
|
|
float radius = ball.Radius; |
|
|
|
|
|
|
|
|
|
Vector2 simulatedPosition = Simulate(origin, velocity, radius, FutureSeconds); |
|
|
|
|
float target = Simulate(origin, velocity, radius, FutureSeconds, 0, out bool impossible); |
|
|
|
|
if (!impossible) |
|
|
|
|
return target; |
|
|
|
|
|
|
|
|
|
approaching.Remove(ball); |
|
|
|
|
// This ball was impossible to catch, try next one |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return IdlePosition; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return simulatedPosition.x; |
|
|
|
|
private void ApproachPosition(float pos) { |
|
|
|
|
// Move smoothly, velocity capped by Speed |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void FixedUpdate() { |
|
|
|
@ -137,7 +169,10 @@ namespace Game { |
|
|
|
|
const float h = 0.5f; |
|
|
|
|
goingLeft = target < X() - h; |
|
|
|
|
goingRight = target > X() + h; |
|
|
|
|
TryMove(Time.fixedDeltaTime); |
|
|
|
|
if (goingLeft || goingRight) |
|
|
|
|
TryLinearMove(Time.fixedDeltaTime); |
|
|
|
|
else |
|
|
|
|
ApproachPosition(target); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |