|
|
|
@ -1,34 +1,33 @@ |
|
|
|
|
using System; |
|
|
|
|
using System.Collections; |
|
|
|
|
using Unity.Netcode; |
|
|
|
|
using UnityEngine; |
|
|
|
|
using UnityEngine.InputSystem; |
|
|
|
|
using UnityEngine.PlayerLoop; |
|
|
|
|
using Random = UnityEngine.Random; |
|
|
|
|
using System.Linq; |
|
|
|
|
using UnityEngine.Serialization; |
|
|
|
|
|
|
|
|
|
namespace Game { |
|
|
|
|
public class Player : NetworkBehaviour { |
|
|
|
|
|
|
|
|
|
public enum ESide {Top, Bottom} |
|
|
|
|
|
|
|
|
|
public ESide Side { get; set; } |
|
|
|
|
|
|
|
|
|
private int score; |
|
|
|
|
|
|
|
|
|
protected bool GoingLeft, GoingRight; |
|
|
|
|
protected bool goingLeft, goingRight; |
|
|
|
|
|
|
|
|
|
// Units per second |
|
|
|
|
private float baseSpeed = 10; |
|
|
|
|
protected float Speed => 10; |
|
|
|
|
|
|
|
|
|
// Unit distance from zero |
|
|
|
|
private float baseBorder = 7; |
|
|
|
|
private float Border => 10; |
|
|
|
|
|
|
|
|
|
private SpeedModification speedModification; |
|
|
|
|
private BorderModification borderModification; |
|
|
|
|
|
|
|
|
|
private float GetSpeed() { |
|
|
|
|
return baseSpeed; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private float GetBorder() { |
|
|
|
|
return baseBorder; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private float LeftSide() { |
|
|
|
|
return X() - transform.localScale.x / 2; |
|
|
|
|
} |
|
|
|
@ -41,45 +40,167 @@ namespace Game { |
|
|
|
|
return transform.position.x; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected float Y() { |
|
|
|
|
return transform.position.y; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected void TryMove(float h) { |
|
|
|
|
Vector2 trans = new Vector2((GoingLeft ? -1 : 0) + (GoingRight ? 1 : 0), 0); |
|
|
|
|
trans *= baseSpeed * h; |
|
|
|
|
Vector2 trans = new Vector2((goingLeft ? -1 : 0) + (goingRight ? 1 : 0), 0); |
|
|
|
|
trans *= Speed * h; |
|
|
|
|
transform.Translate(trans); |
|
|
|
|
Debug.Log(trans.magnitude); |
|
|
|
|
if (LeftSide() < -baseBorder) |
|
|
|
|
transform.Translate(Vector2.right * (-baseBorder - LeftSide())); |
|
|
|
|
if (RightSide() > baseBorder) |
|
|
|
|
transform.Translate(Vector2.left * (RightSide() - baseBorder)); |
|
|
|
|
if (LeftSide() < -Border) |
|
|
|
|
transform.Translate(Vector2.right * (-Border - LeftSide())); |
|
|
|
|
if (RightSide() > Border) |
|
|
|
|
transform.Translate(Vector2.left * (RightSide() - Border)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void Start() { |
|
|
|
|
float y = Side switch { |
|
|
|
|
ESide.Bottom => BorderSize.Singleton.y1, |
|
|
|
|
ESide.Top => BorderSize.Singleton.y2, |
|
|
|
|
_ => throw new ArgumentOutOfRangeException() |
|
|
|
|
}; |
|
|
|
|
transform.position = new Vector2(0, y); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public class AIPlayer : Player { |
|
|
|
|
public enum Difficulty { |
|
|
|
|
public enum EDifficulty { |
|
|
|
|
VeryEasy, Easy, Medium, Hard, VeryHard |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Difficulty difficulty = Difficulty.VeryEasy; |
|
|
|
|
public EDifficulty Difficulty { get; set; } |
|
|
|
|
|
|
|
|
|
// True if ball y velocity points towards player |
|
|
|
|
private bool BallApproaches(Ball ball) { |
|
|
|
|
var ballVy = ball.rb.velocity.y; |
|
|
|
|
return Side switch { |
|
|
|
|
ESide.Bottom => ballVy < 0, |
|
|
|
|
ESide.Top => ballVy > 0, |
|
|
|
|
_ => throw new Exception("Side not set on player!") |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Not manhattan, only Y direction |
|
|
|
|
private float YDistanceToBall(Ball ball) { |
|
|
|
|
return Mathf.Abs(ball.rb.position.y - Y()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// o: Origin, e: End |
|
|
|
|
public static bool Intersect(Vector2 o1, Vector2 e1, Vector2 o2, Vector2 e2, out Vector2 point) { |
|
|
|
|
point = Vector2.zero; |
|
|
|
|
|
|
|
|
|
Vector2 m1 = e1 - o1; |
|
|
|
|
Vector2 m2 = e2 - o2; |
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
* o1x + r * m1x = o2x + s * m2x |
|
|
|
|
* o1y + r * m1y = o2y + s * m2y |
|
|
|
|
* |
|
|
|
|
* Solve for r and s: |
|
|
|
|
* Formulas below achieved by reordering |
|
|
|
|
* Order is irrelevant, but if m1x == 0 then first approach results in division by zero -> |
|
|
|
|
* Invert order then, and if division by zero is still a problem, both lines are parallel anyway |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
float r, s; |
|
|
|
|
if (m1.x != 0) { |
|
|
|
|
s = (o1.y + o2.x * m1.y / m1.x - o1.x * m1.y / m1.x - o2.y) / (m2.y - m2.x * m1.y / m1.x); |
|
|
|
|
r = (o2.x + s * m2.x - o1.x) / m1.x; |
|
|
|
|
} else if (m2.x != 0) { |
|
|
|
|
r = (o2.y + o1.x * m2.y / m2.x - o2.x * m2.y / m2.x - o1.y) / (m1.y - m1.x * m2.y / m2.x); |
|
|
|
|
s = (o1.x + r * m1.x - o2.x) / m2.x; |
|
|
|
|
} else { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (s is > 0 and < 1 && r is > 0 and < 1) { |
|
|
|
|
point = o1 + r * m1; |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Vector2 Simulate(Vector2 origin, Vector2 velocity, float radius, float secondsLeft) { |
|
|
|
|
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); |
|
|
|
|
|
|
|
|
|
Vector2 end = origin + velocity * secondsLeft; |
|
|
|
|
if (area.Contains(end)) |
|
|
|
|
return end; |
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
|
bool borderHit = false; |
|
|
|
|
Vector2 borderHitPoint = Vector2.zero; |
|
|
|
|
|
|
|
|
|
// Left vertical border |
|
|
|
|
if (Intersect(origin, end, new Vector2(area.xMin, area.yMin), new Vector2(area.xMin, area.yMax), out point)) { |
|
|
|
|
borderHit = true; |
|
|
|
|
borderHitPoint = point; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Right vertical border |
|
|
|
|
if (Intersect(origin, end, new Vector2(area.xMax, area.yMin), new Vector2(area.xMax, area.yMax), out point)) { |
|
|
|
|
borderHit = true; |
|
|
|
|
borderHitPoint = point; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Any border -> invert x velocity and simulate all seconds left |
|
|
|
|
if (borderHit) { |
|
|
|
|
secondsLeft -= (borderHitPoint - origin).magnitude / Speed; |
|
|
|
|
velocity = new Vector2(-velocity.x, velocity.y); |
|
|
|
|
origin = borderHitPoint; |
|
|
|
|
return Simulate(origin, velocity, radius, secondsLeft); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// No intersection -> Ball outside of field -> dont simulate further |
|
|
|
|
return origin; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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 X(); |
|
|
|
|
|
|
|
|
|
return 0; |
|
|
|
|
Ball ball = approaching.Aggregate(approaching[0], (prev, current) => YDistanceToBall(current) < YDistanceToBall(prev) ? current : prev); |
|
|
|
|
Vector2 origin = ball.rb.position; |
|
|
|
|
Vector2 velocity = ball.rb.velocity; |
|
|
|
|
float radius = ball.Radius; |
|
|
|
|
return Simulate(origin, velocity, radius, FutureSeconds).x; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void FixedUpdate() { |
|
|
|
|
float target = GetTargetPosition(); |
|
|
|
|
const float h = 1; |
|
|
|
|
GoingLeft = target < X() - h; |
|
|
|
|
GoingRight = target > X() + h; |
|
|
|
|
const float h = 0.5f; |
|
|
|
|
goingLeft = target < X() - h; |
|
|
|
|
goingRight = target > X() + h; |
|
|
|
|
TryMove(Time.fixedDeltaTime); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public class RealPlayer : Player { |
|
|
|
|
public bool isThisClient; |
|
|
|
|
|
|
|
|
|
private void FixedUpdate() { |
|
|
|
|
if (!isThisClient) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
var keyboard = Keyboard.current; |
|
|
|
|
GoingLeft = keyboard.aKey.isPressed; |
|
|
|
|
GoingRight = keyboard.dKey.isPressed; |
|
|
|
|
goingLeft = keyboard.aKey.isPressed; |
|
|
|
|
goingRight = keyboard.dKey.isPressed; |
|
|
|
|
|
|
|
|
|
TryMove(Time.fixedDeltaTime); |
|
|
|
|
} |
|
|
|
|