|
|
@ -1,15 +1,45 @@ |
|
|
|
using System; |
|
|
|
|
|
|
|
using System.Linq; |
|
|
|
using System.Linq; |
|
|
|
using UnityEngine; |
|
|
|
using UnityEngine; |
|
|
|
|
|
|
|
|
|
|
|
namespace Game { |
|
|
|
namespace Game { |
|
|
|
|
|
|
|
|
|
|
|
public class AIPlayer : Player { |
|
|
|
public class AIPlayer : Player { |
|
|
|
|
|
|
|
private const float SmoothTime = 0.2f; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private float currentSmoothV; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private bool isApproaching; |
|
|
|
|
|
|
|
private float lastDirection; |
|
|
|
|
|
|
|
|
|
|
|
public Difficulty Difficulty { get; set; } |
|
|
|
public Difficulty Difficulty { get; set; } |
|
|
|
|
|
|
|
|
|
|
|
private float FutureSeconds => (float) Difficulty * 0.5f; |
|
|
|
private float FutureSeconds => (float) Difficulty * 0.5f; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private float IdlePosition => Difficulty >= Difficulty.Medium ? 0 : X; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private float DistortAmount => 1 - 1 / ((float) Difficulty + 1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void FixedUpdate() { |
|
|
|
|
|
|
|
var dt = Time.fixedDeltaTime; |
|
|
|
|
|
|
|
var target = GetTargetPosition(); |
|
|
|
|
|
|
|
var h = Mathf.Max(Speed * dt, Width / 2); |
|
|
|
|
|
|
|
goingLeft = target < X - h; |
|
|
|
|
|
|
|
goingRight = target > X + h; |
|
|
|
|
|
|
|
if (goingLeft || goingRight) { |
|
|
|
|
|
|
|
isApproaching = false; |
|
|
|
|
|
|
|
lastDirection = goingLeft ? -1 : 1; |
|
|
|
|
|
|
|
TryLinearMove(dt); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
if (!isApproaching) { |
|
|
|
|
|
|
|
isApproaching = true; |
|
|
|
|
|
|
|
currentSmoothV = Speed * lastDirection; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ApproachPosition(target); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// True if ball y velocity points towards player |
|
|
|
// True if ball y velocity points towards player |
|
|
|
private bool BallApproaches(Ball ball) { |
|
|
|
private bool BallApproaches(Ball ball) { |
|
|
|
var ballVy = ball.Rb.velocity.y; |
|
|
|
var ballVy = ball.Rb.velocity.y; |
|
|
@ -26,8 +56,8 @@ namespace Game { |
|
|
|
point = Vector2.zero; |
|
|
|
point = Vector2.zero; |
|
|
|
rs = Vector2.zero; |
|
|
|
rs = Vector2.zero; |
|
|
|
|
|
|
|
|
|
|
|
Vector2 m1 = e1 - o1; |
|
|
|
var m1 = e1 - o1; |
|
|
|
Vector2 m2 = e2 - o2; |
|
|
|
var m2 = e2 - o2; |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
/* |
|
|
|
* |
|
|
|
* |
|
|
@ -62,12 +92,10 @@ namespace Game { |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private float IdlePosition => Difficulty >= Difficulty.Medium ? 0 : X; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO Also must include fact that players have a height, maybe check the incoming angle |
|
|
|
// TODO Also must include fact that players have a height, maybe check the incoming angle |
|
|
|
// angle: 0 -> perpendicular, from -90 to 90 |
|
|
|
// angle: 0 -> perpendicular, from -90 to 90 |
|
|
|
private bool IsPositionReachableInTime(float futurePosition, float seconds, float radius, float angle) { |
|
|
|
private bool IsPositionReachableInTime(float futurePosition, float seconds, float radius, float angle) { |
|
|
|
float requiredDistance = Mathf.Abs(futurePosition - X) - Width / 2 - radius; |
|
|
|
var requiredDistance = Mathf.Abs(futurePosition - X) - Width / 2 - radius; |
|
|
|
if (requiredDistance < 0) |
|
|
|
if (requiredDistance < 0) |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
if (Mathf.Abs(futurePosition) > Border) |
|
|
|
if (Mathf.Abs(futurePosition) > Border) |
|
|
@ -77,33 +105,33 @@ namespace Game { |
|
|
|
|
|
|
|
|
|
|
|
private float Simulate(Vector2 origin, Vector2 velocity, float radius, float secondsLeft, float secondsUsed, out bool ignore) { |
|
|
|
private float Simulate(Vector2 origin, Vector2 velocity, float radius, float secondsLeft, float secondsUsed, out bool ignore) { |
|
|
|
ignore = false; |
|
|
|
ignore = false; |
|
|
|
Vector2 r = new Vector2(radius, radius); |
|
|
|
var r = new Vector2(radius, radius); |
|
|
|
Vector2 validAreaOrigin = -Dimensions.Singleton.PlaySizeBoards / 2 + r; |
|
|
|
var validAreaOrigin = -Dimensions.Singleton.PlaySizeBoards / 2 + r; |
|
|
|
Vector2 validAreaSize = Dimensions.Singleton.PlaySizeBoards - r * 2; |
|
|
|
var validAreaSize = Dimensions.Singleton.PlaySizeBoards - r * 2; |
|
|
|
Rect area = new Rect(validAreaOrigin, validAreaSize); |
|
|
|
var area = new Rect(validAreaOrigin, validAreaSize); |
|
|
|
|
|
|
|
|
|
|
|
// Try to follow this line from origin -> end |
|
|
|
// Try to follow this line from origin -> end |
|
|
|
Vector2 end = origin + velocity * secondsLeft; |
|
|
|
var end = origin + velocity * secondsLeft; |
|
|
|
|
|
|
|
|
|
|
|
// Line ends in playground |
|
|
|
// Line ends in playground |
|
|
|
if (area.Contains(end)) |
|
|
|
if (area.Contains(end)) |
|
|
|
return end.x; |
|
|
|
return end.x; |
|
|
|
|
|
|
|
|
|
|
|
float playerY = Side == Side.Bottom ? area.yMin : area.yMax; |
|
|
|
var playerY = Side == Side.Bottom ? area.yMin : area.yMax; |
|
|
|
Vector2 playerLeft = new Vector2(area.xMin, playerY); |
|
|
|
var playerLeft = new Vector2(area.xMin, playerY); |
|
|
|
Vector2 playerRight = new Vector2(area.xMax, playerY); |
|
|
|
var playerRight = new Vector2(area.xMax, playerY); |
|
|
|
|
|
|
|
|
|
|
|
// Horizontal line (player line) -> stop simulating |
|
|
|
// Horizontal line (player line) -> stop simulating |
|
|
|
if (Intersect(origin, end, playerLeft, playerRight, out Vector2 point, out Vector2 rs)) { |
|
|
|
if (Intersect(origin, end, playerLeft, playerRight, out var point, out var rs)) { |
|
|
|
secondsUsed += secondsLeft * rs.x; |
|
|
|
secondsUsed += secondsLeft * rs.x; |
|
|
|
if (!IsPositionReachableInTime(point.x, secondsUsed, radius, Vector2.Angle(velocity, transform.up))) |
|
|
|
if (!IsPositionReachableInTime(point.x, secondsUsed, radius, Vector2.Angle(velocity, transform.up))) |
|
|
|
ignore = true; |
|
|
|
ignore = true; |
|
|
|
return point.x; |
|
|
|
return point.x; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool borderHit = false; |
|
|
|
var borderHit = false; |
|
|
|
Vector2 borderHitPoint = Vector2.zero; |
|
|
|
var borderHitPoint = Vector2.zero; |
|
|
|
Vector2 borderRs = Vector2.zero; |
|
|
|
var borderRs = Vector2.zero; |
|
|
|
|
|
|
|
|
|
|
|
// Left vertical border |
|
|
|
// Left vertical border |
|
|
|
if (Intersect(origin, end, new Vector2(area.xMin, area.yMin), new Vector2(area.xMin, area.yMax), out point, out rs)) { |
|
|
|
if (Intersect(origin, end, new Vector2(area.xMin, area.yMin), new Vector2(area.xMin, area.yMax), out point, out rs)) { |
|
|
@ -121,7 +149,7 @@ namespace Game { |
|
|
|
|
|
|
|
|
|
|
|
// Any border -> invert x velocity and simulate again from there |
|
|
|
// Any border -> invert x velocity and simulate again from there |
|
|
|
if (borderHit) { |
|
|
|
if (borderHit) { |
|
|
|
float secondsUsedHere = borderRs.x * secondsLeft; |
|
|
|
var secondsUsedHere = borderRs.x * secondsLeft; |
|
|
|
secondsLeft -= secondsUsedHere; |
|
|
|
secondsLeft -= secondsUsedHere; |
|
|
|
secondsUsed += secondsUsedHere; |
|
|
|
secondsUsed += secondsUsedHere; |
|
|
|
velocity = new Vector2(-velocity.x, velocity.y); |
|
|
|
velocity = new Vector2(-velocity.x, velocity.y); |
|
|
@ -134,24 +162,22 @@ namespace Game { |
|
|
|
return 0; |
|
|
|
return 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private float DistortAmount => 1 - 1 / ((float) Difficulty + 1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private float Distort(float target) { |
|
|
|
private float Distort(float target) { |
|
|
|
float max = Width / 3; |
|
|
|
var max = Width / 3; |
|
|
|
float distortionOffset = DistortAmount * max; |
|
|
|
var distortionOffset = DistortAmount * max; |
|
|
|
distortionOffset *= target > X ? -1 : 1; |
|
|
|
distortionOffset *= target > X ? -1 : 1; |
|
|
|
return target + distortionOffset; |
|
|
|
return target + distortionOffset; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private float GetTargetPosition() { |
|
|
|
private float GetTargetPosition() { |
|
|
|
var approaching = GameManager.Singleton.Balls.Where(BallApproaches).ToList(); |
|
|
|
var approaching = GameManager.Singleton.Balls.Where(BallApproaches).ToList(); |
|
|
|
|
|
|
|
|
|
|
|
while (approaching.Count > 0) { |
|
|
|
while (approaching.Count > 0) { |
|
|
|
|
|
|
|
|
|
|
|
// Nearest by Y-Distance |
|
|
|
// Nearest by Y-Distance |
|
|
|
Ball ball = approaching.Aggregate(approaching[0], (prev, current) => YDistanceToBall(current) < YDistanceToBall(prev) ? current : prev); |
|
|
|
var ball = approaching.Aggregate(approaching[0], (prev, current) => YDistanceToBall(current) < YDistanceToBall(prev) ? current : prev); |
|
|
|
|
|
|
|
|
|
|
|
float target = Simulate(ball.Rb.position, ball.Rb.velocity, ball.Radius, FutureSeconds, 0, out bool ignore); |
|
|
|
var target = Simulate(ball.Rb.position, ball.Rb.velocity, ball.Radius, FutureSeconds, 0, out var ignore); |
|
|
|
if (!ignore) |
|
|
|
if (!ignore) |
|
|
|
return Distort(target); |
|
|
|
return Distort(target); |
|
|
|
|
|
|
|
|
|
|
@ -162,35 +188,10 @@ namespace Game { |
|
|
|
return IdlePosition; |
|
|
|
return IdlePosition; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private float currentSmoothV; |
|
|
|
|
|
|
|
private const float SmoothTime = 0.2f; |
|
|
|
|
|
|
|
private void ApproachPosition(float pos) { |
|
|
|
private void ApproachPosition(float pos) { |
|
|
|
float result = Mathf.SmoothDamp(X, pos, ref currentSmoothV, SmoothTime, Speed); |
|
|
|
var result = Mathf.SmoothDamp(X, pos, ref currentSmoothV, SmoothTime, Speed); |
|
|
|
transform.position = new Vector2(result, Y); |
|
|
|
transform.position = new Vector2(result, Y); |
|
|
|
ClampInsideBorders(); |
|
|
|
ClampInsideBorders(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private bool isApproaching; |
|
|
|
|
|
|
|
private float lastDirection; |
|
|
|
|
|
|
|
private void FixedUpdate() { |
|
|
|
|
|
|
|
float dt = Time.fixedDeltaTime; |
|
|
|
|
|
|
|
float target = GetTargetPosition(); |
|
|
|
|
|
|
|
float h = Mathf.Max(Speed * dt, Width / 2); |
|
|
|
|
|
|
|
goingLeft = target < X - h; |
|
|
|
|
|
|
|
goingRight = target > X + h; |
|
|
|
|
|
|
|
if (goingLeft || goingRight) { |
|
|
|
|
|
|
|
isApproaching = false; |
|
|
|
|
|
|
|
lastDirection = goingLeft ? -1 : 1; |
|
|
|
|
|
|
|
TryLinearMove(dt); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else { |
|
|
|
|
|
|
|
if (!isApproaching) { |
|
|
|
|
|
|
|
isApproaching = true; |
|
|
|
|
|
|
|
currentSmoothV = Speed * lastDirection; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
ApproachPosition(target); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|