• Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
Question by Vessai · Nov 24, 2016 at 01:31 AM · c#unity 5scripting problemunityeditordialogue

Polymorphism for decisions in a dialogue editor

To give some context, I am creating a custom editor window to create and edit node-based dialogue files that can then be interpreted during run time, using Unity's ScriptableObject class. It assigns each node an ID which is the same as its index in a list of nodes. Each node contains information on what will be said and what the possible responses are, as well as which nodes, defined by their IDs, each possible response links to.

The current issue I have is that, at certain points, there will be decisions that have to be made based on what choices the player has previously made in the game, how far through the game they are etc. as to where the conversation will go, and sometimes I will need to modify variable values based on how the player responds to certain things. However, I am having trouble implementing this sort of functionality. I thought about using a load of classes that derive from a base abstract class, but I ran into numerous problems with this - I didn't know how I would provide some kind of ability to assign it within the custom editor, and I'm not entirely sure how to create the necessary asset files in that case.
Is there any way that I could implement some kind of decision-making functionality in this way? I'm not sure if I've included all of the relevant information, but I'll provide any more as necessary to the best of my ability.

I'm interested in understanding what is going on and feel like I might be slightly out of my depth in what I'm trying to do, so if any solutions could be explained that would be great.

Thanks.

Comment
Bunny83

People who like this

1 Show 4
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image christoph_r · Nov 23, 2016 at 09:46 PM 0
Share

What exactly are you asking for? A way to implement node-specific checks that can be selected (or even edited) in your custom editor?

avatar image Vessai christoph_r · Nov 23, 2016 at 10:51 PM 0
Share

Yes, some kind of way of executing a piece of code, not necessarily created within the editor itself, that would be able to decide, based on variables within the game, which path the conversation should take. I would also like it to be able to modify existing variables, if, for example, a specific response to a question is chosen that causes the NPC to react differently to the player from then on.

avatar image christoph_r Vessai · Nov 24, 2016 at 01:22 AM 0
Share

So essentially you need a way to selectively run code based on values you can set in your code editor. There is actually a large number of ways of doing it, but finding a solution that scales well is a bit more tricky. Why exactly aren't ScriptableObjects an option, with different types of ScriptableObjects equaling certain actions? You could create multiple instances of those ScriptableObjects with varying parameters that could then be assigned accordingly to your dialogue 'action slots'.

Show more comments

1 Reply

· Add your reply
  • Sort: 
avatar image
Best Answer

Answer by Bunny83 · Nov 24, 2016 at 04:19 PM

Well, it all highly depends on how your system is actually used at runtime. Pure dialog-trees are completely static data that is simply used at runtime. However you want now an information flow in the opposite direction. So your static dialog data should reference runtime state. This is in general a bit tricky.

The easiest way would probably be introducing "variables" / parameteres just like the Mecanim system does. It uses this class to represent a parameter which is created inside the mecanim system.

The more variable types you want to support the more complicated / complex things might get. In theory a float would be enough to represent fractional number, whole numbers and booleans.

The link between your dialog nodes and the variables should be made by a string. Indices are dangerous as when you remove an element from the beginning the indices after would be wrong. You also need a name for each variable to actually "set" the value from scripting.

For example something like that:

 [System.Serializable]
 public class DynVariable
 {
     public string name;
     public float initialValue;
     private float currentValue;
     public float Value
     {
         get { return currentValue; }
         set { currentValue = value; }
     }
 }
 
 public class YourDialogMasterScript : ScriptableObject
 {
     public List<DynVariable> variables;
     private Dictionary<string, DynVariable> varLookup = null;
     public void SetVariable(string aName, float aValue)
     {
         if (varLookup == null)
             InitVariables();
         DynVariable tmp;
         if (varLookup.TryGetValue(aName, out tmp))
         {
             tmp.Value = aValue;
             return;
         }
         Debug.LogWarning("SetVariable: variable with name '" + aName + "' doesn't exist");
     }
     public float GetVariable(string aName)
     {
         if (varLookup == null)
             InitVariables();
         DynVariable tmp;
         if (varLookup.TryGetValue(aName, out tmp))
         {
             return tmp.Value;
         }
         Debug.LogWarning("GetVariable: variable with name '"+aName+"' doesn't exist");
         return 0f;
     }
     void InitVariables()
     {
         varLookup = new Dictionary<string, DynVariable>();
         for (int i = 0; i < variables.Count; i++)
         {
             varLookup.Add(variables[i].name, variables[i]);
             variables[i].Value = variables[i].initialValue;
         }
 
     }
     void OnEnable()
     {
         if (varLookup == null)
             InitVariables();
     }
 }
 
 public enum EComparison
 {
     Equal,
     NotEqual,
     Greater,
     GreaterOrEqual,
     Lower,
     LowerOrEqual,
 }
 
 [System.Serializable]
 public class DynVarCondition
 {
     public string variableName;
     public EComparison comparison;
     public float refValue;
     public bool roundToInt = false;
     public bool ConditionMet(YourDialogMasterScript aMaster)
     {
         float v = aMaster.GetVariable(variableName);
         if (roundToInt)
             v = Mathf.RoundToInt(v);
         switch(comparison)
         {
             case EComparison.NotEqual: return v != refValue;
             case EComparison.Greater: return v > refValue;
             case EComparison.GreaterOrEqual: return v >= refValue;
             case EComparison.Lower: return v < refValue;
             case EComparison.LowerOrEqual: return v <= refValue;
             case EComparison.Equal: 
             default: return v == refValue;
         }
     }
 }
 
 public class YourDialogNodeTransition
 {
     public List<DynVarCondition> conditions;
     public bool ConditionsMet(YourDialogMasterScript aMaster)
     {
         for (int i = 0; i < conditions.Count; i++)
             if (!conditions[i].ConditionMet(aMaster))
                 return false;
         return true;
     }
 }

DynVariable represents an actual "variable". They are defined in your dialog master script or any other global script. From scripting you can use the GetVariable and SetVariable methods to read and write the variable at runtime.

Inside your dialog node transitions or other place where you need an external condition you simply add a list of "DynVarConditions". There the user can specify a variable name, a reference value and how they should be compared. At runtime when you would use the "ConditionsMet" method to evaluate if the conditions are currently met or not.

Note that all conditions need to be met. So it's an implicit "and" connection between them. If you want an optional "or" it gets way more complicated as you would need a true evaluation tree. The WarCraft3 map editor uses such a node based evaluation tree, but that's far more than a dialog editor but a whole visual scripting solution. You have to decide how much you want to solve visually and hom much you want to solve via scripting. Some things are way easier to solve with a simple C# script.

As for the usability you could create some custom inspectors / editorwindows for editing those variables / conditions so the user can't create duplicates (with the same name) and can only select existing variables from a list instead of typing in the variable name. But that's not necessary.

How you actually implement that in your current system is up to you ^^.

Comment
Vessai
christoph_r

People who like this

2 Show 2 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Bunny83 · Nov 24, 2016 at 04:52 PM 1
Share

ps: The warcraft3 condition tree is really easy and straight forward to use, however creating such a system requires a lot of classes and to get the usability right a lot of editor scripting. So it's your decision if it's worth implementing ^^.

I've written this ExpressionParser some time ago, however it only evaluates mathematical expressions and not logical expressions ^^.

avatar image Vessai · Nov 24, 2016 at 08:22 PM 0
Share

Awesome, this looks like it should be able to do everything I need! I probably should have thought of this considering how much I've been using the mecanim system recently.

In response to the concerns about using indices - I have dealt with the problems you listed. when I want to remove a node, I simply set that item in the list to null, and have a check when drawing the nodes for the ones that are null, and when adding a node, I check if there are any null nodes and decide where the new node should be added accordingly - it all seems to be working so far. It might not be incredibly efficient, but it taught me a fair few things working out how to sort out something like that.

I think I've got it sorted in my head how I would implement the system you've suggested, so I'll get on it. Thanks for the help!

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Welcome to Unity Answers

If you’re new to Unity Answers, please check our User Guide to help you navigate through our website and refer to our FAQ for more information.

Before posting, make sure to check out our Knowledge Base for commonly asked Unity questions.

Check our Moderator Guidelines if you’re a new moderator and want to work together in an effort to improve Unity Answers and support our users.

Follow this Question

Answers Answers and Comments

7 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Unity Build not taking touch input. Game works fine in Unity Remote 0 Answers

Built project, now scripts are missing. 2 Answers

Multiple Cars not working 1 Answer

RayCast not working 5 Answers

Can I make Money collecting script without attaching it to a object ? I tried but I got error 2 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges