Vereinheitlichung der Pfadfindung anstelle einer anderen KI-Logik

Für ein einfaches Spiel musste ich das KI-Verhalten mit grundlegenden Funktionen implementieren: Patrouillieren, Verfolgen und Kämpfen. Die Aufgabe selbst ist einfach, es gab jedoch zwei Arten von Orten mit unterschiedlichen Abstraktionsebenen.





In einem Fall fand die Aktion auf engstem Raum statt, im anderen mitten auf den Straßen der Stadt. In kleinen Räumen wurde ein Navigationsraster generiert, aber an einem großen Ort wurde die Diagrammpfadfindung verwendet, um die Leistung aufrechtzuerhalten.





Alle Arten von Verhaltensweisen wurden bereits geschrieben, und die Logik ist an allen Orten gleich. Für die KI war es egal, welche Pfadfindung verwendet wurde. Die Hauptsache ist, den Weg zum Ziel zu finden und Ihre Aufgabe zu erledigen!





Für mich habe ich zwei Lösungen identifiziert. Die erste bestand darin, das Verhalten beispielsweise mithilfe des Strategiemusters an das Gelände anzupassen. In diesem Fall müsste jedoch für jeden Navigationstyp eine zusätzliche Logik geschrieben werden. Die zweite Lösung bestand darin, die Pfadfindungsdaten zu vereinheitlichen. Bei diesem Ansatz musste die KI nicht durch unnötige Logik ergänzt werden, und die Suchmaschinen übernahmen die gesamte Arbeit!





Implementierung

Hauptobjekte:





  • IPath <TPoint> (Pfaddaten)





  • IPathProvider <TPoint> (Suchmaschine oder Pfad bereitstellendes Objekt)





  • IPathResponse <TPoint> (enthält den Pfad der von der Suchmaschine empfangenen Antwort)





  • IPathRequestToken <TPoint> (Token zum Generieren der Antwort)





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));
}
      
      



. .








All Articles