Skip to content
This repository has been archived by the owner on Oct 19, 2020. It is now read-only.

Coroutines

pixeltris edited this page Jun 2, 2019 · 9 revisions

USharp has coroutines which function in a similar way to Unity coroutines.

Coroutines can be used to execute code over several frames in a safe manner. Each coroutine belongs to a UObject, when that UObject is destroyed any coroutines associated with that UObject will also be destroyed. Additionally when the game is paused whilst playing in the editor, all coroutines timers will be paused (if waiting on 'X' frames or game time).

In the following example an AStaticMeshActor is spawned on BeginPlay with a simple box mesh and a dynamic material. Every second the material color is changed.

It's advisable to start coroutines in BeginPlay (or later). Starting a coroutine in the Initialize function will start coroutines on many undesirable objects created internally by UE4.

[UClass]
class ACoroutineTest : AActor
{
    private Coroutine myCoroutine;

    // Note that this isn't tagged as a UProperty. This reference to the actor wont be visible to UE4 in any way. If for some reason
    // this actor were destroyed by UE4, our reference here would be very unsafe! As we know that this actor will exist in the world
    // for the duration of the game, it should be safe enough to hold a reference to the actor in this way.
    private AStaticMeshActor spawnedMeshActor;

    [UProperty, EditAnywhere, BlueprintReadWrite]
    public UStaticMeshComponent Mesh { get; set; }

    public override void Initialize(FObjectInitializer initializer)
    {
        base.Initialize(initializer);

        UStaticMesh cubeMesh = ConstructorHelpers.FObjectFinder<UStaticMesh>.Find("StaticMesh'/Engine/BasicShapes/Cube.Cube'");
        UMaterial basicShapeMaterial = ConstructorHelpers.FObjectFinder<UMaterial>.Find("Material'/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial'");
        if (cubeMesh != null && basicShapeMaterial != null)
        {
            Mesh = initializer.CreateDefaultSubobject<UStaticMeshComponent>(this, (FName)"Mesh");
            Mesh.SetStaticMesh(cubeMesh);
            Mesh.SetMaterial(0, basicShapeMaterial);// Make sure to call SetMaterial on the UStaticMeshComponent (not the UStaticMesh!)
            RootComponent = Mesh;
        }
    }

    protected override void BeginPlay()
    {
        base.BeginPlay();

        // This gives an example of dynamically spawning an actor into the world, setting the mesh, and creating a UMaterialInstanceDynamic
        // and then setting the "Color" parameter of that material

        UStaticMesh cubeMesh = ConstructorHelpers.FObjectFinder<UStaticMesh>.Find("StaticMesh'/Engine/BasicShapes/Cube.Cube'");
        UMaterial basicShapeMaterial = ConstructorHelpers.FObjectFinder<UMaterial>.Find("Material'/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial'");
        if (cubeMesh == null || basicShapeMaterial == null)
        {
            return;
        }

        // Place the new actor on top of our cube mesh
        FVector location = Mesh.GetWorldLocation();
        int padding = 20;
        FVector min, max;
        Mesh.GetLocalBounds(out min, out max);
        location.Z += (Math.Abs(min.Z) + Math.Abs(max.Z)) + padding;

        AStaticMeshActor meshActor = World.SpawnActor<AStaticMeshActor>(location, default(FRotator));
        // This could be written using the UClass.GetClass<T> syntax (which is what the generic version calls)
        //AStaticMeshActor meshActor = World.SpawnActor(UClass.GetClass<AStaticMeshActor>(), default(FVector), default(FRotator)) as AStaticMeshActor;

        // Allow the mesh to be movable (though we aren't ever moving it via code!)
        meshActor.SetMobility(EComponentMobility.Movable);

        meshActor.StaticMeshComponent.SetStaticMesh(cubeMesh);

        UMaterialInstanceDynamic dynamicMaterial = UMaterialInstanceDynamic.Create(basicShapeMaterial, this);
        // This could also be done using the UStaticMeshComponent.CreateDynamicMaterialInstance() function
        //UMaterialInstanceDynamic dynamicMaterial = meshActor.StaticMeshComponent.CreateDynamicMaterialInstance(0, basicShapeMaterial, FName.None);

        meshActor.StaticMeshComponent.SetMaterial(0, dynamicMaterial);

        // Start a coroutine which will change the color of the material every 2 seconds (using the "Color" parameter of the material)
        // - Couroutines don't exist in UE4, they have been adapted from Unity and as such have a very similar API.
        // - Storing the Coroutine object isn't required, but may be useful for additional control over the coroutine.
        // - Coroutine objects can be pooled. Use StartCoroutine(MyCoroutine(), false) to disable pooling.
        myCoroutine = StartCoroutine(this, ChangeColor(dynamicMaterial));

        spawnedMeshActor = meshActor;
    }

    private System.Collections.IEnumerator ChangeColor(UMaterialInstanceDynamic dynamicMaterial)
    {
        // The coroutine will pause when the game is paused, and will be automatically removed when the actor is destroyed (when the game ends)

        // NOTE:
        // dynamicMaterial is only kept alive from UE4 garbage collection because it's on the actor mesh. If for some reason the material
        // were removed from the actor mesh, then UE4 would mark the material for garbage collection. Any further access via C# could result
        // in invalid memory being accessed. To keep a UObject alive forever (if not referenced by a property) you can call AddToRoot or set IsRooted
        while (true)
        {
            const string paramName = "Color";
            dynamicMaterial.SetVectorParameterValue((FName)paramName, FLinearColor.Blue);
            yield return Coroutine.WaitForSeconds(2);
            dynamicMaterial.SetVectorParameterValue((FName)paramName, FLinearColor.Red);
            yield return Coroutine.WaitForSeconds(2);
            dynamicMaterial.SetVectorParameterValue((FName)paramName, FLinearColor.Green);
            yield return Coroutine.WaitForSeconds(2);
            dynamicMaterial.SetVectorParameterValue((FName)paramName, FLinearColor.Yellow);
            yield return Coroutine.WaitForSeconds(2);
        }
    }
}
Clone this wiki locally