/// execute the current DialogPiece <br/> stops at Text and Choice pieces private void ExecuteDialog() { Dictionary <int, bool> jumped = new Dictionary <int, bool>(); again: if (dialogIndex >= currentDialog.Length) { // no more dialog inDialog = false; gameObject.SetActive(false); if (onDialogComplete != null) { onDialogComplete(); } //erase all active sprites foreach (GameObject image in currentDialogSprites.Values) { GameObject.Destroy(image); } currentDialogSprites.Clear(); return; } DialogPiece p = currentDialog[dialogIndex]; if (p.type == DialogPieceType.Text) { TextDialogPiece piece = p as TextDialogPiece; speaking = true; currentLine = piece.text; letterIndex = 0; letterTimer = 0; dialogText.text = ""; continueArrow.SetActive(false); if (piece.charName != "") { nameBox.SetActive(true); nameText.text = piece.charName; if (currentDialogSprites.TryGetValue(piece.charName.ToLower(), out GameObject image)) { if (activeSprite) { activeSprite.GetComponent <Image>().CrossFadeAlpha(transparentSpriteAlpha, spriteFadeDuration, true); } if (!String.IsNullOrEmpty(piece.expression)) { SetImageSprite(image, GetCharacterSprite(piece.charName, piece.expression, piece.line)); } image.GetComponent <Image>().CrossFadeAlpha(1, spriteFadeDuration, true); activeSprite = image; } else { if (!String.IsNullOrEmpty(piece.expression)) { throw new DialogException(piece.line, "character '" + piece.charName + "' doesn't have a sprite"); } } } else { nameBox.SetActive(false); } } else if (p.type == DialogPieceType.Choice) { ChoiceDialogPiece piece = p as ChoiceDialogPiece; foreach (Transform oldBtn in choiceContainer.transform) { GameObject.Destroy(oldBtn.gameObject); } float totalHeight = 0; for (int i = 0; i < piece.choices.Count; i++) { ChoiceTextPositionPair pair = piece.choices[i]; Transform newButton = Instantiate(choiceButtonPrefab, choiceContainer).transform; newButton.localPosition = new Vector3(0, -i * (choiceButtonHeight + choiceButtonMargin), 0); newButton.GetChild(0).GetComponent <Text>().text = pair.text; newButton.GetComponent <DialogOptionButton>().onClick.AddListener(delegate { continueArrow.transform.localScale = new Vector3(1, 1, 1); choiceBox.SetActive(false); inChoice = false; dialogIndex = pair.position; ExecuteDialog(); }); totalHeight += choiceButtonHeight + (i < piece.choices.Count - 1 ? choiceButtonMargin : 0); } choiceContainer.transform.localPosition = new Vector3(0, totalHeight / 2, 0); inChoice = true; continueArrow.transform.localScale = new Vector3(1, -1, 1); choiceBox.SetActive(true); } else if (p.type == DialogPieceType.Jump) { JumpDialogPiece piece = p as JumpDialogPiece; if (jumped.ContainsKey(piece.jumpTo)) { throw new DialogException(piece.line, "infinite jump loop"); } jumped.Add(piece.jumpTo, true); dialogIndex = piece.jumpTo; goto again; } else if (p.type == DialogPieceType.Sprite) { SpriteDialogPiece piece = p as SpriteDialogPiece; if (piece.filename != "" && !dialogSpriteFilenames.ContainsKey(piece.character.ToLower())) { dialogSpriteFilenames.Add(piece.character.ToLower(), piece.filename); } Sprite sprite = GetCharacterSprite(piece.character, piece.expression, piece.line); if (activeSprite) { activeSprite.GetComponent <Image>().CrossFadeAlpha(transparentSpriteAlpha, spriteFadeDuration, true); } GameObject image; if (!currentDialogSprites.ContainsKey(piece.character)) { //create the object if it doesn't exist image = new GameObject(piece.character + " sprite"); Image newImage = image.AddComponent <Image>(); currentDialogSprites.Add(piece.character, image); RectTransform rect = image.GetComponent <RectTransform>(); rect.SetParent(this.transform); rect.SetSiblingIndex(0); newImage.canvasRenderer.SetAlpha(0); } else { image = currentDialogSprites[piece.character]; } activeSprite = image; image.GetComponent <Image>().CrossFadeAlpha(1, spriteFadeDuration, true); image.transform.localPosition = SpritePositions[piece.position].localPosition; SetImageSprite(image, sprite); dialogIndex++; goto again; } }
/// parses a dialog script into an array of DialogPieces public static DialogPiece[] ParseDialog(string dialogText, char newLine = '\n') { string[] lines = dialogText.Split(newLine); List <DialogPiece> pieces = new List <DialogPiece>(); Dictionary <string, int> labels = new Dictionary <string, int>(); // stores references to labels that haven't been defined yet, and what should happen when they're defined Dictionary <string, List <LabelReference> > labelReferences = new Dictionary <string, List <LabelReference> >(); void LabelRef(string label, int line, Action <int> action) { //if the label is already defined, execute the action immediately //otherwise, store it in references for later if (labels.TryGetValue(label, out int position)) { action(position); } else { if (!labelReferences.ContainsKey(label)) { labelReferences.Add(label, new List <LabelReference>()); } labelReferences[label].Add(new LabelReference(line, action)); } } for (int i = 0; i < lines.Length; i++) { string l = lines[i].Trim(); //ignore empty lines if (String.IsNullOrWhiteSpace(l)) { continue; } if (l[0] == '@') { //label string labelName = l.Substring(1); if (labels.ContainsKey(labelName)) { throw new DialogParseException(i, "label '" + labelName + "' is defined more than once"); } int position = pieces.Count; if (labelReferences.TryGetValue(labelName, out List <LabelReference> references)) { foreach (LabelReference labelRef in references) { labelRef.action(position); } labelReferences.Remove(labelName); } labels[labelName] = position; } else if (l[0] == '?') { //choice string[] options; if (l == "?") { List <string> opList = new List <string>(); while (true) { i++; string op = lines[i].Trim(); if (op == String.Empty) { continue; } opList.Add(op[op.Length - 1] == ',' ? op.Substring(0, op.Length - 1) : op); if (op[op.Length - 1] != ',') { break; } } options = opList.ToArray(); } else { options = l.Substring(1).Split(','); } List <ChoiceTextPositionPair> choices = new List <ChoiceTextPositionPair>(); for (int o = 0; o < options.Length; o++) { string option = options[o].Trim(); string text = option.Substring(0, option.IndexOf(':')).Trim(); string label = option.Substring(option.IndexOf(':') + 1).Trim(); if (label == "next") { choices.Add(new ChoiceTextPositionPair(text, pieces.Count + 1)); } else { LabelRef(label, i, position => choices.Add(new ChoiceTextPositionPair(text, position))); } } ChoiceDialogPiece thePiece = new ChoiceDialogPiece(i, choices); pieces.Add(thePiece); } else if (l[0] == '>') { //jump string labelName = l.Substring(1).Trim(); JumpDialogPiece thePiece = new JumpDialogPiece(i, 0); LabelRef(labelName, i, position => thePiece.jumpTo = position); pieces.Add(thePiece); } else if (l[0] == '!') { //misc command string command = l.Substring(1, l.IndexOf(' ') - 1); string rest = l.Substring(command.Length + 2).Trim(); if (command == "sprite") { int colon = rest.IndexOf(':'); int comma = rest.IndexOf(','); //comma = comma == -1 ? rest.Length : comma; string character = rest.Substring(0, colon).Trim().ToLower(); string alias = ""; if (character.Contains("-")) { int hyphen = character.IndexOf('-'); alias = character.Substring(hyphen + 1); character = character.Substring(0, hyphen); } string position = rest.Substr(colon + 1, comma == -1 ? rest.Length : comma).Trim().ToLower(); string expression = comma == -1 ? "" : rest.Substring(comma + 1).Trim().ToLower(); pieces.Add(new SpriteDialogPiece(i, character, position, expression, alias)); } } else { //normal dialog if (!l.Contains(":")) { throw new DialogParseException(i, "invalid line syntax"); } int colon = l.IndexOf(':'); string charName = l.Substring(0, colon); string expression = ""; int paren = charName.IndexOf('('); if (paren != -1) { expression = charName.Substr(paren + 1, charName.IndexOf(')')); charName = charName.Substring(0, paren).Trim(); } string messageText = l.Substring(colon + 1).Trim(); pieces.Add(new TextDialogPiece(i, charName, messageText, expression)); } } //if there are any references still left, it means their labels don't exist foreach (var kvp in labelReferences) { throw new DialogParseException(kvp.Value[0].line, "undefined label '" + kvp.Key + "'"); } return(pieces.ToArray()); }