Pong/Scripts/Objects/Enemy.cs
Fries 8072cf4961 the enemy can now scale to the resolution.
i made a BasePaddle class so i can put commonly used fields in a base class. this means i made a new plugin called CustomTypes which contains both Base classes.
2023-05-23 21:48:30 -07:00

105 lines
3.2 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();
ScalingManager.NewEnemyPosition += SetNewPosition;
_collisionShape = GetNode<CollisionShape2D>("CollisionShape2D");
GenerateCastArea();
_spaceState = GetWorld2D().DirectSpaceState;
_query = new PhysicsShapeQueryParameters2D
{
Shape = new RectangleShape2D { Size = _scanArea.Size },
Exclude = new Array<Rid>(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);
}
/// <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(double 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");
// 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);
}
}