Reactions

Checked with version: 5.5

-

Difficulty: Intermediate

Phase 4 continues teaching how to build an interaction system focusing on reactions. We will create a system to perform actions based on condition state using polymorphism, further editor scripting and discuss serialization. Download the asset package for Phase 4 here.

Reactions

Intermediate Adventure Game Tutorial

Download the asset package for Phase 4 at the Unity Asset Store here.

ReactionCollectionEditor

Code snippet

using System;
using UnityEngine;
using System.Collections.Generic;
using UnityEditor;
[CustomEditor(typeof(ReactionCollection))]
public class ReactionCollectionEditor : EditorWithSubEditors<ReactionEditor, Reaction>
{
    private ReactionCollection reactionCollection;
    private SerializedProperty reactionsProperty;
    private Type[] reactionTypes;
    private string[] reactionTypeNames;
    private int selectedIndex;
    private const float dropAreaHeight = 50f;
    private const float controlSpacing = 5f;
    private const string reactionsPropName = "reactions";
    private readonly float verticalSpacing = EditorGUIUtility.standardVerticalSpacing;
    private void OnEnable ()
    {
        reactionCollection = (ReactionCollection)target;
        reactionsProperty = serializedObject.FindProperty(reactionsPropName);
        CheckAndCreateSubEditors (reactionCollection.reactions);
        SetReactionNamesArray ();
    }
    private void OnDisable ()
    {
        CleanupEditors ();
    }
    protected override void SubEditorSetup (ReactionEditor editor)
    {
        editor.reactionsProperty = reactionsProperty;
    }
    public override void OnInspectorGUI ()
    {
        serializedObject.Update ();
        CheckAndCreateSubEditors(reactionCollection.reactions);
        for (int i = 0; i < subEditors.Length; i++)
        {
            subEditors[i].OnInspectorGUI ();
        }
        if (reactionCollection.reactions.Length > 0)
        {
            EditorGUILayout.Space();
            EditorGUILayout.Space ();
        }
        Rect fullWidthRect = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.Height(dropAreaHeight + verticalSpacing));
        Rect leftAreaRect = fullWidthRect;
        leftAreaRect.y += verticalSpacing * 0.5f;
        leftAreaRect.width *= 0.5f;
        leftAreaRect.width -= controlSpacing * 0.5f;
        leftAreaRect.height = dropAreaHeight;
        Rect rightAreaRect = leftAreaRect;
        rightAreaRect.x += rightAreaRect.width + controlSpacing;
        TypeSelectionGUI (leftAreaRect);
        DragAndDropAreaGUI (rightAreaRect);
        DraggingAndDropping(rightAreaRect, this);
        serializedObject.ApplyModifiedProperties ();
    }
    private void TypeSelectionGUI (Rect containingRect)
    {
        Rect topHalf = containingRect;
        topHalf.height *= 0.5f;
        
        Rect bottomHalf = topHalf;
        bottomHalf.y += bottomHalf.height;
        selectedIndex = EditorGUI.Popup(topHalf, selectedIndex, reactionTypeNames);
        if (GUI.Button (bottomHalf, "Add Selected Reaction"))
        {
            Type reactionType = reactionTypes[selectedIndex];
            Reaction newReaction = ReactionEditor.CreateReaction (reactionType);
            reactionsProperty.AddToObjectArray (newReaction);
        }
    }
    private static void DragAndDropAreaGUI (Rect containingRect)
    {
        GUIStyle centredStyle = GUI.skin.box;
        centredStyle.alignment = TextAnchor.MiddleCenter;
        centredStyle.normal.textColor = GUI.skin.button.normal.textColor;
        GUI.Box (containingRect, "Drop new Reactions here", centredStyle);
    }
    private static void DraggingAndDropping (Rect dropArea, ReactionCollectionEditor editor)
    {
        Event currentEvent = Event.current;
        if (!dropArea.Contains (currentEvent.mousePosition))
            return;
        switch (currentEvent.type)
        {
            case EventType.DragUpdated:
                DragAndDrop.visualMode = IsDragValid () ? DragAndDropVisualMode.Link : DragAndDropVisualMode.Rejected;
                currentEvent.Use ();
                break;
            case EventType.DragPerform:
                
                DragAndDrop.AcceptDrag();
                
                for (int i = 0; i < DragAndDrop.objectReferences.Length; i++)
                {
                    MonoScript script = DragAndDrop.objectReferences[i] as MonoScript;
                    Type reactionType = script.GetClass();
                    Reaction newReaction = ReactionEditor.CreateReaction (reactionType);
                    editor.reactionsProperty.AddToObjectArray (newReaction);
                }
                currentEvent.Use();
                break;
        }
    }
    private static bool IsDragValid ()
    {
        for (int i = 0; i < DragAndDrop.objectReferences.Length; i++)
        {
            if (DragAndDrop.objectReferences[i].GetType () != typeof (MonoScript))
                return false;
            
            MonoScript script = DragAndDrop.objectReferences[i] as MonoScript;
            Type scriptType = script.GetClass ();
            if (!scriptType.IsSubclassOf (typeof(Reaction)))
                return false;
            if (scriptType.IsAbstract)
                return false;
        }
        return true;
    }
    private void SetReactionNamesArray ()
    {
        Type reactionType = typeof(Reaction);
        Type[] allTypes = reactionType.Assembly.GetTypes();
        List<Type> reactionSubTypeList = new List<Type>();
        for (int i = 0; i < allTypes.Length; i++)
        {
            if (allTypes[i].IsSubclassOf(reactionType) && !allTypes[i].IsAbstract)
            {
                reactionSubTypeList.Add(allTypes[i]);
            }
        }
        reactionTypes = reactionSubTypeList.ToArray();
        List<string> reactionTypeNameList = new List<string>();
        for (int i = 0; i < reactionTypes.Length; i++)
        {
            reactionTypeNameList.Add(reactionTypes[i].Name);
        }
        reactionTypeNames = reactionTypeNameList.ToArray();
    }
}

TextReaction

Code snippet

using UnityEngine;
public class TextReaction : Reaction
{
    public string message;
    public Color textColor = Color.white;
    public float delay;
    private TextManager textManager;
    protected override void SpecificInit()
    {
        textManager = FindObjectOfType<TextManager> ();
    }
    protected override void ImmediateReaction()
    {
        textManager.DisplayMessage (message, textColor, delay);
    }
}

TextReactionEditor

Code snippet

using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(TextReaction))]
public class TextReactionEditor : ReactionEditor
{
    private SerializedProperty messageProperty;
    private SerializedProperty textColorProperty;
    private SerializedProperty delayProperty;
    private const float messageGUILines = 3f;
    private const float areaWidthOffset = 19f;
    private const string textReactionPropMessageName = "message";
    private const string textReactionPropTextColorName = "textColor";
    private const string textReactionPropDelayName = "delay";
    protected override void Init ()
    {
        messageProperty = serializedObject.FindProperty (textReactionPropMessageName);
        textColorProperty = serializedObject.FindProperty (textReactionPropTextColorName);
        delayProperty = serializedObject.FindProperty (textReactionPropDelayName);
    }
    protected override void DrawReaction ()
    {
        EditorGUILayout.BeginHorizontal ();
        EditorGUILayout.LabelField ("Message", GUILayout.Width (EditorGUIUtility.labelWidth - areaWidthOffset));
        messageProperty.stringValue = EditorGUILayout.TextArea (messageProperty.stringValue, GUILayout.Height (EditorGUIUtility.singleLineHeight * messageGUILines));
        EditorGUILayout.EndHorizontal ();
        EditorGUILayout.PropertyField (textColorProperty);
        EditorGUILayout.PropertyField (delayProperty);
    }
    protected override string GetFoldoutLabel ()
    {
        return "Text Reaction";
    }
}