// Progress in the conversation protected void progress() { if (_currentSentence >= _currentDialogue.Sentences.Count - 1) { // Progress to the next dialogue if (_currentDialogue.NextId != -1) { var nextDialogue = _conversation.Dialogues.Find(d => d.Id == _currentDialogue.NextId); if (nextDialogue == null) { DialogueLogger.LogError($"Trying to navigate to a dialogue with the Id {_currentDialogue.NextId}, but there's isn't one present in the current conversation"); return; } _currentDialogue = nextDialogue; startDialogue(); } else { finishedConversation(); } } else { // There's more to show _currentSentence++; StartCoroutine(showSentence(parseSentenceForCustomTags(_currentDialogue.Sentences[_currentSentence]))); } }
// Return the modification's value e.g. <speed=0.1> return 0.1. Not the tags content private object parseModValue(Modifications modType, string commandText) { // We're already a string if (modType == Modifications.SEND_MESSAGE || modType == Modifications.REMOVE_VARAIBLE || modType == Modifications.CHANGE_SPRITE || modType == Modifications.ACTION || modType == Modifications.ACTION_WITH_MESSAGE || modType == Modifications.ACTION_WITH_TARGET || modType == Modifications.LOG || modType == Modifications.LOG_WARNING || modType == Modifications.LOG_ERROR || modType == Modifications.CHANGE_THEME || modType == Modifications.BG_CONVERSATION) { return(commandText); } // Parse float else if (modType == Modifications.SPEED || modType == Modifications.WAIT) { var floatVal = 0f; if (float.TryParse(commandText, out floatVal)) { return(floatVal); } } DialogueLogger.LogError($"Couldn't parse parameter value for {modType} text modification"); return(null); }
// Retrieve the sprite from the repo and apply it if it exists private void findAndChangeSprite(string name) { var imageSprite = getSprite(name, _elementsName); var buttonSprite = getSprite(name, _buttonElementName); if (imageSprite == null) { DialogueLogger.LogError($"GameObject with the name {gameObject.name} can't change sprite with theme, {name} doesn't contain a sprite for {_elementsName}. Skipping"); } else { _thisImage.sprite = imageSprite; } if (buttonSprite == null) { DialogueLogger.LogError($"GameObject with the name {gameObject.name} can't change sprite with theme, {name} doesn't contain a sprite for {_buttonElementName}. Skipping"); } else { var tempState = new SpriteState(); switch (_buttonStateToChange) { case buttonStates.HIGHLIGHT: tempState.highlightedSprite = buttonSprite; break; case buttonStates.PRESSED: tempState.pressedSprite = buttonSprite; break; case buttonStates.SELECTED: tempState.selectedSprite = buttonSprite; break; case buttonStates.DISABLED: tempState.disabledSprite = buttonSprite; break; } _thisButton.spriteState = tempState; } }
// Pre cast the variables for quicker retrieval public void Cast() { if (!FromRepo) { switch (Type.ToLower()) { case "short": _castValue = short.Parse(Value); break; case "int": _castValue = int.Parse(Value); break; case "long": _castValue = long.Parse(Value); break; case "float": _castValue = float.Parse(Value); break; case "bool": _castValue = bool.Parse(Value); break; case "string": _castValue = Value; break; default: DialogueLogger.LogError($"Unsupported type {Type} using in variable"); break; } } }
// Initialize private void Start() { _thisButton = GetComponent <Button>(); _thisImage = _thisButton?.image; // Check we've got a name before registering for the action if (string.IsNullOrEmpty(_elementsName) || string.IsNullOrEmpty(_buttonElementName)) { DialogueLogger.LogError($"GameObject with the name {gameObject.name} needs an Elements Name and Button Element Name value to be able to change theme"); return; } // Check we've got the components if (_thisImage == null || _thisButton == null) { DialogueLogger.LogError($"GameObject with the name {gameObject.name} needs an Image and Button component to be able to change button theme"); return; } // Check the button is using sprites if (_thisButton.transition != Selectable.Transition.SpriteSwap) { DialogueLogger.LogError($"GameObject with the name {gameObject.name}'s Button component needs to be in SpriteSwap transition mode to be able to change button theme"); return; } DialogueController.Instance.ThemeChanged += findAndChangeSprite; }
// Start the conversation if possible public void StartConversation(Conversation conversation) { if (_inConversation) { DialogueLogger.LogWarning($"Trying to start a conversation while already in a conversation"); return; } if (_uiController == null) { DialogueLogger.LogError("Trying to start a conversation, but there's not DialogueUIController assigned"); return; } // Find starting point _currentConversation = null; _currentDialogue = null; _currentSentence = 0; foreach (var diag in conversation.Dialogues) { if ((_currentDialogue == null || diag.Id > _currentDialogue.Id) && diag.CanBeUsedAsStartingPoint) { if (diag.StartConditions.Count > 0) { if (diag.EvaluateStartingConditions()) { _currentDialogue = diag; } } else { _currentDialogue = diag; } } } if (_currentDialogue == null) { DialogueLogger.LogError($"Couldn't find starting point for conversation"); return; } // Start the conversation _inConversation = true; _currentConversation = conversation; if (needToChangeTheme()) { ChangeTheme(_currentDialogue.Theme); } _uiController.ShowSentence(_currentDialogue.SpeakersName, parseSentenceForCustomTags(_currentDialogue.Sentences[_currentSentence]), SpriteRepo.Instance.RetrieveSprite(_currentDialogue.CharacterSpritesName, string.IsNullOrEmpty(_currentDialogue.StartingSprite) ? "Default" : _currentDialogue.StartingSprite), _currentDialogue.AutoProceed); ConversationStarted?.Invoke(); }
public T GetContent <T>() { if (ModificationContent is T) { return((T)ModificationContent); } DialogueLogger.LogError($"Trying to cast variable of type {ModificationContent.GetType()} to type {typeof(T)}. Returning default"); return(default(T)); }
// Used to return the name=value pair if possible private string[] getTagNameValuePair(string text, char separator) { var tempSplit = text.Split(separator); if (tempSplit.Length != 2) { DialogueLogger.LogError($"Cannot parse command {text}"); return(null); } return(tempSplit); }
// Remove the variable if it exists public void Remove(string name) { if (!_variables.ContainsKey(name)) { DialogueLogger.LogError($"Trying to remove a variable {name} but it hasn't been registered"); return; } else { _variables.Remove(name); VariableRemoved(name); } }
// Get the conversation from the repo if it exists protected Conversation findConversation(string convoName) { // Check if repo exists and the conversation has been loaded var tempoConvo = ConversationRepo.Instance?.RetrieveConversation(convoName); if (tempoConvo == null) { DialogueLogger.LogError($"Tried to start conversation {convoName} but it doesn't exist is the ConversationRepo"); return(null); } return(tempoConvo); }
// Find the action in the conversation then pass it on for validating and execution public static void PerformAction(Conversation conversation, string actionName) { var action = getAction(conversation, actionName); if (action != null) { performAction(action); } else { DialogueLogger.LogError("Cannot find the selectedAction for the option selected. Skipping action"); } }
// Validate the action private static bool validateAction(DialogueAction action) { // Check we've got all the pieces we neewd switch (action.ActionType) { case DialogueAction.Types.LOG: case DialogueAction.Types.LOG_WARNING: case DialogueAction.Types.LOG_ERROR: // All we need's a message if (string.IsNullOrEmpty(action.Message) || string.IsNullOrWhiteSpace(action.Message)) { DialogueLogger.LogError($"An action with the name {action.Name} is trying to log with an empty message value"); return(false); } break; case DialogueAction.Types.SEND_MESSAGE: // We need a target and a message if (string.IsNullOrEmpty(action.Message) || string.IsNullOrWhiteSpace(action.Message)) { DialogueLogger.LogError($"An action with the name {action.Name} is trying to send a message with an empty message value"); return(false); } if (string.IsNullOrEmpty(action.Target) || string.IsNullOrWhiteSpace(action.Target)) { DialogueLogger.LogError($"An action with the name {action.Name} is trying to send a message with an empty target value"); return(false); } break; case DialogueAction.Types.CHANGE_THEME: // All we need's a message if (string.IsNullOrEmpty(action.Message) || string.IsNullOrWhiteSpace(action.Message)) { DialogueLogger.LogError($"An action with the name {action.Name} is trying to change the theme with an empty message value"); return(false); } break; case DialogueAction.Types.START_BG_CONVERSATION: // Just need message, the name of the conversation to start if (string.IsNullOrEmpty(action.Message) || string.IsNullOrWhiteSpace(action.Message)) { DialogueLogger.LogError($"An action with the name {action.Name} is trying to start a background conversation with an empty message value"); return(false); } break; } return(true); }
// Do we need to know the type when retrieving for evaluation? public object GetValue() { if (FromRepo) { return(VariableRepo.Instance.Retrieve(Name)); } if (_castValue == null) { DialogueLogger.LogError("Trying to retrieve a variable value for comparison that is null"); } return(_castValue); }
// Navigates to a dialogue inside the current conversation private void goToDialogue(int index) { var nextDialogue = _currentConversation.Dialogues.Find(d => d.Id == index); if (nextDialogue == null) { DialogueLogger.LogError($"Trying to navigate to a dialogue with the Id {index}, but there's isn't one present in the current conversation"); return; } _lastSpeaker = _currentDialogue.SpeakersName; _currentDialogue = nextDialogue; _currentSentence = -1; // Incremented in Next() Next(); }
// Initialize public virtual void Initialize(Conversation conversation) { _conversation = conversation; findStartingPoint(conversation); if (_currentDialogue == null) { DialogueLogger.LogError($"Couldn't find starting point for background conversation"); finishedConversation(); return; } BackgroundDialogueController.Instance.CloseConversation += finishedConversation; startDialogue(); }
// Retrieve a sprite from a registered theme public Sprite RetrieveSprite(string themeName, string spriteName) { if (string.IsNullOrEmpty(themeName)) { DialogueLogger.Log("Trying to retrieve sprite from repo, but the themeName parameter is empty"); return(null); } if (!_themeSprites.ContainsKey(themeName)) { DialogueLogger.LogError($"Trying to retrieve sprite {themeName} for the theme {themeName}, but it is not registered in the repo."); return(null); } // Seems to be consistently faster than Linq return(_themeSprites[themeName].ThemeSprites.Find(s => s.Name == spriteName)?.Sprite); }
// Return the sprite for the character if found public Sprite RetrieveSprite(string character, string name = "Default") { if (string.IsNullOrEmpty(character)) { DialogueLogger.Log("Trying to retrieve sprite from repo, but the character parameter is empty"); return(null); } if (!_characterSprites.ContainsKey(character)) { DialogueLogger.LogError($"trying to retrieve sprite {name} for the character {character}, but it is not registered in the repo."); return(null); } // Seems to be consistently faster than Linq return(_characterSprites[character].CharacterSprites.Find(s => s.Name == name)?.Sprite); }
// Validate then execute private static void performAction(DialogueAction action) { if (!validateAction(action)) { return; } switch (action.ActionType) { case DialogueAction.Types.LOG: DialogueLogger.Log(action.Message); break; case DialogueAction.Types.LOG_WARNING: DialogueLogger.LogWarning(action.Message); break; case DialogueAction.Types.LOG_ERROR: DialogueLogger.LogError(action.Message); break; case DialogueAction.Types.CLOSE_CONVERSATION: DialogueController.Instance?.StopCurrentConversation(); break; case DialogueAction.Types.SEND_MESSAGE: var targetObject = GameObject.Find(action.Target); if (targetObject == null) { DialogueLogger.LogError($"Trying to execute a send message action, but GameObject {action.Target} was not found. Skipping action"); return; } targetObject.SendMessage(action.Message, SendMessageOptions.DontRequireReceiver); break; case DialogueAction.Types.CHANGE_THEME: DialogueController.Instance?.ChangeTheme(action.Message); break; case DialogueAction.Types.CLOSE_BG_CONVERSATIONS: BackgroundDialogueController.Instance?.CloseConversations(); break; case DialogueAction.Types.START_BG_CONVERSATION: BackgroundDialogueController.Instance?.StartConversation(action.Message); break; default: DialogueLogger.LogError($"Action with the name {action.Name} has na unrecognised action type {action.ActionType}. The action type loaded from the conversation JSON is {action.Type}. Skipping action"); break; } }
// Sends the conversation to the repo public void LoadConversation() { // Can't load if there's no name if (string.IsNullOrEmpty(_name)) { DialogueLogger.LogError($"Object {gameObject.name} cannot register a conversation without a name"); return; } // Can't load if there's no conversation if (_file == null) { DialogueLogger.LogError($"Object {gameObject.name} has an empty conversation file"); return; } ConversationRepo.Instance.RegisterConversation(_name, _file); }
// Already found, just start public void StartConversation(Conversation conversation) { if (_uiControllerPrefab == null) { DialogueLogger.LogError("Trying to start a background conversation, but there's not BaseBackgroundDialogueUIController assigned"); return; } var tempObject = Instantiate(_uiControllerPrefab).GetComponent <BaseBackgroundDialogueUIController>(); if (tempObject == null) { DialogueLogger.LogError($"Background conversation spawned does't have a component of type BaseBackgroundDialogueUIController"); return; } tempObject.Initialize(conversation); }
// Registers a complex modification private void registerComplexModification(Modifications modType, string value, string content, int startingIndex) { // Would've already check the tag is custom to get this far, the only thing that's really required is the value, should definitely be able to handle empty content if (string.IsNullOrWhiteSpace(value) || string.IsNullOrEmpty(value)) { DialogueLogger.LogError($"Trying to parse custom tag {modType}, but it has an empty value"); return; } var modValue = parseModValue(modType, value); if (modValue != null) { _modifications.Add(new ComplexModification { Index = startingIndex, ModType = modType, ModificationValue = modValue, ModificationContent = content }); } }
// Parse type. Done this was instead of using JSONConverter to spit out an error that's a bit more helpful public bool GetActionType() { // Figure type switch (Type.ToLower()) { case "log": ActionType = Types.LOG; break; case "log_warning": case "log warning": case "logwarning": ActionType = Types.LOG_WARNING; break; case "log_error": case "log error": case "logerror": ActionType = Types.LOG_ERROR; break; case "close_conversation": case "close conversation": case "closeconversation": ActionType = Types.CLOSE_CONVERSATION; break; case "send_message": case "send message": case "sendmessage": ActionType = Types.SEND_MESSAGE; break; case "change_theme": case "change theme": case "changetheme": ActionType = Types.CHANGE_THEME; break; case "close_bg_conversations": case "close bg conversations": case "closebgconversations": ActionType = Types.CLOSE_BG_CONVERSATIONS; break; case "start_bg_conversation": case "start bg conversation": case "startbgconversation": ActionType = Types.START_BG_CONVERSATION; break; default: DialogueLogger.LogError($"Unsupported action type {Type} found in action with the name {Name}."); break; } return(true); }
// Registration through the dialogue system will always be a string, preparse as variableType and register public void Register(string name, string variable, TypeCode variableType) { // We're already of type string if (variableType == TypeCode.String) { Register(name, variable); return; } // I don't like this, there's got to be a better way of doing it try { object castVariable = null; switch (variableType) { case TypeCode.Int16: castVariable = short.Parse(variable); break; case TypeCode.Int32: castVariable = int.Parse(variable); break; case TypeCode.Int64: castVariable = long.Parse(variable); break; case TypeCode.Single: castVariable = float.Parse(variable); break; case TypeCode.Boolean: castVariable = bool.Parse(variable); break; } Register(name, castVariable); } catch (Exception e) { DialogueLogger.LogError($"Error registering variable {variable} to the type {variableType}. Error message: {e.Message}"); } }
// Get the right implementation of IComparison from the Comparison variable private void getComparer() { switch (Comparison) { case ">": _comparer = new GreaterThan(); break; case "<": _comparer = new LessThan(); break; case ">=": _comparer = new GreateOrEqualTo(); break; case "<=": _comparer = new LessToEqualTo(); break; case "==": _comparer = new EqualTo(); break; case "!=": _comparer = new NotEqualTo(); break; default: DialogueLogger.LogError($"Unsupported comparison operator {Comparison} used"); break; } }
// Initialize private void Start() { _thisImage = GetComponent <Image>(); _thisSprite = GetComponent <SpriteRenderer>(); // Check we've got a name before registering for the action if (string.IsNullOrEmpty(_elementsName)) { DialogueLogger.LogError($"GameObject with the name {gameObject.name} needs an Elements Name value to be able to change theme"); return; } if (_thisImage == null && _thisSprite) { DialogueLogger.LogError($"GameObject with the name {gameObject.name} needs an Image or Sprite Renderer component to be able to change theme"); return; } DialogueController.Instance.ThemeChanged += findAndChangeSprite; }
// Retrieve the sprite from the repo and apply it if it exists private void findAndChangeSprite(string name) { var tempSprite = getSprite(name, _elementsName); if (tempSprite == null) { DialogueLogger.LogError($"GameObject with the name {gameObject.name} can't change sprite with theme, {name} doesn't contain a sprite for {_elementsName}. Skipping"); return; } // If we're just an image if (_thisImage != null) { _thisImage.sprite = tempSprite; } // If we're a sprite renderer if (_thisSprite != null) { _thisSprite.sprite = tempSprite; } }
// Register and validate a conversation in real-time public void RegisterConversation(string name, TextAsset file) { var tempObject = _deserializer.Deserialize <Conversation>(file.text); if (tempObject == null) { DialogueLogger.LogError($"There was an error deserializing file {file.name}"); return; } tempObject.PreValidation(); if (valdateConversation(tempObject, name)) { if (_conversations.ContainsKey(name)) { DialogueLogger.LogWarning($"Conversation {name} already registered, overwritting"); } _conversations[name] = tempObject; tempObject.FinishedParsing(); } }
// Test the variables according to the comparison string public bool Evaluate() { // Check the comparer if (_comparer == null) { DialogueLogger.LogError("Trying to evaluate a condition, but the comparison operator is null"); return(false); } // Get values var var1 = Variables[0].GetValue(); var var2 = Variables[1].GetValue(); if (var1 == null || var2 == null) { DialogueLogger.LogWarning($"Trying to evaluate a condition but one or more of the variables are null"); return(false); } // Evaluate // We're using dynamics here, so be careful. We'll have to validate types on the conversation load return(_comparer.Execute(var1, var2)); }
// Check all the values are valid private bool validateSprites(Theme_SO sprites) { var names = new List <string>(); if (sprites.ThemeSprites.Count == 0) { DialogueLogger.LogWarning($"Trying to register theme sprites for {sprites.Name} but the sprite list is empty"); } foreach (var spritePair in sprites.ThemeSprites) { // Check name if (string.IsNullOrEmpty(spritePair.Name) || string.IsNullOrWhiteSpace(spritePair.Name)) { DialogueLogger.LogError($"Error registering theme sprites for {sprites.Name}, one or more of the names is empty"); return(false); } // Check for duplicate names if (names.Contains(spritePair.Name)) { DialogueLogger.LogError($"Error registering theme sprites for {sprites.Name}, there is already a sprite with the name {spritePair.Name}. Each sprite must have a unique name"); return(false); } names.Add(spritePair.Name); // Check sprites if (spritePair.Sprite == null) { DialogueLogger.LogError($"Error registering theme sprites for {sprites.Name} with the sprite name {spritePair.Name}, but the sprite is empty"); return(false); } } return(true); }
// If you're UI supports tags, use this to execute all tags at a given position in the sentence // I don't like this here. But unfortunately, the default and background conversations are too different (in my example implementation) to put it in a parent class. // Please note, not all tags are used here protected IEnumerator processTagsForPosition(TextModifications textMod, int index) { var mods = textMod.GetAnyTextModsForPosition(index); // Check for custom modifications foreach (var mod in mods) { // Commands if (mod.ModType == TextModifications.Modifications.CLOSE_BG_CONVERSATIONS) { BackgroundDialogueController.Instance?.CloseConversations(); } // Simple modifications e.g. <command=value> if (mod.ModType == TextModifications.Modifications.SPEED) { _speedMultiplyer = (mod as SimpleModification).GetValue <float>(); } else if (mod.ModType == TextModifications.Modifications.REMOVE_VARAIBLE) { VariableRepo.Instance?.Remove((mod as SimpleModification).GetValue <string>()); } else if (mod.ModType == TextModifications.Modifications.WAIT) { yield return(new WaitForSeconds((mod as SimpleModification).GetValue <float>())); } else if (mod.ModType == TextModifications.Modifications.ACTION) { performAction((mod as SimpleModification).GetValue <string>()); } else if (mod.ModType == TextModifications.Modifications.LOG) { DialogueLogger.Log((mod as SimpleModification).GetValue <string>()); } else if (mod.ModType == TextModifications.Modifications.LOG_WARNING) { DialogueLogger.LogWarning((mod as SimpleModification).GetValue <string>()); } else if (mod.ModType == TextModifications.Modifications.LOG_ERROR) { DialogueLogger.LogError((mod as SimpleModification).GetValue <string>()); } else if (mod.ModType == TextModifications.Modifications.BG_CONVERSATION) { BackgroundDialogueController.Instance?.StartConversation((mod as SimpleModification).GetValue <string>()); } // Complex modifications e.g. <command=value>content</command> else if (mod.ModType == TextModifications.Modifications.SEND_MESSAGE) { var revievingObject = GameObject.Find((mod as SimpleModification).GetValue <string>()); if (revievingObject == null) { DialogueLogger.LogError($"Trying to execute a send message command, but GameObject {(mod as SimpleModification).GetValue<string>()} was not found"); continue; } revievingObject.SendMessage((mod as ComplexModification).GetContent <string>(), SendMessageOptions.DontRequireReceiver); } else if (mod.ModType == TextModifications.Modifications.ACTION_WITH_MESSAGE) { performActionWithMessage((mod as SimpleModification).GetValue <string>(), (mod as ComplexModification).GetContent <string>()); } else if (mod.ModType == TextModifications.Modifications.ACTION_WITH_TARGET) { performActionWithTarget((mod as SimpleModification).GetValue <string>(), (mod as ComplexModification).GetContent <string>()); } } }