How to Create an Opinion System between Characters

Let us say that I have a Scriptable Object ‘Characters’ and I create three Characters of that Scriptable Object. Now I want three of the characters to have a opinion (int value) about the other two characters. Like Character 1 has 30 opinion about Character 2 and Character 1 has -30 opinion about Character 2.

The easy way I think is to create two ints in the Characters Scriptable Object but if I had 1000 Characters, do I have to create 1000 ints. Any idea on how to create this system?

Here is how I would do it:

  1. Create a new class inheriting from ScriptableObject
  2. In this class, declare a 2D array containing the opinions, and an array containing your characters
  3. Make a custom inspector to display this grid

EDIT : Because I have already faced you problem in the past, but never took the time to implement a solution, I decided to give it a try. However, you will have to make some changes to fit your Character class.

Character.cs

using UnityEngine;

[CreateAssetMenu()]
public class Character : ScriptableObject
{
    public string Name;
}

OpinionsTable.cs

using UnityEngine;


[CreateAssetMenu()]
public class OpinionsTable : ScriptableObject
{
    [SerializeField]
    private Character[] characters;

    [SerializeField]
    private int[] opinions;

    public int GetOpinion( Character character1, Character character2 )
    {
        for( int i = 0 ; i < characters.Length ; ++i )
        {
            if( character1 == characters *)*

{
for( int j = 0 ; j < characters.Length ; ++j )
{
if( character2 == characters[j] )
return opinions[i * characters.Length + j];;
}
Debug.LogError( “Character2 not found” );
}
}
Debug.LogError( “Character1 not found” );
return -1;
}
public void SetOpinion( Character character1, Character character2, int opinion )
{
for( int i = 0 ; i < characters.Length ; ++i )
{
if( character1 == characters )
{
for( int j = 0 ; j < characters.Length ; ++j )
{
if( character2 == characters[j] )
{
opinions[i * characters.Length + j] = opinion;
// Remove the following line if Opinion( A, B ) must be different from Opinion( B, A )
opinions[j * characters.Length + i] = opinion;
return ;
}
}
Debug.LogError( “Character2 not found” );
}
}
Debug.LogError( “Character1 not found” );
}
}
----
## OpinionsTableEditor.cs
>> IN A FOLDER CALLED EDITOR <<
using UnityEngine;
using UnityEditor;

[CustomEditor( typeof( OpinionsTable ) )]
public class OpinionsTableEditor : Editor
{
const float opinionsLabelWidth = 50;
const float opinionCellSize = 25;
SerializedProperty characters;
SerializedProperty opinions;
int opinionsTableWidth = 0;
Rect opinionsTableRect;

void OnEnable()
{
// Retrieve the serialized properties
characters = serializedObject.FindProperty( “characters” );
opinions = serializedObject.FindProperty( “opinions” );
}

public override void OnInspectorGUI()
{
serializedObject.Update();

// Check if the number of characters has been changed
// If so, resize the opinions
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField( characters, true );
if( EditorGUI.EndChangeCheck() )
{
opinions.arraySize = characters.arraySize * characters.arraySize;
}

// Draw opinions if there is more than one character
if ( opinions.arraySize > 1 )
DrawOpinions( opinions, characters );
else
EditorGUILayout.LabelField( “Not enough characters to draw opinions matrix” );

serializedObject.ApplyModifiedProperties();
}

private void DrawOpinions( SerializedProperty opinions, SerializedProperty characters )
{
int charactersCount = characters.arraySize;
if ( Event.current.type == EventType.Layout )
opinionsTableWidth = Mathf.FloorToInt( EditorGUIUtility.currentViewWidth );

// Get the rect of the whole matric, labels included
Rect rect = GUILayoutUtility.GetRect(opinionsTableWidth, opinionsTableWidth, EditorStyles.inspectorDefaultMargins);

if ( opinionsTableWidth > 0 && Event.current.type == EventType.Repaint )
opinionsTableRect = rect;

// Draw matrix and labels only if the rect has been computed
if( opinionsTableRect.width > 0 )
{
// Compute size of opinion cell
float cellWidth = Mathf.Min( (opinionsTableRect.width - opinionsLabelWidth) / charactersCount, opinionCellSize );
Rect opinionCell = new Rect( opinionsTableRect.x + opinionsLabelWidth, opinionsTableRect.y + opinionsLabelWidth, cellWidth, cellWidth );
Matrix4x4 guiMatrix = GUI.matrix;

// Draw vertical labels
for ( int i = 1 ; i <= charactersCount ; ++i )
{
Rect verticalLabelRect = new Rect( opinionsTableRect.x + opinionsLabelWidth + i * opinionCell.width, opinionsTableRect.y, opinionsLabelWidth, opinionsLabelWidth );
Character character = characters.GetArrayElementAtIndex( i - 1 ).objectReferenceValue as Character;
EditorGUIUtility.RotateAroundPivot( 90f, new Vector2( verticalLabelRect.x, verticalLabelRect.y ) );
EditorGUI.LabelField( verticalLabelRect, character == null ? “???” : character.Name );
GUI.matrix = guiMatrix;
}

// Draw matrix
for ( int i = 0 ; i < charactersCount ; ++i )
{
// Draw horizontal labels
SerializedProperty characterProperty = characters.GetArrayElementAtIndex( i );
Character character = characterProperty == null ? null : characters.GetArrayElementAtIndex( i ).objectReferenceValue as Character;
EditorGUI.LabelField( new Rect( opinionsTableRect.x, opinionCell.y, opinionsLabelWidth, opinionCell.height ), character == null ? “???” : character.Name ) ;

for ( int j = 0 ; j < charactersCount ; ++j )
{
opinionCell.x = opinionsTableRect.x + opinionsLabelWidth + j * cellWidth;
if ( j > i )
{
SerializedProperty opinion = opinions.GetArrayElementAtIndex( i * charactersCount + j );
opinion.intValue = EditorGUI.IntField( opinionCell, opinion.intValue );
}
// Remove following else if Opinion( A, B ) must be different from Opinion( B, A )
else
{
// Put grey box because the matrix is symmetrical
EditorGUI.DrawRect( opinionCell, Color.grey );
}
}
opinionCell.y += cellWidth;
}
}
}
}
## Result
[135558-opinionstable.png|135558]_
_

If you want to store the opinion value for other characters within each character you can simply do:

[System.Serializable]
public class Opinion
{
    public Characters other;
    public int opinion;
}

Inside the “Characters” class (which you really need to rename to “Character”) you can declare an array or list of that Opinion class

public List<Opinion> opinions;

Now you can add as many relations between your scriptable objects in the inspector. Just increase the count and drag and drop the other character into the “other” slot and set the value you want.

To read that information at runtime you would need to iterate through the list and find the character you’re interested in. If you need to do this alot, it would make sense to create a dictionary at startup for easy and quick lookup:

Dictionary<Characters, Opinion> opinionLookup = new Dictionary<Characters, Opinion>();
void Start()
{
    foreach(var op in opinions)
    {
        opinionLookup[op.other] = op;
    }
}

int GetOpinion(Character aOther)
{
    Opinion op;
    if (opinionLookup.TryGetValue(aOther, out op))
        return op.opinion;
    return -1; // default return value if no opinion exists for this other character in this one
}