[Solved] Quest HUD / Quest Tracking Change Events

Announcements, support questions, and discussion for Quest Machine.
KingCeryn
Posts: 70
Joined: Sun Aug 23, 2020 5:12 pm

[Solved] Quest HUD / Quest Tracking Change Events

Post by KingCeryn »

So I'm trying to modify the Quest Heading and Quest Info Text for when Quests pop up, but I'm having some trouble getting the Content Panel to work. Its using a Vertical Layout Group, but I'm trying to add a "fade bar" background image behind the Heading Text, but obviously because of the group it just positions above/below the heading,
Are there any examples or tips for customizing it? I'm also trying to modify the Heading to be aligned to the Right, so quests with longer names strecth out away from the edge of the screen.

I''d be open to just limiting it to only tracking one Quest at a time, so I could disable to layout group and add whatever I need that way, but was just curious what my options were. Also if i go that route, is there a way to switch what quest is currently tracked?

Also, slightly off topic, but I was also looking for a way to activate a UnityEvent on different Quest states? I have some minimap pointers and such, and I'm trying to trigger different events such as "ChangeQuestMinimapTarget" or "Clear Quest MinimapTarget" , when different nodes are met. Is there a component in QM similiar to Dialogue System's "QuestStateListener?"
Attachments
Quest Header.PNG
Quest Header.PNG (207.03 KiB) Viewed 2103 times
Last edited by KingCeryn on Mon Jan 24, 2022 5:33 pm, edited 3 times in total.
User avatar
Tony Li
Posts: 21928
Joined: Thu Jul 18, 2013 1:27 pm

Re: Quest HUD Customization

Post by Tony Li »

Hi,
KingCeryn wrote: Sat Dec 25, 2021 4:04 pmSo I'm trying to modify the Quest Heading and Quest Info Text for when Quests pop up, but I'm having some trouble getting the Content Panel to work. Its using a Vertical Layout Group, but I'm trying to add a "fade bar" background image behind the Heading Text, but obviously because of the group it just positions above/below the heading,
Are there any examples or tips for customizing it?
The Quest HUD GameObject's Unity UI Quest HUD component points to an Active Quest Heading Template and a Completed Quest Heading Template.

These template GameObjects only need a Unity UI Text Template component. The actual Text or TextMeshProUGUI component can be on a child GameObject. (Remember to assign that Text or TextMeshProUGUI too the Unity UI Text Template's Text field.) Example:

customizeQuestHUDHeading.png
customizeQuestHUDHeading.png (168.1 KiB) Viewed 2097 times

In the screenshot above, the Active Heading Template Background Image has an Image component and a Vertical Layout Group with these checkboxes ticked: Control Child Size (both) and Child Force Expand > Width.

KingCeryn wrote: Sat Dec 25, 2021 4:04 pmI'm also trying to modify the Heading to be aligned to the Right, so quests with longer names strecth out away from the edge of the screen.
You should just be able to change the Text or TextMeshProUGUI's alignment to Right.
KingCeryn wrote: Sat Dec 25, 2021 4:04 pmAlso, slightly off topic, but I was also looking for a way to activate a UnityEvent on different Quest states? I have some minimap pointers and such, and I'm trying to trigger different events such as "ChangeQuestMinimapTarget" or "Clear Quest MinimapTarget" , when different nodes are met. Is there a component in QM similiar to Dialogue System's "QuestStateListener?"
You can use a UnityEvent quest action in a quest node's Actions section. If you add it to the node's States > Active > Actions list, the event will run when that node becomes active. If you add it to States > True > Actions, the event will run when the node becomes true (i.e., step completed).

Alternatively, you can use a Message action to send a message using Quest Machine's Message System. Then you can add a Message Events component to a target and configure the Messages To Listen For section to listen for that message and perform some action.

Or you can write a custom quest action. There's a starter template in Quest Machine / Templates / QuestActionTemplate.cs.
KingCeryn
Posts: 70
Joined: Sun Aug 23, 2020 5:12 pm

Re: Quest HUD Customization

Post by KingCeryn »

Ah, sorry the first part with the UI isnt really working, Ive tried every combination of things but ive sorta just given up on it for now. Really only seems to work with the Text HEADING and Text BODY, no background images, etc. Gives me errors when I try to put the Unity UI Text Template on the "Background Image," and assign the child Active Heading Text to the slot.
Only way it works is if I just leave the two text childs alone.

Is there any way to set some kind of event when the "Current Tracked Quest" changes? I've been trying to follow the "Quest Journal Active Toggle" that changes the Quest HUD text, to see if i can manually add some kind of event, but I cant seem to find which script handles that actual change. Basically still trying to change things like "MinimapQuestPointers," etc.

The message system is working, my issue is that once i finish one quest and then track another, the message from that quest doesnt fire again. What I'd like to do is maybe write some kind of "Re-do Current Node Actions" whenever a Quest is re-tracked
User avatar
Tony Li
Posts: 21928
Joined: Thu Jul 18, 2013 1:27 pm

Re: Quest HUD Customization

Post by Tony Li »

Here's a quest HUD prefab with background images:

QM_QuestHUDWithBackground_2021-12-26.unitypackage
KingCeryn wrote: Sun Dec 26, 2021 1:23 pmIs there any way to set some kind of event when the "Current Tracked Quest" changes?
Quest Machine uses the Message System to send the message "Quest Track Toggle Changed" when quest tracking changes. A Message System message can have a parameter value and any number of additional argument values. When it sends "Quest Track Toggle Changed", the parameter will be a quest ID and the first argument value will be a Boolean true/false indicating whether the quest is being tracked or not. You can use the Message Events component that I mentioned above, or you can handle it in code. Here's an example:

Code: Select all

public class MinimapManager : MonoBehaviour, IMessageHandler
{
    void OnEnable() 
    { 
        // Listen for "Quest Track Toggle Changed" messages:
        MessageSystem.AddListener(this, QuestMachineMessages.QuestTrackToggleChangedMessage, string.Empty);
    }
    
    void OnDisable()
    {
        // Stop listening:
        MessageSystem.RemoveListener(this);
    }
    
    void OnMessage(MessageArgs messageArgs)
    {
        // A quest's tracking was just turned on or off. Update minimap icons:
        string questID = messageArgs.parameter;
        bool isTracked = (bool)messageArgs.values[0];
        if (isTracked)
        {
            ShowMinimapIconsForQuest(questID);
        }
        else
        {
            HideMinimapIconsForQuest(questID);
        }
    }
}
(EDIT: Fixed a typo [missing string.Empty] in OnEnable method.)
KingCeryn wrote: Sun Dec 26, 2021 1:23 pmThe message system is working, my issue is that once i finish one quest and then track another, the message from that quest doesnt fire again. What I'd like to do is maybe write some kind of "Re-do Current Node Actions" whenever a Quest is re-tracked
You should be able to handle it like the code example above.
KingCeryn
Posts: 70
Joined: Sun Aug 23, 2020 5:12 pm

Re: Quest HUD Customization

Post by KingCeryn »

Trying to test that code snippet, but getting errors

EDIT: fixed it with some additions:
Attachments
FixedExample.PNG
FixedExample.PNG (44.27 KiB) Viewed 2081 times
Last edited by KingCeryn on Sun Dec 26, 2021 7:09 pm, edited 2 times in total.
KingCeryn
Posts: 70
Joined: Sun Aug 23, 2020 5:12 pm

Re: Quest HUD Customization

Post by KingCeryn »

I have all my events setup for each node in the quests, via the Message Events component, but I'm struggling to "re-send" the messages when one quest is finished or tracking changes. I cant really wrap my head around how to do that via the code you posted,
Here's a photo of the kind of messages I have on the "listener" game object for each quest, it works properly by turning things on and off for each stage of the quest,

But if I have say TWO quests in my log, and I finish QUEST A, or I switch tracking to QUEST B, it doesn't receive the messages for the current node on QUEST B when I do so. And I'm not seeing how I might do that with this code. I can understand getting the "QuestTrackToggleChanged" message, but what would I do to get it to re-send the message for the "Current Node" of the "Current Quest"?

For example is there a way to like, access the current quest directly via. the Database, and use GetNode to somehow "re-do" the "Actions"? Thats where I have my messages being sent, when each node becomes active.
Attachments
ActionsNode.PNG
ActionsNode.PNG (32.43 KiB) Viewed 2080 times
questmessagessetup.PNG
questmessagessetup.PNG (78.18 KiB) Viewed 2083 times
User avatar
Tony Li
Posts: 21928
Joined: Thu Jul 18, 2013 1:27 pm

Re: Quest HUD Customization

Post by Tony Li »

Hi,

I fixed a typo in the code of my post above. The OnEnable method's MessageSystem.AddListener() was missing "string.Empty" for the required parameter value at the end. When the required parameter is an empty string, it matches all quests. By adding string.Empty, the script will receive a "Quest Track Toggle Changed" message whenever any quest's tracking state changes.

However, perhaps in your case it would be better to approach this from a different angle:

It sounds like each minimap quest pointer wants to be visible only when its corresponding quest node is active.

A script like this will do it:

Code: Select all

using UnityEngine;
using PixelCrushers;
using PixelCrushers.QuestMachine;

public class MinimapQuestPointer : MonoBehaviour, IMessageHandler
{
    public StringField requiredQuestID;
    public StringField requiredQuestNodeID;

    void OnEnable()
    {
        MessageSystem.AddListener(this, QuestMachineMessages.QuestStateChangedMessage, string.Empty);
    }

    void OnDisable()
    {
        MessageSystem.RemoveListener(this);
    }

    public void OnMessage(MessageArgs messageArgs)
    {
        // "Quest State Changed" is sent when a quest state or quest node state changes. 
        // - Parameter: Quest ID. 
        // - Value 0: [StringField] Quest node ID, or null for main quest state.
        // - Value 1: [QuestState] / [QuestNodeState] New state.
        if (messageArgs.values[0] == null)
        {
            // If value 0 is null, this is the main quest state so just exit:
            return;
        }
        else
        {
            string questID = messageArgs.parameter;
            StringField questNodeID = (StringField)messageArgs.values[0];
            QuestNodeState questNodeState = (QuestNodeState)messageArgs.values[1];
            if ((questID == requiredQuestID.value) && (questNodeID.value == requiredQuestNodeID.value))
            {
                // This is the quest node we're interested in, so set the quest pointer based on the node's current state:
                if (questNodeState == QuestNodeState.Active)
                {
                    // (Quest node is active, so show the pointer here.)
                }
                else
                {
                    // (Quest node is not active, so hide the pointer here.)
                }
            }
        }
    }
}
You may also want to extend this script to set the quest pointer's state when changing scenes or loading saved games. If so, you could use a version like this:
Spoiler

Code: Select all

using PixelCrushers;
using PixelCrushers.QuestMachine;

public class MinimapQuestPointer : Saver, IMessageHandler
{
    public StringField requiredQuestID;
    public StringField requiredQuestNodeID;

    public override void OnEnable()
    {
        base.OnEnable();
        MessageSystem.AddListener(this, QuestMachineMessages.QuestStateChangedMessage, string.Empty);
    }

    public override void OnDisable()
    {
        base.OnEnable();
        MessageSystem.RemoveListener(this);
    }

    public void OnMessage(MessageArgs messageArgs)
    {
        // "Quest State Changed" is sent when a quest state or quest node state changes. 
        // - Parameter: Quest ID. 
        // - Value 0: [StringField] Quest node ID, or null for main quest state.
        // - Value 1: [QuestState] / [QuestNodeState] New state.
        if (messageArgs.values[0] == null)
        {
            // If value 0 is null, this is the main quest state so just exit:
            return;
        }
        else
        {
            string questID = messageArgs.parameter;
            StringField questNodeID = (StringField)messageArgs.values[0];
            QuestNodeState questNodeState = (QuestNodeState)messageArgs.values[1];
            if (StringField.Equals(questID, requiredQuestID) && StringField.Equals(questNodeID, requiredQuestNodeID))
            {
                // This is the quest node we're interested in, so set the quest pointer based on the node's current state:
                if (questNodeState == QuestNodeState.Active)
                {
                    // (Quest node is active, so show the pointer here.)
                }
                else
                {
                    // (Quest node is not active, so hide the pointer here.)
                }
            }
        }
    }

    public override string RecordData()
    {
        // We don't need to save anything since we'll restore the quest pointer's state from the quest node's state.
        return string.Empty;
    }

    public override void ApplyData(string s)
    {
        if (QuestMachine.GetQuestNodeState(requiredQuestID, requiredQuestNodeID) == QuestNodeState.Active)
        {
            // (Quest node is active, so show the pointer here.)
        }
        else
        {
            // (Quest node is not active, so hide the pointer here.)
        }
    }
}
KingCeryn
Posts: 70
Joined: Sun Aug 23, 2020 5:12 pm

Re: Quest HUD Customization

Post by KingCeryn »

Ah, I still cant seem to get this to work. I added a UnityEvent and a DebugLog message to test if it detects the Quest Node becoming active, but nothing happens/appears in the console.


Thanks for helping! Ill keep trying with this.

EDIT: I should note that I'm doing this with the Dialogue Systems integration, so I'm recieiving all the quests through the Dialogue Systems UI, and Lua code for each conversation, I'm not sure if that would make a difference from using just Quest Machine alone.

EDIT: That first example above that you posted, which I ammended, was successfully recognizing when the Quest was tracked/untracked, is there a simple way to maybe just force some kind of "Do QuestNode Actions"? That would I think accomplish what I'm trying to do.
I basically just need it to re-send the Message I have being sent in the "Actions" panel for the current Node, when it re-tracks the quest.
Attachments
DebugLog.PNG
DebugLog.PNG (37.17 KiB) Viewed 2077 times
KingCeryn
Posts: 70
Joined: Sun Aug 23, 2020 5:12 pm

Re: Quest HUD Customization

Post by KingCeryn »

Right now im playing with the "ExecuteStateActions" in the Quest.cs, and also trying to access the "stateInfo.actionList.Execute();" command in the "SetState" function in QuestNode.cs, I think this is what I'm looking for but still struggling to get it to work.
User avatar
Tony Li
Posts: 21928
Joined: Thu Jul 18, 2013 1:27 pm

Re: Quest HUD Customization

Post by Tony Li »

I guess technically you can do something like this if you have a reference to a QuestNode:

Code: Select all

// Execute state actions:
var stateInfo = node.GetStateInfo(QuestNodeState.Active);
if (stateInfo != null && stateInfo.actionList != null)
{
	for (int i = 0; i < stateInfo.actionList.Count; i++)
	{
		if (stateInfo.actionList[i] == null) continue;
		stateInfo.actionList[i].Execute();
	}
}
But I'm not really convinced that re-running the actions is the best approach. If some of those actions do something that should only happen once, such as spawning a boss enemy, you probably don't want to run it again and spawn a duplicate boss.

The Dialogue System integration shouldn't be getting in the way here. You don't need to worry about the Dialogue System for this part.

If you'd like me to put together an example, let me know and I can put one together in the morning. You're also welcome to send a reproduction project to tony (at) pixelcrushers.com at any time.
Post Reply