rewrote AI steering thing

item-info
HumanoidSandvichDispenser 2022-11-12 16:45:04 -08:00
parent 9f4d7b4928
commit 9cd76af028
11 changed files with 1376 additions and 195 deletions

View File

@ -5,7 +5,9 @@ namespace SupaLidlGame.Characters
{ {
public partial class Character : CharacterBody2D public partial class Character : CharacterBody2D
{ {
[Export]
public float Speed { get; protected set; } = 128.0f; public float Speed { get; protected set; } = 128.0f;
public float JumpVelocity { get; protected set; } = -400.0f; public float JumpVelocity { get; protected set; } = -400.0f;
public float AccelerationMagnitude { get; protected set; } = 256.0f; public float AccelerationMagnitude { get; protected set; } = 256.0f;

View File

@ -5,124 +5,5 @@ namespace SupaLidlGame.Characters
{ {
public partial class Enemy : NPC public partial class Enemy : NPC
{ {
public override void _Ready()
{
base._Ready();
}
public override void _Process(double delta)
{
if ((_thinkTimeElapsed -= delta) <= 0)
{
_thinkTimeElapsed = 0.25;
Think();
}
//Direction = (Target.GlobalPosition - GlobalPosition).Normalized();
base._Process(delta);
}
public override void _Draw()
{
for (byte i = 0; i < 16; i++)
{
Vector2 diff = WeightVec(i) * 128;
Color c = _dirIdx == i ? new Color(0, 255, 64) : new Color(0, 128, 24);
DrawLine(Vector2.Zero, diff, c, 2.0f);
}
base._Draw();
}
private Vector2 WeightVec(int idx)
{
//GD.Print(_weights[idx]);
return WeightVecNorm(idx) * _weights[idx];
}
private Vector2 WeightVecNorm(int idx)
{
// sin(2pix/16)
float x = Mathf.Cos(Mathf.Pi * idx / 8);
float y = Mathf.Sin(Mathf.Pi * idx / 8);
return new Vector2(x, y);
}
private Character FindBestTarget()
{
float bestDist = float.MaxValue;
Character bestChar = null;
foreach (Node node in GetParent().GetChildren())
{
if (node != this && node is Character character)
{
float dist = Position.DistanceTo(character.Position);
if (dist < bestDist)
{
bestDist = dist;
bestChar = character;
}
}
}
return bestChar;
}
private void Think()
{
Target = FindBestTarget();
float bestWeight = 0;
Vector2 want = GlobalPosition.DirectionTo(Target.GlobalPosition);
float dist = GlobalPosition.DistanceSquaredTo(Target.GlobalPosition);
for (byte i = 0; i < 16; i++)
{
Vector2 dir = WeightVecNorm(i);
// if close enough, _weights[i] will be instead calculated dot to a perpendicular weight
if (dist < 16384)
{
Vector2 dirA = WeightVecNorm((i + 4) % 16);
Vector2 dirB = WeightVecNorm((i - 4) % 16);
float dot = Mathf.Max(dirA.Dot(want) + dirA.Dot(Direction),
dirB.Dot(want) + dirB.Dot(Direction));
_weights[i] = (dot + 1) / 2;
}
else
{
_weights[i] = (dir.Dot(want) + 1) / 2;
}
// check each weight with a raycast to see if it will hit any object
// if it hits an object, subtract the weight by how close the ray is
if (_weights[i] > 0)
{
GD.Print("casting...");
var spaceState = GetWorld2d().DirectSpaceState;
var args = new PhysicsRayQueryParameters2D();
args.From = GlobalPosition;
args.To = GlobalPosition + dir * 256 * _weights[i];
args.CollideWithBodies = true;
args.Exclude.Add(this.GetRid());
var result = spaceState.IntersectRay(args);
GD.Print(result.Count);
if (result.Count > 0)
{
var pos = result["position"].AsVector2();
var sub = pos.DistanceTo(GlobalPosition + dir);
_weights[i] -= sub;
GD.Print("hit!");
}
}
if (_weights[i] > bestWeight)
{
bestWeight = _weights[i];
_dirIdx = i;
}
//GD.Print(_weights[i]);
}
Direction = WeightVecNorm((byte)_dirIdx);
QueueRedraw();
}
} }
} }

View File

@ -8,6 +8,7 @@ size = Vector2(32, 16)
[node name="ExampleEnemy" type="CharacterBody2D"] [node name="ExampleEnemy" type="CharacterBody2D"]
script = ExtResource("1_4x3dm") script = ExtResource("1_4x3dm")
Speed = 32.0
[node name="Icon" type="Sprite2D" parent="."] [node name="Icon" type="Sprite2D" parent="."]
scale = Vector2(0.25, 0.25) scale = Vector2(0.25, 0.25)

View File

@ -5,20 +5,29 @@ namespace SupaLidlGame.Characters
{ {
public partial class NPC : Character public partial class NPC : Character
{ {
/// <summary>Time in seconds it takes for the NPC to think</summary> /// <summary>
public const float ThinkTime = 0.25f; /// Time in seconds it takes for the NPC to think FeelsDankCube
/// </summary>
public const float ThinkTime = 0.125f;
public Character Target { get; protected set; } public Character Target { get; protected set; }
public float[] Weights => _weights; public float[] Weights => _weights;
protected float[] _weights = new float[16]; float[] _weights = new float[16];
protected int _dirIdx = 0; Vector2[] _weightDirs = new Vector2[16];
int _bestWeightIdx;
protected double _thinkTimeElapsed = 0; protected double _thinkTimeElapsed = 0;
public override void _Ready() public override void _Ready()
{ {
base._Ready(); base._Ready();
Array.Fill(_weights, 0); Array.Fill(_weights, 0);
for (int i = 0; i < 16; i++)
{
float y = Mathf.Sin(Mathf.Pi * i * 2 / 16);
float x = Mathf.Cos(Mathf.Pi * i * 2 / 16);
_weightDirs[i] = new Vector2(x, y);
}
} }
public override void _Process(double delta) public override void _Process(double delta)
@ -29,43 +38,39 @@ namespace SupaLidlGame.Characters
Think(); Think();
} }
Direction = _weightDirs[_bestWeightIdx];
//Direction = (Target.GlobalPosition - GlobalPosition).Normalized(); //Direction = (Target.GlobalPosition - GlobalPosition).Normalized();
base._Process(delta); base._Process(delta);
} }
public override void _Draw() public override void _Draw()
{ {
for (byte i = 0; i < 16; i++) for (int i = 0; i < 16; i++)
{ {
Vector2 diff = WeightVec(i) * 128; Vector2 vec = _weightDirs[i] * _weights[i] * 128;
Color c = _dirIdx == i ? new Color(0, 255, 64) : new Color(0, 128, 24); Color c = Colors.Green;
DrawLine(Vector2.Zero, diff, c, 2.0f); if (_bestWeightIdx == i)
{
c = Colors.Blue;
}
else if (_weights[i] < 0)
{
c = Colors.Red;
vec = -vec;
}
DrawLine(Vector2.Zero, vec, c);
} }
base._Draw(); base._Draw();
} }
private Vector2 WeightVec(int idx) protected virtual Character FindBestTarget()
{
//GD.Print(_weights[idx]);
return WeightVecNorm(idx) * _weights[idx];
}
private Vector2 WeightVecNorm(int idx)
{
// sin(2pix/16)
float x = Mathf.Cos(Mathf.Pi * idx / 8);
float y = Mathf.Sin(Mathf.Pi * idx / 8);
return new Vector2(x, y);
}
private Character FindBestTarget()
{ {
float bestDist = float.MaxValue; float bestDist = float.MaxValue;
Character bestChar = null; Character bestChar = null;
foreach (Node node in GetParent().GetChildren()) foreach (Node node in GetParent().GetChildren())
{ {
if (node != this && node is Character character) if (node != this && node is Player character)
{ {
float dist = Position.DistanceTo(character.Position); float dist = Position.DistanceTo(character.Position);
if (dist < bestDist) if (dist < bestDist)
@ -80,59 +85,97 @@ namespace SupaLidlGame.Characters
private void Think() private void Think()
{ {
Target = FindBestTarget(); Vector2 pos = FindBestTarget().GlobalPosition;
float bestWeight = 0; Vector2 dir = GlobalPosition.DirectionTo(pos);
float dist = GlobalPosition.DistanceSquaredTo(pos);
Vector2 want = GlobalPosition.DirectionTo(Target.GlobalPosition); for (int i = 0; i < 16; i++)
float dist = GlobalPosition.DistanceSquaredTo(Target.GlobalPosition);
for (byte i = 0; i < 16; i++)
{ {
Vector2 dir = WeightVecNorm(i); float directDot = _weightDirs[i].Dot(dir);
// if close enough, _weights[i] will be instead calculated dot to a perpendicular weight directDot = (directDot + 1) / 2;
if (dist < 16384)
// this dot product resembles values of sine rather than cosine
// use it to weigh direction horizontally
Vector2 rotatedDir = new Vector2(-dir.y, dir.x);
float horizDot = Math.Abs(_weightDirs[i].Dot(rotatedDir));
// this is a smaller weight so they are more likely to
// pick the direction they are currently heading when
// choosing between two horizontal weights
float currDirDot = (_weightDirs[i].Dot(Direction) + 1) / 16;
// square so lower values are even lower
horizDot = Mathf.Pow((horizDot + 1) / 2, 2) + currDirDot;
// "When will I use math in the real world" Clueful
if (dist > 1024)
{ {
Vector2 dirA = WeightVecNorm((i + 4) % 16); _weights[i] = directDot;
Vector2 dirB = WeightVecNorm((i - 4) % 16); }
float dot = Mathf.Max(dirA.Dot(want) + dirA.Dot(Direction), else if (dist > 64)
dirB.Dot(want) + dirB.Dot(Direction)); {
_weights[i] = (dot + 1) / 2; float directDotWeighting = (dist - 64) / 960;
float horizDotWeighting = 1 - directDotWeighting;
_weights[i] = (directDot * directDotWeighting) +
(horizDot * horizDotWeighting);
} }
else else
{ {
_weights[i] = (dir.Dot(want) + 1) / 2; // shorter than 64
_weights[i] = horizDot;
} }
// check each weight with a raycast to see if it will hit any object // now we shall subtract weights whose rays collide
// if it hits an object, subtract the weight by how close the ray is // with something
if (_weights[i] > 0)
{ {
GD.Print("casting...");
var spaceState = GetWorld2d().DirectSpaceState; var spaceState = GetWorld2d().DirectSpaceState;
var args = new PhysicsRayQueryParameters2D(); var exclude = new Godot.Collections.Array<RID>();
args.From = GlobalPosition; exclude.Add(this.GetRid());
args.To = GlobalPosition + dir * 256 * _weights[i]; var rayParams = new PhysicsRayQueryParameters2D
args.CollideWithBodies = true; {
args.Exclude.Add(this.GetRid()); Exclude = exclude,
var result = spaceState.IntersectRay(args); CollideWithBodies = true,
GD.Print(result.Count); From = GlobalPosition,
To = GlobalPosition + (_weightDirs[i] * 16)
};
var result = spaceState.IntersectRay(rayParams);
// if our ray cast hits something
if (result.Count > 0) if (result.Count > 0)
{ {
var pos = result["position"].AsVector2(); // then we subtract the dot product of other directions
var sub = pos.DistanceTo(GlobalPosition + dir); for (int j = 0; j < 16; j++)
_weights[i] -= sub; {
GD.Print("hit!"); if (i == j)
{
_weights[i] = 0;
}
else
{
float dot = _weightDirs[i].Dot(_weightDirs[j]);
_weights[j] -= (dot + 1) / 4;
}
}
} }
} }
}
float bestWeight = 0;
for (int i = 0; i < 16; i++)
{
if (_weights[i] > bestWeight) if (_weights[i] > bestWeight)
{ {
bestWeight = _weights[i]; bestWeight = _weights[i];
_dirIdx = i; _bestWeightIdx = i;
} }
//GD.Print(_weights[i]);
} }
Direction = WeightVecNorm((byte)_dirIdx);
QueueRedraw(); QueueRedraw();
} }
} }

View File

@ -3,16 +3,16 @@ using System;
namespace SupaLidlGame.Characters namespace SupaLidlGame.Characters
{ {
public partial class Player : Character public partial class Player : Character
{ {
public override void _Ready() public override void _Ready()
{ {
Speed = 256.0f;
}
public override void _Process(double delta) }
{
Direction = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down"); public override void _Process(double delta)
} {
} Direction = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down");
}
}
} }

View File

@ -19,3 +19,6 @@ shape = SubResource("RectangleShape2D_bfqew")
[node name="Camera2D" type="Camera2D" parent="."] [node name="Camera2D" type="Camera2D" parent="."]
current = true current = true
zoom = Vector2(4, 4)
[node name="StateManager" type="Node" parent="."]

View File

@ -0,0 +1,99 @@
using Godot;
using System;
namespace SupaLidlGame.Prototyping
{
public partial class ContextBasedSteering : Node2D
{
float[] _weights = new float[16];
Vector2[] _weightDirs = new Vector2[16];
int _bestWeightIdx;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
Array.Fill(_weights, 1);
for (int i = 0; i < 16; i++)
{
float y = Mathf.Sin(Mathf.Pi * i * 2 / 16);
float x = Mathf.Cos(Mathf.Pi * i * 2 / 16);
_weightDirs[i] = new Vector2(x, y);
}
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process(double delta)
{
Vector2 pos = GetLocalMousePosition();
Vector2 dir = GlobalPosition.DirectionTo(pos);
float dist = GlobalPosition.DistanceSquaredTo(pos);
for (int i = 0; i < 16; i++)
{
float directDot = _weightDirs[i].Dot(dir);
directDot = (directDot + 1) / 2;
// this dot product resembles values of sine rather than cosine
// use it to weigh direction horizontally
Vector2 rotatedDir = new Vector2(-dir.y, dir.x);
float horizDot = Math.Abs(_weightDirs[i].Dot(rotatedDir));
// square so lower values are even lower
horizDot = Mathf.Pow((horizDot + 1) / 2, 2);
// "When will I use math in the real world" Clueful
if (dist > 1024)
{
_weights[i] = directDot;
}
else if (dist > 256)
{
float directDotWeighting = (dist - 256) / 768;
float horizDotWeighting = 1 - directDotWeighting;
_weights[i] = (directDot * directDotWeighting) +
(horizDot * horizDotWeighting);
}
else
{
// shorter than 256
_weights[i] = horizDot;
}
// now we shall subtract weights whose rays collide
// with something
{
}
}
float bestWeight = 0;
for (int i = 0; i < 16; i++)
{
if (_weights[i] > bestWeight)
{
bestWeight = _weights[i];
_bestWeightIdx = i;
}
}
QueueRedraw();
}
public override void _Draw()
{
for (int i = 0; i < 16; i++)
{
Vector2 vec = _weightDirs[i] * _weights[i] * 128;
Color c = Colors.Green;
if (_bestWeightIdx == i)
{
c = Colors.Blue;
}
DrawLine(GlobalPosition, GlobalPosition + vec, c);
}
}
}
}

View File

@ -0,0 +1,9 @@
[gd_scene load_steps=2 format=3 uid="uid://dmbgefwamg0u7"]
[ext_resource type="Script" path="res://Prototyping/ContextBasedSteering.cs" id="1_t0k7y"]
[node name="ContextBasedSteeringTest" type="Node2D"]
[node name="ContextBasedSteering" type="Node2D" parent="."]
position = Vector2(410, 242)
script = ExtResource("1_t0k7y")

File diff suppressed because one or more lines are too long

BIN
Sprites/tileset.png 100755

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://gm2pcnfg7h8j"
path="res://.godot/imported/tileset.png-c259079e18121438fd392d302e3ca0d5.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Sprites/tileset.png"
dest_files=["res://.godot/imported/tileset.png-c259079e18121438fd392d302e3ca0d5.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1