RPG Core Combat Game Glitches (Quests and Ability issues)

Hi all. So, I left this project for a while, and I recently decided to return to coding it again

For the moment, I have a glitch with my quest system, where at the end of the quest when I try to close the dialogue, I get the reward, but the dialogue never closes. As a result, everytime I hit the ‘X’ button at the top right hand corner of my dialogue, I get an infinite amount of rewards. How do I get rid of this glitch?

Another glitch I have is when using my Fire Spray Ability. For some reason, this ability used to work, but now it stopped working completely, no matter what I click. How do I fix this?

These should be tackled in separate threads, as they are completely unrelated, and the crosstalk between the two issues will get confusing. We’ll start with the Quest system glitches here, and then please open a new topic with the Fire Spray issues.

In both cases, I’m going to need to see some code, and any error messages that might be occuring (please post related Fire Spray code and errors in the new thread you’ll create).

So you’re closing the X in a dialogue, and this is triggering rewards, presumably because the current DialogueNode has an ExitTrigger to give a reward.

That’s really more of a symptom, though… the cause is that somehow the X button is causing the current node to be reloaded…

Paste in both your DialogueUI and PlayerConversant.cs scripts and we’ll take a look.

Hi Brian, here is the PlayerConversant.cs Script:

using GameDevTV.Saving;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using System;
using RPG.Core; // for the Evaluator of the Dialogues (line 133)
using RPG.Movement;

namespace RPG.Dialogue {

    public class PlayerConversant : MonoBehaviour, IAction
    {

        [SerializeField] string playerName;
        
        Dialogue currentDialogue;
        DialogueNode currentNode = null;
        AIConversant currentConversant = null;  // the conversation being occured between the Player and the AI (to trigger events during the conversation for instance)
        bool isChoosing = false;

        // OUT OF COURSE CONTENT ------------------------------------------------------------------------------------------------
        private Dialogue targetDialogue;    // the Dialogue our Player is aiming to go to, for conversation purposes
        private AIConversant targetConversant;  // the Target Conversant of our Quest Giver = Player Conversant
        public float acceptanceRadius; // the minimum distance our Player has to be from the NPC before they can talk
        // ----------------------------------------------------------------------------------------------------------------------

        public event Action onConversationUpdated;

        

        public void StartDialogue(AIConversant newConversant, Dialogue newDialogue) {

            currentConversant = newConversant;
            currentDialogue = newDialogue;
            currentNode = currentDialogue.GetRootNode();
            TriggerEnterAction();
            onConversationUpdated();    // this line subscribes our Dialogue to the event Action we created above, so that it follows along when something new to that event occurs

        }

        public void Quit() {

            // When the conversation is over, or the 'x' button is clicked, this function is called

            currentDialogue = null; // no node available
            TriggerExitAction();
            currentNode = null; // no Nodes available to process through
            isChoosing = false; // no choices of conversations available
            currentConversant = null;   // quitting the AI Conversant Dialogue
            onConversationUpdated();    // makes the dialogue hide itself when the chat is over

        }

        public bool IsActive() {

            // This function (a 'getter' function) returns whether we have a current Dialogue to refer to (after the 2 seconds of the IEnumerator)
            return currentDialogue != null;
        }

        public bool IsChoosing() {

            // getter
            return isChoosing;

        }

        public string GetText() {

            // getter
            if (currentDialogue == null) {

                return "";

            }

            return currentNode.GetText();

        }

        public IEnumerable<DialogueNode> GetChoices() {

            return FilterOnCondition(currentDialogue.GetPlayerChildren(currentNode));

        }

        public void SelectChoice(DialogueNode chosenNode) {

            currentNode = chosenNode;
            TriggerEnterAction();
            isChoosing = false;

            // OPTIONAL: Implement this line only if you don't want the next conversation to display what your button just had written on it:
            Next();
            // if we didnt call 'Next()', which calls 'onConversationUpdated()' event subscription, we could've called it here instead

        }

        public void Next() {

            int numPlayerResponses = FilterOnCondition(currentDialogue.GetPlayerChildren(currentNode)).Count();

            if (numPlayerResponses > 0) {

                isChoosing = true;
                TriggerExitAction();
                onConversationUpdated();
                return;

            }

            DialogueNode[] children = FilterOnCondition(currentDialogue.GetAIChildren(currentNode)).ToArray();  // filters our Player <-> Quest Giver responses based on the process of our Quests
            int randomIndex = UnityEngine.Random.Range(0, children.Count());    // UnityEngine is mentioned here because Random.Range comes from both UnityEngine and System (which we need for event Action), hence we need to specify which function we are calling
            TriggerExitAction();
            currentNode = children[randomIndex];
            TriggerEnterAction();
            onConversationUpdated();

        }

        public bool HasNext() {

            return FilterOnCondition(currentDialogue.GetAllChildren(currentNode)).Count() > 0;

        }

        private IEnumerable<DialogueNode> FilterOnCondition(IEnumerable<DialogueNode> inputNode) {

            // This function ensures we can play different Nodes on our dialogues, based on the Progress Status of our Quests (Start, Pending, Complete, etc)
            foreach (var node in inputNode) {

                if (node.CheckCondition(GetEvaluators())) {

                    yield return node;  // if a condition (E.g: A quest has been done) is met, we include it in our filter, otherwise it's excluded from the Filter

                }

            }

        }

        private IEnumerable<IPredicateEvaluator> GetEvaluators() {

            return GetComponents<IPredicateEvaluator>();

        }

        private void TriggerEnterAction() {

            if (currentNode != null) {

               TriggerAction(currentNode.GetOnEnterAction());

            }

        }

        private void TriggerExitAction() {

            if (currentNode != null)
            {

                TriggerAction(currentNode.GetOnExitAction());

            }

        }

        private void TriggerAction(string action) {

            if (action == "") return;

            foreach(DialogueTrigger trigger in currentConversant.GetComponents<DialogueTrigger>()) {

                trigger.Trigger(action);

            }

        }

        public string GetCurrentConversantName()
        {
            
            if (isChoosing) {

                return playerName;

            }

            else {

                return currentConversant.GetName();

            }

        }

        // MORE OUT OF COURSE CONTENT -----------------------------------------------------------------------------

        public void Cancel()
        {
            targetConversant = null;
        }

        public void StartConversation(AIConversant conversant, Dialogue dialogue) {

            GetComponent<ActionSchedular>().StartAction(this);
            targetConversant = conversant;
            targetDialogue = dialogue;

        }

        void Update() {

            if (!targetConversant) return;
            if (Vector3.Distance(transform.position, targetConversant.transform.position) > acceptanceRadius) {

                transform.LookAt(targetConversant.transform);
                GetComponent<Mover>().MoveTo(targetConversant.transform.position, 1.0f);

            }

            else {

                GetComponent<Mover>().Cancel();
                StartDialogue(targetConversant, targetDialogue);
                targetConversant = null;

            }

        }

        // -----------------------------------------------------------------------------------------------------------

    }

}

and here is my DialogueUI.cs Script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using RPG.Dialogue;
using TMPro;

namespace RPG.UI {

    public class DialogueUI : MonoBehaviour
    {

        PlayerConversant playerConversant;
        [SerializeField] TextMeshProUGUI AIText;
        [SerializeField] Button nextButton;
        [SerializeField] GameObject AIResponse;
        [SerializeField] Transform choiceRoot;
        [SerializeField] GameObject choicePrefab;
        [SerializeField] Button quitButton; // quit button, to quit the dialogue midway through
        [SerializeField] TextMeshProUGUI conversantName;    // name of our conversant in a dialogue conversation
        
        // Start is called before the first frame update
        void Start()
        {

        playerConversant = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerConversant>();
        playerConversant.onConversationUpdated += UpdateUI; // subscribing our UI Upate to our Conversation UI Updates (so our UI Updates with our Dialogue)
        nextButton.onClick.AddListener(() => playerConversant.Next());   // calls the 'Next' function (yes, the function not a variable) Subscriber (to the onClick event) when our player hits 'Next' in the dialogue, using the Lambda "() => {}" function
        quitButton.onClick.AddListener(() => playerConversant.Quit());  // Lambda Function, "() => {}", is used here to add a listener for our quit button. When clicked the script is closed
        UpdateUI();

        }

        // Update is called once per frame
        void UpdateUI()
        {

            // The following line ensures the dialogue is invisible until the return time 
            // of the IEnumerator in 'playerConversant.cs' time counter is over
            gameObject.SetActive(playerConversant.IsActive());

            if (!playerConversant.IsActive()) {

                return;

            }

            conversantName.text = playerConversant.GetCurrentConversantName();
            AIResponse.SetActive(!playerConversant.IsChoosing());
            choiceRoot.gameObject.SetActive(playerConversant.IsChoosing());
            
            if (playerConversant.IsChoosing())
            {
                BuildChoiceList();

            }

            else {

                AIText.text = playerConversant.GetText();
                nextButton.gameObject.SetActive(playerConversant.HasNext());


            }

        }

        private void BuildChoiceList()
        {
            
            foreach (Transform item in choiceRoot) {

                // avoids Dangling Nodes in the Hierarchy, which will eventually slow our entire game down
                Destroy(item.gameObject);


            }

            foreach (DialogueNode choice in playerConversant.GetChoices())
            {

                GameObject choiceInstance = Instantiate(choicePrefab, choiceRoot);
                var textComp = choiceInstance.GetComponentInChildren<TextMeshProUGUI>();
                textComp.text = choice.GetText();
                Button button = choiceInstance.GetComponentInChildren<Button>();
                button.onClick.AddListener(() => {          // "() => {}" is a 'lambda' function ("()" is the argument, "{}" is the internals of the function), which only works when a button is clicked (do some research about this)

                    playerConversant.SelectChoice(choice);

                });

            }
        }
    }

}

Just to be extremely clear with my issue: Everytime, at the end of the quest, I hit the ‘X’ button, I get the reward but the dialogue never closes, and then you can pretty much keep clicking the button forever and it’ll never close, it’ll just keep rewarding you forever (as fun as that sounds to have, I’d like to eliminate this bug from my game :slight_smile: )

And you’re getting no error messages… say ones in DialogueTrigger.cs?

It looks (based on the code) like PlayerConversant.Quit() is working…
The currentDialogue is cleared before the Action fires
TriggerExitAction is being called (or you wouldn’t get the reward)
but currentNode is NOT being cleared, or pressing the button again would not be able to call the TriggerExitAction() – it would do nothing…

So somewhere in TriggerExitAction/TriggerAction an error is occurring which is stopping Quit from executing.

To confirm my analysis, put a Debug.Log before and after the TriggerExitAction in Quit

Debug.Log("Quit Dialogue - Before TriggerExitAction");
TriggerExitAction();
Debug.Log("Quit Dialogue - After TriggerExitAction");

Also put a Debug.Log in UpdateUI()

Debug.Log($"UpdateUI playerConversant.IsActive() == {playerConversant.IsActive()}");
gameObject.SetActive(playerConversant.IsActive());
if(!playerConversant.IsActive())
{
    Debug.Log("Exiting DialogueUI as playerConversant is not active");
    return;
}
Debug.Log("Continuing with DialogueUI as playeConversant is active");
//rest of method

Make sure that collapse is turned off in the console.
I predict after pressing the quit button that you’re just going to get

Quit Dialogue - Before TriggerExitAction

If everything is working normally, you would get

QuitDialogue - Before TriggerExitAction
QuitDialogue - After ExitAction
UpdateUI playerConversant.IsActive() == falseO;
Exiting DialogueUI as playerConversant is not active

The debugging works perfectly, as expected, and the function works well surprisingly. What I did notice though, is that sometimes it works flawlessly, and sometimes it doesn’t. The ‘X’ button mainly fails to work when I’m receiving the reward occasionally for some reason (at the end of the quest), and there are other times like now where it works with no issues at all. I doubt it’s a coding issue, sounds like it’s a Unity Engine problem

I actually recommend against making any exit triggers on the final nodes. Something like a “Here you go, thanks!” after the node that gives the reward on exit, or alternatively, give the reward in Enter instead.

Is that like a simple string to place at the end of the quest?

Yes, just a node saying goodby, or thank you, etc. So that the last node in the dialogue isn’t trying to do something on exit.

Privacy & Terms