using System; using System.Collections.Generic; using Godot.Collections; namespace Pong.Scripts.Objects; public partial class Enemy : RigidBody2D { /// /// draw shapes to the screen (like ). /// [Export] private bool _drawDebugShapes; [Export] private double _moveSpeed; private CollisionShape2D _collisionShape; private Rect2 _scanArea; private PhysicsDirectSpaceState2D _spaceState; private readonly Variant _collider = "collider"; private PhysicsShapeQueryParameters2D _query; public override void _Ready() { _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) { Scan(delta); } public override void _Draw() { if (!_drawDebugShapes) return; DrawRect(_scanArea, Colors.Aqua); } private void Scan(double delta) { var result = _spaceState.IntersectShape(_query); if (result.Count <= 0) { LinearVelocity = Vector2.Zero; return; } TrackBall(delta, result); } /// /// 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) { // 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 * Constants.Meter; // lerp the velocity to smooth out jerky movement. 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); } }