Pong/Scripts/Nodes/Enemy.cs
Fries 9b0ebcefba walls can reset the ball to its starting position
WallManager binds to the BodyEntered signal on the Area2D class and
detects if the body that entered the area is the ball class, and if it
is, it will reset the ball to its starting position. right now, theres
no scoring system. this will be added later.
2023-05-19 00:18:38 -07:00

90 lines
2.7 KiB
C#

using System;
using System.Collections.Generic;
using Godot;
using Godot.Collections;
namespace Pong.Scripts.Nodes;
public partial class Enemy : RigidBody2D
{
/// <summary>
/// draw shapes to the screen (like <see cref="_scanArea"/>).
/// </summary>
[Export] private bool _drawDebugShapes;
[Export] private double _moveSpeed;
private CollisionShape2D _collisionShape;
private Rect2 _scanArea;
public override void _Ready()
{
_collisionShape = GetNode<CollisionShape2D>("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)
{
using var spaceState = GetWorld2D().DirectSpaceState;
using var query = new PhysicsShapeQueryParameters2D
{
Shape = new RectangleShape2D { Size = _scanArea.Size },
Exclude = new Array<Rid>(new[] { GetRid() })
};
var result = spaceState.IntersectShape(query);
if (result.Count <= 0)
{
LinearVelocity = Vector2.Zero;
return;
}
TrackBall(delta, result);
}
/// <summary>
/// track the distance between the ball and the enemy paddle on the y axis and move velocity accordingly.
/// </summary>
/// <param name="delta">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.</param>
/// <param name="result">a dictionary of objects that collided with the cast. this method only works if
/// theres an object that has the Ball class.</param>
private void TrackBall(double delta, IReadOnlyList<Dictionary> result)
{
// checks if the collider is a ball, if not, return.
if (result[0]["collider"].As<Nodes.Ball>() 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);
}
/// <summary>
/// generate the shape that the cast uses to detect collisions.
/// </summary>
/// <exception cref="InvalidOperationException">if the collision shape is not a rectangle shape.</exception>
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);
}
}