diff --git a/BouncyMaterial.tres b/BouncyMaterial.tres
new file mode 100644
index 0000000..3df6451
--- /dev/null
+++ b/BouncyMaterial.tres
@@ -0,0 +1,5 @@
+[gd_resource type="PhysicsMaterial" format=3 uid="uid://e05n66x8ug77"]
+
+[resource]
+friction = 0.0
+bounce = 1.25
diff --git a/Scenes/Enemy.tscn b/Scenes/Enemy.tscn
new file mode 100644
index 0000000..f97d24b
--- /dev/null
+++ b/Scenes/Enemy.tscn
@@ -0,0 +1,23 @@
+[gd_scene load_steps=5 format=3 uid="uid://krt6x241s3x6"]
+
+[ext_resource type="PhysicsMaterial" uid="uid://e05n66x8ug77" path="res://BouncyMaterial.tres" id="1_e3kk5"]
+[ext_resource type="Script" path="res://Scripts/Enemy.cs" id="1_fbrtv"]
+
+[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_iw3nx"]
+size = Vector2(50, 75)
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_dbik4"]
+size = Vector2(12, 150)
+
+[node name="Enemy" type="RigidBody2D"]
+physics_material_override = ExtResource("1_e3kk5")
+gravity_scale = 0.0
+lock_rotation = true
+script = ExtResource("1_fbrtv")
+
+[node name="Sprite" type="Sprite2D" parent="."]
+scale = Vector2(0.25, 2)
+texture = SubResource("PlaceholderTexture2D_iw3nx")
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+shape = SubResource("RectangleShape2D_dbik4")
diff --git a/Scenes/Paddle.tscn b/Scenes/Paddle.tscn
index e631d5b..0310304 100644
--- a/Scenes/Paddle.tscn
+++ b/Scenes/Paddle.tscn
@@ -1,11 +1,8 @@
[gd_scene load_steps=5 format=3 uid="uid://bklo6torhapa0"]
+[ext_resource type="PhysicsMaterial" uid="uid://e05n66x8ug77" path="res://BouncyMaterial.tres" id="1_76uik"]
[ext_resource type="Script" path="res://Scripts/Paddle.cs" id="1_uv7s3"]
-[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_r5a55"]
-friction = 0.0
-bounce = 1.0
-
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_iw3nx"]
size = Vector2(50, 75)
@@ -13,7 +10,7 @@ size = Vector2(50, 75)
size = Vector2(12, 150)
[node name="Paddle" type="RigidBody2D"]
-physics_material_override = SubResource("PhysicsMaterial_r5a55")
+physics_material_override = ExtResource("1_76uik")
gravity_scale = 0.0
lock_rotation = true
script = ExtResource("1_uv7s3")
diff --git a/Scenes/Pong.tscn b/Scenes/Pong.tscn
index 8323a60..73e9b48 100644
--- a/Scenes/Pong.tscn
+++ b/Scenes/Pong.tscn
@@ -1,10 +1,13 @@
-[gd_scene load_steps=4 format=3 uid="uid://kmfgtiugs4m0"]
+[gd_scene load_steps=6 format=3 uid="uid://kmfgtiugs4m0"]
[ext_resource type="PackedScene" uid="uid://bklo6torhapa0" path="res://Scenes/Paddle.tscn" id="1_5rs0o"]
+[ext_resource type="Script" path="res://Scripts/SceneManager.cs" id="1_m8437"]
[ext_resource type="PackedScene" uid="uid://cggi01qnnlnwg" path="res://Scenes/Ball.tscn" id="2_u2ksv"]
[ext_resource type="PackedScene" uid="uid://c5n541vsuvfk8" path="res://Scenes/Walls.tscn" id="3_jfis7"]
+[ext_resource type="PackedScene" uid="uid://krt6x241s3x6" path="res://Scenes/Enemy.tscn" id="4_uwvof"]
[node name="Pong" type="Node2D"]
+script = ExtResource("1_m8437")
[node name="Paddle" parent="." instance=ExtResource("1_5rs0o")]
position = Vector2(-350, 0)
@@ -18,3 +21,7 @@ _ballSpeed = 50.0
_maxRandomAngle = 0.42
[node name="Walls" parent="." instance=ExtResource("3_jfis7")]
+
+[node name="Enemy" parent="." instance=ExtResource("4_uwvof")]
+position = Vector2(350, 0)
+_moveSpeed = 50.0
diff --git a/Scripts/Ball.cs b/Scripts/Ball.cs
index 59b0518..78518bb 100644
--- a/Scripts/Ball.cs
+++ b/Scripts/Ball.cs
@@ -10,7 +10,7 @@ public partial class Ball : CharacterBody2D
private Vector2 _velocity;
///
- /// this property multiples the ballSpeed by the Meter constant.
+ /// this property multiples the ballSpeed by the Meter constant.
///
private double BallSpeed => _ballSpeed * Constants.Meter;
@@ -32,7 +32,7 @@ public partial class Ball : CharacterBody2D
///
/// this method moves the ball and bounces if it collides with something.
///
- /// delta time from the _PhysicsProcess method.
+ /// delta time from the _PhysicsProcess method.
private void CollisionCheck(double delta)
{
var collision = MoveAndCollide(_velocity * delta);
@@ -42,9 +42,10 @@ public partial class Ball : CharacterBody2D
///
/// this method generates a random number between 0 and 1 and
- /// either returns Vector2.Left or Vector2.Right based on that number.
+ /// either returns Vector2.Left or Vector2.Right
+ /// based on that number.
///
- private Vector2 GetRandomStartingDirection() {
+ private static Vector2 GetRandomStartingDirection() {
using var rng = new RandomNumberGenerator();
rng.Randomize();
var range = rng.RandiRange(0,1);
diff --git a/Scripts/Enemy.cs b/Scripts/Enemy.cs
new file mode 100644
index 0000000..5f5e2fd
--- /dev/null
+++ b/Scripts/Enemy.cs
@@ -0,0 +1,90 @@
+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);
+ }
+}
diff --git a/Scripts/Paddle.cs b/Scripts/Paddle.cs
index 9949560..a2ff130 100644
--- a/Scripts/Paddle.cs
+++ b/Scripts/Paddle.cs
@@ -9,7 +9,7 @@ public partial class Paddle : RigidBody2D
private double _verticalInput;
///
- /// property that multiples the moveSpeed by the Meter constant.
+ /// property that multiples the moveSpeed by the Meter constant.
///
private double MoveSpeed => _moveSpeed * Constants.Meter;
diff --git a/Scripts/SceneManager.cs b/Scripts/SceneManager.cs
new file mode 100644
index 0000000..b0472c5
--- /dev/null
+++ b/Scripts/SceneManager.cs
@@ -0,0 +1,14 @@
+using Godot;
+
+namespace Pong.Scripts;
+
+public partial class SceneManager : Node2D
+{
+ public override void _Process(double delta)
+ {
+ if (Input.IsActionJustPressed("scene_reload"))
+ {
+ GetTree().ReloadCurrentScene();
+ }
+ }
+}
diff --git a/project.godot b/project.godot
index 984e028..2b8af3d 100644
--- a/project.godot
+++ b/project.godot
@@ -43,6 +43,11 @@ paddle_down={
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"echo":false,"script":null)
]
}
+scene_reload={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":82,"key_label":0,"unicode":114,"echo":false,"script":null)
+]
+}
[rendering]