Neocortex
IntegrationsUnreal SDK

Quiz

Run a spoken quiz, game show or test in Unreal Engine 5 with a Neocortex AI character as the host, for one player or several, using the Quiz Agent and a question set.

Team plan

Quizzes need a Team plan and at least one question set.

UNeocortexQuizAgent lets one of your characters host a quiz, a game show or a spoken test. You write the questions in a question set on the web platform. Question order, retries, hints, judging and scoring happen on the server. Your game hands over what the player said and shows what comes back.

How a run works on the server is covered in Web API Quiz.

Setting up

  • Add a Neocortex Quiz Agent component to the host's actor
  • Pick the host with the Pick button next to Character ID and the set next to Question Set ID. Both lists come from your account.
  • Optional: set Question Count (0 uses the set's own count) and Question Order (SetDefault, InOrder or Shuffled). Begin On Start starts the quiz when play begins.

The host speaks in the character's voice. Turn off Speak Host Lines for a silent host. The voice plays on the agent's Audio Component, or on one created on the owner. For lip sync, set the host's Neocortex Face Animator to follow the quiz agent through its Quiz Agent property.

For a ready screen, UNeocortexQuizUI is a whole quiz show for one to four players: the host's lines and the answers as bubbles, tapped choices, "reply as" chips with each player's score, the question card, and Start, Abort, Resync, Hint, Pass and Pass Everyone.

UNeocortexQuizUI* Screen = CreateWidget<UNeocortexQuizUI>(PlayerController, UNeocortexQuizUI::StaticClass());
Screen->PlayerNames = { TEXT("Anna"), TEXT("Ben") };
Screen->SetQuiz(Quiz);
Screen->AddToViewport();

Running a quiz

QuizScene.cpp
void AQuizScene::BeginPlay()
{
    Super::BeginPlay();
    Quiz->OnHostLine.AddDynamic(this, &AQuizScene::ShowLine);
    Quiz->OnQuestionChanged.AddDynamic(this, &AQuizScene::ShowQuestion);
    Quiz->OnUpcomingQuestion.AddDynamic(this, &AQuizScene::PreloadScene);
    Quiz->OnLeaderboardChanged.AddDynamic(this, &AQuizScene::ShowStandings);
    Quiz->OnQuizFinished.AddDynamic(this, &AQuizScene::ShowResults);
    Quiz->Begin();
}

void AQuizScene::ShowQuestion(const FNeocortexQuizQuestion& Question)
{
    QuestionLabel->SetText(FText::FromString(Question.QuestionText));
    Scenery->ShowScene(Question.Keyword);
    for (const FNeocortexQuizOption& Option : Question.Options)
    {
        AddOptionButton(Option.Text, Option.Id); // calls Quiz->Choose(Option.Id) when tapped
    }
}

Begin() starts the run. The host welcomes the player and asks the first question. After that your game only reports what the player did:

Player didCall
Tapped an optionChoose(OptionId)
SpokeSay(Transcript)
TypedSay(Text, true)
Skipped, or your timer ran outPassTurn()

Hints, repeats and stopping are just words: Say(TEXT("Can I have a hint?")). LastIntent tells you how the host read the last thing said, for example Hint or Repeat. A typed answer is judged as written, so a misspelling is not treated as a mishearing.

OnQuestionChanged fires as the host starts asking the question, so the scene changes on the right line. OnUpcomingQuestion gives the next question's Keyword early, so you can load its scene while the host is still talking. Progress holds which question is on the table (Asked) out of Total.

The question

FNeocortexQuizQuestion carries what the game may show, and nothing it may not: the answer, the rubric and the hint text stay on the server.

MemberDescription
QuestionTextThe question.
Type"free_text", "multiple_choice" or "true_false".
OptionsThe choices to show, each with Id and Text. Empty for an open answer.
KeywordThe author's name for the scene this question needs, such as SHOW_RED_APPLE.
bHasHintWhether the player can ask for a clue.

Voice answers

Open the microphone when the host expects something, transcribe the recording, and pass the text to Say():

Voice answers
void AQuizScene::BeginPlay()
{
    Super::BeginPlay();
    Quiz->OnExpectingChanged.AddDynamic(this, &AQuizScene::HandleExpecting);
    Microphone->OnWavReady.AddDynamic(this, &AQuizScene::HandleWav);
    Agent->OnTranscribed.AddDynamic(this, &AQuizScene::HandleTranscribed); // a Smart Agent used only to transcribe
}

void AQuizScene::HandleExpecting(ENeocortexQuizExpecting Expecting)
{
    if (Expecting != ENeocortexQuizExpecting::Nothing) Microphone->StartListening();
}

void AQuizScene::HandleWav(const TArray<uint8>& Wav)
{
    Agent->TranscribeBytes(Wav);
}

void AQuizScene::HandleTranscribed(const FString& Text)
{
    if (Text.TrimStartAndEnd().IsEmpty()) Microphone->StartListening(); // nothing heard, listen again
    else Quiz->Say(Text);
}

Expecting is Answer for an attempt at the question, Confirmation for a yes or a no (usually to an offer of another try), and Nothing while the host is talking or the quiz is over.

Don't send an empty transcript with Say(TEXT("")). An empty string is a deliberate pass, which means the player chose to skip.

Several players

Fill Participants in the Details panel, or pass a roster to BeginWithPlayers(), and name the player on each answer:

Several players
FNeocortexQuizParticipant Anna; Anna.Id = TEXT("anna"); Anna.DisplayName = TEXT("Anna");
FNeocortexQuizParticipant Ben;  Ben.Id = TEXT("ben");   Ben.DisplayName = TEXT("Ben");
Quiz->BeginWithPlayers({ Anna, Ben });

Quiz->ChooseAs(TEXT("anna"), OptionId);
Quiz->SayAs(TEXT("ben"), Transcript);
Quiz->PassAs(TEXT("ben")); // Ben's timer ran out

The host waits until everyone has answered. OnPendingChanged and PendingParticipants track who is still to answer, and AnsweredParticipants who has. Timers are yours. The Quiz Show sample runs one to four players.

OnResult raises how each player's answer was judged (ParticipantId, Verdict, Score), and OnLeaderboardChanged the standings (ParticipantId, DisplayName, TotalScore, Rank). Players on the same score share a rank.

Input while the host is busy

While a turn is in flight, bBusy is true and OnBusyChanged fires. With Queue Input While Busy on (the default), what the player says is held and sent once the host is ready. Only the newest answer per player is kept, and an answer older than Max Pending Input Age seconds (default 4) is dropped.

OnInputDropped(Text, Reason) tells you when something never reached the host:

ReasonWhy
SupersededThe player said something newer, which is sent instead.
ExpiredIt waited too long to still answer what the host asked.
FinishedThe run has already ended.
NoSessionThe quiz has not started yet.
FailedThe turn it would have gone on failed.
BusyThe host was busy and Queue Input While Busy is off.

Errors and recovery

When a turn fails, the agent drops held input and calls Resync() by itself, which reads the run back from the server and reopens input where it left off. Call Resync() yourself after a lost connection.

When your team has run out of credits, or a usage limit is reached, the agent raises OnCreditsExhausted(Code) and keeps input closed until you call Resync().

Abort() silences the host and abandons whatever is in flight, without ending the run on the server.

The full list of members and events is in the API Reference.

On this page