QuestListUI Redraw (in and out of the Unity Engine) gameplay bug

Alright as the name suggests, I have a minor problem with my QuestListUI (and I tried re-activating my OnChange() action event in QuestList.cs, but that didn’t work either). For some reason, when I get a quest given to me through the ‘GiveQuest’ trigger, my QuestListUI does not get updated unless I save my game, quit to the main menu, and then return. Only then do I see my QuestList updated to include my new quest

How do we get the QuestListUI to immediately get the Quest List updated the moment we get the quest given to us? I don’t want to quit my game, return to the main menu and then return to my game for this to work. I also checked my Redraw function and my ‘QuestList’ and ‘QuestListUI’ scripts, and both seem to be working perfectly fine… Please help :slight_smile:

This works in the course repo, so it’s possible something is missing from the scripts.

Paste in your QuestList and QuestListUI scripts and we’ll take a look.

Sure, here you go (and for the post regarding my infinity glitch, there’s no way I can respond there regarding the fix of the infinity reward glitch):

using System;
using System.Collections.Generic;
using UnityEngine;
using GameDevTV.Saving;
using GameDevTV.Inventories;
using GameDevTV.Utils;
using Newtonsoft.Json.Linq;

namespace RPG.Quests {

    public class QuestList : MonoBehaviour, IPredicateEvaluator, IJsonSaveable //, ISaveable
    {
        
        List<QuestStatus> statuses = new List<QuestStatus>();   // for Restoring State, our object state is this list, which is [System.Serializable]
        public event Action onUpdate;

        private void Update() {

            CompleteObjectivesByPredicates();

        }

        public void AddQuest(Quest quest) {

            if (HasQuest(quest)) return;
            QuestStatus newStatus = new QuestStatus(quest);
            statuses.Add(newStatus);

            // This line below is responsible for my quest dialogue not closing properly at the end of the conversation:
            /* if (onUpdate != null) {

            onUpdate();

            } */

        }

        public bool HasQuest(Quest quest)
        {

            return GetQuestStatus(quest) != null;
        
        }

        public IEnumerable<QuestStatus> GetStatuses()
        {
            
            return statuses;

        }

        public void CompleteObjective(Quest quest, string objective)
        {
            QuestStatus status = GetQuestStatus(quest);
            status.CompleteObjective(objective);
            if (status.IsComplete()) {

                GiveReward(quest);

            }
            
            // This function below is responsible for my infinity reward glitch, so I commented it out:
            /* if (OnUpdate != null) {

                OnUpdate();

            } */
        }

        /* public object CaptureState() {

            List<object> state = new List<object>();
            
            foreach (QuestStatus status in statuses) {

                state.Add(status.CaptureState());

            }

            return state;

        }

        public void RestoreState(object state) {

            List<object> stateList = state as List<object>;
            if (stateList == null) return;

            statuses.Clear();

            foreach (object objectState in stateList) {

                statuses.Add(new QuestStatus(objectState));

            }

        } */

        public JToken CaptureAsJToken() {

            JArray state = new JArray();
            IList<JToken> stateList = state;
            foreach (QuestStatus status in statuses) {

                stateList.Add(status.CaptureAsJToken());

            }

            return state;

        }

        public void RestoreFromJToken(JToken state) {

            if (state is JArray stateArray) {

                statuses.Clear();
                IList<JToken> stateList = stateArray;

                foreach (JToken token in stateList) {

                    statuses.Add(new QuestStatus(token));

                }

            }

        }

        private QuestStatus GetQuestStatus(Quest quest)
        {

            foreach (QuestStatus status in statuses)
            {

                if (status.GetQuest() == quest)
                {

                    return status;

                }

            }

            return null;

        }

        private void GiveReward(Quest quest)
        {
            foreach (var reward in quest.GetRewards()) {

                bool success = GetComponent<Inventory>().AddToFirstEmptySlot(reward.item, reward.number);

                if (!success) {

                    GetComponent<ItemDropper>().DropItem(reward.item, reward.number);   // if our player didn't have enough inventory slots to take their reward, drop the rewards on the ground

                }

            }
        }

        /* public bool? Evaluate(string predicate, string[] parameters)
        {
            
            switch(predicate) {

                case "HasQuest": return HasQuest(Quest.GetByName(parameters[0]));
                case "CompletedQuest": return GetQuestStatus(Quest.GetByName(parameters[0])).IsComplete();

            }
            
            return null;

        } */
        
        public bool? Evaluate(EPredicate predicate, string[] parameters) {

            // New function to go with the Enum Predicate we developed in 'IPredicateEvaluator.cs' (Replacing the string-based 'Evaluate' function above)

            switch (predicate) {

                // Quest is given to player:
                case EPredicate.HasQuest: return HasQuest(Quest.GetByName(parameters[0]));
                
                // Quest Completed:
                case EPredicate.CompletedQuest:

                QuestStatus status = GetQuestStatus(Quest.GetByName(parameters[0]));
                if (status == null) return false;
                return status.IsComplete();

                // Completed Objective:
                case EPredicate.CompletedObjective:

                QuestStatus testStatus = GetQuestStatus(Quest.GetByName(parameters[0]));
                if (testStatus == null) return false;
                return testStatus.IsObjectiveComplete(parameters[1]);

            }

                // If no condition is met above, return null
                return null;

        }

        public void CompleteObjectivesByPredicates()
        {

            // (Re-summarize this function - Shops and Abilities course, Lecture 94/96):

            // This function enables Predicates in quests 
            // (so if we don't have a skill level accomplished yet for example, we can't work on the quest)
            // (IT OPENS A TON OF POSSIBILITIES FOR QUESTS AS A RESULT!)

            foreach (QuestStatus status in statuses) {

                if (status.IsComplete()) continue;
                Quest quest = status.GetQuest();

                foreach (Quest.Objective objective in quest.GetObjectives()) {

                    if (status.IsObjectiveComplete(objective.reference)) { continue; }

                    if (!objective.usesCondition) continue;
                    if (objective.completionCondition.Check(GetComponents<IPredicateEvaluator>())) {

                        CompleteObjective(quest, objective.reference);

                    }

                }

            }

        }

    }

}

QuestListUI.cs:

using RPG.Quests;
using UnityEngine;

public class QuestListUI : MonoBehaviour
{

    [SerializeField] QuestItemUI questPrefab;
    QuestList questList;

    // Start is called before the first frame update
    void Start()
    {

        questList = GameObject.FindGameObjectWithTag("Player").GetComponent<QuestList>();
        questList.onUpdate += Redraw;
        Redraw();

    }

    private void Redraw()
    {

        foreach (Transform item in transform)
        {

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


        }

        foreach (QuestStatus status in questList.GetStatuses())
        {

            QuestItemUI UIInstance = Instantiate<QuestItemUI>(questPrefab, transform);
            UIInstance.Setup(status);


        }
    }
}

Does your QuestItemUI subscribe to the QuestList?

Uhh… No? Here’s my ‘QuestItemUI.cs’:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Quests;
using TMPro;

public class QuestItemUI : MonoBehaviour
{
    
    [SerializeField] TextMeshProUGUI title;
    [SerializeField] TextMeshProUGUI progress;

    QuestStatus status;

    public void Setup(QuestStatus status) {

        this.status = status;
        title.text = status.GetQuest().GetTitle();
        progress.text = status.GetCompletedCount() + "/" + status.GetQuest().GetObjectiveCount();

    }

    public QuestStatus GetQuestStatus() {

        return status;

    }

}

Try changing the onUpdate call to

onUpdate?.Invoke();

This null checks… that being said, it shouldn’t be null unless something else is subscribing…
It’s not a UnityEvent, so we can rule that out.

Tried that in ‘QuestList.cs’, both areas where it gets called (can’t find anywhere else this function gets called). Again, that re-ignited the infinity reward glitch problem…

Which code editor are you using?

umm… Visual Studio 2019, the blue one I believe (when applying the JSON files, I realized the 2022 went purple, not sure if they’re the same software or not tbh)

Go to the declaration for your event

public event Action onUpdated;

and right click on the onUpdated and choose “Find All References”
It will give you all the locations onUpdated is called, and all locations where it is subscribed and unsubscribed.

says ‘no results’, even when I uncommented the calls for it (although they’re glitching my game… the ones in ‘QuestList.cs’)

Edit: wait, says something when I only highlights ‘onUpdate’…

onUpdate calls

Here are all the ‘onUpdate’ calls

Ok… definitely not what I was expecting (but the correct results for a normal project)…
That means the issue is in QuestListUI.Redraw…

what were you expecting though? (and can I comment the onUpdate calls in ‘QuestList.cs’ again?)

I was expecting some other class to be subscribing, but this is not the case…
Do NOT uncomment the calls, because we need to call it to find this bug.

Ok, I know we ruled this out earlier, but I seem to recall that the line WAS in Questlist, specifically in the instantiate line. That would indicate that the prefab was no longer valid. Should not be the case, since the prefab should be in assets…

Go into your QuestListUI and click on the questPrefab. Make sure your assets folder is open and to a different directory as the questPrefab. When you click on it, the folder with the questPrefab should open and the prefab should get a brief highlight… Nothing in the heirarchy should do this…

UMM… Ok I’m not exactly sure I understood what you meant, but I did double click the quest Prefab in the questListUI, and it lead me to a quest prefab I had. I opened that, seemed fine to me…

That’s what I meant, trying to ensure it’s not tied to a scene object…

The same test needs to be repeated within the Core prefab in prefab mode (which contains a QuestListUI prefab, head to that copy of the prefab and do the same click test) and within each scene itself, to ensure that the prefab reference didn’t get overwritten and serialized in those locations.

I only have two scenes, the game itself and the main menu, and the core prefab’s Quest Prefab is linked to an asset rather than a scene prefab as well

Let’s write in some Debugs… At this point, I’m pretty baffled, because I can’t see a reason for a null reference in this Redraw function…

    private void Redraw()
    {
        foreach (Transform item in transform)
        {
            Destroy(item.gameObject);
        }
        Debug.Assert(questList, "questList does not exist");
        foreach (QuestStatus status in questList.GetStatuses())
        {
            Debug.Assert(status!=null, "status in list does not exist");
            Debug.Assert(questPrefab, "questPrefab does not exist");
            QuestItemUI uiInstance = Instantiate<QuestItemUI>(questPrefab, transform);
            Debug.Assert(uiInstance, $"uiInstance is null.  Does {questPrefab} have a QuestItemUI component?");
            uiInstance.Setup(status);
        }
    }

Privacy & Terms