Updating variables via script

Announcements, support questions, and discussion for the Dialogue System.
Post Reply
Nokinori
Posts: 9
Joined: Tue Jun 27, 2023 5:11 pm

Updating variables via script

Post by Nokinori »

I've created a script that updates a variable in both dialogue system and in playmaker, for scenarios in which I need both variables synchronized simultaneously. Unfortunately, it's behaving inconsistently.

Sequence of events:
1. A sequence command Dual(test, false) changes the variable Main.test to false
2. A conversation hits a condition Main.test == true and follows the 'false' branch.
3. A sequence command Dual(test, true) changes the variable Main.test to true
4. A conversation hits a condition Main.test == true and still follows the 'false' branch.

During these events, the variable updates correctly in the runtime variable inspector. Manually changing the value to false and back to true causes the conversation to correctly follow the 'true' branch. And using my custom sequencer command to then change it back to false causes the conversation to correctly follow the 'false' branch.

Here is my code:

Code: Select all

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using PixelCrushers.DialogueSystem;
using HutongGames.PlayMaker;
using Sirenix.OdinInspector;

namespace PixelCrushers.DialogueSystem.SequencerCommands {
    public class SequencerCommandDual : SequencerCommand {

        [InfoBox("This script enables the custom sequencer command DualVariable, which sets the same variable in Dialogue System and on a Playmaker FSM. Syntax: DualVariable([Variable Name], [Value], [FSM Name]. If [FSM Name] is left blank, it defaults to 'Main'.)")]
        public void Awake() {
            // The GameObject name is always "Database"
            string gameObjectName = "Database";

            // Get the parameters
            string variableName = GetParameter(0); // The name of the variable to set
            string value = GetParameter(1); // The value to set the variable to
            string fsmName = GetParameter(2); // The name of the FSM component, defaults to "Main" if blank

            // Default the FSM name to "Main" if it's blank
            fsmName = string.IsNullOrEmpty(fsmName) ? "Main" : fsmName;

            // Find the GameObject "Database"
            GameObject database = GameObject.Find(gameObjectName);
            if (database == null) {
                Debug.LogError("SequencerCommandDualVariable: GameObject '" + gameObjectName + "' not found");
                Stop();
                return;
            }

            // Get the specified FSM component from the "Database" GameObject
            PlayMakerFSM fsm = null;
            foreach (var component in database.GetComponents<PlayMakerFSM>()) {
                if (component.FsmName.Equals(fsmName)) {
                    fsm = component;
                    break;
                }
            }

            if (fsm == null) {
                Debug.LogError("SequencerCommandDualVariable: FSM '" + fsmName + "' not found in GameObject '" + gameObjectName + "'");
                Stop();
                return;
            }

            // Set the FSM variable
            SetFSMVariable(fsm, fsmName, variableName, value);

            //Set the variable within Dialogue System
            SetDialogueSystemVariable(fsmName, variableName, value);

            // Send the 'Start' event to the FSM
            fsm.SendEvent("Update / Clothes");

            // Done
            Stop();
        }

        private void SetFSMVariable(PlayMakerFSM fsm, string fsmName, string variableName, string value) {
            var fsmVariable = fsm.FsmVariables.FindVariable(variableName);
            if (fsmVariable == null) {
                Debug.LogError("SequencerCommandDualVariable: Variable '" + variableName + "' not found in FSM '" + fsmName + "'");
                return;
            }

            // Set the value based on the type of the variable
            if (fsmVariable is FsmBool fsmBoolVar) {
                fsmBoolVar.Value = bool.Parse(value);
            }
            else if (fsmVariable is FsmFloat fsmFloatVar) {
                fsmFloatVar.Value = float.Parse(value);
            }
            else if (fsmVariable is FsmInt fsmIntVar) {
                fsmIntVar.Value = int.Parse(value);
            }
            else if (fsmVariable is FsmString fsmStringVar) {
                fsmStringVar.Value = value;
            }
            else {
                Debug.LogError("SequencerCommandDualVariable: Unsupported variable type for '" + variableName + "'");
            }
        }

        private void SetDialogueSystemVariable(string fsmName, string variableName, string value) {
            string fullVariableName = fsmName + "." + variableName;
            DialogueLua.SetVariable(fullVariableName, value);
        }
    }
}
The code also updates other variables such as Float, Int, and String correctly, but causes conditions to fail during dialogue. The only condition I've found which works correctly is bool false.

Is there something wrong with way I'm calling 'DialogueLua.SetVariable()' ? And if so, then I can't figure out why it is updating correctly in the runtime variable inspector, but not correctly triggering dialogue conditions.
User avatar
Tony Li
Posts: 20990
Joined: Thu Jul 18, 2013 1:27 pm

Re: Updating variables via script

Post by Tony Li »

Hi,

Could the issue be that Conversations Evaluate Conditions One Extra Level Ahead ?


Also, instead of maintaining two parallel variables, which could get out of sync, why not use one or the other?

If you use a DS variable, you can use the "Get Variable" Playmaker action to get its value in your Playmaker FSMs.

If you use a Playmaker variable, you can use a GetFsmXXX() Lua function such as GetFsmString() to get the value in your conversations' Conditions fields and SetFsmString() set the value in your Script fields.
Nokinori
Posts: 9
Joined: Tue Jun 27, 2023 5:11 pm

Re: Updating variables via script

Post by Nokinori »

Thank you for the link, I didn't realize it worked that way. Unfortunately, that isn't the issue, though. The variables are being set in one conversation, and then the next conversation references them with a branching pathway. It's a simple set of a/b test conversations I put together to make sure my script worked, so there aren't any other external variables I know of that could be interfering with it.

As for the parallel variables, thank you for the advice there too. I would love to only use a single set, but I need to update dialogue and playmaker states at the same time in some instances, which would get really messy with constantly calling for getting and setting variables. I thought it would be more streamlined and modular to require all variable updates to update both variables at the same time, which should keep them in sync. That's the idea behind the script, to ensure that both variables must be updated in sync. Does that seem like a bad idea?
User avatar
Tony Li
Posts: 20990
Joined: Thu Jul 18, 2013 1:27 pm

Re: Updating variables via script

Post by Tony Li »

Hi,

It's fine as long as you make sure to keep the variables in sync.

Try setting the Dialogue Manager's Other Settings > Debug Level to Info to get more information about how the conversation is evaluating Conditions. More details here:



If that doesn't help, can you send a reproduction project or reproduction steps to tony (at) pixelcrushers.com?
Nokinori
Posts: 9
Joined: Tue Jun 27, 2023 5:11 pm

Re: Updating variables via script

Post by Nokinori »

I've tried a few things to figure out what's going on with no luck, so I sent you a reproduction.
User avatar
Tony Li
Posts: 20990
Joined: Thu Jul 18, 2013 1:27 pm

Re: Updating variables via script

Post by Tony Li »

Hi Noki,

The issue is that this sequencer command:

Code: Select all

Dual(TestBool, true)
runs this code:

Code: Select all

private void SetDialogueSystemVariable(string fsmName, string variableName, string value) {
    string fullVariableName = fsmName + "." + variableName;
    DialogueLua.SetVariable(fullVariableName, value);
}
Note that the variable value is a string. So you're setting Variable["Main.TestBool"] to the string "true", not to the Boolean value true.

Maybe check something like:

Code: Select all

private void SetDialogueSystemVariable(string fsmName, string variableName, string value) {
    string fullVariableName = fsmName + "." + variableName;
    if (fsmVariable is FsmBool) 
   {
       bool boolValue = value == "true";
        DialogueLua.SetVariable(fullVariableName, boolValue);
    }
    else
    {
        DialogueLua.SetVariable(fullVariableName, value); // May also want to check FsmFloat & FsmInt.
    }
}
Nokinori
Posts: 9
Joined: Tue Jun 27, 2023 5:11 pm

Re: Updating variables via script

Post by Nokinori »

I thought I needed to send the entire command as a string and let Lua parse it. I didn't realize you could call it as a different type of value. Thank you so much, it's working now.
User avatar
Tony Li
Posts: 20990
Joined: Thu Jul 18, 2013 1:27 pm

Re: Updating variables via script

Post by Tony Li »

Glad to help!
Nokinori
Posts: 9
Joined: Tue Jun 27, 2023 5:11 pm

Re: Updating variables via script

Post by Nokinori »

I'm posting the completed code here for reference, in case anyone else is trying to do something similar. This code allows the custom sequencer command 'Dual' to set a variable value or perform an addition, subtraction, multiplication, or division operation (+=, -=, *=, or /=) on the same variable for both Dialogue System and in a Playmaker FSM.

It should go without saying, but operations should only be used on Int and Float variables. I should probably check for other variables and

Syntax: Dual([Variable Name], [Value or Operation], [FSM Name]). | If [FSM Name] is left blank, it defaults to 'Main'.

FSM Name also serves as the start of the name of the variable. For example, 'Dual(emotion, happy, Claire)' would set the string variable 'emotion' on the FSM 'Claire' on the GameObject 'Database' to 'happy' while also setting the Dialogue Manager text variable Claire.emotion to 'happy'.

Code: Select all

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using PixelCrushers.DialogueSystem;
using HutongGames.PlayMaker;
using Sirenix.OdinInspector;

namespace PixelCrushers.DialogueSystem.SequencerCommands {
    public class SequencerCommandDual : SequencerCommand {

        [InfoBox("This script enables the custom sequencer command Dual, which sets or modifies variables on a Playmaker FSM and Dialogue System.  |  Syntax: Dual([Variable Name], [Value or Operation], [FSM Name]).  |  If [FSM Name] is left blank, it defaults to 'Main'.")]
        public void Awake() {
            // The GameObject name is always "Database"
            string gameObjectName = "Database";

            // Get the parameters
            string variableName = GetParameter(0); // The name of the variable
            string value = GetParameter(1); // The value or arithmetic operation
            string fsmName = GetParameter(2); // The name of the FSM component, defaults to "Main" if blank

            // Default the FSM name to "Main" if it's blank
            fsmName = string.IsNullOrEmpty(fsmName) ? "Main" : fsmName;

            // Find the GameObject "Database"
            GameObject database = GameObject.Find(gameObjectName);
            if (database == null) {
                Debug.LogError("SequencerCommandDual: GameObject '" + gameObjectName + "' not found");
                Stop();
                return;
            }

            // Get the specified FSM component from the "Database" GameObject
            PlayMakerFSM fsm = null;
            foreach (var component in database.GetComponents<PlayMakerFSM>()) {
                if (component.FsmName.Equals(fsmName)) {
                    fsm = component;
                    break;
                }
            }

            if (fsm == null) {
                Debug.LogError("SequencerCommandDual: FSM '" + fsmName + "' not found in GameObject '" + gameObjectName + "'");
                Stop();
                return;
            }

            // Process the value or operation and set the FSM variable
            var finalValue = ProcessValueOrOperation(fsm, fsmName, variableName, value);
            SetFSMVariable(fsm, fsmName, variableName, finalValue);

            // Set the Dialogue System variable
            SetDialogueSystemVariable(fsmName, variableName, finalValue);

            // Done
            Stop();
        }

        private string ProcessValueOrOperation(PlayMakerFSM fsm, string fsmName, string variableName, string value) {
            var fsmVariable = fsm.FsmVariables.FindVariable(variableName);
            if (fsmVariable == null) {
                Debug.LogError("SequencerCommandDual: Variable '" + variableName + "' not found in FSM '" + fsmName + "'");
                return value;
            }

            if (value.Contains("+=") || value.Contains("-=") || value.Contains("*=") || value.Contains("/=")) {
                // Handle arithmetic operation
                float originalValue = 0f;
                if (fsmVariable is FsmFloat fsmFloatVar) {
                    originalValue = fsmFloatVar.Value;
                } else if (fsmVariable is FsmInt fsmIntVar) {
                    originalValue = fsmIntVar.Value;
                } else {
                    // Log debug error for incorrect variable type
                    Debug.LogError("SequencerCommandDual: Attempted operation on variable of incorrect type.");
                }

                // Perform operation
                float operationValue = float.Parse(value.Substring(2));
                if (value.StartsWith("+=")) {
                    originalValue += operationValue;
                } else if (value.StartsWith("-=")) {
                    originalValue -= operationValue;
                } else if (value.StartsWith("*=")) {
                    originalValue *= operationValue;
                } else if (value.StartsWith("/=")) {
                    if (operationValue != 0f) {
                        originalValue /= operationValue;
                    } else {
                        // Log debug error for division by zero
                        Debug.LogError("SequencerCommandDual: Division by zero error.");
                        originalValue = 0f; // Set value to 0
                    }
                }

                return originalValue.ToString();
            }

            // Direct setting of variable
            return value;
        }

        private void SetFSMVariable(PlayMakerFSM fsm, string fsmName, string variableName, string value) {
            var fsmVariable = fsm.FsmVariables.FindVariable(variableName);
            if (fsmVariable == null) {
                Debug.LogError("SequencerCommandDual: Variable '" + variableName + "' not found in FSM '" + fsmName + "'");
                return;
            }

            // Set the value based on the type of the variable
            if (fsmVariable is FsmBool fsmBoolVar) {
                fsmBoolVar.Value = bool.Parse(value);
            }
            else if (fsmVariable is FsmInt fsmIntVar) {
                fsmIntVar.Value = int.Parse(value);
            }
            else if (fsmVariable is FsmFloat fsmFloatVar) {
                fsmFloatVar.Value = float.Parse(value);
            }
            else if (fsmVariable is FsmString fsmStringVar) {
                fsmStringVar.Value = value;
            }
            else {
                Debug.LogError("SequencerCommandDual: Unsupported variable type for '" + variableName + "'");
            }
        }

        private void SetDialogueSystemVariable(string fsmName, string variableName, string value) {
            string fullVariableName = fsmName + "." + variableName;

            // Try parsing the value to different types and set accordingly
            if (bool.TryParse(value, out bool boolValue)) {
                DialogueLua.SetVariable(fullVariableName, boolValue);
            } else if (int.TryParse(value, out int intValue)) {
                DialogueLua.SetVariable(fullVariableName, intValue);
            } else if (float.TryParse(value, out float floatValue)) {
                DialogueLua.SetVariable(fullVariableName, floatValue);
            } else {
                DialogueLua.SetVariable(fullVariableName, value);
            }
        }
    }
}
The code is free to use or modify in any way.
User avatar
Tony Li
Posts: 20990
Joined: Thu Jul 18, 2013 1:27 pm

Re: Updating variables via script

Post by Tony Li »

Thanks for sharing the code!
Post Reply