Untuk satu game langsung, saya perlu mengimplementasikan perilaku AI dengan fungsi dasar: patroli, pengejaran, dan pertempuran. Tugasnya sendiri sederhana, namun ada dua jenis lokasi dan dengan tingkat abstraksi yang berbeda.
Dalam satu kasus, aksi terjadi di ruang terbatas, dan kasus lainnya, di tengah jalan kota. Di ruang kecil, kisi navigasi dibuat, tetapi di lokasi yang besar, pencarian jalur grafik digunakan untuk mempertahankan kinerja.
Semua jenis perilaku telah ditulis, dan logikanya sama di semua lokasi. Tidak masalah bagi AI pencarian jalan apa yang digunakan. Hal utama adalah mencapai tujuan dan menyelesaikan tugas Anda!
Untuk diri saya sendiri, saya telah mengidentifikasi dua solusi. Yang pertama adalah menyesuaikan perilaku dengan medan, misalnya, menggunakan pola strategi. Namun dalam kasus ini, logika tambahan harus ditulis untuk setiap jenis navigasi. Solusi kedua adalah menyatukan data pencarian jalan. Dengan pendekatan ini, AI tidak perlu dilengkapi dengan logika yang tidak perlu, dan mesin pencari mengambil alih semua pekerjaan!
Penerapan
Objek utama:
IPath <TPoint> (data jalur)
IPathProvider <TPoint> (mesin telusur atau objek yang menyediakan jalur)
IPathResponse <TPoint> (berisi jalur respons yang diterima dari mesin pencari)
IPathRequestToken <TPoint> (token untuk menghasilkan respons)
IPath
. , , , . , , Vector3 Vector2 , .
public interface IPath<TPoint>
{
// .
TPoint Current { get; }
// .
IEnumerable<TPoint> Points { get; }
// .
bool Continue(TPoint origin);
}
IPath , , , - null, , . Continue.
β . ? null? , , , .. .
public class EmptyPath<TPoint> : IPath<TPoint>
{
public TPoint Current => default(TPoint);
public IEnumerable<TPoint> Points => null;
public bool Continue(TPoint origin) => false;
}
// , .
public class EmptyPathException : Exception
{
public EmptyPathException()
: base("Path is empty! Try using EmptyPath<TPoint> instead of Path<TPoint>")
{}
}
:
public class Path<TPoint> : IPath<TPoint>
{
// .
// .
protected readonly Func<TPoint, TPoint, bool> ContinueFunc;
protected readonly IEnumerator<TPoint> PointsEnumerator;
// .
public TPoint Current { get; protected set; }
// .
public IEnumerable<TPoint> Points { get; protected set; }
// .
// .
public bool Continued { get; protected set; }
public Path(IEnumerable<TPoint> points, Func<TPoint, TPoint, bool> continueFunc)
{
// .
if(points == null)
throw new EmptyPathException();
ContinueFunc = continueFunc;
PointsEnumerator = points.GetEnumerator();
Points = points;
//
// .
MovePointer();
}
// .
public bool Continue(TPoint origin)
{
// .
if (ContinueFunc(origin, Current))
MovePointer();
// .
return Continued;
}
// ,
// .
protected void MovePointer()
{
// .
if (PointsEnumerator.MoveNext())
{
Current = PointsEnumerator.Current;
Continued = true;
}
else
{
//
Continued = false;
}
}
}
Func<TPoint, TPoint, bool> ContinueFunc β (, ). , . .
IEnumerator<TPoint> PointsEnumerator β .
Path , . : null , .
IPath . . / , .
:)
IPathProvider IPathResponse
, , .
IPathProvider<TPoint> β , , . . :
public interface IPathProvider<TPoint>
{
// , , .
IPathResponse<TPoint> RequestPath(TPoint entryPoint, TPoint endPoint);
}
:
public interface IPathResponse<TPoint>
{
// .
bool Ready { get; }
// , null.
IPath<TPoint> Path { get; }
}
IPathResponse<TPoint> Path Ready, . / true.
:
public sealed class PathResponseSync<TPoint> : IPathResponse<TPoint>
{
public bool Ready { get; private set; }
public IPath<TPoint> Path { get; private set; }
public PathResponseSync(IPath<TPoint> path)
{
if(path == null)
throw new EmptyPathException();
Path = path;
Ready = true;
}
}
, . .
. , IPathResponse .
:
public sealed class PathRequestToken<TPoint>
{
public bool IsReady { get; private set; }
public IPath<TPoint> Path { get; private set; }
public void Ready(IPath<TPoint> path)
{
if (path == null)
throw new EmptyPathException();
IsReady = true;
Path = path;
}
}
IPathResponse. , IPathResponse. , .
:
public sealed class PathResponse<TPoint> : IPathResponse<TPoint>
{
private readonly PathRequestToken<TPoint> _token;
public bool Ready => _token.IsReady;
public IPath<TPoint> Path => _token.Path;
public PathResponse(PathRequestToken<TPoint> token)
{
_token = token;
}
// .
public static void New(out PathRequestToken<TPoint> token,
out PathResponse<TPoint> response)
{
token = new PathRequestToken<TPoint>();
response = new PathResponse<TPoint>(token);
}
}
/ .
, .
, , , .
, ! : IPathResponse.
, Update :
..
private IPathProvider<Vector3> _pathProvider;
private IPathResponse<Vector3> _pathResponse;
..
public override void Update(float deltaTime)
{
// .
_pathUpdateTimer += deltaTime;
if (_pathUpdateTimer >= Owner.PathUpdateRate)
{
_pathUpdateTimer = 0f;
if (Target == null)
Target = _scanFunction(Owner);
if (Target == null)
return;
// .
_pathResponse = _pathProvider
.RequestPath(Position, Target.transform.position);
}
// , .
if (_pathResponse != null)
{
//
if (_pathResponse.Ready)
{
var path = _pathResponse.Path;
//
// .
if (path.Continue(Position))
{
// -
var nextPosition = Vector3.MoveTowards( Position, path.Current,
Owner.MovementSpeed * deltaTime);
Position = nextPosition;
}
}
}
}
:
public static bool Vector3Continuation(Vector3 origin, Vector3 current)
{
var distance = (origin - current).sqrMagnitude;
return distance <= float.Epsilon;
}
:
public IPathResponse<Vector3> RequestPath(Vector3 entryPoint, Vector3 endPoint)
{
// , ...
// LinkedAPoint.
var pathRaw = _jastar.FindPath(startPointJastar, endPointJastar);
// , .
if(pathRaw.Count == 0)
return new PathResponseSync<Vector3>(new EmptyPath<Vector3>());
var vectorList = pathRaw.ToVector3List();
// .
return new PathResponseSync<Vector3>(
new Path<Vector3>(vectorsList, PathFuncs.Vector3Continuation));
}