Quiz
Run a spoken quiz, game show or test in Godot 4 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.
NeocortexQuizAgent 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
NeocortexQuizAgentnode - Pick the host in its
character_iddropdown and the set in itsquestion_set_iddropdown. Both lists come from your account. - Optional: set
question_count(0 uses the set's own count) andquestion_order(SET_DEFAULT,IN_ORDERorSHUFFLED)
The host speaks in the character's voice. Turn off speak_host_lines for a silent host. An AudioStreamPlayer is created for the voice when you don't assign an audio_player.
For a quick text UI, add a NeocortexQuizUI beside the agent, with a chat panel, a text input and a thinking indicator under it. The input only shows while the host is waiting for an answer.
Running a quiz
extends Node
@onready var quiz: NeocortexQuizAgent = $NeocortexQuizAgent
func _ready() -> void:
quiz.host_line.connect(func(line: NeocortexQuizLine): subtitles.show_line(line.text))
quiz.question_changed.connect(_show_question)
quiz.upcoming_question.connect(func(next): scenery.preload_scene(next.keyword))
quiz.leaderboard_changed.connect(func(standings: Array): scoreboard.show_standings(standings))
quiz.quiz_finished.connect(func(standings: Array): results.show_results(standings))
quiz.begin()
func _show_question(question: NeocortexQuizQuestion) -> void:
question_label.text = question.question_text
scenery.show_scene(question.keyword)
for option in question.options:
var button := Button.new()
button.text = option.text
button.pressed.connect(quiz.choose.bind(option.id))
options_box.add_child(button)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(option_id) |
| Spoke | say(transcript) |
| Typed | say(text, true) |
| Skipped, or your timer ran out | pass_turn() |
Hints, repeats and stopping are just words: say("Can I have a hint?"). last_intent 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.
question_changed fires as the host starts asking the question, so the scene changes on the right line. upcoming_question gives the next question's keyword early, so you can load its scene while the host is still talking.
The question
NeocortexQuizQuestion 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 |
|---|---|
question_text | 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. |
is_choice() | True when the player picks from options. |
keyword | The author's name for the scene this question needs, such as SHOW_RED_APPLE. |
has_hint | 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():
@onready var mic: NeocortexAudioReceiver = $NeocortexAudioReceiver
var api := ApiRequest.new()
func _ready() -> void:
add_child(api)
quiz.expecting_changed.connect(func(expecting: NeocortexEnums.QuizExpecting):
if expecting != NeocortexEnums.QuizExpecting.NOTHING:
mic.start_microphone())
mic.audio_recorded.connect(_on_recorded)
func _on_recorded(wav: AudioStreamWAV) -> void:
var text := await api.request_transcription(quiz.character_id, wav)
if text.strip_edges().is_empty():
mic.start_microphone() # 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(""). An empty string is a deliberate pass, which means the player chose to skip.
Several players
Fill participants in the Inspector, or pass a roster to begin(), and name the player on each answer:
quiz.begin([
NeocortexQuizParticipant.create("anna", "Anna"),
NeocortexQuizParticipant.create("ben", "Ben"),
])
quiz.choose_as("anna", option_id)
quiz.say_as("ben", transcript)
quiz.pass_as("ben") # Ben's timer ran outThe host waits until everyone has answered. pending_changed and pending_participants track who is still to answer, and answered_participants who has. Timers are yours. The Quiz Show sample runs one to four players.
result raises how each player's answer was judged (participant_id, verdict, score), and leaderboard_changed the standings (participant_id, display_name, total_score, rank). Players on the same score share a rank.
Input while the host is busy
While a turn is in flight, is_busy is true and busy_changed 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.
input_dropped(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. |
NO_SESSION | 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 usage, or a usage limit is reached, the agent raises credits_exhausted(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 signals is in the API Reference.
Group Chat
Put several AI characters in one conversation in Godot 4 with the Neocortex Group Director, each with its own voice, joining, leaving and talking to each other.
API Reference
GDScript reference for the Neocortex Godot SDK 0.2 with AI NPC agents, chat lines, actions, group chat, quizzes, usage, voice, lip sync and errors in Godot 4.