/// <summary>
        /// Recreates the entire curve editor GUI depending on the currently selected scene object.
        /// </summary>
        private void RebuildGUI()
        {
            GUI.Clear();
            guiCurveEditor = null;
            guiFieldDisplay = null;

            if (selectedSO == null)
            {
                GUILabel warningLbl = new GUILabel(new LocEdString("Select an object to animate in the Hierarchy or Scene windows."));

                GUILayoutY vertLayout = GUI.AddLayoutY();
                vertLayout.AddFlexibleSpace();
                GUILayoutX horzLayout = vertLayout.AddLayoutX();
                vertLayout.AddFlexibleSpace();

                horzLayout.AddFlexibleSpace();
                horzLayout.AddElement(warningLbl);
                horzLayout.AddFlexibleSpace();

                return;
            }

            // Top button row
            GUIContent playIcon = new GUIContent(EditorBuiltin.GetAnimationWindowIcon(AnimationWindowIcon.Play),
                new LocEdString("Play"));
            GUIContent recordIcon = new GUIContent(EditorBuiltin.GetAnimationWindowIcon(AnimationWindowIcon.Record),
                new LocEdString("Record"));

            GUIContent prevFrameIcon = new GUIContent(EditorBuiltin.GetAnimationWindowIcon(AnimationWindowIcon.FrameBack),
                new LocEdString("Previous frame"));
            GUIContent nextFrameIcon = new GUIContent(EditorBuiltin.GetAnimationWindowIcon(AnimationWindowIcon.FrameForward),
                new LocEdString("Next frame"));

            GUIContent addKeyframeIcon = new GUIContent(EditorBuiltin.GetAnimationWindowIcon(AnimationWindowIcon.AddKeyframe),
                new LocEdString("Add keyframe"));
            GUIContent addEventIcon = new GUIContent(EditorBuiltin.GetAnimationWindowIcon(AnimationWindowIcon.AddEvent),
                new LocEdString("Add event"));

            GUIContent optionsIcon = new GUIContent(EditorBuiltin.GetLibraryWindowIcon(LibraryWindowIcon.Options),
                new LocEdString("Options"));

            playButton = new GUIToggle(playIcon, EditorStyles.Button);
            recordButton = new GUIToggle(recordIcon, EditorStyles.Button);

            prevFrameButton = new GUIButton(prevFrameIcon);
            frameInputField = new GUIIntField();
            nextFrameButton = new GUIButton(nextFrameIcon);

            addKeyframeButton = new GUIButton(addKeyframeIcon);
            addEventButton = new GUIButton(addEventIcon);

            optionsButton = new GUIButton(optionsIcon);

            playButton.OnToggled += x =>
            {
                if(x)
                    SwitchState(State.Playback);
                else
                    SwitchState(State.Normal);
            };

            recordButton.OnToggled += x =>
            {
                if (x)
                    SwitchState(State.Recording);
                else
                    SwitchState(State.Normal);
            };

            prevFrameButton.OnClick += () =>
            {
                SetCurrentFrame(currentFrameIdx - 1);

                switch (state)
                {
                    case State.Recording:
                    case State.Normal:
                        PreviewFrame(currentFrameIdx);
                        break;
                    default:
                        SwitchState(State.Normal);
                        break;
                }
            };

            frameInputField.OnChanged += x =>
            {
                SetCurrentFrame(x);

                switch (state)
                {
                    case State.Recording:
                    case State.Normal:
                        PreviewFrame(currentFrameIdx);
                        break;
                    default:
                        SwitchState(State.Normal);
                        break;
                }
            };

            nextFrameButton.OnClick += () =>
            {
                SetCurrentFrame(currentFrameIdx + 1);

                switch (state)
                {
                    case State.Recording:
                    case State.Normal:
                        PreviewFrame(currentFrameIdx);
                        break;
                    default:
                        SwitchState(State.Normal);
                        break;
                }
            };

            addKeyframeButton.OnClick += () =>
            {
                SwitchState(State.Normal);
                guiCurveEditor.AddKeyFrameAtMarker();
            };

            addEventButton.OnClick += () =>
            {
                SwitchState(State.Normal);
                guiCurveEditor.AddEventAtMarker();
            };

            optionsButton.OnClick += () =>
            {
                Vector2I openPosition = ScreenToWindowPos(Input.PointerPosition);
                AnimationOptions dropDown = DropDownWindow.Open<AnimationOptions>(this, openPosition);
                dropDown.Initialize(this);
            };

            // Property buttons
            addPropertyBtn = new GUIButton(new LocEdString("Add property"));
            delPropertyBtn = new GUIButton(new LocEdString("Delete selected"));

            addPropertyBtn.OnClick += () =>
            {
                Action openPropertyWindow = () =>
                {
                    Vector2I windowPos = ScreenToWindowPos(Input.PointerPosition);
                    FieldSelectionWindow fieldSelection = DropDownWindow.Open<FieldSelectionWindow>(this, windowPos);
                    fieldSelection.OnFieldSelected += OnFieldAdded;
                };

                if (clipInfo.clip == null)
                {
                    LocEdString title = new LocEdString("Warning");
                    LocEdString message =
                        new LocEdString("Selected object doesn't have an animation clip assigned. Would you like to create" +
                                        " a new animation clip?");

                    DialogBox.Open(title, message, DialogBox.Type.YesNoCancel, type =>
                    {
                        if (type == DialogBox.ResultType.Yes)
                        {
                            string clipSavePath;
                            if (BrowseDialog.SaveFile(ProjectLibrary.ResourceFolder, "*.asset", out clipSavePath))
                            {
                                SwitchState(State.Empty);
                                clipSavePath = Path.ChangeExtension(clipSavePath, ".asset");

                                AnimationClip newClip = new AnimationClip();

                                ProjectLibrary.Create(newClip, clipSavePath);
                                LoadAnimClip(newClip);

                                Animation animation = selectedSO.GetComponent<Animation>();
                                if (animation == null)
                                    animation = selectedSO.AddComponent<Animation>();

                                animation.DefaultClip = newClip;
                                EditorApplication.SetSceneDirty();

                                SwitchState(State.Normal);
                                openPropertyWindow();
                            }
                        }
                    });
                }
                else
                {
                    if (clipInfo.isImported)
                    {
                        LocEdString title = new LocEdString("Warning");
                        LocEdString message =
                            new LocEdString("You cannot add/edit/remove curves from animation clips that" +
                                            " are imported from an external file.");

                        DialogBox.Open(title, message, DialogBox.Type.OK);
                    }
                    else
                    {
                        SwitchState(State.Normal);
                        openPropertyWindow();
                    }
                }
            };

            delPropertyBtn.OnClick += () =>
            {
                if (clipInfo.clip == null)
                    return;

                SwitchState(State.Normal);

                if (clipInfo.isImported)
                {
                    LocEdString title = new LocEdString("Warning");
                    LocEdString message =
                        new LocEdString("You cannot add/edit/remove curves from animation clips that" +
                                        " are imported from an external file.");

                    DialogBox.Open(title, message, DialogBox.Type.OK);
                }
                else
                {
                    LocEdString title = new LocEdString("Warning");
                    LocEdString message = new LocEdString("Are you sure you want to remove all selected fields?");

                    DialogBox.Open(title, message, DialogBox.Type.YesNo, x =>
                    {
                        if (x == DialogBox.ResultType.Yes)
                        {
                            RemoveSelectedFields();
                            ApplyClipChanges();
                        }
                    });
                }
            };

            GUIPanel mainPanel = GUI.AddPanel();
            GUIPanel backgroundPanel = GUI.AddPanel(1);

            GUILayout mainLayout = mainPanel.AddLayoutY();

            buttonLayout = mainLayout.AddLayoutX();
            buttonLayout.AddSpace(5);
            buttonLayout.AddElement(playButton);
            buttonLayout.AddElement(recordButton);
            buttonLayout.AddSpace(5);
            buttonLayout.AddElement(prevFrameButton);
            buttonLayout.AddElement(frameInputField);
            buttonLayout.AddElement(nextFrameButton);
            buttonLayout.AddSpace(5);
            buttonLayout.AddElement(addKeyframeButton);
            buttonLayout.AddElement(addEventButton);
            buttonLayout.AddSpace(5);
            buttonLayout.AddElement(optionsButton);
            buttonLayout.AddFlexibleSpace();

            buttonLayoutHeight = playButton.Bounds.height;

            GUITexture buttonBackground = new GUITexture(null, EditorStyles.HeaderBackground);
            buttonBackground.Bounds = new Rect2I(0, 0, Width, buttonLayoutHeight);
            backgroundPanel.AddElement(buttonBackground);

            GUILayout contentLayout = mainLayout.AddLayoutX();
            GUILayout fieldDisplayLayout = contentLayout.AddLayoutY(GUIOption.FixedWidth(FIELD_DISPLAY_WIDTH));

            guiFieldDisplay = new GUIAnimFieldDisplay(fieldDisplayLayout, FIELD_DISPLAY_WIDTH,
                Height - buttonLayoutHeight * 2, selectedSO);
            guiFieldDisplay.OnEntrySelected += OnFieldSelected;

            GUILayout bottomButtonLayout = fieldDisplayLayout.AddLayoutX();
            bottomButtonLayout.AddElement(addPropertyBtn);
            bottomButtonLayout.AddElement(delPropertyBtn);

            horzScrollBar = new GUIResizeableScrollBarH();
            horzScrollBar.OnScrollOrResize += OnHorzScrollOrResize;

            vertScrollBar = new GUIResizeableScrollBarV();
            vertScrollBar.OnScrollOrResize += OnVertScrollOrResize;

            GUITexture separator = new GUITexture(null, EditorStyles.Separator, GUIOption.FixedWidth(3));
            contentLayout.AddElement(separator);

            GUILayout curveLayout = contentLayout.AddLayoutY();
            GUILayout curveLayoutHorz = curveLayout.AddLayoutX();
            GUILayout horzScrollBarLayout = curveLayout.AddLayoutX();
            horzScrollBarLayout.AddElement(horzScrollBar);
            horzScrollBarLayout.AddFlexibleSpace();

            editorPanel = curveLayoutHorz.AddPanel();
            curveLayoutHorz.AddElement(vertScrollBar);
            curveLayoutHorz.AddFlexibleSpace();

            scrollBarHeight = horzScrollBar.Bounds.height;
            scrollBarWidth = vertScrollBar.Bounds.width;

            Vector2I curveEditorSize = GetCurveEditorSize();
            guiCurveEditor = new GUICurveEditor(this, editorPanel, curveEditorSize.x, curveEditorSize.y);
            guiCurveEditor.OnFrameSelected += OnFrameSelected;
            guiCurveEditor.OnEventAdded += OnEventsChanged;
            guiCurveEditor.OnEventModified += EditorApplication.SetProjectDirty;
            guiCurveEditor.OnEventDeleted += OnEventsChanged;
            guiCurveEditor.OnCurveModified += () =>
            {
                SwitchState(State.Normal);

                ApplyClipChanges();
                PreviewFrame(currentFrameIdx);

                EditorApplication.SetProjectDirty();
            };
            guiCurveEditor.OnClicked += () =>
            {
                if(state != State.Recording)
                    SwitchState(State.Normal);
            };
            guiCurveEditor.Redraw();

            horzScrollBar.SetWidth(curveEditorSize.x);
            vertScrollBar.SetHeight(curveEditorSize.y);

            UpdateScrollBarSize();
        }
 private static extern void Internal_CreateInstance(AnimationClip instance);
        /// <summary>
        /// Refreshes the contents of the curve and property display by loading animation curves from the provided
        /// animation clip.
        /// </summary>
        /// <param name="clip">Clip containing the animation to load.</param>
        private void LoadAnimClip(AnimationClip clip)
        {
            EditorPersistentData persistentData = EditorApplication.PersistentData;

            if (persistentData.dirtyAnimClips.TryGetValue(clip.UUID, out clipInfo))
            {
                // If an animation clip is imported, we don't care about it's cached curve values as they could have changed
                // since last modification, so we re-load the clip. But we persist the events as those can only be set
                // within the editor.
                if (clipInfo.isImported)
                {
                    EditorAnimClipInfo newClipInfo = EditorAnimClipInfo.Create(clip);
                    newClipInfo.events = clipInfo.events;
                }
            }
            else
                clipInfo = EditorAnimClipInfo.Create(clip);

            persistentData.dirtyAnimClips[clip.UUID] = clipInfo;

            AnimFieldInfo[] fieldInfos = new AnimFieldInfo[clipInfo.curves.Count];

            int idx = 0;
            foreach (var curve in clipInfo.curves)
                fieldInfos[idx++] = new AnimFieldInfo(curve.Key, curve.Value);

            guiFieldDisplay.SetFields(fieldInfos);

            guiCurveEditor.Events = clipInfo.events;
            guiCurveEditor.DisableCurveEdit = clipInfo.isImported;

            SetCurrentFrame(0);
            FPS = clipInfo.sampleRate;
        }
Example #4
0
        /// <summary>
        /// Rebuilds internal curve -> property mapping about the currently playing animation clip. This mapping allows the
        /// animation component to know which property to assign which values from an animation curve. This Should be called
        /// whenever playback for a new clip starts, or when clip curves change.
        /// </summary>
        internal void RefreshClipMappings()
        {
            primaryClip = _native.GetClip(0);

            RebuildFloatProperties(primaryClip);
            UpdateSceneObjectMapping();
        }
Example #5
0
        /// <summary>
        /// Called whenever an animation event triggers.
        /// </summary>
        /// <param name="clip">Clip that the event originated from.</param>
        /// <param name="name">Name of the event.</param>
        private void EventTriggered(AnimationClip clip, string name)
        {
            // Event should be in format "ComponentType/MethodName"
            if (string.IsNullOrEmpty(name))
                return;

            string[] nameEntries = name.Split('/');
            if (nameEntries.Length != 2)
                return;

            string typeName = nameEntries[0];
            string methodName = nameEntries[1];

            Component[] components = SceneObject.GetComponents();
            for (int i = 0; i < components.Length; i++)
            {
                if (components[i].GetType().Name == typeName)
                {
                    components[i].Invoke(methodName);
                    break;
                }
            }
        }
Example #6
0
 /// <summary>
 /// Changes the state of a playing animation clip. If animation clip is not currently playing the state change is
 /// ignored.
 /// </summary>
 /// <param name="clip">Clip to change the state for.</param>
 /// <param name="state">New state of the animation (e.g. changing the time for seeking).</param>
 public void SetState(AnimationClip clip, AnimationClipState state)
 {
     switch (this.state)
     {
         case State.Active:
         case State.EditorActive:
             _native.SetState(clip, state);
             break;
     }
 }
Example #7
0
        /// <summary>
        /// Allows the caller to play an animation clip during edit mode. This form of animation playback is limited as
        /// you have no control over clip properties, and features like blending, cross fade or animation events are not
        /// supported.
        /// 
        /// Caller will need to manually call <see cref="UpdateFloatProperties"/> in order to apply evaluated animation data
        /// to relevant float properties (if required).
        /// 
        /// Caller will also need to manually call <see cref="RefreshClipMappings"/> whenever the curves internal to the 
        /// animation clip change. This should be called before the call to <see cref="UpdateFloatProperties"/>.
        /// </summary>
        /// <param name="clip">Animation clip to play.</param>
        /// <param name="startTime">Time to start playing at, in seconds.</param>
        /// <param name="freeze">If true, only the frame at the specified time will be shown, without advancing the 
        ///                      animation.</param>
        internal void EditorPlay(AnimationClip clip, float startTime, bool freeze = false)
        {
            switch (state)
            {
                case State.Inactive:
                    SwitchState(State.EditorActive);
                    break;
            }

            switch (state)
            {
                case State.EditorActive:
                    if (freeze)
                        Sample(clip, startTime);
                    else
                    {
                        AnimationClipState clipState = AnimationClipState.Create();
                        clipState.time = startTime;

                        SetState(clip, clipState);
                    }

                    RefreshClipMappings();

                    break;
            }
        }
Example #8
0
 /// <summary>
 /// Plays the specified animation clip. 
 /// </summary>
 /// <param name="clip">Clip to play.</param>
 public void Play(AnimationClip clip)
 {
     switch (state)
     {
         case State.Active:
             _native.Play(clip);
             break;
     }
 }
Example #9
0
 /// <summary>
 /// Samples an animation clip at the specified time, displaying only that particular frame without further playback.
 /// </summary>
 /// <param name="clip">Animation clip to sample.</param>
 /// <param name="time">Time to sample the clip at.</param>
 public void Sample(AnimationClip clip, float time)
 {
     switch (state)
     {
         case State.Active:
         case State.EditorActive:
             _native.Sample(clip, time);
             break;
     }
 }
Example #10
0
 /// <summary>
 /// Retrieves detailed information about a currently playing animation clip.
 /// </summary>
 /// <param name="clip">Clip to retrieve the information for.</param>
 /// <param name="state">Animation clip state containing the requested information. Only valid if the method returns
 ///                     true.</param>
 /// <returns>True if the state was found (animation clip is playing), false otherwise.</returns>
 public bool GetState(AnimationClip clip, out AnimationClipState state)
 {
     switch (this.state)
     {
         case State.Active:
         case State.EditorActive:
             return _native.GetState(clip, out state);
         default:
             state = new AnimationClipState();
             return false;
     }
 }
Example #11
0
 /// <summary>
 /// Fades the specified animation clip in, while fading other playing animation out, over the specified time period.
 /// </summary>
 /// <param name="clip">Clip to fade in.</param>
 /// <param name="fadeLength">Determines the time period over which the fade occurs. In seconds.</param>
 public void CrossFade(AnimationClip clip, float fadeLength)
 {
     switch (state)
     {
         case State.Active:
             _native.CrossFade(clip, fadeLength);
             break;
     }
 }
Example #12
0
 /// <summary>
 /// Plays the specified animation clip on top of the animation currently playing in the main layer. Multiple such
 /// clips can be playing at once, as long as you ensure each is given its own layer. Each animation can also have a
 /// weight that determines how much it influences the main animation.        
 /// </summary>
 /// <param name="clip">Clip to additively blend. Must contain additive animation curves.</param>
 /// <param name="weight">Determines how much of an effect will the blended animation have on the final output.
 ///                      In range [0, 1].</param>
 /// <param name="fadeLength">Applies the blend over a specified time period, increasing the weight as the time
 ///                          passes. Set to zero to blend immediately. In seconds.</param>
 /// <param name="layer">Layer to play the clip in. Multiple additive clips can be playing at once in separate layers
 ///                     and each layer has its own weight.</param>
 public void BlendAdditive(AnimationClip clip, float weight, float fadeLength, int layer)
 {
     switch (state)
     {
         case State.Active:
             _native.BlendAdditive(clip, weight, fadeLength, layer);
             break;
     }
 }
Example #13
0
        /// <summary>
        /// Changes the state of the animation state machine. Doesn't check for valid transitions.
        /// </summary>
        /// <param name="state">New state of the animation.</param>
        private void SwitchState(State state)
        {
            this.state = state;

            switch (state)
            {
                case State.Active:
                    if (_native != null)
                        _native.Destroy();

                    _native = new NativeAnimation();
                    _native.OnEventTriggered += EventTriggered;

                    animatedRenderable = SceneObject.GetComponent<Renderable>();

                    // Restore saved values after reset
                    _native.WrapMode = serializableData.wrapMode;
                    _native.Speed = serializableData.speed;
                    _native.Cull = serializableData.cull;

                    UpdateBounds();

                    if (serializableData.defaultClip != null)
                        _native.Play(serializableData.defaultClip);

                    primaryClip = _native.GetClip(0);
                    if (primaryClip != null)
                        RebuildFloatProperties(primaryClip);

                    SetBoneMappings();
                    UpdateSceneObjectMapping();

                    if (animatedRenderable != null)
                        animatedRenderable.RegisterAnimation(this);
                    break;
                case State.EditorActive:
                    if (_native != null)
                        _native.Destroy();

                    _native = new NativeAnimation();

                    animatedRenderable = SceneObject.GetComponent<Renderable>();

                    UpdateBounds();
                    SetBoneMappings();

                    if (animatedRenderable != null)
                        animatedRenderable.RegisterAnimation(this);
                    break;
                case State.Inactive:
                    if (animatedRenderable != null)
                        animatedRenderable.UnregisterAnimation();

                    if (_native != null)
                    {
                        _native.Destroy();
                        _native = null;
                    }

                    primaryClip = null;
                    mappingInfo.Clear();
                    floatProperties = null;
                    break;
            }
        }
Example #14
0
        /// <summary>
        /// Builds a list of properties that will be animated using float animation curves.
        /// </summary>
        /// <param name="clip">Clip to retrieve the float animation curves from.</param>
        private void RebuildFloatProperties(AnimationClip clip)
        {
            if (clip == null)
            {
                floatProperties = null;
                return;
            }

            AnimationCurves curves = clip.Curves;

            List<FloatCurvePropertyInfo> newFloatProperties = new List<FloatCurvePropertyInfo>();
            for (int i = 0; i < curves.FloatCurves.Length; i++)
            {
                bool isMorphCurve = curves.FloatCurves[i].Flags.HasFlag(AnimationCurveFlags.MorphWeight) ||
                                    curves.FloatCurves[i].Flags.HasFlag(AnimationCurveFlags.MorphFrame);

                if (isMorphCurve)
                    continue;

                string suffix;
                SerializableProperty property = FindProperty(SceneObject, curves.FloatCurves[i].Name, out suffix);
                if (property == null)
                    continue;

                int elementIdx = 0;
                if (!string.IsNullOrEmpty(suffix))
                {
                    PropertySuffixInfo suffixInfo;
                    if (PropertySuffixInfos.TryGetValue(suffix, out suffixInfo))
                        elementIdx = suffixInfo.elementIdx;
                }

                Action<float> setter = null;

                Type internalType = property.InternalType;
                switch (property.Type)
                {
                    case SerializableProperty.FieldType.Vector2:
                        if (internalType == typeof(Vector2))
                        {
                            setter = f =>
                            {
                                Vector2 value = property.GetValue<Vector2>();
                                value[elementIdx] = f;
                                property.SetValue(value);
                            };
                        }

                        break;
                    case SerializableProperty.FieldType.Vector3:
                        if (internalType == typeof(Vector3))
                        {
                            setter = f =>
                            {
                                Vector3 value = property.GetValue<Vector3>();
                                value[elementIdx] = f;
                                property.SetValue(value);
                            };
                        }
                        break;
                    case SerializableProperty.FieldType.Vector4:
                        if (internalType == typeof(Vector4))
                        {
                            setter = f =>
                            {
                                Vector4 value = property.GetValue<Vector4>();
                                value[elementIdx] = f;
                                property.SetValue(value);
                            };
                        }
                        else if (internalType == typeof(Quaternion))
                        {
                            setter = f =>
                            {
                                Quaternion value = property.GetValue<Quaternion>();
                                value[elementIdx] = f;
                                property.SetValue(value);
                            };
                        }
                        break;
                    case SerializableProperty.FieldType.Color:
                        if (internalType == typeof(Color))
                        {
                            setter = f =>
                            {
                                Color value = property.GetValue<Color>();
                                value[elementIdx] = f;
                                property.SetValue(value);
                            };
                        }
                        break;
                    case SerializableProperty.FieldType.Bool:
                            setter = f =>
                            {
                                bool value = f > 0.0f;
                                property.SetValue(value);
                            };
                        break;
                    case SerializableProperty.FieldType.Int:
                        setter = f =>
                        {
                            int value = (int)f;
                            property.SetValue(value);
                        };
                        break;
                    case SerializableProperty.FieldType.Float:
                        setter = f =>
                        {
                            property.SetValue(f);
                        };
                        break;
                }

                if (setter == null)
                    continue;

                FloatCurvePropertyInfo propertyInfo = new FloatCurvePropertyInfo();
                propertyInfo.curveIdx = i;
                propertyInfo.setter = setter;

                newFloatProperties.Add(propertyInfo);
            }

            floatProperties = newFloatProperties.ToArray();
        }