add enemy ai to the game
the enemy can now track the player with a cast that uses a shape generated in code. the enemy moves using some fancy math that gets the sign of the distance between the ball and the enemy paddles y position to get a normalised number for velocity and the velocity is lerped to smooth out any janky movement.
This commit is contained in:
parent
e50f0aefb1
commit
4200a3e7df
9 changed files with 153 additions and 11 deletions
5
BouncyMaterial.tres
Normal file
5
BouncyMaterial.tres
Normal file
|
@ -0,0 +1,5 @@
|
|||
[gd_resource type="PhysicsMaterial" format=3 uid="uid://e05n66x8ug77"]
|
||||
|
||||
[resource]
|
||||
friction = 0.0
|
||||
bounce = 1.25
|
23
Scenes/Enemy.tscn
Normal file
23
Scenes/Enemy.tscn
Normal file
|
@ -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")
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -10,7 +10,7 @@ public partial class Ball : CharacterBody2D
|
|||
private Vector2 _velocity;
|
||||
|
||||
/// <summary>
|
||||
/// this property multiples the ballSpeed by the Meter constant.
|
||||
/// this property multiples the ballSpeed by the <see cref="Constants.Meter">Meter</see> constant.
|
||||
/// </summary>
|
||||
private double BallSpeed => _ballSpeed * Constants.Meter;
|
||||
|
||||
|
@ -32,7 +32,7 @@ public partial class Ball : CharacterBody2D
|
|||
/// <summary>
|
||||
/// this method moves the ball and bounces if it collides with something.
|
||||
/// </summary>
|
||||
/// <param name="delta">delta time from the _PhysicsProcess method.</param>
|
||||
/// <param name="delta">delta time from the <see cref="_PhysicsProcess">_PhysicsProcess</see> method.</param>
|
||||
private void CollisionCheck(double delta)
|
||||
{
|
||||
var collision = MoveAndCollide(_velocity * delta);
|
||||
|
@ -42,9 +42,10 @@ public partial class Ball : CharacterBody2D
|
|||
|
||||
/// <summary>
|
||||
/// this method generates a random number between 0 and 1 and
|
||||
/// either returns Vector2.Left or Vector2.Right based on that number.
|
||||
/// either returns <see cref="Vector2.Left">Vector2.Left</see> or <see cref="Vector2.Right">Vector2.Right</see>
|
||||
/// based on that number.
|
||||
/// </summary>
|
||||
private Vector2 GetRandomStartingDirection() {
|
||||
private static Vector2 GetRandomStartingDirection() {
|
||||
using var rng = new RandomNumberGenerator();
|
||||
rng.Randomize();
|
||||
var range = rng.RandiRange(0,1);
|
||||
|
|
90
Scripts/Enemy.cs
Normal file
90
Scripts/Enemy.cs
Normal file
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace Pong.Scripts;
|
||||
|
||||
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)
|
||||
{
|
||||
var spaceState = GetWorld2D().DirectSpaceState;
|
||||
|
||||
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<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);
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ public partial class Paddle : RigidBody2D
|
|||
private double _verticalInput;
|
||||
|
||||
/// <summary>
|
||||
/// property that multiples the moveSpeed by the Meter constant.
|
||||
/// property that multiples the moveSpeed by the <see cref="Constants.Meter">Meter</see> constant.
|
||||
/// </summary>
|
||||
private double MoveSpeed => _moveSpeed * Constants.Meter;
|
||||
|
||||
|
|
14
Scripts/SceneManager.cs
Normal file
14
Scripts/SceneManager.cs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
|
||||
|
|
Loading…
Reference in a new issue