Flocking Behaviour

Game Engine - Unreal Engine, C++

Learning Outcome - Flocking Behaviour using differernt approach

About the Project :

  • I implemented Flocking Behaviour using Sphere Cast instead for Line tracing.
  • The result of behaviour will be similar to how flock work using line trace, just avoiding will not work well with other physics objects are placed in world.
  • But I have figured out to assist flocks. Red Colored Arrow at 0:13s indicates the assistants I added for guiding flock to the direction of arrow, to keep them inside the box.
  • Using Sphere Cast will use more memory than line trace, so it will have performace issues if we increase the flock numbers. This approach won't work in real game, but I just wanted to implement flock using different method.
  • Below are some details of flocking algorithm from above video.

Flock class : Driver class for all Flock Agents

    void AFlock::Tick(float DeltaTime)
    {
        Super::Tick(DeltaTime);
                          
        for(AFlockAgent* Agent : AllAgents)
        {
            //get all actor around "Agent" actor
            TArray Context = GetNearByActors(Agent);
            
            //calculating cohision,alignment,avoidance based on near by actors
            FVector Move = BehaviourComponent->CalculateMove(Agent, Context, this);
                          
            Move *= DriveFactor;
                          
            //limit the speed of agent over maxspeed
            if (Move.SizeSquared() > SquareMaxSpeed)
            {
                Move = Move.GetClampedToSize(0.0f, 1.0f) * MaxSpeed;
            }
            
            FRotator Rotate = BehaviourComponent->CalculateRotate(Agent, Context, this);
                          
            Agent->Move(Move, DeltaTime, Rotate);
        }
    }
      
    //basic sphere overlap to find all actors inside sphere
    TArray AFlock::GetNearByActors(class AFlockAgent* Agent)
    {
        TArray Context;
                          
        TArray ContextActors;
                          
        TArray> ObjectTypesForSphere;
        ObjectTypesForSphere.Add(UEngineTypes::ConvertToObjectType(ECollisionChannel::ECC_WorldDynamic));
                          
        UClass* SeekAgent = AActor::StaticClass();
                          
        TArray IgnoreActors;
        IgnoreActors.Add(Agent);
                            
        UKismetSystemLibrary::SphereOverlapActors(GetWorld(), Agent->GetActorLocation(), NeighbourRadius, ObjectTypesForSphere, SeekAgent, IgnoreActors, ContextActors);
                          
        for (AActor* CollisionActor : ContextActors)
        {
            Context.Add(CollisionActor->GetActorTransform());
        }
                          
        return Context;
    }
                        

FlockAgent class : Agent class for Flock mesh and colision

    //move and rotate
    void AFlockAgent::Move(FVector DesiredPosition, float DeltaTime, FRotator DesiredRotation)
    {
        FVector Location = GetActorLocation();
                          
        Location += DesiredPosition * DeltaTime;
                          
        SetActorLocation(Location);
                          
        FVector Forward = DesiredPosition - GetActorLocation();
        FRotator Rot = UKismetMathLibrary::MakeRotFromXZ(Forward, FVector::UpVector);
                            
        SetActorRotation(DesiredRotation);
    }
                        

CompositeBehaviour class : Behaviour class for calculating cohision, alignment, avoidance

    FVector UCompositeBehaviour::CalculateMove(class AFlockAgent* Agent, TArray Context, class AFlock* Flock)
    {
        FVector Move;
        FVector PartialCohisionMove;
        FVector PartialAllignmentMove;
        FVector PartialAvoidanceMove;
      
        //calculating alignment
        if (AllignmentBool)
        {
            PartialAllignmentMove = CalculateAllignmentMove(Agent, Context, Flock) * AllignmentWeight;
      
            //clamp the alignment over weight
            if (PartialAllignmentMove.SizeSquared() > AllignmentWeight * AllignmentWeight && PartialAllignmentMove != FVector(0.0f, 0.0f, 0.0f))
            {
                PartialAllignmentMove = PartialAllignmentMove.GetClampedToSize(0.8f, 1.0f) * AllignmentWeight;
            }
      
            //GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Red, FString::Printf(TEXT("PartialAllignment : (%f , %f, %f)"), PartialAllignmentMove.X, PartialAllignmentMove.Y, PartialAllignmentMove.Z));
        }
      
        //calculating cohision
        if (CohisionBool)
        {
            PartialCohisionMove = CalculateCohisionMove(Agent, Context, Flock) * CohisionWeight;
      
            //clamp the cohision over weight
            if (PartialCohisionMove.SizeSquared() > CohisionWeight * CohisionWeight && PartialCohisionMove != FVector(0.0f, 0.0f, 0.0f))
            {
                PartialCohisionMove = PartialCohisionMove.GetClampedToSize(0.8f, 1.0f) * CohisionWeight;
            }
      
            //GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Red, FString::Printf(TEXT("PartialCohision : (%f , %f, %f)"), PartialCohisionMove.X, PartialCohisionMove.Y, PartialCohisionMove.Z));
        }
      
        //calculating avoidance
        if (AvoidanceBool)
        {
            PartialAvoidanceMove = CalculateAvoidanceMove(Agent, Context, Flock) * AvoidanceWeight;
      
            //clamp hte avoidance over weight
            if (PartialAvoidanceMove.SizeSquared() > AvoidanceWeight * AvoidanceWeight && PartialCohisionMove != FVector(0.0f, 0.0f, 0.0f))
            {
                PartialAvoidanceMove = PartialAvoidanceMove.GetClampedToSize(0.8f, 1.0f) * AvoidanceWeight;
            }
      
            //GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Red, FString::Printf(TEXT("PartialAvoidance : (%f , %f, %f)"), PartialAvoidanceMove.X, PartialAvoidanceMove.Y, PartialAvoidanceMove.Z));
        }
      
        //adding all behaviour movement
        Move = PartialCohisionMove + PartialAllignmentMove + PartialAvoidanceMove;
      
        //GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Orange, FString::Printf(TEXT("Move : (%f , %f, %f)"), Move.X, Move.Y, Move.Z));
      
        return Move;
    }
      
    FVector UCompositeBehaviour::CalculateCohisionMove(class AFlockAgent* Agent, TArray Context, class AFlock* Flock)
    {
        //if no Neighbour flock around, apply cohision randomly
        if (Context.Num() == 0)
            return FVector(FMath::RandRange(0.0f, 1000.0f), FMath::RandRange(0.0f, 1000.0f), FMath::RandRange(0.0f, 1000.0f));
      
        FVector CohisionMove = FVector(0.0f, 0.0f, 0.0f);
      
        //adding the location of all neighbour flock
        for (FTransform Item : Context)
        {
            CohisionMove += Item.GetLocation();
        }

        //average
        CohisionMove /= Context.Num();
      
        //remove self location
        CohisionMove -= Agent->GetActorLocation();

        CohisionMove = FMath::Lerp(Agent->GetActorForwardVector(), CohisionMove, AgentSmoothTime);
      
        //GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Cyan, FString::Printf(TEXT("Cohision : (%f , %f, %f)"), CohisionMove.X, CohisionMove.Y, CohisionMove.Z));
      
        return CohisionMove;
    }
      
    FVector UCompositeBehaviour::CalculateAllignmentMove(class AFlockAgent* Agent, TArray Context, class AFlock* Flock)
    {
        //if no neighbour flock around, apply alignment randomly
        if (Context.Num() == 0)
            return FVector(FMath::RandRange(0.0f, 360.0f), FMath::RandRange(0.0f, 360.0f), FMath::RandRange(0.0f, 360.0f));
      
        FVector AllignmentMove = FVector(0.0f, 0.0f, 0.0f);
      
        //adding the rotation of all neighbour flock
        for (FTransform Item : Context)
        {
            AllignmentMove += Item.GetRotation().GetForwardVector();
        }

        //average
        AllignmentMove /= Context.Num();
      
        //GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Cyan, FString::Printf(TEXT("Allignment : (%f , %f, %f)"), AllignmentMove.X, AllignmentMove.Y, AllignmentMove.Z));
      
        return AllignmentMove;
    }
      
    //similar to alignment, but this will create the rotation
    FRotator UCompositeBehaviour::CalculateRotate(class AFlockAgent* Agent, TArray Context, class AFlock* Flock)
    {
        FVector PartialAllignmentMove;
      
        if (AllignmentBool)
        {
            PartialAllignmentMove = CalculateAllignmentMove(Agent, Context, Flock) * AllignmentWeight;
      
            //clamp the alignment over weight
            if (PartialAllignmentMove.SizeSquared() > AllignmentWeight * AllignmentWeight && PartialAllignmentMove != FVector(0.0f, 0.0f, 0.0f))
            {
                PartialAllignmentMove = PartialAllignmentMove.GetClampedToSize(0.8f, 1.0f) * AllignmentWeight;
            }
      
          //GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Red, FString::Printf(TEXT("PartialAllignment : (%f , %f, %f)"), PartialAllignmentMove.X, PartialAllignmentMove.Y, PartialAllignmentMove.Z));
        }
      
        FVector Forward = PartialAllignmentMove - Flock->GetActorLocation();
        FRotator Rot = UKismetMathLibrary::MakeRotFromXZ(Forward, FVector::UpVector);
      
        return Rot;
    }
      
    FVector UCompositeBehaviour::CalculateAvoidanceMove(class AFlockAgent* Agent, TArray Context, class AFlock* Flock)
    {
        //if no neighbour flock around, apply avoidance randomly
        if (Context.Num() == 0)
            return FVector(FMath::RandRange(0.0f, 1000.0f), FMath::RandRange(0.0f, 1000.0f), FMath::RandRange(0.0f, 1000.0f));
      
        FVector AvoidanceMove = FVector(0.0f, 0.0f, 0.0f);
        int AgentsToAvoid = 0;
      
        //adding all flock location
        for (FTransform Item : Context)
        {
            if ((Item.GetLocation() - Agent->GetActorLocation()).SizeSquared() < Flock->SquareAvoidanceRadius)
            {
                AgentsToAvoid++;
                AvoidanceMove += (Agent->GetActorLocation() - Item.GetLocation());
            }
        }
      
        //average
        if (AgentsToAvoid > 0)
            AvoidanceMove /= AgentsToAvoid;
      
        //GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Cyan, FString::Printf(TEXT("Avoidance : (%f , %f, %f)"), AvoidanceMove.X, AvoidanceMove.Y, AvoidanceMove.Z));
      
        return AvoidanceMove;
    }
                        

Inspired by :