Candy Logo Copyright © 2025-2026 Candy Arts

Variations
A Spoken Line contains one or more Variations, which are alternate versions of its text. Each time the Line runs, the Engine displays the Variation that best fits the current situation. With Variations, a single Spoken Line can be translated into other languages, address the player character according to their gender, react to the state of your game, or use a random phrasing each time so that repeated Lines don't sound mechanical. For example, a shopkeeper's greeting can have three Variations:
Default Welcome! Take a look around. Default_P=F Welcome, miss! Take a look around. Default_P=M Welcome, sir! Take a look around.
If the player character's Gender property is "F", the second Variation is displayed. If it is "M", the third one is displayed. In any other case, the Line falls back to Default. Variations are written in the Variations key of the Spoken Line. Each Variation has its own Text, Direction and Weight (see Spoken Line). A Variation with an empty Text is ignored. Variation Names A Variation's name states when it can be displayed. The name consists of parts separated by underscores (_).
PartDescription
LanguageRequired. Always the first part. The language the Variation is written in, such as Default or French.
tag=valueA tag with a value, such as P=F. It passes when the tag currently has that value.
Bare tagA tag without a value, such as Shout. It passes only when it is listed as a passed tag.
#NMarks a numbered alternative, such as Default_#2. See Random Variations below.
TTSAlways the last part. Marks the text that text-to-speech reads instead of the displayed text, such as Default_TTS. It is never displayed.
A name can combine any number of tags, in any order, such as Default_S=F_P=M_Shout. Language names, tag values, and the names of player and speaker tags are case-sensitive: P=F doesn't pass for a player character whose Gender is "f". Language The first part of a Variation's name is its language. The original language of your Dialogues is always named Default, whatever language it actually is. Translations are named after their language, such as French. The Engine only considers the Variations of its current language. A Line without any Variation in the current language is skipped: it doesn't fall back to Default. The current language is set by language in Candy_Properties.gd. An Engine can override it with its own language (see Engine_Language). For everything else about languages, see Translating Speech. Tags Tags are the conditions in a Variation's name. A Variation can only be displayed if every one of its tags passes. Player Tags and Speaker Tags The Engine's player_tags and speaker_tags dictionaries map a tag name to an Actor property. A player tag reads that property from the player character's entry in the actors dictionary, and a speaker tag reads it from the entry of the Actor speaking the Line. The tag passes when the property's value is equal to the tag's value. The default settings are:
player_tags = {"P": "Gender"} speaker_tags = {"S": "Gender"}
With these settings, Default_P=F passes when the player character's Gender is "F", and Default_S=F passes when the speaker's Gender is "F". If the Actor doesn't have the property, the tag fails. A tag can be added for any Actor property: with "R": "Race" added to speaker_tags, for example, Default_R=Elf passes when the speaker's Race property is "Elf". The player character is the Actor named by current_player in Player_Refs.gd. Bare Tags A bare tag has no value, such as Shout in Default_Shout. It passes only when it is listed as a passed tag (see below). Otherwise, Variations containing it are never displayed. Bare tags are useful for versions of a Line switched on for a specific Line, Actor or Engine. For example, a Line can have a Default Variation and a Default_Shout Variation written in capitals. When an Actor lists Shout in their PassedTags property, their Lines display the Shout Variation wherever one exists. Passed and Disabled Tags A tag can be forced to pass or to fail at three levels.
LevelPassed tagsDisabled tags
Enginepassed_tagsdisabled_tags
SpeakerPassedTagsDisabledTags
LinePassed_TagsDisabled_Tags
A passed tag passes whatever its value. If S is passed, both Default_S=F and Default_S=M pass. A disabled tag always fails, so the Variations containing it are never displayed. Passed and disabled tags take precedence over player tags, speaker tags and tag scripts. When a tag script's tag is passed or disabled, its script doesn't run. When several levels list the same tag, the most specific level decides: the Line first, then the speaker, then the Engine. A level that lists a tag as both passed and disabled disables it and reports a warning. Each entry is one tag name. It can also be a ¢ expression that returns a tag name or an Array of tag names, such as ¢["Shout", "S"] (see CandyRef Expressions). Unknown Tags A tag with a value fails if nothing decides it: it isn't passed or disabled, isn't in player_tags or speaker_tags, and has no tag script. This isn't reported, so a misspelled tag silently prevents its Variation from being displayed. How a Variation Is Chosen Each time a Spoken Line runs, the Engine chooses its Variation in four steps. 1. Every Variation in the current language is checked. A Variation is eligible when its Text isn't empty and all its tags pass. Numbered alternatives and TTS Variations are not checked in this step. 2. Eligible Variations that are less specific than another eligible Variation are discarded. A Variation is less specific than another when all of its tags, with the same values, also appear in the other one. A Variation without tags is less specific than any tagged Variation, so Default is only displayed when no tagged Variation is eligible. 3. If several Variations remain, one of them is picked. When the Engine's random_variations is on, the pick is random, weighted by each Variation's Weight. When it is off, the first one in the Line's Variations list is picked. 4. When random_variations is on, the picked Variation can be replaced by one of its numbered alternatives (see Random Variations below). For example, a Line has these four Variations, the speaker's Gender is "F" and the player character's Gender is "M":
Default Default_S=F Default_P=M Default_S=F_P=M
All four are eligible. Default, Default_S=F and Default_P=M are less specific than Default_S=F_P=M, so they are discarded, and Default_S=F_P=M is displayed. If the player character's Gender were "F" instead, only Default and Default_S=F would be eligible, and Default_S=F would be displayed. Two Variations with unrelated tags, such as Default_S=F and Default_P=M, are not less specific than each other. When both are eligible, both remain for step 3. Random Variations When the Engine's random_variations is on (the default), a Line can display a different text each time it runs. Randomness applies in two places:
  • Between the Variations that remain after step 2, when there are several.
  • Between the picked Variation and its numbered alternatives.
A numbered alternative is named after its Variation, followed by _#N: Default_#1, Default_#2, Default_S=F_#1 and so on. It has no conditions of its own. Whenever its Variation is picked, the Engine picks one text at random among the Variation and its alternatives. Both random picks are weighted by Weight. A Variation with a Weight of 2 is twice as likely to be picked as one with a Weight of 1. For example:
Default Weight 1 Hello. Default_#1 Weight 1 Hi there. Default_#2 Weight 2 Good to see you.
Each time this line runs, "Hello." and "Hi there." each have a 25% chance of being displayed, and "Good to see you." has a 50% chance. Weight must be a whole number, written directly: ¢ expressions aren't evaluated. An empty or invalid Weight counts as 1, and so does any value below 1. When random_variations is off, numbered alternatives are never displayed and Weight has no effect. When No Variation Is Eligible If no Variation is eligible, the Line is skipped and the Dialogue continues with the next Line. A Line can rely on this: a Line whose only Variation is Default_P=F only runs when the player character's Gender is "F". By default, a skipped Line doesn't count toward the Lines of a Media_Pause. Set the Engine's mp_count_variations to true to count it, so that paused media resumes after the same number of Lines whatever the language and tags. Tag Scripts For a tag whose value comes from your game rather than from an Actor property, add a script to the Candy_DE/Scripts/Variation_Tags folder. The script must extend CandyVariationTag, and its file name is the tag's name, case-insensitive. Weather.gd decides the Weather tag, as in Default_Weather=Rain. Nothing needs registering. The scripts you add to that folder are 'safe': updates never modify them. The script overrides one of these two functions:
  • current_value() returns the tag's current value as a String. The tag passes when this value is equal to the value in the Variation's name.
  • matches() receives the value from the Variation's name and returns whether the tag passes. Use it when one value can't describe the situation, such as a range of numbers or several accepted values.
Both functions receive the Engine running the Line, the speaker's Actor Reference and the CandyContext of the Line being run. matches() also receives the value to check. This Weather.gd script reads the weather from your game's own GameWorld autoload:
extends CandyVariationTag func current_value(_engine: CandyEngine, _speaker: String, _ctx: CandyContext) -> String: return GameWorld.weather
This TimeOfDay.gd script decides Variations such as Default_TimeOfDay=Night from the game's clock:
extends CandyVariationTag func matches(_engine: CandyEngine, value: String, _speaker: String, _ctx: CandyContext) -> bool: var hour: int = GameWorld.hour if value == "Morning": return hour >= 6 and hour < 12 if value == "Night": return hour >= 20 or hour < 6 return false
Rules for tag scripts:
  • Both functions can use await. The Line waits for them to finish.
  • The script checks each tag value at most once per Line, even when several Variations contain it.
  • One instance of the script checks every Line of the Engine. Don't store state in its member variables: store it in your game's code instead.
  • Bare tags are never decided by scripts.
  • A tag that is listed in player_tags or speaker_tags is decided by that dictionary, so a script with the same name is never used. The Engine reports this at startup.
Naming Rules For the Engine to parse Variation names:
  • Don't use underscores (_), equals signs (=) or spaces in language names, tag names or tag values.
  • Don't name a tag TTS, and don't start a tag name with #.
  • Don't name a player tag and a speaker tag alike.
Conditional Speech Variations express one thought, idea or sentiment in different ways. To have Actors express different things conditionally, select between different Spoken Lines with Dispositions or Condition Commands. For that reason, Variations can't display different portraits: a Variation that needs a different portrait is a case for Dispositions or Condition Commands. Voice Files Each Variation can have its own voice file. A Spoken Line has a single Voice key, so all its Variations share the same file name. A Variation whose voice file doesn't exist is displayed without voice, so every Variation that should be voiced requires its own file. To play another language's file instead, such as the original's, set voice_fallback_language in Candy_Properties.gd. See Voice. Other Features Using the Chosen Variation
  • Text-to-speech: in Advanced mode, text-to-speech reads the TTS Variation of the chosen Variation, named after it with _TTS added, such as Default_TTS or Default_#2_TTS. See TTS.
  • Word Substitution: the rules applied to the text are chosen according to the Variation's name. See Word Substitution.
  • Lexicon: the keywords highlighted in the text are chosen according to the Variation's name, so each language can have its own keywords. See Lexicon.
  • Hook functions: tts() and llm_query() receive the chosen Variation's name in candy_args["variation"]. See TTS and LLM.