AI Controlled Butterfly - C++ tutorial

From Epic Wiki
Jump to: navigation, search

Template:Rating

Original Author: LeszekS

Overview

I am very new to Unreal engine, started with Blueprints first and now doing in C++. This is my first individual project inspired by flying butterflies in Epic's Blueprints tutorial ( can be found under "Learn" section of Epic Games Launcher ). I learned the butterfly AI logic from that tutorial and did it my way in C++. Also assets I used were migrated from that project. I think this tutorial can be helpful for all C++ programmers starting with Unreal.

Here is how the final work looks like ...

"AI Controlled Butterfly"

... and some quick recording.

<youtube>https://youtu.be/cT5CrV4VAxE</youtube>

What can you learn?

You can learn following Unreal C++ programming topics from this tutorial:

  1. Create actor's components and build their hierarchy. Load mesh and assign it to component.
  2. Create and load float curve asset into the program.
  3. Use of FTimeline following float curve to animate meshes.
  4. Move the object changing its location and rotation. Very easy vector and rotatory mathematics used for that.
  5. Checking for collision with the line tracing.
  6. Using random ranges.
  7. Creating simple state machine to implement AI logic.
  8. Debugging with the help of HUD Message or Output Log.

Requirements

You need to know basics of Unreal: C++ project creation, C++ class creation, using Content Browser and create assets, compile C++ project, basic scene set up, analyse C++ code. I will not guide you through details of the source code but it is well commented to help you understand its logic.

Step by step

Here is a quick guide to reproduce my project:

  1. Create new C++ project in Unreal Project Browser. Use "Basic Code" template, no need for Starter Content.
  2. Download Blueprints project from Epic Games Launcher/Learn.
  3. Open Blueprints project, in Content Browser go to Content/Assets/Meshes folder. Migrate following assets: S_Butterfly_Body, S_Butterfly_Left_Wing, S_Butterfly_Right_Wing to your just created C++ project folder. As a target for migration select Content folder of your project.
  4. Open your C++ project in Unreal. In Content Browser create 3 Curve Float assets ( right click then Miscellaneous/Curve and then select CurveFloat ):
    • CF_ButterflyFlight with key points: ( 0, 1 ), ( 0.2, -1 ), ( 0.5, 1 )AIControledButterfly02.png
    • CF_ButterflyLanding with key points: ( 0, 0 ), ( 1.5, 1 )AIControledButterfly03.png
    • CF_ButterflyResting with key points: ( 0, 0.1 ), ( 0.66, 0.79 ), ( 1.65, 0.95 ), ( 2.5, 0.23 ), ( 4.77, 0.18 ), ( 5.75, 0.78 ), ( 8.15, 0.92 ), ( 9.2, 0.1 ), ( 10, 0.35 )AIControledButterfly04.png
  5. Create new C++ class using Actor as the parent and naming it ButterflyActor. Compile your C++ project.
  6. You can now replace your ButterflyActor.cpp and .h files with my files. Recompile again.
  7. You can either drag C++ ButterFlyActor to your scene or create Blueprint using ButterflyActor as a parent and even change its logic there. While ButterflyActor object is selected in the Editor you can change TargetAttractor, MaxFlightRange, GroundLevel parameters found under Butterfly category in Details panel.
  8. Enjoy the fly!

AI logic debrief

I hope my comments in source code are clear enough to help you understand it. Here is brief logic description:

  1. The first route is toward TargetAttractor location. Then target is exchanged with random location but not farther then MaxFlightRange from TargetAttractor.
  2. Flying state: - sets random flight time on the beginning; - every tick: changes location and rotation of the root component following move logic; checks for collision, if hit found then switch to Landing Approach state; if approaches close enough to the target then selects randomly new target; checks a flight time and if finished then targets to GroundLevel; - with use of time line controls also swing of wings and the body.
  3. Landing Approach state: - using new time line changes location and rotation of the root component different way than during Flying: - continues to swing wings but 2.5 time faster now; - if landing time line is not playing any more then ends this state.
  4. Resting state: - sets random time of resting; - plays wings swing time line with the different curve float; - if time of resting is up then ends this state and triggers Lift Off.
  5. Lift Off state: - sets the time of Lift Off and triggers fast wings swing; - smoothly change just location of the root component; - when the lift off time is up then ends current state and triggers Flying.

Source Code

Here are ButterflyActor.h and ButterflyActor.cpp files: <syntaxhighlight lang="cpp"> // Fill out your copyright notice in the Description page of Project Settings.

  1. pragma once
  1. include "CoreMinimal.h"
  2. include "GameFramework/Actor.h"
  3. include "ButterflyActor.generated.h"


UCLASS() class BUTTERFLYPROJECT_API AButterflyActor : public AActor { GENERATED_BODY()

public: // Sets default values for this actor's properties AButterflyActor();

protected: // Called when the game starts or when spawned virtual void BeginPlay() override;

public: // Called every frame virtual void Tick( float DeltaTime ) override; // called before garbage collection virtual void BeginDestroy();

  1. if WITH_EDITOR

// this is to update TargetAttractor parameter after it changed in Editor virtual void PostEditChangeProperty( FPropertyChangedEvent& PropertyChangeEvent );

  1. endif

// components created in that class UPROPERTY( VisibleDefaultsOnly, Category = Butterfly ) class UArrowComponent* ButterflyRoot;

UPROPERTY( VisibleDefaultsOnly, Category = Butterfly ) class UStaticMeshComponent* Body;

UPROPERTY( VisibleDefaultsOnly, Category = Butterfly ) class UStaticMeshComponent* LeftWing;

UPROPERTY( VisibleDefaultsOnly, Category = Butterfly ) class UStaticMeshComponent* RightWing;

UPROPERTY( VisibleDefaultsOnly, BlueprintReadOnly, Category = Butterfly ) class UArrowComponent* FlightTarget; // base location to find new random target UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = Butterfly ) FVector TargetAttractor; // max range value from TargetAttractor UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = Butterfly ) float MaxFlightRange; // Z level for forced landing UPROPERTY( EditAnywhere, BlueprintReadWrite, Category = Butterfly ) float GroundLevel;

protected: // state machine states enum ButterflyStateEnum { DoNothing, Resting, LiftOff, Flying, LandingApproach, }; // stores state machine current status ButterflyStateEnum ButterflyState; // check for collisions during the flight void CheckCollision(); // offsets location during the flight void FlyingMoveLocation( float DeltaTime ); // chnage rotation during hte flight void FlyingMoveRotation( float DeltaTime ); // randomly selects new target void ChooseNewTarget(); // rotate the wings, if Reset is true then positions them in default rotation void RotateWings( float Value, bool Reset = false ); // moves the body void RotateBody( float Value, bool Reset = false ); // below methos service states of state machine void LiftOffStart(); void LiftOffEnd();

void FlyingStart(); UFUNCTION() void FlyingContinues( float Value ); // method passed to Timeline processing has to be declared with UFUNCTION()!!! void FlyingEnd();

void LandingApproachStart(); UFUNCTION() void LandingApproachContinues( float Value ); // method passed to Timeline processing has to be declared with UFUNCTION()!!! void LandingApproachEnd();

void RestingStart(); UFUNCTION() void RestingContinues( float Value ); // method passed to Timeline processing has to be declared with UFUNCTION()!!! void RestingEnd();

// below variables are made available to the Blueprint logic UPROPERTY( BlueprintReadWrite ) float ForwardSpeed; UPROPERTY( BlueprintReadWrite ) float UpwardSpeed; UPROPERTY( BlueprintReadWrite ) FVector CurrentTargetLocation;

// internal backup variable FVector CurrentTargetLocationBackup; // landing locations FVector LandingApproachStartLocation; FRotator LandingApproachStartRotation; FVector LandingApproachEndLocation; FVector LandingApproachEndNormal; // landing support locations FVector LandingLocationSlightlyBelow; FVector LandingLocationSlightlyAbove; // timers counting seconds till next event float SecondsTillNextSwing; float SecondsTillLiftOff; float SecondsTillLiftOffEnd; float SecondsTillNextLanding; // setups struct fields and binds method void MaintainTimeLine( struct FTimeline* TimeLine, class UCurveFloat* CurveFloat, const FName& MethodName, bool Looping = false ); //pointers to Timeline structures struct FTimeline* FTLButterflyFlight; struct FTimeline* FTLButterflyLanding; struct FTimeline* FTLButterflyResting; // load mesh asset static void LoadMeshAsset( UStaticMeshComponent* MeshComponent, const TCHAR* AssetPath ); // load curve float asset static UCurveFloat* LoadCurveFloatAsset( const TCHAR* AssetPath );

}; </syntaxhighlight>


<syntaxhighlight lang="cpp"> // Fill out your copyright notice in the Description page of Project Settings.

  1. include "ButterflyActor.h"
  1. include "Components/ArrowComponent.h"
  2. include "Kismet/KismetMathLibrary.h"
  3. include "Components/TimelineComponent.h"
  4. include "Math/UnrealMathUtility.h"
  5. include "DrawDebugHelpers.h"
  6. include "ConstructorHelpers.h"


// prints message to the screen with selected color

  1. define HUDMessage(color,text) if( GEngine ) GEngine->AddOnScreenDebugMessage( -1, 5.0f, color, text );


// those variables store resources, which should be loaded only once static UCurveFloat* CFButterflyFlight; static UCurveFloat* CFButterflyLanding; static UCurveFloat* CFButterflyResting;


// Sets default values AButterflyActor::AButterflyActor() : ForwardSpeed( 100.0f ), UpwardSpeed( 1.0f ), MaxFlightRange( 400.0f ), GroundLevel( 0 ), ButterflyState( DoNothing ), SecondsTillNextSwing( 0.0f ), SecondsTillLiftOff( 32.0f ), SecondsTillLiftOffEnd( 1.8f ), SecondsTillNextLanding( 9 ) { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true;

// create components hierachy // start from the root, which is starting position arrow component ButterflyRoot = CreateDefaultSubobject<UArrowComponent>( TEXT( "ButterflyRoot" ) ); ButterflyRoot->SetArrowColor( FLinearColor( FColor::Green ) ); ButterflyRoot->bEditableWhenInherited = true; ButterflyRoot->SetRelativeScale3D( FVector( 4.0f, 4.0f, 4.0f ) ); // make it a root component RootComponent = ButterflyRoot; // buterfly body mesh component now Body = CreateDefaultSubobject<UStaticMeshComponent>( TEXT( "Body" ) ); Body->SetupAttachment( RootComponent ); LoadMeshAsset( Body, TEXT( "/Game/Assets/Meshes/S_Butterfly_Body.S_Butterfly_Body" ) ); // left wing mesh component LeftWing = CreateDefaultSubobject<UStaticMeshComponent>( TEXT( "LeftWing" ) ); LeftWing->SetupAttachment( Body ); LoadMeshAsset( LeftWing, TEXT( "/Game/Assets/Meshes/S_Butterfly_Left_Wing.S_Butterfly_Left_Wing" ) ); // right wing mesh component RightWing = CreateDefaultSubobject<UStaticMeshComponent>( TEXT( "RightWing" ) ); RightWing->SetupAttachment( Body ); LoadMeshAsset( RightWing, TEXT( "/Game/Assets/Meshes/S_Butterfly_Right_Wing.S_Butterfly_Right_Wing" ) ); // target position arrow component FlightTarget = CreateDefaultSubobject<UArrowComponent>( TEXT( "FlightTarget" ) ); FlightTarget->SetupAttachment( RootComponent ); // some default target position FlightTarget->SetRelativeLocation( FVector( 40.0f, 20.0f, 10.0f ) );

// load ButterflyFlight curve float if ( CFButterflyFlight == nullptr ) CFButterflyFlight = LoadCurveFloatAsset( TEXT( "/Game/Assets_Mine/CF_ButterflyFlight" ) ); // load butterfly landing curve float if ( CFButterflyLanding == nullptr ) CFButterflyLanding = LoadCurveFloatAsset( TEXT( "/Game/Assets_Mine/CF_ButterflyLanding" ) ); // load butterfly resting curve float if ( CFButterflyResting == nullptr ) CFButterflyResting = /*UMyStaticLibrary::*/LoadCurveFloatAsset( TEXT( "/Game/Assets_Mine/CF_ButterflyResting" ) );

// create FTimeLine structures so can be used during the Play FTLButterflyFlight = new FTimeline; FTLButterflyLanding = new FTimeline; FTLButterflyResting = new FTimeline; }

void AButterflyActor::BeginDestroy() { //UE_LOG( LogTemp, Warning, TEXT("In AButterflyActor::BeginDestroy()") ); // clean up FTimeline structures delete FTLButterflyFlight; delete FTLButterflyLanding; delete FTLButterflyResting; // must call for the super class!!! Super::BeginDestroy(); }

// Called when the game starts or when spawned void AButterflyActor::BeginPlay() { Super::BeginPlay(); // initial target location CurrentTargetLocation = TargetAttractor;

// setup butterfly flight timeline if ( CFButterflyFlight ) { MaintainTimeLine( FTLButterflyFlight, CFButterflyFlight, FName( "FlyingContinues" ), true ); } // setup butterfly landing timeline if ( CFButterflyLanding ) { MaintainTimeLine( FTLButterflyLanding, CFButterflyLanding, FName( "LandingApproachContinues" ) ); } // setup butterfly resting timeline if ( CFButterflyResting ) { MaintainTimeLine( FTLButterflyResting, CFButterflyResting, FName( "RestingContinues" ) ); }

//FlyingStart(); // start from resting state now RestingStart(); }

// Called every frame void AButterflyActor::Tick( float DeltaTime ) { Super::Tick( DeltaTime ); // let the update of timelines FTLButterflyFlight->TickTimeline( DeltaTime ); FTLButterflyLanding->TickTimeline( DeltaTime ); FTLButterflyResting->TickTimeline( DeltaTime ); // act according to the current state switch ( ButterflyState ) { case LiftOff: // count seconds till end of LiftOff phase if ( SecondsTillLiftOffEnd <= 0 ) LiftOffEnd(); else SecondsTillLiftOffEnd -= DeltaTime; // just move the butterfly location, no rotation FlyingMoveLocation( DeltaTime ); break;

case Flying: // move the buterfly location FlyingMoveLocation( DeltaTime ); // and rotate it accordingly FlyingMoveRotation( DeltaTime ); // in case of collision start LandApproach state CheckCollision(); // check if close to current target if ( ( CurrentTargetLocation - ButterflyRoot->GetComponentLocation() ).Size() < 60 ) ChooseNewTarget(); // count seconds till landing, so butterfly is not flying to long if ( SecondsTillNextLanding <= 0 ) CurrentTargetLocation.Z = GroundLevel; else SecondsTillNextLanding -= DeltaTime;

break;

case LandingApproach: // if landing timeline is not playing then finish this state if ( !FTLButterflyLanding->IsPlaying() ) { //UE_LOG(LogTemp, Warning, TEXT("LandingApproachEnd in Tick()") ); LandingApproachEnd(); }

break; case Resting: // if resting timeline is not playing then ... if ( !FTLButterflyResting->IsPlaying() ) if ( SecondsTillNextSwing > 0 ) // ... count seconds ... SecondsTillNextSwing -= DeltaTime; else { // ... or paly next swing if it is time for that FTLButterflyResting->PlayFromStart(); // assign random value till next Swing SecondsTillNextSwing = FMath::FRandRange( 5.0f, 10.0f ); } // count seconds till LiftOff ... SecondsTillLiftOff -= DeltaTime; if ( SecondsTillLiftOff <= 0 ) { // ... finish this state and start LiftOff RestingEnd(); LiftOffStart(); }

break; default: break; } }

void AButterflyActor::FlyingMoveLocation( float DeltaTime ) { // calculate move offset in DeltaTime and sets root component FVector DeltaLocation( ForwardSpeed, 0.0f, UpwardSpeed ); DeltaLocation *= DeltaTime; ButterflyRoot->AddLocalOffset( DeltaLocation ); }

void AButterflyActor::FlyingMoveRotation( float DeltaTime ) { // calculate rotation change in DeltaTime and sets root component FRotator TargetRotation = UKismetMathLibrary::FindLookAtRotation( ButterflyRoot->GetComponentLocation(), CurrentTargetLocation ); FRotator NewDirectionRotation = FMath::RInterpTo( ButterflyRoot->GetComponentRotation(), TargetRotation, DeltaTime, 1.2 ); ButterflyRoot->SetWorldRotation( NewDirectionRotation ); }

void AButterflyActor::ChooseNewTarget() { float RandomHX = FMath::FRandRange( -0.7f*MaxFlightRange, MaxFlightRange ); float RandomHY = FMath::FRandRange( -0.7f*MaxFlightRange, MaxFlightRange ); float RandomV = FMath::FRandRange( -0.15f*MaxFlightRange, 0.5f*MaxFlightRange ); CurrentTargetLocation = TargetAttractor + FVector( RandomHX, RandomHY, RandomV ); //UE_LOG(LogTemp, Warning, TEXT("Approaching CurrentTarget Location")); }

void AButterflyActor::CheckCollision() { // get current location of the root component FVector ButterflyRootLocation = ButterflyRoot->GetComponentLocation(); // trace if any collision is in the front

  1. define TRACE_LENGTH 70

// forward vector is unit vector hence it is mulitplied, then slightly put down and finaly added to the current location FVector TraceEndLocation = ButterflyRootLocation + ( ( ButterflyRoot->GetForwardVector()*TRACE_LENGTH ) - FVector( 0.0f, 0.0f, 15.0f ) ); // you can see tracing vector below //DrawDebugLine(GetWorld(), ButterflyRootLocation, TraceEndLocation, FColor::Red, false, -1.0f, 0, 1.0f); // set parameters for tracing FHitResult Hit; FCollisionObjectQueryParams ObjectQueryParams( ECC_WorldStatic ); FCollisionQueryParams QueryParams( FName(), true, this ); // call tracing function bool Result = GetWorld()->LineTraceSingleByObjectType( Hit, ButterflyRootLocation, TraceEndLocation, ObjectQueryParams, QueryParams ); // if hit founf then ... if ( Result ) { //UE_LOG(LogTemp, Log, TEXT("-")); //UE_LOG( LogTemp, Warning, TEXT("Trace hit for %s into %s"), *GetName(), *Hit.Actor->GetName() ); //UE_LOG(LogTemp, Warning, TEXT("Location: %s ImpactPoint: %s"), *Hit.Location.ToString(), *Hit.ImpactPoint.ToString() ); //UE_LOG(LogTemp, Warning, TEXT("Normal: %s ImpactNormal: %s"), *Hit.Normal.ToString(), *Hit.ImpactNormal.ToString()); // store hit location in local variables LandingApproachEndLocation = Hit.Location; LandingApproachEndNormal = Hit.Normal; // ... start LandingApproach LandingApproachStart(); } }

void AButterflyActor::RotateWings( float Value, bool Reset ) { // neutral values for the mesh positions float XLeftWing = 0, XRightWing = 0;

if ( !Reset ) { // set mesh positions following Value XLeftWing = FMath::Lerp( 0.0f, 80.0f, Value ); XRightWing = FMath::Lerp( 0.0f, -80.0f, Value ); } // change mesh LeftWing->SetRelativeRotation( FRotator( 0.0f, 0.0f, XLeftWing ) ); RightWing->SetRelativeRotation( FRotator( 0.0f, 0.0f, XRightWing ) ); //UE_LOG( LogTemp, Warning, TEXT( "Value: %f XLeftWing: %f XRightWing: %f" ), Value, XLeftWing, XRightWing ); }

void AButterflyActor::RotateBody( float Value, bool Reset ) { // neutral values for the mesh positions float ZBody = 0, YBody = 0;

if ( !Reset ) { // set mesh positions following Value ZBody = FMath::Lerp( 0.5f, -0.5f, Value ); YBody = FMath::Lerp( 4.0f, -2.0f, Value ); } // change mesh Body->SetRelativeLocation( FVector( 0.0f, 0.0f, ZBody ) ); Body->SetRelativeRotation( FRotator( YBody, 0.0f, 0.0f ) ); }

void AButterflyActor::RestingStart() { // change state machine status ButterflyState = Resting; // play the timeline FTLButterflyResting->PlayFromStart(); // get random time till LiftOff SecondsTillLiftOff = FMath::FRandRange( 11.0f, 18.0f ); }

void AButterflyActor::RestingContinues( float Value ) { // animate wings in line with the Value value RotateWings( Value ); //UE_LOG(LogTemp, Warning, TEXT("RestingContinues")); }

void AButterflyActor::RestingEnd() { // stop the timeline FTLButterflyResting->Stop(); }

void AButterflyActor::FlyingStart() { // get random for till the landing SecondsTillNextLanding = FMath::RandRange( 9.0f, 18.0f ); // set velocity parameters ForwardSpeed = 100; UpwardSpeed = 0; // change state machine status ButterflyState = Flying; // .. and start timeline play FTLButterflyFlight->PlayFromStart(); FTLButterflyFlight->SetPlayRate( 1.0f ); }

void AButterflyActor::FlyingContinues( float Value ) { //UE_LOG( LogTemp, Warning, TEXT( "Inside FlyingContinues %f" ), Value ); // animate wings and body in line with the Value value RotateWings( Value ); RotateBody( Value ); }

void AButterflyActor::FlyingEnd() { // stop timeline FTLButterflyFlight->Stop(); // ... prepare for the next fly, this can be moved to FlyingStart but I wanted the first fly targeted TargetAttractor and not a random values ChooseNewTarget(); }

void AButterflyActor::LandingApproachStart() { // store local variables used later on LandingApproachStartLocation = ButterflyRoot->GetComponentLocation(); LandingApproachStartRotation = ButterflyRoot->GetComponentRotation(); // calculate landing support locations LandingLocationSlightlyBelow = LandingApproachEndLocation + ( LandingApproachEndNormal * -5 ); LandingLocationSlightlyAbove = LandingApproachEndLocation + ( LandingApproachEndNormal * 5 ); // flight procedure continues with 2.5 faster wing rotation but flying navigation discontinues FTLButterflyFlight->SetPlayRate( 2.5 ); // change state machine status ButterflyState = LandingApproach; // trigger landing timeline to navigate landing FTLButterflyLanding->PlayFromStart(); }

void AButterflyActor::LandingApproachContinues( float Value ) { // handle location change for LandingApproach FVector NewLocation = FMath::Lerp( LandingApproachStartLocation, LandingLocationSlightlyAbove, Value ); ButterflyRoot->SetWorldLocation( NewLocation ); // ... and now rotation excepet last 0.01 if ( Value < 0.99f ) { // find the target rotation ... FRotator TargetRotation = UKismetMathLibrary::FindLookAtRotation( ButterflyRoot->GetComponentLocation(), LandingLocationSlightlyBelow ); // ... and pitch it 90 degree TargetRotation += FRotator( 90.0f, 0.0f, 0.0f ); // lerp (linear interpolation) it so the move is smooth FRotator NewRotation = FMath::Lerp( LandingApproachStartRotation, TargetRotation, Value ); ButterflyRoot->SetWorldRotation( NewRotation ); //UE_LOG(LogTemp, Warning, TEXT("TargetRotation: %s NewRotation: %s"), *TargetRotation.ToString(), *NewRotation.ToString()); } }

void AButterflyActor::LandingApproachEnd() { // finish Flying state FlyingEnd(); FTLButterflyLanding->Stop(); // ... and start Resting RestingStart(); // rotate wings so they stay sligthly up RotateWings( 0.45f ); // .. and reset body to original position RotateBody( 0.0f, true ); //UE_LOG( LogTemp, Warning, TEXT("LandingApproachEnd") ); //UE_LOG(LogTemp, Warning, TEXT("End Location: %s Rotaion: %s"), *ButterflyRoot->GetComponentLocation().ToString(), *ButterflyRoot->GetComponentRotation().ToString()); }

void AButterflyActor::LiftOffStart() { // set velocity differnt to Flying state ForwardSpeed = 15; UpwardSpeed = 30; // temprarly chanbe current target location CurrentTargetLocationBackup = CurrentTargetLocation; CurrentTargetLocation = LandingApproachStartLocation; // set time till LiftOff end SecondsTillLiftOffEnd = 1.2f; // set new state machine status !!! watchout for code racing this must be after FTLButterflyLanding->PlayFromStart(); ButterflyState = LiftOff; // play Flying timeline FTLButterflyFlight->PlayFromStart(); FTLButterflyFlight->SetPlayRate( 2.5f ); }

void AButterflyActor::LiftOffEnd() { // restore current target location CurrentTargetLocation = CurrentTargetLocationBackup; // clean up landing support variables LandingApproachStartLocation = LandingApproachEndLocation = LandingApproachEndNormal = LandingLocationSlightlyBelow = LandingLocationSlightlyAbove = FVector( ForceInitToZero ); LandingApproachStartRotation = FRotator( ForceInitToZero ); // start Flying state FlyingStart(); }

void AButterflyActor::LoadMeshAsset( UStaticMeshComponent * MeshComponent, const TCHAR * AssetPath ) { ConstructorHelpers::FObjectFinder<UStaticMesh> MeshAsset( AssetPath );

if ( MeshAsset.Succeeded() ) { MeshComponent->SetStaticMesh( MeshAsset.Object ); } }

UCurveFloat * AButterflyActor::LoadCurveFloatAsset( const TCHAR * AssetPath ) { ConstructorHelpers::FObjectFinder<UCurveFloat> CurveAsset( AssetPath ); if ( CurveAsset.Succeeded() ) { return CurveAsset.Object; }

return nullptr; }

void AButterflyActor::MaintainTimeLine( FTimeline * TimeLine, UCurveFloat* CurveFloat, const FName & MethodName, bool Looping ) { FOnTimelineFloat ProgressionFunction; ProgressionFunction.BindUFunction( this, MethodName );

TimeLine->AddInterpFloat( CurveFloat, ProgressionFunction ); TimeLine->SetLooping( Looping ); }

  1. if WITH_EDITOR

void AButterflyActor::PostEditChangeProperty( FPropertyChangedEvent& PropertyChangeEvent ) { // this is very simple method of handling that, full example can be found here: // https://docs.unrealengine.com/latest/INT/API/Runtime/Engine/GameFramework/AActor/PostEditChangeProperty/

// HUDMessage(FColor::Red, TEXT("Property changed")) FlightTarget->SetWorldLocation( TargetAttractor );

Super::PostEditChangeProperty( PropertyChangeEvent ); }

  1. endif

</syntaxhighlight>

Next Steps

There are still some functionalities that can be added, e.g. wings color set in object's Details panel, audio effects, collision sphere capsule encapsulating butterfly so 2 actors do not overlap each other or body and wings are not hiding in the ground. That is the work for next tutorial.


Thank you Epic for very inspiring and helpful tutorials!