using System; using System.Collections.Generic; using Godot; using Godot.Collections; namespace Pong.Scripts; 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; public override void _Ready() { _collisionShape = GetNode("CollisionShape2D"); GenerateCastArea(); } 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 spaceState = GetWorld2D().DirectSpaceState; var query = new PhysicsShapeQueryParameters2D { Shape = new RectangleShape2D { Size = _scanArea.Size }, Exclude = new Array(new[] { GetRid() }) }; 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(0, 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); } }