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,InOrderorShuffled). 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
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 did | Call |
|---|---|
| Tapped an option | Choose(OptionId) |
| Spoke | Say(Transcript) |
| Typed | Say(Text, true) |
| Skipped, or your timer ran out | PassTurn() |
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.
| Member | Description |
|---|---|
QuestionText | The question. |
Type | "free_text", "multiple_choice" or "true_false". |
Options | The choices to show, each with Id and Text. Empty for an open answer. |
Keyword | The author's name for the scene this question needs, such as SHOW_RED_APPLE. |
bHasHint | Whether 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():
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:
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 outThe 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:
| Reason | Why |
|---|---|
Superseded | The player said something newer, which is sent instead. |
Expired | It waited too long to still answer what the host asked. |
Finished | The run has already ended. |
NoSession | The quiz has not started yet. |
Failed | The turn it would have gone on failed. |
Busy | The 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.
Group Chat
Put several AI characters in one conversation in Unreal Engine 5 with the Neocortex Group Director, each with its own voice, joining, leaving and talking to each other.
API Reference
Reference for the Neocortex Unreal Engine plugin, covering the Smart Agent, Group Director, Quiz Agent, usage gate, microphone, lip sync and chat widgets, usable from C++ and Blueprints.