127 lines
3.6 KiB
C#
127 lines
3.6 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// draw shapes to the screen (like <see cref="_scanArea"/>).
|
|
/// </summary>
|
|
[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();
|
|
_collisionShape = GetNode<CollisionShape2D>("CollisionShape2D");
|
|
}
|
|
|
|
public override void _ExitTree()
|
|
{
|
|
_spaceState.Dispose();
|
|
_query.Dispose();
|
|
_collisionShape.Dispose();
|
|
}
|
|
|
|
internal void GenerateCollisions()
|
|
{
|
|
GenerateCastArea();
|
|
GenerateQuery();
|
|
}
|
|
|
|
private void GenerateQuery()
|
|
{
|
|
_query = new PhysicsShapeQueryParameters2D
|
|
{
|
|
Shape = new RectangleShape2D { Size = _scanArea.Size },
|
|
Exclude = new Array<Rid>(new[] { GetRid() })
|
|
};
|
|
}
|
|
|
|
public override void _IntegrateForces(PhysicsDirectBodyState2D state)
|
|
{
|
|
_spaceState ??= GetWorld2D().DirectSpaceState;
|
|
|
|
if (!Running) return;
|
|
|
|
if (NewPosition.HasValue)
|
|
{
|
|
state.Transform = new Transform2D(0, NewPosition.Value);
|
|
|
|
_query.Dispose();
|
|
|
|
GenerateCastArea();
|
|
GenerateQuery();
|
|
NewPosition = null;
|
|
}
|
|
|
|
Scan((float)GetPhysicsProcessDeltaTime(), state);
|
|
}
|
|
|
|
public override void _Draw()
|
|
{
|
|
if (!_drawDebugShapes) return;
|
|
DrawRect(_scanArea, Colors.Aqua);
|
|
}
|
|
|
|
private void Scan(float delta, PhysicsDirectBodyState2D state)
|
|
{
|
|
var result = _spaceState.IntersectShape(_query);
|
|
|
|
if (result.Count <= 0)
|
|
{
|
|
LinearVelocity = Vector2.Zero;
|
|
return;
|
|
}
|
|
|
|
TrackBall(delta, result, state);
|
|
}
|
|
|
|
/// <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>
|
|
/// <param name="state"></param>
|
|
private void TrackBall(float delta, IReadOnlyList<Dictionary> result, PhysicsDirectBodyState2D state)
|
|
{
|
|
// checks if the collider is a ball, if not, return.
|
|
if (result[0][_collider].As<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 { 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);
|
|
}
|
|
|
|
/// <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");
|
|
|
|
var gameArea = ScalingManager.GameArea;
|
|
var verticalGrowSize = gameArea.Y.ByWallSizeExtents / 2;
|
|
|
|
// grow the area the enemy can see by around half of the screen area.
|
|
_scanArea = shape.GetRect().GrowSide(Side.Left, gameArea.X.ByWallSizeExtents)
|
|
.GrowIndividual(0, verticalGrowSize, 0, verticalGrowSize);
|
|
}
|
|
}
|