Thomas Winged

hero image

Wheel of Fortune - Wheel

This post is a part of a more extensive project entry. If you are interested in it, make sure to check it out and click >> here<< !

What would be the game Wheel of Fortune without the actual wheel? For sure, it would be, (un)fortunately, awkward to take part in such a show. In this entry, I will describe my approach to creating this pawn, representing most of the steps I took and the problems I encountered.

Base construction

The most important parts of the wheel pawn are wedges. Without them, there would be no fun. They differ in size, color, and reward/punishment. Some wedges are more expansive, while others are thinner. Usually, they come in the size of 1 to 3 smaller zones. It's best visible in the photo below.


Various sizes of wedges

To let the game designer construct the wheel using appropriate wedges, I created a structure describing each section. Primary properties are count of zones that this wedge occupies and action that needs to be resolved when the wheel lands on that wedge. I also included radial span inside the structure below, expressed in the degree unit. I bundled it within this structure despite making it editable only in C++ because I thought it would be nice to debug radial values directly in the editor. Besides, this span will be essential for us to determine the result of wheel rotation, and I will calculate inside the AWheel::OnConstruction method taking into account all other specified sections.

CommonGameData.h - structure describing the wheel's section
struct FWheelSection

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Wheel")
    int ZonesCount = 3;

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Wheel")
    FVector2D RadialSpan;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Wheel")
    TSubclassOf<UWedge> WedgeType;

Adding an editable array property to the AWheel actor allows me to specify wheel elements inside the editor and read desired content of the wheel. So let's create a child Blueprint class of the wheel and fill in the data.

Wheel.h - array of wedges popullable in the editor
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Wheel")
TArray<FWheelSection> Wedges;


The populated array of wheel sections in the editor

So now, after picking some wedges, I can calculate the radial span of each of them. Using this information, I will construct the actual wheel visualization. First, let's count the number of zones by iterating over each FWheelSection element of the array, getting TotalZonesCount, and then using this value to calculate in/out radial span.

Wheel.cpp - calculating the radial span of each wedge
void AWheel::UpdateWedgesRadialSpan(int TotalZonesCount)
    int WedgeStartZoneIndex = 0;
    int WedgeEndZoneIndex = 0;
    for (int i = 0; i < Wedges.Num(); i++)
        WedgeEndZoneIndex += Wedges[i].ZonesCount;
        Wedges[i].RadialSpan = FVector2D(
            WedgeStartZoneIndex * 360.f / TotalZonesCount,
            WedgeEndZoneIndex * 360.f / TotalZonesCount
        WedgeStartZoneIndex = WedgeEndZoneIndex;

Now we got all information needed to construct a prototype wheel. Let's dive into the editor for now.

Visual prototype

My idea of debug representation of the wheel is to construct it out of instanced mesh wedges. Each wedge can be a simple plane with applied material that masks it into a wedge-like shape.

First, we need a procedurally generated radial slice using shaders. For this, I created a material function called MF_RadialSlice. In its heart lies a native VectorToRadialValue node which creates a circular gradient. You can read more about it here.


Generating the radial gradient using shaders

The next step is to take this gradient, add a desired radial out value, and cap it using Floor to get a nice solid black and white mask. We receive a nice radial slice by doing the same with radial in value and then subtracting results from each other.


Subtracting two floored radial gradients gives us a radial slice

Let's use it as a mask in our prototype wedge material. Because I want this material to be controllable per mesh instance, I use PerInstanceCustomData in combination with VertexInterpolator to pass values. I also added control for wedge emissiveness, as I would like the wedge to glow when the wheel stops on it. And, of course, each wedge needs to have a unique color. All parameters can be controlled per material or mesh instance basis.


A prototype of the wedge material

Now we can move on to creating actual instances of wedges. I will use InstancedStaticMeshComponent for this task. So add it to the actor and assign a plane mesh and our prototype wedge material.


Using InstancedStaticMeshComponent for wedges

Important part: make sure to set count of custom data floats that we will pass to the material's PerInstanceCustomData via the SetCustomDataValue node. I often forget about this step, and later I spend a long time figuring out what has gone wrong.



In the construction script, let's iterate over the Wedges array and construct instances. On the top, I saved some local variables for later use, as I prefer not to drag long cables across the node graph.


Constructing mesh instances

For each instance pass the radial span and give it a touch of gorgeous random color. As you remember, this data will be read by PerInstanceCustomData nodes inside our debug wedge material.


Passing data to mesh instances

Any color by itself will not let me differentiate wedges, so I will also add the names of their actions using TextRenderComponents. I did some simple math translating the text in 3D relatively to the wedge radial span and centered it to the wedge surface.


Adding debug text components

As a cherry on top of a pie, I placed an arrow to visualize the current rotation of the wheel. And now, wait for it... We got our wheel prototype! We can compose it using any wedges we want, and the mesh instances will be constructed accordingly. Cool!


Wedge can be constructed from various wedges inside the editor


The wheel object can be perceived purely as a pawn. Disregarding all its visual parts, as this game could be a text game too, there are two things we are interested in. The first one is controlling its action - making it a spin. The second one is reading its output - on which wedge it stopped. These will be our sockets for connecting its mechanism to a larger ecosystem. As usual, in the case of pawns, the player controller will execute its action and read its output. And so, I implemented spinning the wheel as a public AWheel::Spin method, while sending the output through dynamic multicast delegate AWheel::WheelLanded as I don't want to couple this object with any concrete implementation of player controller, which would result in cyclic dependency error.

Wheel.h - public methods of the wheel allowing connection to a larger ecosystem


    UFUNCTION(BlueprintCallable, Category="Wheel")
    void Spin();

    FWheelLanded WheelLanded;

CommonGameData.h - declaration of the delegate informing about the result of the wheel spin
// Informs about at which section the wheel stopped and about radial coordinates of section start and end for visual purposes
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FWheelLanded, TSubclassOf<UWedge>, WheelLandedOn, FVector2D, SectionRadialSpan);

Let's give it a spin!

To visually define the style of wheel rotation, I decided to use timeline (UTimelineComponent) in combination with interpolation along a curve (UCurveFloat). In the editor, I created a couple of movement variations. Then I chose the most dynamic and pleasing to the eye, assigning it to the editable protected property SpinSpeedCurve. For the record, I could use all of them simultaneously, either randomizing the curve from the list or substituting it after landing some wedge, just for fun. Additional parameters to control the animation of the wheel are animation duration, randomization, and maximum angle.

AWheel.h - declaring animation properties of the wheel class
    UPROPERTY(EditAnywhere, Category="Wheel")
    float SpinStrength = 720.0;

    UPROPERTY(EditAnywhere, Category="Wheel")
    float SpinStrengthVariance = 180;

    UPROPERTY(EditAnywhere, Category="Wheel")
    float SpinDuration = 12;

    UPROPERTY(EditAnywhere, Category="Wheel")
    UCurveFloat* SpinSpeedCurve;


Different styles of wheel rotation animation
AWheel::AWheel() - constructing the timeline component
SpinTimeline = CreateDefaultSubobject<UTimelineComponent>(TEXT("SpinTimeline"));

Since the timeline component reports the progress of the animation, I also need three global variables to store and control the current state of the rotation and use them later during rotation interpolation.

AWheel.h - global variables for tracking rotation of the wheel
    float InitialSpinRotation = 0;
    float CurrentSpinRotation = 0;
    float TargetSpinRotation = 0;

Then in AWheel::BeginPlay, I bind timeline delegates, attaching the curve to the interpolation callback and linking the timeline termination signal.

Wheel.cpp - binding timeline to curve and functions
if (IsValid(SpinSpeedCurve))
    FOnTimelineFloat SpinTimelineCallback;
    SpinTimelineCallback.BindUFunction(this, FName("UpdateWheelRotation"));
    SpinTimeline->AddInterpFloat(SpinSpeedCurve, SpinTimelineCallback);

    FOnTimelineEvent SpinTimelineOnFinish;
    SpinTimelineOnFinish.BindUFunction(this, FName("FinishWheelRotation"));

The first one - delegate SpinTimelineCallback calls theUpdateWheelRotation method in which I modify the rotation of the wheel. I also broadcast the result of this operation for further use - it will let me make the wheel's arrow tick simulating a collision with protruding tubes defining the boundaries of each zone of the wheel.

Wheel.cpp - updating wheel's rotation
void AWheel::UpdateWheelRotation(float Progress)
    const float PreviousSpinRotation = CurrentSpinRotation;
    CurrentSpinRotation = InitialSpinRotation + (TargetSpinRotation - InitialSpinRotation) * Progress;

    const FRotator TickRotation = FRotator(0, -(CurrentSpinRotation - PreviousSpinRotation), 0);


The second delegate, which notifies the end of the timeline, calls the FinishWheelRotation method. Without going into details, which you can preview in the (not yet) provided source code, I determine the wedge on which the wheel stopped and broadcast this final information out into the world using the delegate created at the beginning.

AWheel - announcing a result of wheel spin
void AWheel::FinishWheelRotation()
    const FWheelSection Wedge = GetWedgeFromRotation(CurrentSpinRotation);

    UE_LOG(LogTemp, Warning, TEXT("%s >> Wheel landed on [%s]"), ANSI_TO_TCHAR(__FUNCTION__), *Wedge.WedgeType->GetName());
    WheelLanded.Broadcast(Wedge.WedgeType, Wedge.RadialSpan);

Having the timeline part covered, let's finally give it a spin! In the most essential AWheel::Spin method that the player controller will use, I randomize the rotation on which the wheel will stop, set the rotation speed and play the timeline.

Note: If you are either a bad guy or a businessman, this is a place to implement some logic of faking the result ^ ^

AWheel.cpp - finally we can spin the wheel!
void AWheel::Spin()
    if (!SpinTimeline->IsPlaying())
        InitialSpinRotation = TargetSpinRotation;
        const float SpinStrengthRandomization = FMath::RandRange(-SpinStrengthVariance, +SpinStrengthVariance);
        TargetSpinRotation = InitialSpinRotation + SpinStrength + SpinStrengthRandomization;

        SpinTimeline->SetPlayRate((SpinStrength - SpinStrengthRandomization) / SpinStrength / SpinDuration);

        const FString PredictedWedge = GetWedgeFromRotation(TargetSpinRotation).WedgeType->GetName();
        UE_LOG(LogTemp, Warning, TEXT("%s >> Succesfully spinned the wheel, it will land on [%s] wedge"), ANSI_TO_TCHAR(__FUNCTION__), *PredictedWedge);

One last thing - as you can see in the video above, when the wheel stops, the wedge below the arrow glows for a moment. To achieve this effect, I used another timeline component. Again, without going into details, when the wheel stops, I determine the wedge index that the arrow points to. This index is also the same index of a mesh instance of this wedge. With this setup, I can pass the strength value of the glow effect via PerInstanceCustomData.


Making the drawn wedge glow


I already mentioned a couple of times how vital wedges are in the overall mechanism of the wheel pawn, but I still need to explain how they work under the hood.

Starting with some examples, each wedge has a more or less unique action that needs to be resolved. Some of them require to be resolved instantly, e.g., Bankrupt wedge, which interrupts the contestant's turn and clears out his pending reward. On the other hand, wedges like $1000 cash prize require guessing a consonant before gratifying the player with the juicy tip.

In the beginning, I decided to differentiate the action of each kind of wedge using Enum. I chose three distinct wedges and implemented their resolve mechanism inside Game Mode. So there was one significant function with a switch statement, executing various actions and checking some stuff. Then I expanded it many times until my class was filled with enormous switch statements, and I lost track of what I was doing. It took me a lot of time to debug after making small changes. It had to stop.

For the rescue came Command Pattern. To explain it briefly for those who are not familiar what it, I will quote a definition from Robert Nystrom's book:

"Encapsulate a request as an object, thereby letting users parameterize clients with different requests, queue or log requests, and support undoable operation."

In this case, it means encapsulating mechanism of processing each type of wedge action in its own smaller object. This way, handling the mechanism is easier, as I can pass it around in my code. And also, it's easier for our brain to assimilate and understand the outcome of that action. No more switch statements as each case is encapsulated in its own object. Let's take a look at the base implementation of the UWedge object:

UWedge.h - base of command pattern
#include "UObject/Object.h"
#include "WheelOfFortune/Core/Mode/StandardMode.h"

UCLASS(Abstract, Blueprintable, BlueprintType)
class WHEELOFFORTUNE_API UWedge : public UObject

    virtual void Resolve(AStandardMode* Mode, int GuessedLetters);

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Wheel Of Fortune|Wedge")
    bool bInstantResolve = false;

And so the abstract UWedge class has only two members that are common across all wedges - method for executing action of the effect, and boolean informing about whether the action should happen instantaneously, or should it be postponed until contestant guessed the letter.

Now let's look at the concrete implementation of Bankrupt action. Remember when I wrote about piles of cases inside the switch statement? Now we got this:

#include "Wedge.h"

class WHEELOFFORTUNE_API UWedge_Bankrupt : public UWedge


    virtual void Resolve(AStandardMode* Mode, int GuessedLetters) override;

#include "Wedge_Bankrupt.h"

    bInstantResolve = true;

void UWedge_Bankrupt::Resolve(AStandardMode* Mode, int GuessedLetters)

As simple as that - when the action is resolved, all it does is call the AStandardMode::HandleBankrupt method. And who resolves these actions? PlayerController receives information about the wedge when the wheel lands on it. Notice that some wedges require delayed resolving of action (waiting for an answer) while others should be resolved instantaneously (bankrupt), hence the bInstanceResolve bool.

AMainPlayerController.cpp - resolving wedge actions
void AMainPlayerController::OnWheelLanded(TSubclassOf<UWedge> WheelLandedOn, FVector2D SectionRadialSpan)
    CurrentWedge = NewObject<UWedge>(GetTransientPackage(), WheelLandedOn);

    if (CurrentWedge->bInstantResolve)
        State = NewObject<UControllerState_GuessConsonant>(this);

void AMainPlayerController::ResolveCurrentWedge(int LettersGuessed)
    if (IsValid(CurrentWedge))
        CurrentWedge->Resolve(GameMode, LettersGuessed);
        CurrentWedge = nullptr;

Let's take a look at another example of implementation of UWedge - this time cash prize wedge:

#include "Wedge.h"

class WHEELOFFORTUNE_API UWedge_Cash : public UWedge

    virtual void Resolve(AStandardMode* Mode, int GuessedLetters) override;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Wheel Of Fortune|Wedge")
    int Money = 0;

#include "Wedge_Cash.h"

void UWedge_Cash::Resolve(AStandardMode* Mode, int GuessedLetters)
    Mode->HandleCashWin(Money * GuessedLetters);

This way, I created a library that's easy to use, expand, or modify without the risk of breaking other wedge mechanics. New actions can be easily added when fresh wedges are introduced. By making these objects inherit from UObject, and because UWedge is BlueprintType, they can be easily assigned to an array that describes each section of the wheel, as I showed you earlier. What's even more remarkable is that a non-programming game designer can create new derivative actions inside the editor, creating new blueprints based on the UWedge parent class because it's Blueprintable.


Creating own wedge actions inside the editor

There is just one thing that unsettles my nerves. It's the fact that the UWedge is coupled with AStandardMode. I could create an interface for each wedge action type for complete decoupling and communicate with GameMode through them. But for now, I will leave it like this.

Improving visual look

The wheel is constructed, spinning, and giving correct results. In brief - it's working just fine. It's time to give it a new, better face. I am not a shader expert, so I followed the wheel layout currently visible on the wheel prototype and recreated it in Photoshop. I used Materialize software to generate PBR maps like normal, metalness, height.


PBR textures used for wheel

With this set of textures, the next step was to create a PBR material, which will be applied to a flat plane and completely replace the prototype's instanced wedge meshes.


Wheel's brand new PBR material... Juicy!

You may notice I used decreased MipBias for almost all textures. This is my simple trick for making textures of important objects placed far away sharper. I could not use it with the height map because it messes up with tesselation. Let me know if you know a better way to ensure the correct sharpness of textures.


On the left: MipBias 0; on the right: MipBias -1

Next step - adding protruding tubes, defining each zone of the wheel, and animating the arrow, so it ticks like it's colliding with the pipes. I could use some collision for this task, but meh, I'm not sure if it would work with a fast-spinning wheel, and it would cost a lot of calculations. So without further ado, let's create the tubes! I will again use InstancedStaticMeshComponent with a simple cylinder as a mesh and some lovely metal material.


Constructing protruding tubes

Using information about the count of wheel zones (from the Wedges array of the wheel, which the designer populated in the first place), I calculate the angle between them and interpolate arrow rotation based on modulo between wheel's current rotation and unit zone angle. I will provide a screenshot of my node graph without a mouthful explanation of the procedure. In case you wonder where input WheelRotation comes from - the execution of the TickArrow method is bound to the AWheel::WheelRotated delegate in the Event Graph.


Mechanism of ticking arrow

So now, when we spin with the wheel, this is what we get with arrow animation. It's not perfect, but it does not need to be. It's just a game, after all.

After tuning the look of the wheel by adding a fancy cube in the middle, this is the final comparison between the prototype and refreshed version of the wheel. Is it not beautiful?

Before and after redesigning wheel's visual
wheel_before.webp wheel_after.webp
Before and after redesigning wheel's visual


Creating this pawn was fun. Especially the backend part was a messy pile of enums at the beginning, and later, after implementing Command Pattern became an elegant solution. Anyway, this wheel is just one element of the whole game show. Make sure to check out the remaining parts of the entire project entry. Click >> here<< to go to the Table of Content of the project.

Links and files

Raw .psd project file of the wheel board: >>> LINK <<<