using System; using System.Collections.Generic; using Godot.Collections; using Pong.Scripts.Data; using Pong.Scripts.Managers; namespace Pong.Scripts.Objects; public partial class Enemy : BasePaddle { /// /// draw shapes to the screen (like ). /// [Export] private bool _drawDebugShapes; private CollisionShape2D _collisionShape; private Rect2 _scanArea; private PhysicsDirectSpaceState2D _spaceState; private readonly Variant _collider = "collider"; private PhysicsShapeQueryParameters2D _query; public override void _EnterTree() { base._EnterTree(); ScalingManager.NewEnemyPosition += SetNewPosition; _collisionShape = GetNode("CollisionShape2D"); GenerateCastArea(); _spaceState = GetWorld2D().DirectSpaceState; _query = new PhysicsShapeQueryParameters2D { Shape = new RectangleShape2D { Size = _scanArea.Size }, Exclude = new Array(new[] { GetRid() }) }; } public override void _PhysicsProcess(double delta) { } public override void _IntegrateForces(PhysicsDirectBodyState2D state) { if (NewPosition.HasValue) { state.Transform = new Transform2D(0, NewPosition.Value); NewPosition = null; } Scan(GetPhysicsProcessDeltaTime(), state); } public override void _Draw() { if (!_drawDebugShapes) return; DrawRect(_scanArea, Colors.Aqua); } private void Scan(double delta, PhysicsDirectBodyState2D state) { var result = _spaceState.IntersectShape(_query); if (result.Count <= 0) { LinearVelocity = Vector2.Zero; return; } TrackBall(delta, result, state); } /// /// track the distance between the ball and the enemy paddle on the y axis and move velocity accordingly. /// /// how long it took to complete the last frame in seconds. this should be constant as /// should be executed in the physics process method which should be separate from the main frame rate. /// a dictionary of objects that collided with the cast. this method only works if /// theres an object that has the Ball class. /// private void TrackBall(double delta, IReadOnlyList result, PhysicsDirectBodyState2D state) { // checks if the collider is a ball, if not, return. if (result[0][_collider].As() is not { } ball) return; // gets the sign of the distance between the ball and the paddle on the y axis var normalisedDistance = new Vector2 { Y = Mathf.Sign(ball.Position.Y - Position.Y) }; var linearVelocity = normalisedDistance * MoveSpeed.ByMeter(); // lerp the velocity to smooth out jerky movement. state.LinearVelocity = LinearVelocity.Lerp(linearVelocity, delta); } /// /// generate the shape that the cast uses to detect collisions. /// /// if the collision shape is not a rectangle shape. private void GenerateCastArea() { if (_collisionShape.Shape is not RectangleShape2D shape) throw new InvalidOperationException("the collision shape needs to be a rectangle shape"); // grow the area the enemy can see by around half of the screen area. _scanArea = shape.GetRect().GrowSide(Side.Left, 400).GrowIndividual(0, 190, 0, 190); } }