├── Juicy ├── Plugins │ ├── DOTween │ │ ├── Modules │ │ │ ├── DOTween.Modules.asmdef │ │ │ ├── DOTweenModuleSprite.cs │ │ │ ├── DOTweenModuleUtils.cs │ │ │ └── DOTweenModulePhysics2D.cs │ │ ├── DOTween.dll │ │ ├── Editor │ │ │ ├── Imgs │ │ │ │ ├── Footer.png │ │ │ │ ├── Header.jpg │ │ │ │ ├── DOTweenIcon.png │ │ │ │ └── Footer_dark.png │ │ │ ├── DOTweenEditor.dll │ │ │ └── DOTweenEditor.XML │ │ ├── Resources │ │ │ └── DOTweenSettings.asset │ │ └── readme.txt │ └── PoolAttendant │ │ ├── TinyTools.PoolAttendant.asmdef │ │ ├── PoolSettings.cs │ │ ├── PoolEntity.cs │ │ ├── Editor │ │ ├── DefaultPoolItemDrawer.cs │ │ └── ReorderableListDefaultPoolItem.cs │ │ └── PoolAttendantExtention.cs ├── Images │ ├── img_juicy.png │ ├── img_feedback_time.png │ ├── img_feedback_audio.png │ ├── img_feedback_camera.png │ ├── img_feedback_event.png │ ├── img_feedback_light.png │ ├── img_feedback_object.png │ ├── img_feedback_animator.png │ ├── img_feedback_default.png │ ├── img_feedback_particle.png │ └── img_feedback_renderer.png ├── Runtime │ ├── Utils │ │ ├── ToggleGroup.cs │ │ ├── Ease.cs │ │ ├── AdvancedAnimationCurve.cs │ │ ├── CustomPosition.cs │ │ ├── ColorChooser.cs │ │ ├── JuicyUtils.cs │ │ ├── AnimationCurve3d.cs │ │ ├── Reset.cs │ │ ├── MonoBehaviourExtensions.cs │ │ ├── Target.cs │ │ ├── JuicyScreenFlasher.cs │ │ ├── FromToValue.cs │ │ ├── Timing.cs │ │ ├── AudioInstance.cs │ │ ├── ReferenceTarget.cs │ │ └── JuicyCameraShaker.cs │ ├── Feedback │ │ ├── JuicyFeedbackCameraBase.cs │ │ ├── JuicyFeedbackObjectBase.cs │ │ ├── JuicyFeedbackTimeBase.cs │ │ ├── JuicyFeedbackLightBase.cs │ │ ├── JuicyFeedbackParticlePooled.cs │ │ ├── JuicyFeedbackParticle.cs │ │ ├── JuicyFeedbackEventSimple.cs │ │ ├── JuicyFeedbackAudioPooled.cs │ │ ├── JuicyFeedbackTimeFreeze.cs │ │ ├── JuicyFeedbackObjectPunch.cs │ │ ├── JuicyFeedbackObjectShake.cs │ │ ├── JuicyFeedbackTimeChange.cs │ │ ├── JuicyFeedbackAudio.cs │ │ ├── JuicyFeedbackEventLifetime.cs │ │ ├── JuicyFeedbackLightColor.cs │ │ ├── JuicyFeedbackScreenFlash.cs │ │ ├── JuicyFeedbackLightIntensity.cs │ │ ├── JuicyFeedbackBase.cs │ │ ├── JuicyFeedbackObjectRotate.cs │ │ ├── JuicyFeedbackCameraZoom.cs │ │ ├── JuicyFeedbackObjectScale.cs │ │ ├── JuicyFeedbackObjectMove.cs │ │ ├── JuicyFeedbackAudioBase.cs │ │ ├── JuicyFeedbackObjectFade.cs │ │ ├── JuicyFeedbackAnimator.cs │ │ ├── JuicyFeedbackObjectTint.cs │ │ ├── JuicyFeedbackCameraShake.cs │ │ ├── JuicyFeedbackObjectCreator.cs │ │ └── JuicyFeedbackShader.cs │ ├── Attributes │ │ ├── HeaderAttribute.cs │ │ ├── MinMaxRange.cs │ │ ├── TimingAttribute.cs │ │ └── FeedbackAttribute.cs │ ├── TinyTools.Juicy.asmdef │ ├── JuicyFeedbackMasterList.cs │ ├── JuicyFeedback.cs │ └── JuicyFeedbackList.cs └── Editor │ ├── JuicyFeedbackMasterListEditor.cs │ ├── TinyTools.Juicy.Editor.asmdef │ ├── Decorator │ └── HeaderDecoratorDrawer.cs │ ├── Utils │ ├── JuicyPropertyDrawerBase.cs │ ├── ColorChooserDrawer.cs │ ├── ReferenceTargetDrawer.cs │ ├── ResetDrawer.cs │ ├── JuicyEditorBase.cs │ ├── AnimationCurve3dDrawer.cs │ ├── AdvancedAnimationCurveDrawer.cs │ ├── ToggleGroupDrawer.cs │ ├── FromToValueDrawerBase.cs │ ├── EaseDrawer.cs │ ├── FeedbackCopyHelper.cs │ ├── TargetDrawer.cs │ ├── JuicyStyles.cs │ └── ReorderableListPropertyDrawer.cs │ ├── Attributes │ ├── MinMaxRangeAttributeDrawer.cs │ └── TimingAttributeDrawer.cs │ ├── Feedback │ ├── JuicyFeedbackBaseEditor.cs │ └── JuicyFeedbackAnimatorEditor.cs │ └── JuicyFeedbackDrawer.cs ├── LICENSE.md ├── .gitignore └── README.md /Juicy/Plugins/DOTween/Modules/DOTween.Modules.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DOTween.Modules" 3 | } 4 | -------------------------------------------------------------------------------- /Juicy/Images/img_juicy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmeiburg/juicy/HEAD/Juicy/Images/img_juicy.png -------------------------------------------------------------------------------- /Juicy/Plugins/PoolAttendant/TinyTools.PoolAttendant.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TinyTools.PoolAttendant" 3 | } 4 | -------------------------------------------------------------------------------- /Juicy/Images/img_feedback_time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmeiburg/juicy/HEAD/Juicy/Images/img_feedback_time.png -------------------------------------------------------------------------------- /Juicy/Plugins/DOTween/DOTween.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmeiburg/juicy/HEAD/Juicy/Plugins/DOTween/DOTween.dll -------------------------------------------------------------------------------- /Juicy/Images/img_feedback_audio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmeiburg/juicy/HEAD/Juicy/Images/img_feedback_audio.png -------------------------------------------------------------------------------- /Juicy/Images/img_feedback_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmeiburg/juicy/HEAD/Juicy/Images/img_feedback_camera.png -------------------------------------------------------------------------------- /Juicy/Images/img_feedback_event.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmeiburg/juicy/HEAD/Juicy/Images/img_feedback_event.png -------------------------------------------------------------------------------- /Juicy/Images/img_feedback_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmeiburg/juicy/HEAD/Juicy/Images/img_feedback_light.png -------------------------------------------------------------------------------- /Juicy/Images/img_feedback_object.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmeiburg/juicy/HEAD/Juicy/Images/img_feedback_object.png -------------------------------------------------------------------------------- /Juicy/Images/img_feedback_animator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmeiburg/juicy/HEAD/Juicy/Images/img_feedback_animator.png -------------------------------------------------------------------------------- /Juicy/Images/img_feedback_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmeiburg/juicy/HEAD/Juicy/Images/img_feedback_default.png -------------------------------------------------------------------------------- /Juicy/Images/img_feedback_particle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmeiburg/juicy/HEAD/Juicy/Images/img_feedback_particle.png -------------------------------------------------------------------------------- /Juicy/Images/img_feedback_renderer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmeiburg/juicy/HEAD/Juicy/Images/img_feedback_renderer.png -------------------------------------------------------------------------------- /Juicy/Plugins/DOTween/Editor/Imgs/Footer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmeiburg/juicy/HEAD/Juicy/Plugins/DOTween/Editor/Imgs/Footer.png -------------------------------------------------------------------------------- /Juicy/Plugins/DOTween/Editor/Imgs/Header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmeiburg/juicy/HEAD/Juicy/Plugins/DOTween/Editor/Imgs/Header.jpg -------------------------------------------------------------------------------- /Juicy/Plugins/DOTween/Editor/DOTweenEditor.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmeiburg/juicy/HEAD/Juicy/Plugins/DOTween/Editor/DOTweenEditor.dll -------------------------------------------------------------------------------- /Juicy/Plugins/DOTween/Editor/Imgs/DOTweenIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmeiburg/juicy/HEAD/Juicy/Plugins/DOTween/Editor/Imgs/DOTweenIcon.png -------------------------------------------------------------------------------- /Juicy/Plugins/DOTween/Editor/Imgs/Footer_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmeiburg/juicy/HEAD/Juicy/Plugins/DOTween/Editor/Imgs/Footer_dark.png -------------------------------------------------------------------------------- /Juicy/Runtime/Utils/ToggleGroup.cs: -------------------------------------------------------------------------------- 1 | namespace TinyTools.Juicy 2 | { 3 | public class ToggleGroup 4 | { 5 | public bool isActive = false; 6 | } 7 | } -------------------------------------------------------------------------------- /Juicy/Editor/JuicyFeedbackMasterListEditor.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | 3 | namespace TinyTools.Juicy 4 | { 5 | [CustomEditor(typeof(JuicyFeedbackMasterList))] 6 | public sealed class JuicyFeedbackMasterListEditor : JuicyEditorBase 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /Juicy/Editor/TinyTools.Juicy.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TinyTools.Juicy.Editor", 3 | "references": [ 4 | "TinyTools.Juicy" 5 | ], 6 | "includePlatforms": [ 7 | "Editor" 8 | ], 9 | "excludePlatforms": [] 10 | } 11 | -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackCameraBase.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace TinyTools.Juicy 4 | { 5 | public abstract class JuicyFeedbackCameraBase : JuicyFeedbackBase 6 | { 7 | [SerializeField] protected new CameraReferenceTarget camera = new CameraReferenceTarget(); 8 | } 9 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Utils/Ease.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Serializable] 7 | public sealed class Ease 8 | { 9 | public AnimationCurve curve = new AnimationCurve( 10 | new Keyframe(0, 0), 11 | new Keyframe(1, 1)); 12 | } 13 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Attributes/HeaderAttribute.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace TinyTools.Juicy 4 | { 5 | public class HeaderAttribute : PropertyAttribute 6 | { 7 | public string Title { get; } 8 | 9 | public HeaderAttribute(string title) 10 | { 11 | this.Title = title; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackObjectBase.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace TinyTools.Juicy 4 | { 5 | public abstract class JuicyFeedbackObjectBase : JuicyFeedbackBase 6 | { 7 | [SerializeField, Timing] protected Timing timing = new Timing(); 8 | [SerializeField] protected Ease ease = new Ease(); 9 | } 10 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Utils/AdvancedAnimationCurve.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Serializable] 7 | public class AdvancedAnimationCurve 8 | { 9 | public AnimationCurve curve = new AnimationCurve( 10 | new Keyframe(0, 0), 11 | new Keyframe(1, 1)); 12 | } 13 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackTimeBase.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace TinyTools.Juicy 4 | { 5 | public abstract class JuicyFeedbackTimeBase : JuicyFeedbackBase 6 | { 7 | [SerializeField, Timing(TimingAttribute.TimingStyle.HideCooldown | 8 | TimingAttribute.TimingStyle.HideIgnoreTimeScale)] 9 | protected Timing timing = new Timing(); 10 | } 11 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Utils/CustomPosition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Serializable] 7 | public class CustomPosition : ToggleGroup 8 | { 9 | [Tooltip("Relative offset of the new particle effect")] 10 | public Vector3 offset; 11 | [Tooltip("Spawns the particle at a specific transform")] 12 | public Transform spawnAt; 13 | } 14 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackLightBase.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace TinyTools.Juicy 4 | { 5 | public abstract class JuicyFeedbackLightBase : JuicyFeedbackBase 6 | { 7 | [SerializeField, Timing] protected Timing timing = new Timing(); 8 | [SerializeField] protected LightReferenceTarget target = new LightReferenceTarget(); 9 | [SerializeField] protected Ease ease = new Ease(); 10 | } 11 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackParticlePooled.cs: -------------------------------------------------------------------------------- 1 | using TinyTools.PoolAttendant; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Feedback("Particle/Pooled")][AddComponentMenu("")] 7 | public class JuicyFeedbackParticlePooled : JuicyFeedbackParticle 8 | { 9 | protected override ParticleSystem Instantiate() 10 | { 11 | return prefab.GetPooledInstance(); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Utils/ColorChooser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Serializable] 7 | public sealed class ColorChooser 8 | { 9 | public bool useHdr = false; 10 | [ColorUsage(true, true)] 11 | public Color hdrColor = Color.white; 12 | public Color rgbColor = Color.white; 13 | 14 | public Color Value => useHdr ? hdrColor : rgbColor; 15 | } 16 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Attributes/MinMaxRange.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace TinyTools.Juicy 4 | { 5 | public sealed class MinMaxRange : PropertyAttribute 6 | { 7 | public float min = 0f; 8 | public float max = 1f; 9 | 10 | public MinMaxRange() {} 11 | 12 | internal MinMaxRange(float min, float max) 13 | { 14 | this.min = min; 15 | this.max = max; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Utils/JuicyUtils.cs: -------------------------------------------------------------------------------- 1 | namespace TinyTools.Juicy 2 | { 3 | internal static class JuicyUtils 4 | { 5 | public static float Time(bool ignoreTimeScale = false) => 6 | ignoreTimeScale ? UnityEngine.Time.unscaledTime : UnityEngine.Time.time; 7 | 8 | public static float DeltaTime(bool ignoreTimeScale = false) => 9 | ignoreTimeScale ? UnityEngine.Time.unscaledDeltaTime : UnityEngine.Time.deltaTime; 10 | } 11 | } -------------------------------------------------------------------------------- /Juicy/Runtime/TinyTools.Juicy.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TinyTools.Juicy", 3 | "references": [ 4 | "GUID:24f0da3954a4372448142b827f317752", 5 | "GUID:1466e8052a0625e4595f5a2f18504519" 6 | ], 7 | "includePlatforms": [], 8 | "excludePlatforms": [], 9 | "allowUnsafeCode": false, 10 | "overrideReferences": false, 11 | "precompiledReferences": [], 12 | "autoReferenced": true, 13 | "defineConstraints": [], 14 | "versionDefines": [], 15 | "noEngineReferences": false 16 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackParticle.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace TinyTools.Juicy 4 | { 5 | [Feedback("Particle/Simple")][AddComponentMenu("")] 6 | public class JuicyFeedbackParticle : JuicyFeedbackObjectCreator 7 | { 8 | internal override void Stop() 9 | { 10 | base.Stop(); 11 | 12 | if (objectReference == null) { 13 | return; 14 | } 15 | 16 | objectReference.Stop(); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Juicy/Runtime/JuicyFeedbackMasterList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | public class JuicyFeedbackMasterList : MonoBehaviour 7 | { 8 | public List feedbackList; 9 | 10 | public void Play(JuicyFeedbackList list) 11 | { 12 | list.Play(); 13 | } 14 | 15 | public void PlayAll() 16 | { 17 | foreach (var juicyFeedbackList in feedbackList) { 18 | Play(juicyFeedbackList); 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Juicy/Plugins/PoolAttendant/PoolSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace TinyTools.PoolAttendant 6 | { 7 | public class PoolSettings : ScriptableObject 8 | { 9 | public DefaultPoolItemList list = new DefaultPoolItemList(); 10 | } 11 | 12 | [Serializable] 13 | public class DefaultPoolItemList 14 | { 15 | public List items = new List(); 16 | } 17 | 18 | [Serializable] 19 | public class DefaultPoolItem 20 | { 21 | public GameObject prefab; 22 | public int size = 5; 23 | } 24 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackEventSimple.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.Events; 3 | using static TinyTools.Juicy.TimingAttribute.TimingStyle; 4 | 5 | namespace TinyTools.Juicy 6 | { 7 | [Feedback("Event/Simple")][AddComponentMenu("")] 8 | public class JuicyFeedbackEventSimple : JuicyFeedbackBase 9 | { 10 | [SerializeField, Timing(HideDuration)] private Timing timing = new Timing(); 11 | [SerializeField] private UnityEvent _event = new UnityEvent(); 12 | 13 | protected override void Play() 14 | { 15 | timing.Invoke(this, () => _event.Invoke()); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Juicy/Runtime/JuicyFeedback.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DG.Tweening; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Serializable] 7 | public sealed class JuicyFeedback 8 | { 9 | public JuicyFeedbackList feedbackList; 10 | 11 | public void Play() 12 | { 13 | if (feedbackList != null) { 14 | feedbackList.Play(); 15 | } 16 | } 17 | 18 | public void Stop() 19 | { 20 | if (feedbackList != null) { 21 | feedbackList.Stop(); 22 | } 23 | } 24 | 25 | public void Pause() 26 | { 27 | if (feedbackList != null) { 28 | feedbackList.Pause(); 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Attributes/TimingAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | public sealed class TimingAttribute : PropertyAttribute 7 | { 8 | public TimingAttribute() 9 | { 10 | Style = TimingStyle.None; 11 | } 12 | 13 | public TimingAttribute(TimingStyle style) 14 | { 15 | Style |= style; 16 | } 17 | 18 | public TimingStyle Style { get; } = TimingStyle.None; 19 | 20 | [Flags] 21 | public enum TimingStyle 22 | { 23 | None = 0, 24 | HideDuration = 1, 25 | HideCooldown = 2, 26 | HideDelay = 4, 27 | HideIgnoreTimeScale = 8 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Utils/AnimationCurve3d.cs: -------------------------------------------------------------------------------- 1 | namespace TinyTools.Juicy 2 | { 3 | /*[Serializable] 4 | public class AnimationCurve3d 5 | { 6 | [SerializeField] private Type type = Type.X; 7 | [SerializeField] private AdvancedAnimationCurve xCurve; 8 | [SerializeField] private AdvancedAnimationCurve yCurve; 9 | [SerializeField] private AdvancedAnimationCurve zCurve; 10 | 11 | public AdvancedAnimationCurve X => xCurve; 12 | public AdvancedAnimationCurve Y => xCurve; 13 | public AdvancedAnimationCurve Z => xCurve; 14 | 15 | public AnimationCurve3d(Type type) 16 | { 17 | this.type = type; 18 | } 19 | 20 | public enum Type 21 | { 22 | X, 23 | XY, 24 | XYZ, 25 | } 26 | }*/ 27 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Utils/Reset.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | public abstract class Reset 7 | { 8 | public ResetType resetType = ResetType.Stay; 9 | public bool loop = false; 10 | public T resetValue; 11 | } 12 | 13 | [Serializable] 14 | public class Vector3Reset : Reset {} 15 | [Serializable] 16 | public class Vector4Reset : Reset {} 17 | [Serializable] 18 | public class ColorReset : Reset {} 19 | [Serializable] 20 | public class ColorChooserReset : Reset {} 21 | [Serializable] 22 | public class FloatReset : Reset {} 23 | 24 | public enum ResetType 25 | { 26 | Stay, 27 | ToValue, 28 | Yoyo 29 | } 30 | 31 | 32 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Utils/MonoBehaviourExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using UnityEngine; 4 | 5 | namespace TinyTools.Juicy 6 | { 7 | public static class MonoBehaviourExtensions 8 | { 9 | public static Coroutine InvokeDelayed(this MonoBehaviour mono, float delay, Action callback, bool ignoreTimeScale = false) 10 | { 11 | return mono.StartCoroutine(Delay(callback, delay, ignoreTimeScale)); 12 | } 13 | 14 | private static IEnumerator Delay(Action action, float delay, bool ignoreTimeScale) 15 | { 16 | if (ignoreTimeScale) { 17 | yield return new WaitForSecondsRealtime(delay); 18 | } else { 19 | yield return new WaitForSeconds(delay); 20 | } 21 | 22 | action.Invoke(); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackAudioPooled.cs: -------------------------------------------------------------------------------- 1 | using TinyTools.PoolAttendant; 2 | using UnityEngine; 3 | using UnityEngine.Audio; 4 | 5 | namespace TinyTools.Juicy 6 | { 7 | [Feedback("Audio/Pooled")][AddComponentMenu("")] 8 | public class JuicyFeedbackAudioPooled : JuicyFeedbackAudioBase 9 | { 10 | [SerializeField] private AudioInstance prefab = null; 11 | [SerializeField] private AudioMixerGroup group = null; 12 | 13 | private AudioInstance instance; 14 | 15 | protected override void PlayClip(Vector3 position, AudioClip clip, float volume, float pitch) 16 | { 17 | if (prefab == null) { 18 | return; 19 | } 20 | 21 | instance = prefab.GetPooledInstance(); 22 | instance.Play(position, clip, group, volume, pitch); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Juicy/Plugins/PoolAttendant/PoolEntity.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace TinyTools.PoolAttendant 4 | { 5 | public class PoolEntity : MonoBehaviour 6 | { 7 | [SerializeField] 8 | private GameObject prefab = null; 9 | 10 | public GameObject Prefab { 11 | get => prefab; 12 | set => prefab = value; 13 | } 14 | 15 | private void OnEnable() 16 | { 17 | if (prefab == null) { 18 | return; 19 | } 20 | 21 | Pool.Instance.Reparent(gameObject, prefab.GetInstanceID()); 22 | } 23 | 24 | private void OnDestroy() 25 | { 26 | if (prefab == null) { 27 | return; 28 | } 29 | 30 | Pool.Instance.Remove(gameObject, prefab.GetInstanceID()); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackTimeFreeze.cs: -------------------------------------------------------------------------------- 1 | using DG.Tweening; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Feedback("Time/Freeze")][AddComponentMenu("")] 7 | public class JuicyFeedbackTimeFreeze : JuicyFeedbackTimeBase 8 | { 9 | protected override void Play() 10 | { 11 | timing.Invoke(this, PlayDelayed); 12 | } 13 | 14 | private void PlayDelayed() 15 | { 16 | tween?.Kill(); 17 | 18 | float value = 0; 19 | tween = DOTween.To(() => value, x => value = x, 1, 20 | timing.duration) 21 | .OnStart(() => Time.timeScale = 0f) 22 | .OnComplete(ResetTimeScale) 23 | .OnKill(ResetTimeScale) 24 | .SetUpdate(true); 25 | } 26 | 27 | private static void ResetTimeScale() => Time.timeScale = 1; 28 | } 29 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Utils/Target.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | public class Target where T : class 7 | { 8 | public bool targetIsSelf = false; 9 | public T selfTarget; 10 | public T otherTarget; 11 | 12 | public bool IsValid => Value != null; 13 | 14 | public T Value => targetIsSelf ? selfTarget : otherTarget; 15 | } 16 | 17 | [Serializable] 18 | public sealed class RendererTarget : Target {} 19 | [Serializable] 20 | public sealed class AnimatorTarget : Target {} 21 | [Serializable] 22 | public sealed class TransformTarget : Target {} 23 | [Serializable] 24 | public sealed class AudioSourceTarget : Target {} 25 | [Serializable] 26 | public sealed class LightTarget : Target {} 27 | [Serializable] 28 | public sealed class CameraTarget : Target {} 29 | } -------------------------------------------------------------------------------- /Juicy/Editor/Decorator/HeaderDecoratorDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [CustomPropertyDrawer(typeof(HeaderAttribute))] 7 | public sealed class HeaderDecoratorDrawer : DecoratorDrawer 8 | { 9 | private HeaderAttribute Attribute => (HeaderAttribute) attribute; 10 | 11 | public override float GetHeight() 12 | { 13 | return EditorGUIUtility.singleLineHeight + 6; 14 | } 15 | 16 | public override bool CanCacheInspectorGUI() 17 | { 18 | return true; 19 | } 20 | 21 | public override void OnGUI(Rect position) 22 | { 23 | EditorGUI.LabelField(new Rect(position) { 24 | height = EditorGUIUtility.singleLineHeight 25 | }, Attribute.Title, JuicyStyles.HeaderStyle); 26 | 27 | JuicyEditorUtils.DrawLine(position, EditorGUIUtility.singleLineHeight); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackObjectPunch.cs: -------------------------------------------------------------------------------- 1 | using DG.Tweening; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Feedback("Object/Punch")][AddComponentMenu("")] 7 | public class JuicyFeedbackObjectPunch : JuicyFeedbackObjectBase 8 | { 9 | [SerializeField] private TransformTarget target = new TransformTarget(); 10 | [SerializeField] private Vector3FromToValue value = new Vector3FromToValue(); 11 | [SerializeField] private int vibrato = 1; 12 | [Range(0,1), SerializeField] private float elasticity = 0.2f; 13 | 14 | protected override void Play() 15 | { 16 | if (!target.IsValid) { 17 | return; 18 | } 19 | 20 | timing.Invoke(this, PlayDelayed); 21 | } 22 | 23 | private void PlayDelayed() 24 | { 25 | tween = target.Value 26 | .DOBlendablePunchScaleBy(value.value, timing.duration, vibrato, elasticity); 27 | 28 | tween.SetEase(ease.curve); 29 | tween.SetUpdate(timing.ignoreTimeScale); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Marcus Meiburg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackObjectShake.cs: -------------------------------------------------------------------------------- 1 | using DG.Tweening; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Feedback("Object/Shake")][AddComponentMenu("")] 7 | public class JuicyFeedbackObjectShake : JuicyFeedbackObjectBase 8 | { 9 | [SerializeField] private TransformTarget target = new TransformTarget(); 10 | [SerializeField] private Vector3 value = Vector3.one * 20f; 11 | [SerializeField] private int vibrato = 10; 12 | [Range(0,90), SerializeField] private float randomness = 45f; 13 | 14 | protected override void Play() 15 | { 16 | if (!target.IsValid) { 17 | return; 18 | } 19 | 20 | timing.Invoke(this, PlayDelayed); 21 | } 22 | 23 | private void PlayDelayed() 24 | { 25 | if (tween != null && tween.IsActive() && tween.IsPlaying()) { 26 | return; 27 | } 28 | 29 | tween = target.Value 30 | .DOShakeRotation(timing.duration, value, vibrato, randomness) 31 | .SetEase(ease.curve) 32 | .SetUpdate(timing.ignoreTimeScale); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Utils/JuicyScreenFlasher.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.UI; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [AddComponentMenu("")] 7 | public class JuicyScreenFlasher : MonoBehaviour 8 | { 9 | private static JuicyScreenFlasher instance; 10 | 11 | public Image Image { get; private set; } 12 | 13 | private void CreateImage() 14 | { 15 | Canvas canvas = gameObject.AddComponent(); 16 | canvas.renderMode = RenderMode.ScreenSpaceOverlay; 17 | Image = gameObject.AddComponent(); 18 | Image.color = new Color(0,0,0,0); 19 | } 20 | 21 | public static JuicyScreenFlasher Instance { 22 | get { 23 | if (instance != null) { 24 | return instance; 25 | } 26 | 27 | GameObject obj = new GameObject(nameof(JuicyScreenFlasher)); 28 | instance = obj.AddComponent(); 29 | instance.CreateImage(); 30 | 31 | return instance; 32 | } 33 | } 34 | 35 | private void OnDestroy() 36 | { 37 | instance = null; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Mm]emoryCaptures/ 12 | 13 | # Asset meta data should only be ignored when the corresponding asset is also ignored 14 | !/[Aa]ssets/**/*.meta 15 | 16 | # Uncomment this line if you wish to ignore the asset store tools plugin 17 | # /[Aa]ssets/AssetStoreTools* 18 | 19 | # Autogenerated Jetbrains Rider plugin 20 | [Aa]ssets/Plugins/Editor/JetBrains* 21 | 22 | # Visual Studio cache directory 23 | .vs/ 24 | 25 | # Gradle cache directory 26 | .gradle/ 27 | 28 | # Autogenerated VS/MD/Consulo solution and project files 29 | ExportedObj/ 30 | .consulo/ 31 | *.csproj 32 | *.unityproj 33 | *.sln 34 | *.suo 35 | *.tmp 36 | *.user 37 | *.userprefs 38 | *.pidb 39 | *.booproj 40 | *.svd 41 | *.pdb 42 | *.mdb 43 | *.opendb 44 | *.VC.db 45 | 46 | # Unity3D generated meta files 47 | *.pidb.meta 48 | *.pdb.meta 49 | *.mdb.meta 50 | 51 | # Unity3D generated file on crash reports 52 | sysinfo.txt 53 | *.meta 54 | 55 | # Builds 56 | *.apk 57 | *.unitypackage 58 | 59 | # Crashlytics generated file 60 | crashlytics-build.properties 61 | 62 | -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackTimeChange.cs: -------------------------------------------------------------------------------- 1 | using DG.Tweening; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Feedback("Time/Change")][AddComponentMenu("")] 7 | public class JuicyFeedbackTimeChange : JuicyFeedbackTimeBase 8 | { 9 | [SerializeField] 10 | private float amount = 0.5f; 11 | 12 | [SerializeField] private Ease ease = new Ease(); 13 | 14 | protected override void Play() 15 | { 16 | timing.Invoke(this, PlayDelayed); 17 | } 18 | 19 | private void PlayDelayed() 20 | { 21 | tween?.Kill(); 22 | 23 | float value = Time.timeScale; 24 | 25 | tween = DOTween.To(() => value, x => 26 | { 27 | Time.timeScale = Mathf.Clamp(x, 0, float.MaxValue); 28 | }, amount, timing.duration / 2) 29 | .SetUpdate(true) 30 | //.OnUpdate(() => UpdateValue(value)) 31 | .OnKill(ResetTimeScale) 32 | .OnComplete(ResetTimeScale) 33 | .SetLoops(2, LoopType.Yoyo) 34 | .SetEase(ease.curve); 35 | } 36 | 37 | private static void ResetTimeScale() => Time.timeScale = 1; 38 | } 39 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Utils/FromToValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | public abstract class FromToValue 7 | { 8 | public bool isFrom = false; 9 | public T value; 10 | } 11 | 12 | [Serializable] public sealed class BoolFromToValue : FromToValue {} 13 | 14 | [Serializable] 15 | public sealed class FloatFromToValue : FromToValue 16 | { 17 | public FloatFromToValue() {} 18 | public FloatFromToValue(float value) 19 | { 20 | this.value = value; 21 | } 22 | } 23 | [Serializable] public sealed class ColorChooserFromToValue : FromToValue {} 24 | [Serializable] public sealed class ColorFromToValue : FromToValue {} 25 | [Serializable] public sealed class Vector3FromToValue : FromToValue {} 26 | [Serializable] public sealed class Vector4FromToValue : FromToValue {} 27 | 28 | [Serializable] 29 | public sealed class SliderFromToValue : FromToValue 30 | { 31 | public Vector3 range; 32 | 33 | public SliderFromToValue() 34 | { 35 | range = new Vector3(0,0,1); 36 | } 37 | 38 | public SliderFromToValue(float from, float to) 39 | { 40 | range = new Vector3(from, 0, to); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /Juicy/Editor/Utils/JuicyPropertyDrawerBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEditor; 3 | namespace TinyTools.Juicy 4 | { 5 | public abstract class JuicyPropertyDrawerBase : PropertyDrawer 6 | { 7 | protected static readonly float StandardSpacing = EditorGUIUtility.standardVerticalSpacing; 8 | protected static readonly float SingleLineHeight = EditorGUIUtility.singleLineHeight; 9 | 10 | protected bool IsMinimalWidth => EditorGUIUtility.currentViewWidth < 333; 11 | 12 | protected void CacheProperty(ref SerializedProperty property, SerializedProperty parent, string name) 13 | { 14 | if (property == null) { 15 | property = parent.FindPropertyRelative(name); 16 | } 17 | } 18 | 19 | protected IEnumerable GetChildren(SerializedProperty property) 20 | { 21 | SerializedProperty copy = property.Copy(); 22 | 23 | copy.NextVisible(true); 24 | bool hasNext = true; 25 | 26 | while (hasNext) { 27 | if (SerializedProperty.EqualContents(copy, property.GetEndProperty())) { 28 | yield break; 29 | } 30 | 31 | yield return copy; 32 | 33 | hasNext = copy.NextVisible(false); 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Juicy/Plugins/DOTween/Resources/DOTweenSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!114 &11400000 4 | MonoBehaviour: 5 | m_ObjectHideFlags: 0 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | m_GameObject: {fileID: 0} 10 | m_Enabled: 1 11 | m_EditorHideFlags: 0 12 | m_Script: {fileID: 16995157, guid: ef54d80ae6bb030468d142db88833841, type: 3} 13 | m_Name: DOTweenSettings 14 | m_EditorClassIdentifier: 15 | useSafeMode: 1 16 | safeModeOptions: 17 | nestedTweenFailureBehaviour: 0 18 | timeScale: 1 19 | useSmoothDeltaTime: 0 20 | maxSmoothUnscaledTime: 0.15 21 | rewindCallbackMode: 0 22 | showUnityEditorReport: 0 23 | logBehaviour: 0 24 | drawGizmos: 1 25 | defaultRecyclable: 0 26 | defaultAutoPlay: 3 27 | defaultUpdateType: 0 28 | defaultTimeScaleIndependent: 0 29 | defaultEaseType: 6 30 | defaultEaseOvershootOrAmplitude: 1.70158 31 | defaultEasePeriod: 0 32 | defaultAutoKill: 1 33 | defaultLoopType: 0 34 | debugMode: 0 35 | debugStoreTargetId: 0 36 | showPreviewPanel: 1 37 | storeSettingsLocation: 1 38 | modules: 39 | showPanel: 0 40 | audioEnabled: 1 41 | physicsEnabled: 1 42 | physics2DEnabled: 1 43 | spriteEnabled: 1 44 | uiEnabled: 1 45 | textMeshProEnabled: 0 46 | tk2DEnabled: 0 47 | showPlayingTweens: 0 48 | showPausedTweens: 0 49 | -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackAudio.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace TinyTools.Juicy 4 | { 5 | [Feedback("Audio/Simple")][AddComponentMenu("")] 6 | public class JuicyFeedbackAudio : JuicyFeedbackAudioBase 7 | { 8 | [SerializeField] private UseAudioSource useAudioSource = new UseAudioSource(); 9 | 10 | protected override void PlayClip(Vector3 position, AudioClip clip, float volume, float pitch) 11 | { 12 | if (!useAudioSource.isActive) { 13 | if (useCustomPosition.isActive) { 14 | if (useCustomPosition.spawnAt != null) { 15 | position = useCustomPosition.spawnAt.position; 16 | } 17 | 18 | position += useCustomPosition.offset; 19 | } 20 | 21 | AudioSource.PlayClipAtPoint(clip, 22 | position, volume); 23 | 24 | } else { 25 | useAudioSource.source.Value.clip = clip; 26 | if (useAudioSource.group != null) { 27 | useAudioSource.source.Value.outputAudioMixerGroup = useAudioSource.group; 28 | } 29 | 30 | useAudioSource.source.Value.volume = volume; 31 | useAudioSource.source.Value.pitch = pitch; 32 | useAudioSource.source.Value.Play(); 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Attributes/FeedbackAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [AttributeUsage(AttributeTargets.Class, Inherited = false)] 7 | public sealed class FeedbackAttribute : Attribute 8 | { 9 | private readonly string path; 10 | private readonly string name; 11 | private readonly string icon; 12 | 13 | public FeedbackAttribute(string path) 14 | { 15 | this.path = path; 16 | var split = path.Split('/'); 17 | name = split.Last(); 18 | 19 | icon = $"img_feedback_{split.First().ToLower()}"; 20 | } 21 | 22 | public FeedbackAttribute(string path, string icon) : this(path) 23 | { 24 | this.icon = icon; 25 | } 26 | 27 | public static string GetIcon(Type type) 28 | { 29 | var attribute = type.GetCustomAttributes(false) 30 | .OfType().FirstOrDefault(); 31 | return attribute?.icon; 32 | } 33 | 34 | public static string GetName(Type type) 35 | { 36 | var attribute = type.GetCustomAttributes(false) 37 | .OfType().FirstOrDefault(); 38 | return attribute != null ? attribute.name : "Custom"; 39 | } 40 | 41 | public static string GetPath(Type type) 42 | { 43 | var attribute = type.GetCustomAttributes(false) 44 | .OfType().FirstOrDefault(); 45 | return attribute != null ? attribute.path : $"Custom/{type.Name}"; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackEventLifetime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using UnityEngine.Events; 4 | using static TinyTools.Juicy.TimingAttribute.TimingStyle; 5 | 6 | namespace TinyTools.Juicy 7 | { 8 | [Feedback("Event/Lifetime")][AddComponentMenu("")] 9 | public class JuicyFeedbackEventLifetime : JuicyFeedbackBase 10 | { 11 | [SerializeField] private LifetimeEvent onAwake = new LifetimeEvent(); 12 | [SerializeField] private LifetimeEvent onStart = new LifetimeEvent(); 13 | [SerializeField] private LifetimeEvent onEnable = new LifetimeEvent(); 14 | [SerializeField] private LifetimeEvent onDisable = new LifetimeEvent(); 15 | 16 | private void Awake() 17 | { 18 | onAwake.Invoke(this); 19 | } 20 | 21 | private void Start() 22 | { 23 | onStart.Invoke(this); 24 | } 25 | 26 | private void OnEnable() 27 | { 28 | onEnable.Invoke(this); 29 | } 30 | 31 | private void OnDisable() 32 | { 33 | onDisable.Invoke(this); 34 | } 35 | } 36 | 37 | [Serializable] 38 | public class LifetimeEvent : ToggleGroup 39 | { 40 | [SerializeField, Timing(HideDuration | HideCooldown | HideIgnoreTimeScale)] private Timing timing = new Timing(); 41 | [SerializeField] private UnityEvent _event = new UnityEvent(); 42 | 43 | public void Invoke(MonoBehaviour mono) 44 | { 45 | if (!isActive) { 46 | return; 47 | } 48 | timing.Invoke(mono, () => _event.Invoke()); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Utils/Timing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Serializable] 7 | public sealed class Timing 8 | { 9 | [Tooltip("Total duration of the effect")] 10 | public float duration = 0.4f; 11 | [Tooltip("Duration till the effect starts")] 12 | public float delay = 0; 13 | [Tooltip("Duration after the effect can start again")] 14 | public float cooldown = 0; 15 | [Tooltip("Marks if the effect should be played in realtime")] 16 | public bool ignoreTimeScale = false; 17 | 18 | private float currentTimestamp = 0; 19 | 20 | private bool HasDelay => delay > 0; 21 | private bool HasCooldown => cooldown > 0; 22 | 23 | public void Invoke(MonoBehaviour mono, Action callback) 24 | { 25 | if (!mono.gameObject.activeInHierarchy) { 26 | return; 27 | } 28 | 29 | if (HasCooldown && currentTimestamp > JuicyUtils.Time(ignoreTimeScale)) { 30 | return; 31 | } 32 | 33 | if (!HasDelay) { 34 | callback.Invoke(); 35 | return; 36 | } 37 | 38 | currentTimestamp = JuicyUtils.Time(ignoreTimeScale) + cooldown; 39 | 40 | mono.InvokeDelayed(delay, callback, ignoreTimeScale); 41 | } 42 | 43 | public void Pause() 44 | { 45 | } 46 | 47 | public override string ToString() 48 | { 49 | return $"Timing: {duration} : {delay} : {cooldown} : {ignoreTimeScale}"; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /Juicy/Plugins/PoolAttendant/Editor/DefaultPoolItemDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEditorInternal; 3 | using UnityEngine; 4 | 5 | namespace TinyTools.PoolAttendant 6 | { 7 | [CustomPropertyDrawer(typeof(DefaultPoolItem))] 8 | public class DefaultPoolItemDrawer : PropertyDrawer 9 | { 10 | private SerializedProperty prefab; 11 | private SerializedProperty size; 12 | 13 | private const int SizeSize = 30; 14 | private const int Spacing = 4; 15 | 16 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 17 | { 18 | return EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; 19 | } 20 | 21 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 22 | { 23 | using (new EditorGUI.PropertyScope(position, label, property)) { 24 | 25 | prefab = property.FindPropertyRelative("prefab"); 26 | size = property.FindPropertyRelative("size"); 27 | 28 | var textDimensions = GUIStyle.none.CalcSize(new GUIContent("text")); 29 | 30 | EditorGUI.PropertyField( 31 | new Rect(position.x, position.y, SizeSize, position.height), size, GUIContent.none); 32 | 33 | prefab.objectReferenceValue = EditorGUI.ObjectField( 34 | new Rect(position.x + SizeSize + Spacing, position.y, position.width - SizeSize - Spacing, position.height), GUIContent.none, 35 | prefab.objectReferenceValue, typeof(GameObject), false); 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackLightColor.cs: -------------------------------------------------------------------------------- 1 | using DG.Tweening; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Feedback("Light/Color")][AddComponentMenu("")] 7 | public class JuicyFeedbackLightColor : JuicyFeedbackLightBase 8 | { 9 | [SerializeField] private ColorFromToValue color = new ColorFromToValue(); 10 | [SerializeField] private ColorReset resetValue = new ColorReset(); 11 | 12 | protected override void Play() 13 | { 14 | if (!target.IsValid) { 15 | return; 16 | } 17 | 18 | timing.Invoke(this, PlayDelayed); 19 | } 20 | 21 | private void PlayDelayed() 22 | { 23 | float duration = timing.duration; 24 | 25 | if (resetValue.resetType == ResetType.Yoyo) { 26 | duration /= 2; 27 | } 28 | 29 | tween = target.Value().DOBlendableColor(color.value, duration); 30 | 31 | if (color.isFrom) { 32 | tween.From(); 33 | } 34 | 35 | if (resetValue.resetType == ResetType.ToValue) { 36 | tween.onComplete += () => target.Value().color = resetValue.resetValue; 37 | } 38 | 39 | switch (resetValue.resetType) { 40 | case ResetType.Yoyo: 41 | tween.SetLoops(resetValue.loop ? -1 : 2, LoopType.Yoyo); 42 | break; 43 | case ResetType.ToValue: 44 | tween.SetLoops(resetValue.loop ? -1 : 1, LoopType.Restart); 45 | break; 46 | } 47 | 48 | tween.SetEase(ease.curve); 49 | tween.SetUpdate(timing.ignoreTimeScale); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Utils/AudioInstance.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.Audio; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [RequireComponent(typeof(AudioSource))] 7 | public class AudioInstance : MonoBehaviour 8 | { 9 | private AudioSource source; 10 | 11 | private void Awake() 12 | { 13 | source = GetComponent(); 14 | } 15 | 16 | private void OnEnable() 17 | { 18 | source.Stop(); 19 | } 20 | 21 | private void OnDisable() 22 | { 23 | StopAllCoroutines(); 24 | } 25 | 26 | public void Play(AudioClip clip, float volume) 27 | { 28 | Play(clip, null, volume); 29 | } 30 | 31 | public void Play(AudioClip clip, AudioMixerGroup group = null, float volume = 1f, float pitch = 1f) 32 | { 33 | Play(Vector3.zero, clip, group, volume, pitch); 34 | } 35 | 36 | public void Play(AudioClip clip, float volume, float pitch) 37 | { 38 | Play(Vector3.zero, clip, volume, pitch); 39 | } 40 | 41 | public void Play(Vector3 position, AudioClip clip, float volume, float pitch = 1f) 42 | { 43 | Play(position, clip, null, volume, pitch); 44 | } 45 | 46 | public void Play(Vector3 position, AudioClip clip, AudioMixerGroup group, float volume, float pitch) 47 | { 48 | transform.position = position; 49 | source.clip = clip; 50 | source.outputAudioMixerGroup = group; 51 | source.volume = volume; 52 | source.pitch = pitch; 53 | 54 | source.Play(); 55 | 56 | this.InvokeDelayed(clip.length, () => gameObject.SetActive(false)); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackScreenFlash.cs: -------------------------------------------------------------------------------- 1 | using DG.Tweening; 2 | using UnityEngine; 3 | using UnityEngine.UI; 4 | 5 | namespace TinyTools.Juicy 6 | { 7 | [Feedback("Screen/Flash")][AddComponentMenu("")] 8 | public class JuicyFeedbackScreenFlash : JuicyFeedbackBase 9 | { 10 | [SerializeField] private ColorFromToValue value = new ColorFromToValue(); 11 | [SerializeField, Timing] protected Timing timing = new Timing(); 12 | [SerializeField] protected Ease ease = new Ease(); 13 | [SerializeField] protected ColorReset resetValue = new ColorReset(); 14 | 15 | private Image image; 16 | 17 | protected override void Play() 18 | { 19 | image = JuicyScreenFlasher.Instance.Image; 20 | 21 | timing.Invoke(this, PlayDelayed); 22 | } 23 | 24 | private void PlayDelayed() 25 | { 26 | tween?.Kill(); 27 | image.color = new Color(0,0,0,0); 28 | 29 | float duration = timing.duration; 30 | 31 | if (resetValue.resetType == ResetType.Yoyo) { 32 | duration /= 2; 33 | } 34 | 35 | tween = image 36 | .DOColor(value.value, duration) 37 | .SetEase(ease.curve) 38 | .SetUpdate(timing.ignoreTimeScale); 39 | 40 | if (value.isFrom) { 41 | tween.From(); 42 | } 43 | 44 | switch (resetValue.resetType) { 45 | case ResetType.Yoyo: 46 | tween.SetLoops(resetValue.loop ? -1 : 2, LoopType.Yoyo); 47 | break; 48 | case ResetType.ToValue: 49 | tween.SetLoops(resetValue.loop ? -1 : 1, LoopType.Restart); 50 | break; 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackLightIntensity.cs: -------------------------------------------------------------------------------- 1 | using DG.Tweening; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Feedback("Light/Intensity")][AddComponentMenu("")] 7 | public class JuicyFeedbackLightIntensity : JuicyFeedbackLightBase 8 | { 9 | [SerializeField] private FloatFromToValue intensity = new FloatFromToValue(); 10 | [SerializeField] private FloatReset resetValue = new FloatReset(); 11 | 12 | protected override void Play() 13 | { 14 | if (!target.IsValid) { 15 | return; 16 | } 17 | 18 | timing.Invoke(this, PlayDelayed); 19 | } 20 | 21 | private void PlayDelayed() 22 | { 23 | float duration = timing.duration; 24 | 25 | if (resetValue.resetType == ResetType.Yoyo) { 26 | duration /= 2; 27 | } 28 | 29 | tween = target.Value().DOBlendableIntensity(intensity.value, duration); 30 | 31 | if (intensity.isFrom) { 32 | tween.From(); 33 | } 34 | 35 | if (resetValue.resetType == ResetType.ToValue) { 36 | tween.onComplete = () => { target.Value().intensity = 37 | resetValue.resetValue; }; 38 | } 39 | 40 | switch (resetValue.resetType) { 41 | case ResetType.Yoyo: 42 | tween.SetLoops(resetValue.loop ? -1 : 2, LoopType.Yoyo); 43 | break; 44 | case ResetType.ToValue: 45 | tween.SetLoops(resetValue.loop ? -1 : 1, LoopType.Restart); 46 | break; 47 | } 48 | 49 | tween.SetEase(ease.curve); 50 | tween.SetUpdate(timing.ignoreTimeScale); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackBase.cs: -------------------------------------------------------------------------------- 1 | using DG.Tweening; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | public abstract class JuicyFeedbackBase : MonoBehaviour 7 | { 8 | [HideInInspector] public string label; 9 | [HideInInspector] public bool isActive = true; 10 | [HideInInspector] public bool isExpanded = true; 11 | [HideInInspector] public int referenceCount; 12 | 13 | protected Tweener tween; 14 | 15 | internal void PlayFeedback() 16 | { 17 | if (!isActive) { 18 | return; 19 | } 20 | 21 | if (IsTweenPaused()) { 22 | Resume(); 23 | } else { 24 | KillTweenIfInfinityLoop(); 25 | Play(); 26 | } 27 | 28 | tween?.SetAutoKill(true); 29 | } 30 | 31 | protected virtual void Play() {} 32 | 33 | internal virtual void Pause() 34 | { 35 | tween?.Pause(); 36 | } 37 | 38 | internal virtual void Stop() 39 | { 40 | StopAllCoroutines(); 41 | 42 | if (tween == null) { 43 | return; 44 | } 45 | 46 | tween.Pause(); 47 | tween.Rewind(); 48 | tween.Kill(); 49 | } 50 | 51 | private void OnDestroy() 52 | { 53 | Stop(); 54 | } 55 | 56 | private void Resume() 57 | { 58 | tween?.Play(); 59 | } 60 | 61 | private bool IsTweenPaused() 62 | { 63 | return tween != null && tween.IsActive() && !tween.IsPlaying(); 64 | } 65 | 66 | private void KillTweenIfInfinityLoop() 67 | { 68 | // Kill the last tween if loop was infinity 69 | if (tween == null || !tween.IsActive() || tween.Loops() != -1) { 70 | return; 71 | } 72 | 73 | tween.Rewind(); 74 | tween.Kill(); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackObjectRotate.cs: -------------------------------------------------------------------------------- 1 | using DG.Tweening; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Feedback("Object/Rotate")][AddComponentMenu("")] 7 | public class JuicyFeedbackObjectRotate : JuicyFeedbackObjectBase 8 | { 9 | [SerializeField] private TransformTarget target = new TransformTarget(); 10 | [SerializeField] private Vector3FromToValue value = new Vector3FromToValue(); 11 | [SerializeField] private Vector3Reset resetValue = new Vector3Reset(); 12 | [SerializeField] private bool relative = false; 13 | [SerializeField] private RotateMode mode = RotateMode.Fast; 14 | 15 | protected override void Play() 16 | { 17 | if (!target.IsValid) { 18 | return; 19 | } 20 | 21 | timing.Invoke(this, PlayDelayed); 22 | } 23 | 24 | private void PlayDelayed() 25 | { 26 | Vector3 rotation = value.value; 27 | 28 | float duration = timing.duration; 29 | 30 | if (resetValue.resetType == ResetType.Yoyo) { 31 | duration /= 2; 32 | } 33 | 34 | tween = target.Value.DOBlendableRotateBy(rotation, duration, mode); 35 | 36 | if (value.isFrom) { 37 | tween.From(); 38 | } else { 39 | tween.SetRelative(relative); 40 | } 41 | 42 | switch (resetValue.resetType) { 43 | case ResetType.Yoyo: 44 | tween.SetLoops(resetValue.loop ? -1 : 2, LoopType.Yoyo); 45 | break; 46 | case ResetType.ToValue: 47 | tween.onComplete += () => target.Value.rotation = 48 | Quaternion.Euler(resetValue.resetValue); 49 | tween.SetLoops(resetValue.loop ? -1 : 1, LoopType.Incremental); 50 | break; 51 | 52 | } 53 | 54 | tween.SetEase(ease.curve); 55 | tween.SetUpdate(timing.ignoreTimeScale); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackCameraZoom.cs: -------------------------------------------------------------------------------- 1 | using DG.Tweening; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Feedback("Camera/Zoom")][AddComponentMenu("")] 7 | public class JuicyFeedbackCameraZoom : JuicyFeedbackCameraBase 8 | { 9 | [SerializeField, Timing] private Timing timing = new Timing(); 10 | [SerializeField] private FloatFromToValue fieldOfView = 11 | new FloatFromToValue(30f); 12 | 13 | [SerializeField] private FloatReset reset = new FloatReset(); 14 | [SerializeField] private bool relative = false; 15 | [SerializeField] private Ease ease = new Ease(); 16 | 17 | protected override void Play() 18 | { 19 | if (camera.Value() == null) { 20 | return; 21 | } 22 | 23 | timing.Invoke(this, PlayDelayed); 24 | } 25 | 26 | private void PlayDelayed() 27 | { 28 | float duration = timing.duration; 29 | 30 | if (reset.resetType == ResetType.Yoyo) { 31 | duration /= 2; 32 | } 33 | 34 | if (relative) { 35 | tween = camera.Value().DOBlendableFieldOfView( 36 | fieldOfView.value, duration); 37 | } else { 38 | tween = camera.Value().DOFieldOfView( 39 | fieldOfView.value, duration); 40 | } 41 | 42 | if (fieldOfView.isFrom) { 43 | tween.From(); 44 | } else { 45 | tween.SetRelative(relative); 46 | } 47 | 48 | switch (reset.resetType) { 49 | case ResetType.Yoyo: 50 | tween.SetLoops(reset.loop ? -1 : 2, LoopType.Yoyo); 51 | break; 52 | case ResetType.ToValue: 53 | tween.onComplete += () => camera.Value().fieldOfView = reset.resetValue; 54 | tween.SetLoops(reset.loop ? -1 : 1, LoopType.Restart); 55 | break; 56 | } 57 | 58 | tween.SetUpdate(timing.ignoreTimeScale); 59 | tween.SetEase(ease.curve); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /Juicy/Plugins/DOTween/readme.txt: -------------------------------------------------------------------------------- 1 | DOTween and DOTween Pro are copyright (c) 2014-2018 Daniele Giardini - Demigiant 2 | 3 | // IMPORTANT!!! ///////////////////////////////////////////// 4 | // Upgrading DOTween from versions older than 1.2.000 /////// 5 | // (or DOTween Pro older than 1.0.000) ////////////////////// 6 | ------------------------------------------------------------- 7 | If you're upgrading your project from a version of DOTween older than 1.2.000 (or DOTween Pro older than 1.0.000) please follow these instructions carefully. 8 | 1) Import the new version in the same folder as the previous one, overwriting old files. A lot of errors will appear but don't worry 9 | 2) Close and reopen Unity (and your project). This is fundamental: skipping this step will cause a bloodbath 10 | 3) Open DOTween's Utility Panel (Tools > Demigiant > DOTween Utility Panel) if it doesn't open automatically, then press "Setup DOTween...": this will run the upgrade setup 11 | 4) From the Add/Remove Modules panel that opens, activate/deactivate Modules for Unity systems and for external assets (Pro version only) 12 | 13 | // GET STARTED ////////////////////////////////////////////// 14 | 15 | - After importing a new DOTween update, select DOTween's Utility Panel from the "Tools/Demigiant" menu (if it doesn't open automatically) and press the "Setup DOTween..." button to activate/deactivate Modules. You can also access a Preferences Tab from there to choose default settings for DOTween. 16 | - In your code, add "using DG.Tweening" to each class where you want to use DOTween. 17 | - You're ready to tween. Check out the links below for full documentation and license info. 18 | 19 | 20 | // LINKS /////////////////////////////////////////////////////// 21 | 22 | DOTween website (documentation, examples, etc): http://dotween.demigiant.com 23 | DOTween license: http://dotween.demigiant.com/license.php 24 | DOTween repository (Google Code): https://code.google.com/p/dotween/ 25 | Demigiant website (documentation, examples, etc): http://www.demigiant.com 26 | 27 | // NOTES ////////////////////////////////////////////////////// 28 | 29 | - DOTween's Utility Panel can be found under "Tools > Demigiant > DOTween Utility Panel" and also contains other useful options, plus a tab to set DOTween's preferences -------------------------------------------------------------------------------- /Juicy/Editor/Utils/ColorChooserDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [CustomPropertyDrawer(typeof(ColorChooser))] 7 | public sealed class ColorChooserDrawer : JuicyPropertyDrawerBase 8 | { 9 | private SerializedProperty useHdr; 10 | private SerializedProperty hdrColor; 11 | private SerializedProperty rgbColor; 12 | 13 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 14 | { 15 | return IsMinimalWidth ? EditorGUI.GetPropertyHeight(property) + 16 | SingleLineHeight + 17 | StandardSpacing 18 | : EditorGUI.GetPropertyHeight(property); 19 | } 20 | 21 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 22 | { 23 | using (new EditorGUI.PropertyScope(position, label, property)) { 24 | 25 | CacheProperty(ref useHdr, property, nameof(useHdr)); 26 | CacheProperty(ref hdrColor, property, nameof(hdrColor)); 27 | CacheProperty(ref rgbColor, property, nameof(rgbColor)); 28 | 29 | float width = IsMinimalWidth ? 10 : 40; 30 | 31 | Rect valueRect = new Rect(position) { 32 | width = position.width - width, 33 | height = EditorGUIUtility.singleLineHeight 34 | }; 35 | 36 | Rect buttonRect = new Rect(position) { 37 | y = valueRect.y, 38 | x = position.x + valueRect.width, 39 | width = width, 40 | height = EditorGUIUtility.singleLineHeight 41 | }; 42 | 43 | EditorGUI.PropertyField(valueRect, 44 | useHdr.boolValue 45 | ? hdrColor : rgbColor, 46 | new GUIContent(property.displayName)); 47 | 48 | if (GUI.Button(buttonRect, useHdr.boolValue ? "HDR" : "RGB")) { 49 | useHdr.boolValue = !useHdr.boolValue; 50 | } 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackObjectScale.cs: -------------------------------------------------------------------------------- 1 | using DG.Tweening; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Feedback("Object/Scale")][AddComponentMenu("")] 7 | public class JuicyFeedbackObjectScale : JuicyFeedbackObjectBase 8 | { 9 | [SerializeField] private TransformTarget target = new TransformTarget(); 10 | [SerializeField] private Vector3FromToValue value = new Vector3FromToValue(); 11 | [SerializeField] private Vector3Reset reset = new Vector3Reset(); 12 | //[SerializeField] private float resetUniformValue = 1; 13 | 14 | //[SerializeField] private bool uniformScale; 15 | [SerializeField] private bool relative = false; 16 | 17 | protected override void Play() 18 | { 19 | if (!target.IsValid) { 20 | return; 21 | } 22 | 23 | timing.Invoke(this, PlayDelayed); 24 | } 25 | 26 | private void PlayDelayed() 27 | { 28 | /*Vector3 endScale = uniformScale 29 | ? Vector3.one * uniformValue.value 30 | : value.value; 31 | */ 32 | Vector3 endScale = value.value; 33 | 34 | float duration = timing.duration; 35 | 36 | if (reset.resetType == ResetType.Yoyo) { 37 | duration /= 2; 38 | } 39 | 40 | tween = relative ? target.Value.DOBlendableScaleBy(endScale, duration) : 41 | target.Value.DOScale(endScale, duration); 42 | 43 | switch (reset.resetType) { 44 | case ResetType.Yoyo: 45 | tween.SetLoops(reset.loop ? -1 : 2, LoopType.Yoyo); 46 | break; 47 | case ResetType.ToValue: 48 | tween.onComplete += () => target.Value.localScale = reset.resetValue; 49 | tween.SetLoops(reset.loop ? -1 : 1, LoopType.Restart); 50 | break; 51 | } 52 | 53 | if (value.isFrom) { 54 | tween.From(); 55 | } else { 56 | tween.SetRelative(relative); 57 | } 58 | 59 | tween.SetEase(ease.curve); 60 | tween.SetUpdate(timing.ignoreTimeScale); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Utils/ReferenceTarget.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using UnityEngine; 4 | 5 | namespace TinyTools.Juicy 6 | { 7 | public abstract class ReferenceTarget 8 | where T : Component 9 | where TTarget : Target, new() 10 | { 11 | [SerializeField] private Type type = Type.ByReference; 12 | [SerializeField] private string name = string.Empty; 13 | [SerializeField] private T reference = null; 14 | 15 | [SerializeField] private TTarget target = new TTarget(); 16 | 17 | private T cachedReference; 18 | 19 | public bool IsValid => type == Type.ByName ? 20 | !name.Equals(string.Empty) && target.IsValid : 21 | target.IsValid; 22 | 23 | private enum Type 24 | { 25 | ByName, 26 | ByReference, 27 | } 28 | 29 | public T Value() { 30 | 31 | switch (type) { 32 | case Type.ByName: 33 | 34 | if (name.Equals(string.Empty)) { 35 | reference = null; 36 | } 37 | 38 | #if !UNITY_EDITOR 39 | if (cachedReference != null) { 40 | reference = cachedReference; 41 | } 42 | #endif 43 | 44 | GameObject obj = GameObject.Find(name); 45 | 46 | if (obj == null) { 47 | throw new UnityException($"Could not find an object with name '{name}'"); 48 | } 49 | 50 | if (obj.TryGetComponent(out T component)) { 51 | reference = cachedReference = component; 52 | } else { 53 | reference = null; 54 | } 55 | 56 | break; 57 | case Type.ByReference: 58 | reference = target.Value; 59 | break; 60 | 61 | } 62 | 63 | return reference; 64 | } 65 | } 66 | 67 | [Serializable] 68 | public sealed class LightReferenceTarget : ReferenceTarget {} 69 | [Serializable] 70 | public sealed class CameraReferenceTarget : ReferenceTarget {} 71 | } -------------------------------------------------------------------------------- /Juicy/Editor/Utils/ReferenceTargetDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [CustomPropertyDrawer(typeof(ReferenceTarget<,>), true)] 7 | public sealed class ReferenceTargetDrawer : JuicyPropertyDrawerBase 8 | { 9 | private SerializedProperty type; 10 | private SerializedProperty name; 11 | private SerializedProperty target; 12 | 13 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 14 | { 15 | CacheProperty(ref type, property, nameof(type)); 16 | CacheProperty(ref name, property, nameof(name)); 17 | CacheProperty(ref target, property, nameof(target)); 18 | 19 | float height = EditorGUI.GetPropertyHeight(type); 20 | 21 | if (type.enumValueIndex == 0) { 22 | height += EditorGUI.GetPropertyHeight(name); 23 | } else { 24 | height += EditorGUI.GetPropertyHeight(target); 25 | } 26 | 27 | return height + StandardSpacing; 28 | } 29 | 30 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 31 | { 32 | using (new EditorGUI.PropertyScope(position, label, property)) { 33 | Rect typeRect = new Rect(position) { 34 | height = EditorGUI.GetPropertyHeight(type) 35 | }; 36 | 37 | EditorGUI.PropertyField(typeRect, type, new GUIContent("Target", "Target of the effect")); 38 | 39 | EditorGUI.indentLevel++; 40 | 41 | if (type.enumValueIndex == 0) { 42 | EditorGUI.PropertyField(GetPropertyRect(position, name), name); 43 | } else { 44 | EditorGUI.PropertyField(GetPropertyRect(position, target), target); 45 | } 46 | 47 | EditorGUI.indentLevel--; 48 | } 49 | } 50 | 51 | private Rect GetPropertyRect(Rect position, SerializedProperty property) 52 | { 53 | return new Rect(position) { 54 | y = position.y + EditorGUI.GetPropertyHeight(type) + StandardSpacing, 55 | height = EditorGUI.GetPropertyHeight(property), 56 | width = position.width - EditorGUI.indentLevel * 4 57 | }; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackObjectMove.cs: -------------------------------------------------------------------------------- 1 | using DG.Tweening; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Feedback("Object/Move")][AddComponentMenu("")] 7 | public class JuicyFeedbackObjectMove : JuicyFeedbackObjectBase 8 | { 9 | [SerializeField] private TransformTarget target = new TransformTarget(); 10 | [SerializeField] private Vector3Reset reset = new Vector3Reset(); 11 | [SerializeField] private Vector3FromToValue value = new Vector3FromToValue(); 12 | [SerializeField] private bool relative = false; 13 | [SerializeField] private bool snapping = false; 14 | 15 | private Transform targetTransform; 16 | 17 | protected override void Play() 18 | { 19 | if (!target.IsValid) { 20 | return; 21 | } 22 | 23 | timing.Invoke(this, PlayDelayed); 24 | } 25 | 26 | private void PlayDelayed() 27 | { 28 | targetTransform = target.Value; 29 | 30 | float duration = timing.duration; 31 | 32 | if (reset.resetType == ResetType.Yoyo) { 33 | duration /= 2; 34 | } 35 | 36 | if (relative) { 37 | tween = targetTransform.DOBlendableMoveBy( 38 | value.value, 39 | duration, 40 | snapping 41 | ); 42 | } else { 43 | tween = targetTransform.DOMove( 44 | value.value, 45 | duration, 46 | snapping); 47 | } 48 | 49 | if (value.isFrom) { 50 | tween.From(); 51 | } else { 52 | tween.SetRelative(relative); 53 | } 54 | 55 | switch (reset.resetType) { 56 | case ResetType.Yoyo: 57 | tween.SetLoops(reset.loop ? -1 : 2, LoopType.Yoyo); 58 | break; 59 | case ResetType.ToValue: 60 | tween.onComplete += () => targetTransform.position = reset.resetValue; 61 | tween.SetLoops(reset.loop ? -1 : 1, LoopType.Restart); 62 | break; 63 | } 64 | 65 | tween.SetEase(ease.curve); 66 | tween.SetUpdate(timing.ignoreTimeScale); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackAudioBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using UnityEngine.Audio; 4 | using Random = UnityEngine.Random; 5 | 6 | namespace TinyTools.Juicy 7 | { 8 | public abstract class JuicyFeedbackAudioBase : JuicyFeedbackBase 9 | { 10 | [SerializeField, Timing(TimingAttribute.TimingStyle.HideDuration)] protected Timing timing = new Timing(); 11 | [SerializeField] protected AudioClipList clips = new AudioClipList(); 12 | [SerializeField, MinMaxRange(0, 1)] protected Vector2 volume = Vector2.one; 13 | [SerializeField, MinMaxRange(-3, 3)] protected Vector2 pitch = Vector2.one; 14 | [SerializeField, Range(0, 1)] protected float probability = 1f; 15 | [SerializeField] protected CustomPosition useCustomPosition = new CustomPosition(); 16 | 17 | protected override void Play() 18 | { 19 | if (Random.value >= probability) { 20 | return; 21 | } 22 | 23 | if (clips.IsEmpty) { 24 | return; 25 | } 26 | 27 | timing.Invoke(this, PerformDelayed); 28 | } 29 | 30 | protected virtual void PlayClip(Vector3 position, AudioClip clip, float volume, float pitch) 31 | { 32 | } 33 | 34 | private void PerformDelayed() 35 | { 36 | AudioClip currentClip = clips.Random; 37 | 38 | if (currentClip == null) { 39 | return; 40 | } 41 | 42 | float v = Random.Range(volume.x, volume.y); 43 | float p = Random.Range(pitch.x, pitch.y); 44 | 45 | PlayClip(transform.position, currentClip, v, p); 46 | } 47 | } 48 | 49 | [Serializable] 50 | public class UseAudioSource : ToggleGroup 51 | { 52 | public AudioMixerGroup group = null; 53 | public AudioSourceTarget source; 54 | } 55 | 56 | [Serializable] 57 | public class AudioClipList 58 | { 59 | public AudioClip[] clips; 60 | 61 | public bool IsEmpty => Length == 0; 62 | public int Length => clips?.Length ?? 0; 63 | 64 | public AudioClip this[int index] => clips[index]; 65 | 66 | public AudioClip Random => clips?.Length > 0 ? clips[UnityEngine.Random.Range(0, clips.Length)] : null; 67 | public AudioClip First => clips?.Length > 0 ? clips[0] : null; 68 | 69 | } 70 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackObjectFade.cs: -------------------------------------------------------------------------------- 1 | using DG.Tweening; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Feedback("Renderer/Fade")][AddComponentMenu("")] 7 | public class JuicyFeedbackObjectFade : JuicyFeedbackObjectBase 8 | { 9 | [SerializeField] private RendererTarget target = new RendererTarget(); 10 | [SerializeField] private SliderFromToValue value = new SliderFromToValue(); 11 | [SerializeField] private FloatReset resetValue = new FloatReset(); 12 | 13 | private Material material; 14 | 15 | protected override void Play() 16 | { 17 | if (!target.IsValid) { 18 | return; 19 | } 20 | 21 | timing.Invoke(this, PlayDelayed); 22 | } 23 | 24 | private void PlayDelayed() 25 | { 26 | float duration = timing.duration; 27 | 28 | if (resetValue.resetType == ResetType.Yoyo) { 29 | duration /= 2; 30 | } 31 | 32 | if (target.Value is SpriteRenderer r) { 33 | tween = r.DOBlendableFade(value.value, duration); 34 | 35 | if (resetValue.resetType == ResetType.ToValue) { 36 | tween.onComplete = () => { r.DOFade(resetValue.resetValue, 0f);}; 37 | } 38 | 39 | } else { 40 | 41 | material = target.Value.material; 42 | 43 | if (material != null) { 44 | tween = material.DOBlendableFade(value.value, duration); 45 | 46 | if (resetValue.resetType == ResetType.ToValue) { 47 | tween.onComplete = () => { material.DOFade(resetValue.resetValue, 0); }; 48 | } 49 | } 50 | } 51 | 52 | if (value.isFrom) { 53 | tween.From(); 54 | } 55 | 56 | switch (resetValue.resetType) { 57 | case ResetType.Yoyo: 58 | tween.SetLoops(resetValue.loop ? -1 : 2, LoopType.Yoyo); 59 | break; 60 | case ResetType.ToValue: 61 | tween.SetLoops(resetValue.loop ? -1 : 1, LoopType.Restart); 62 | break; 63 | } 64 | 65 | tween.SetEase(ease.curve); 66 | tween.SetUpdate(timing.ignoreTimeScale); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /Juicy/Plugins/PoolAttendant/Editor/ReorderableListDefaultPoolItem.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEditorInternal; 3 | using UnityEngine; 4 | 5 | namespace TinyTools.PoolAttendant 6 | { 7 | [CustomPropertyDrawer(typeof(DefaultPoolItemList))] 8 | public class ReorderableListDefaultPoolItem : PropertyDrawer 9 | { 10 | private SerializedProperty listProperty; 11 | private SerializedObject serializedObject; 12 | private ReorderableList reorderableList; 13 | 14 | private const float DefaultHeight = 65; 15 | 16 | private void Initialize(SerializedProperty property) 17 | { 18 | if (reorderableList != null) { 19 | return; 20 | } 21 | 22 | FindItemsProperty(property); 23 | 24 | serializedObject = property.serializedObject; 25 | 26 | reorderableList = new ReorderableList(serializedObject, listProperty, 27 | false, true, true, true); 28 | 29 | InitializeHeader(); 30 | InitializeItems(); 31 | } 32 | 33 | private void InitializeHeader() 34 | { 35 | reorderableList.drawHeaderCallback = rect => 36 | { 37 | EditorGUI.LabelField(rect, new GUIContent(listProperty.displayName)); 38 | }; 39 | } 40 | 41 | private void InitializeItems() 42 | { 43 | reorderableList.drawElementCallback = (rect, index, active, focused) => 44 | { 45 | SerializedProperty property = listProperty.GetArrayElementAtIndex(index); 46 | 47 | EditorGUI.PropertyField(rect, property); 48 | serializedObject.ApplyModifiedProperties(); 49 | }; 50 | } 51 | 52 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 53 | { 54 | Initialize(property); 55 | 56 | float height = DefaultHeight; 57 | 58 | for (int i = 0; i < reorderableList.count; i++) { 59 | height += EditorGUI.GetPropertyHeight(listProperty.GetArrayElementAtIndex(i)); 60 | } 61 | 62 | return height; 63 | } 64 | 65 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 66 | { 67 | reorderableList.DoList(position); 68 | } 69 | 70 | private void FindItemsProperty(SerializedProperty property) 71 | { 72 | listProperty = property.FindPropertyRelative("items"); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackAnimator.cs: -------------------------------------------------------------------------------- 1 | using DG.Tweening; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Feedback("Animator")][AddComponentMenu("")] 7 | public class JuicyFeedbackAnimator : JuicyFeedbackBase 8 | { 9 | [SerializeField, Timing] private Timing timing = new Timing(); 10 | 11 | [SerializeField] private AnimatorTarget target = new AnimatorTarget(); 12 | 13 | [SerializeField] private bool boolValue = false; 14 | [SerializeField] private int intValue = 0; 15 | [SerializeField] private float floatValue = 0; 16 | 17 | [SerializeField] private int hash = 0; 18 | [SerializeField] private AnimatorControllerParameterType type = 19 | AnimatorControllerParameterType.Float; 20 | #pragma warning disable 0414 21 | [SerializeField] private int selectedIndex = 0; 22 | #pragma warning restore 0414 23 | 24 | [SerializeField] private Ease ease = new Ease(); 25 | 26 | protected override void Play() 27 | { 28 | if (!target.IsValid) { 29 | return; 30 | } 31 | 32 | timing.Invoke(this, PlayDelayed); 33 | } 34 | 35 | private void PlayDelayed() 36 | { 37 | /*if (tween != null && tween.IsPlaying()) { 38 | tween.ChangeValues(0, 10, 39 | timing.duration); 40 | }*/ 41 | switch (type) { 42 | case AnimatorControllerParameterType.Int: 43 | 44 | tween = DOTween.To(() => target.Value.GetInteger(hash), 45 | x => target.Value.SetInteger(hash, x), 46 | intValue, timing.duration 47 | ); 48 | 49 | break; 50 | case AnimatorControllerParameterType.Float: 51 | 52 | tween = DOTween.To(() => target.Value.GetFloat(hash), 53 | x => target.Value.SetFloat(hash, x), 54 | floatValue, timing.duration 55 | ); 56 | 57 | //target.Value.SetFloat(hash, floatValue); 58 | break; 59 | case AnimatorControllerParameterType.Trigger: 60 | target.Value.SetTrigger(hash); 61 | break; 62 | case AnimatorControllerParameterType.Bool: 63 | target.Value.SetBool(hash, boolValue); 64 | break; 65 | } 66 | 67 | tween.SetEase(ease.curve); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /Juicy/Editor/Utils/ResetDrawer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace TinyTools.Juicy 6 | { 7 | [CustomPropertyDrawer(typeof(Reset<>), true)] 8 | public sealed class ResetDrawer : JuicyPropertyDrawerBase 9 | { 10 | private SerializedProperty resetType; 11 | private SerializedProperty loop; 12 | private SerializedProperty resetValue; 13 | 14 | private ResetType type; 15 | private bool ShowLoop => type == ResetType.ToValue || type == ResetType.Yoyo; 16 | 17 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 18 | { 19 | CacheProperty(ref resetType, property, nameof(resetType)); 20 | CacheProperty(ref loop, property, nameof(loop)); 21 | CacheProperty(ref resetValue, property, nameof(resetValue)); 22 | 23 | type = 24 | (ResetType) Enum.GetValues(typeof(ResetType)) 25 | .GetValue(resetType?.enumValueIndex ?? 0); 26 | 27 | float height = ShowLoop 28 | ? EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing 29 | : 0; 30 | 31 | height += type == ResetType.ToValue ? EditorGUIUtility.standardVerticalSpacing + 32 | EditorGUIUtility.singleLineHeight : 0; 33 | 34 | return EditorGUIUtility.singleLineHeight + height; 35 | } 36 | 37 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 38 | { 39 | using (new EditorGUI.PropertyScope(position, label, property)) { 40 | Rect resetRect = new Rect(position) { 41 | height = EditorGUIUtility.singleLineHeight 42 | }; 43 | 44 | Rect loopRect = new Rect(position) { 45 | y = position.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing, 46 | height = EditorGUIUtility.singleLineHeight 47 | }; 48 | 49 | Rect valueRect = new Rect(position) { 50 | y = position.y + EditorGUIUtility.singleLineHeight * 2 + 51 | EditorGUIUtility.standardVerticalSpacing * 2, 52 | height = EditorGUIUtility.singleLineHeight 53 | }; 54 | 55 | EditorGUI.PropertyField(resetRect, resetType); 56 | 57 | if (ShowLoop) { 58 | EditorGUI.PropertyField(loopRect, loop); 59 | } 60 | 61 | if (type == ResetType.ToValue) { 62 | EditorGUI.PropertyField(valueRect, resetValue); 63 | } 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackObjectTint.cs: -------------------------------------------------------------------------------- 1 | using DG.Tweening; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Feedback("Renderer/Tint")][AddComponentMenu("")] 7 | public class JuicyFeedbackObjectTint : JuicyFeedbackObjectBase 8 | { 9 | [SerializeField] private RendererTarget target = new RendererTarget(); 10 | [SerializeField] private ColorFromToValue color = new ColorFromToValue(); 11 | [SerializeField] private ColorReset resetColor = new ColorReset(); 12 | 13 | private Material material; 14 | 15 | protected override void Play() 16 | { 17 | if (!target.IsValid) { 18 | return; 19 | } 20 | 21 | timing.Invoke(this, PlayDelayed); 22 | } 23 | 24 | private void PlayDelayed() 25 | { 26 | float duration = timing.duration; 27 | 28 | if (resetColor.resetType == ResetType.Yoyo) { 29 | duration /= 2; 30 | } 31 | 32 | if (target.Value is SpriteRenderer r) { 33 | CreateSpriteRendererTween(r, duration); 34 | 35 | } else { 36 | CreateDefaultRendererTween(duration); 37 | } 38 | 39 | if (color.isFrom) { 40 | tween.From(); 41 | } 42 | 43 | switch (resetColor.resetType) { 44 | case ResetType.Yoyo: 45 | tween.SetLoops(resetColor.loop ? -1 : 2, LoopType.Yoyo); 46 | break; 47 | case ResetType.ToValue: 48 | tween.SetLoops(resetColor.loop ? -1 : 1, LoopType.Restart); 49 | break; 50 | } 51 | 52 | tween.SetEase(ease.curve); 53 | tween.SetUpdate(timing.ignoreTimeScale); 54 | } 55 | 56 | private void CreateSpriteRendererTween(SpriteRenderer r, float duration) 57 | { 58 | tween = r.DOBlendableColor(color.value, duration); 59 | 60 | if (resetColor.resetType == ResetType.ToValue) { 61 | tween.onComplete = () => { r.color = 62 | resetColor.resetValue; }; 63 | } 64 | } 65 | 66 | private void CreateDefaultRendererTween(float duration) 67 | { 68 | material = target.Value.material; 69 | 70 | if (material == null) { 71 | return; 72 | } 73 | 74 | tween = material.DOBlendableColor(color.value, duration); 75 | 76 | if (resetColor.resetType == ResetType.ToValue) { 77 | tween.onComplete = () => { material.color = 78 | resetColor.resetValue; }; 79 | } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /Juicy/Editor/Utils/JuicyEditorBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEditor; 4 | 5 | namespace TinyTools.Juicy 6 | { 7 | public abstract class JuicyEditorBase : Editor 8 | { 9 | protected SerializedProperty isExpanded; 10 | 11 | private readonly Dictionary cachedProperties = 12 | new Dictionary(); 13 | 14 | private void OnEnable() 15 | { 16 | try 17 | { 18 | CacheProperty(ref isExpanded, nameof(isExpanded)); 19 | Initialize(); 20 | } 21 | catch (Exception) { 22 | // ignored 23 | } 24 | } 25 | 26 | private void OnDisable() 27 | { 28 | cachedProperties.Clear(); 29 | } 30 | 31 | private void Reset() 32 | { 33 | cachedProperties.Clear(); 34 | } 35 | 36 | protected virtual void Initialize() {} 37 | 38 | protected void CacheProperty(ref SerializedProperty property, string name) 39 | { 40 | if (property == null) { 41 | property = serializedObject.FindProperty(name); 42 | } 43 | } 44 | 45 | protected void CacheProperty(ref SerializedProperty property, SerializedProperty parent, string name) 46 | { 47 | if (property == null && parent != null) { 48 | property = parent.FindPropertyRelative(name); 49 | } 50 | } 51 | 52 | protected SerializedProperty CacheProperty(string name) 53 | { 54 | if (cachedProperties.TryGetValue(name, out var cachedProperty)) { 55 | return cachedProperty; 56 | } 57 | 58 | SerializedProperty property = serializedObject.FindProperty(name); 59 | cachedProperties.Add(name, property); 60 | 61 | return property; 62 | } 63 | 64 | protected IEnumerable GetChildren(SerializedObject parent) 65 | { 66 | SerializedProperty property = parent.GetIterator(); 67 | 68 | property = property.Copy(); 69 | 70 | property.NextVisible(true); 71 | bool hasNext = true; 72 | 73 | while (hasNext) 74 | { 75 | if (SerializedProperty.EqualContents(property, property.GetEndProperty())) 76 | { 77 | yield break; 78 | } 79 | 80 | yield return property; 81 | 82 | 83 | hasNext = property.NextVisible(false); 84 | } 85 | } 86 | 87 | protected IEnumerable GetChildren() 88 | { 89 | return GetChildren(serializedObject); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /Juicy/Editor/Attributes/MinMaxRangeAttributeDrawer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace TinyTools.Juicy 6 | { 7 | [CustomPropertyDrawer(typeof(MinMaxRange))] 8 | public sealed class MinMaxRangeAttributeDrawer : JuicyPropertyDrawerBase 9 | { 10 | private MinMaxRange MinMaxAttribute => (MinMaxRange)attribute; 11 | 12 | private const float FieldWidth = 80; 13 | 14 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 15 | { 16 | return EditorGUIUtility.singleLineHeight; 17 | } 18 | 19 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 20 | { 21 | using (new EditorGUI.PropertyScope(position, label, property)) { 22 | 23 | float min = property.vector2Value.x; 24 | float max = property.vector2Value.y; 25 | 26 | if (!IsMinimalWidth) { 27 | 28 | Rect labelRect = new Rect(position) { 29 | x = position.x, 30 | width = EditorGUIUtility.labelWidth 31 | }; 32 | 33 | float width = EditorGUI.indentLevel > 1 ? EditorGUI.indentLevel * 16 - 5 : 16; 34 | 35 | Rect minRect = new Rect(position) { 36 | x = position.x + EditorGUIUtility.labelWidth - width, 37 | width = FieldWidth, 38 | }; 39 | 40 | Rect maxRect = new Rect(position) { 41 | x = position.width - FieldWidth + width + 5, 42 | width = FieldWidth 43 | }; 44 | 45 | float sliderOffset = EditorGUI.indentLevel > 1 ? 20 : 8; 46 | 47 | Rect sliderRect = new Rect(position) { 48 | x = minRect.x + minRect.width - sliderOffset, 49 | xMax = maxRect.x + sliderOffset 50 | }; 51 | 52 | EditorGUI.LabelField(labelRect, label); 53 | 54 | min = (float) Math.Round(EditorGUI.FloatField(minRect, min), 3); 55 | EditorGUI.MinMaxSlider(sliderRect, ref min, 56 | ref max, MinMaxAttribute.min, MinMaxAttribute.max); 57 | max = (float) Math.Round(EditorGUI.FloatField(maxRect, max), 3); 58 | 59 | } else { 60 | EditorGUI.MinMaxSlider(position, label, ref min, ref max, 61 | MinMaxAttribute.min, MinMaxAttribute.max); 62 | } 63 | 64 | property.vector2Value = new Vector2(Mathf.Max(min, MinMaxAttribute.min), 65 | Mathf.Min(max, MinMaxAttribute.max)); 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /Juicy/Plugins/PoolAttendant/PoolAttendantExtention.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace TinyTools.PoolAttendant 4 | { 5 | public static class PoolAttendantExtentions 6 | { 7 | public static T GetPooledInstance(this T mono) where T : Component 8 | { 9 | return mono.gameObject.GetPooledInstance(Vector3.zero); 10 | } 11 | 12 | public static T GetPooledInstance(this T mono, Vector3 position) where T : Component 13 | { 14 | return mono.gameObject.GetPooledInstance(position, mono.transform.rotation); 15 | } 16 | 17 | public static T GetPooledInstance(this T mono, Vector3 position, Quaternion rotation) where T : Component 18 | { 19 | return mono.gameObject.GetPooledInstance(position, rotation, mono.transform.localScale); 20 | } 21 | 22 | public static T GetPooledInstance(this T mono, Vector3 position, Quaternion rotation, Vector3 scale) where T : Component 23 | { 24 | return Pool.Instance.Get(mono.gameObject, position, rotation, scale); 25 | } 26 | 27 | public static GameObject GetPooledInstance(this GameObject prefab) 28 | { 29 | return prefab.GetPooledInstance(Vector3.zero); 30 | } 31 | 32 | public static GameObject GetPooledInstance(this GameObject prefab, Vector3 position) 33 | { 34 | return prefab.GetPooledInstance(position, prefab.transform.rotation); 35 | } 36 | 37 | public static GameObject GetPooledInstance(this GameObject prefab, Vector3 position, Quaternion rotation) 38 | { 39 | return prefab.GetPooledInstance(position, rotation, prefab.transform.localScale); 40 | } 41 | 42 | public static GameObject GetPooledInstance(this GameObject prefab, Vector3 position, Quaternion rotation, Vector3 scale) 43 | { 44 | return Pool.Instance.Get(prefab, position, rotation, scale); 45 | } 46 | 47 | public static T GetPooledInstance(this GameObject prefab) where T : Component 48 | { 49 | return prefab.GetPooledInstance(Vector3.zero); 50 | } 51 | 52 | public static T GetPooledInstance(this GameObject prefab, Vector3 position) where T : Component 53 | { 54 | return prefab.GetPooledInstance(position, prefab.transform.rotation); 55 | } 56 | 57 | public static T GetPooledInstance(this GameObject prefab, Vector3 position, Quaternion rotation) where T : Component 58 | { 59 | return prefab.GetPooledInstance(position, rotation, prefab.transform.localScale); 60 | } 61 | 62 | public static T GetPooledInstance(this GameObject prefab, Vector3 position, Quaternion rotation, Vector3 scale) where T : Component 63 | { 64 | return Pool.Instance.Get(prefab, position, rotation, scale); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackCameraShake.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [Feedback("Camera/Shake")][AddComponentMenu("")] 7 | public class JuicyFeedbackCameraShake : JuicyFeedbackCameraBase 8 | { 9 | [SerializeField, Timing] private Timing timing = new Timing(); 10 | 11 | /// 12 | /// Maximum distance in each direction the transform 13 | /// with translate during shaking. 14 | /// 15 | [SerializeField] 16 | private Vector3 maximumStrength = Vector3.one; 17 | 18 | /// 19 | /// Frequency of the Perlin noise function. Higher values 20 | /// will result in faster shaking. 21 | /// 22 | [SerializeField] 23 | private float frequency = 25; 24 | 25 | /// 26 | /// Higher values will result in a smoother shake falloff. 27 | /// 28 | [SerializeField] 29 | private float smoothness = 1; 30 | 31 | [SerializeField] private float recoverySpeed = 1f; 32 | 33 | [SerializeField] private UseRangeMultiplier useRangeMultiplier = new UseRangeMultiplier(); 34 | [SerializeField] private Ease ease = new Ease(); 35 | 36 | protected override void Play() 37 | { 38 | if (camera.Value() == null) { 39 | return; 40 | } 41 | timing.Invoke(this, PerformShakeDelayed); 42 | } 43 | 44 | private void PerformShakeDelayed() 45 | { 46 | float shakePower = 1f; 47 | 48 | if (useRangeMultiplier.isActive) { 49 | float distance = Vector3.Distance(transform.position, camera.Value().transform.position); 50 | float distance01 = Mathf.Clamp01(distance / useRangeMultiplier.range); 51 | shakePower = (1 - Mathf.Pow(distance01, 2)) * useRangeMultiplier.maximumPower; 52 | } 53 | 54 | JuicyCameraShaker.Instance(camera.Value()).Shake(new JuicyCameraShaker.ShakeProperties { 55 | power = shakePower, 56 | ignoreTimeScale = timing.ignoreTimeScale, 57 | smoothness = smoothness, 58 | duration = timing.duration, 59 | maximumStrength = maximumStrength, 60 | frequency = frequency, 61 | falloffCurve = ease.curve, 62 | recoverySpeed = recoverySpeed 63 | }); 64 | } 65 | } 66 | 67 | [Serializable] 68 | public class UseRangeMultiplier : ToggleGroup 69 | { 70 | /// 71 | /// Range to calculate the minimum and maximum shake amount by distance 72 | /// 73 | public float range = 45; 74 | /// 75 | /// Maximum possible power that will be inflicted 76 | /// on the target when it is immediately beside the 77 | /// effect. 78 | /// 79 | public float maximumPower = 0.6f; 80 | } 81 | } -------------------------------------------------------------------------------- /Juicy/Runtime/JuicyFeedbackList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | #if UNITY_EDITOR 4 | using UnityEditor; 5 | #endif 6 | using UnityEngine; 7 | 8 | namespace TinyTools.Juicy 9 | { 10 | [ExecuteInEditMode] 11 | public sealed class JuicyFeedbackList : MonoBehaviour 12 | { 13 | public List feedbackList = new List(); 14 | 15 | /// 16 | /// Name which can be modified in the editor to name individual feedback elements 17 | /// 18 | public string displayName = ""; 19 | 20 | /// 21 | /// If play on enable is true, the feedback list will play all feedback elements on enable 22 | /// 23 | public bool playOnEnable = false; 24 | /// 25 | /// If enabled all hidden components are visible 26 | /// 27 | public bool debugView; 28 | 29 | private void OnEnable() 30 | { 31 | if (playOnEnable) { 32 | Play(); 33 | } 34 | } 35 | 36 | [ContextMenu("Jausdnjaksndjkasd")] 37 | public void Debug() 38 | { 39 | for(var i = feedbackList.Count - 1; i > -1; i--) 40 | { 41 | if (feedbackList[i] == null) 42 | feedbackList.RemoveAt(i); 43 | } 44 | } 45 | 46 | /// 47 | /// If active, loop through the feedback list and start playing every feedback 48 | /// 49 | public void Play() 50 | { 51 | if (!enabled) return; 52 | 53 | foreach (JuicyFeedbackBase feedback in feedbackList) 54 | { 55 | feedback.PlayFeedback(); 56 | } 57 | } 58 | 59 | /// 60 | /// If active, loop through the feedback list and stops every feedback 61 | /// 62 | public void Stop() 63 | { 64 | if (!enabled) return; 65 | 66 | foreach (JuicyFeedbackBase feedback in feedbackList) { 67 | feedback.Stop(); 68 | } 69 | } 70 | 71 | /// 72 | /// If active, loop through the feedback list and pause every feedback 73 | /// 74 | public void Pause() 75 | { 76 | if (!enabled) return; 77 | 78 | foreach (JuicyFeedbackBase feedback in feedbackList) { 79 | feedback.Pause(); 80 | } 81 | } 82 | 83 | /// 84 | /// Removes a feedback from the list of feedbacks 85 | /// 86 | /// 87 | public void Remove(JuicyFeedbackBase feedback) 88 | { 89 | feedbackList.Remove(feedback); 90 | } 91 | 92 | #if UNITY_EDITOR 93 | private void OnDestroy() 94 | { 95 | if (Application.isPlaying) { 96 | return; 97 | } 98 | 99 | EditorApplication.delayCall += Destroy; 100 | } 101 | 102 | private void Destroy() 103 | { 104 | foreach (JuicyFeedbackBase feedback in feedbackList) { 105 | DestroyImmediate(feedback); 106 | } 107 | 108 | feedbackList.Clear(); 109 | } 110 | #endif 111 | } 112 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackObjectCreator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using Random = UnityEngine.Random; 4 | 5 | namespace TinyTools.Juicy 6 | { 7 | public abstract class JuicyFeedbackObjectCreator : JuicyFeedbackBase where T : Component 8 | { 9 | [SerializeField, Timing(TimingAttribute.TimingStyle.HideDuration)] private Timing timing = new Timing(); 10 | 11 | [Tooltip("Prefab to instantiate")] [SerializeField] 12 | protected T prefab; 13 | [Tooltip("Spawn probability")] 14 | [SerializeField, Range(0, 1)] private float probability = 1f; 15 | [Tooltip("Set a custom position")] 16 | [SerializeField] private CustomPosition customPosition = new CustomPosition(); 17 | [Tooltip("Set a custom rotation")] 18 | [SerializeField] private CustomRotation customRotation = new CustomRotation(); 19 | [Tooltip("Parent the particle system")] 20 | [SerializeField] private CustomParent parent = new CustomParent(); 21 | 22 | protected T objectReference; 23 | protected Transform cachedTransform; 24 | 25 | private void Awake() 26 | { 27 | cachedTransform = transform; 28 | } 29 | 30 | protected override void Play() 31 | { 32 | if (Random.value >= probability) { 33 | return; 34 | } 35 | 36 | timing.Invoke(this, PlayDelayed); 37 | } 38 | 39 | internal override void Pause() 40 | { 41 | base.Pause(); 42 | timing.Pause(); 43 | } 44 | 45 | protected virtual T Instantiate() 46 | { 47 | return Instantiate(prefab.gameObject).GetComponent(); 48 | } 49 | 50 | private void PlayDelayed() 51 | { 52 | objectReference = Instantiate(); 53 | var systemTransform = objectReference.transform; 54 | 55 | Vector3 spawnPosition = cachedTransform.position; 56 | Vector3 offset = customPosition.offset; 57 | 58 | if (customPosition.isActive) { 59 | if (customPosition.spawnAt != null) { 60 | spawnPosition = customPosition.spawnAt.position; 61 | } else { 62 | var worldOffset = cachedTransform.rotation * offset; 63 | spawnPosition = cachedTransform.position + worldOffset; 64 | } 65 | 66 | systemTransform.position = spawnPosition; 67 | systemTransform.localPosition += offset; 68 | } 69 | 70 | systemTransform.position = spawnPosition; 71 | 72 | if (parent.isActive) { 73 | objectReference.transform.SetParent(parent.parentTransform.Value); 74 | } 75 | 76 | if (customRotation.isActive) { 77 | systemTransform.localRotation = customRotation.rotateForward ? 78 | systemTransform.rotation : Quaternion.Euler(customRotation.rotation); 79 | } 80 | } 81 | } 82 | 83 | [Serializable] 84 | public class CustomRotation : ToggleGroup 85 | { 86 | [Tooltip("Rotation value relative to the caller")] 87 | public Vector3 rotation; 88 | [Tooltip("Rotates in the caller forward direction")] 89 | public bool rotateForward; 90 | } 91 | 92 | [Serializable] 93 | public class CustomParent : ToggleGroup 94 | { 95 | [Tooltip("Parents to a specific transform")] 96 | public TransformTarget parentTransform; 97 | } 98 | } -------------------------------------------------------------------------------- /Juicy/Editor/Utils/AnimationCurve3dDrawer.cs: -------------------------------------------------------------------------------- 1 | namespace TinyTools.Juicy 2 | { 3 | /*[CustomPropertyDrawer(typeof(AnimationCurve3d))] 4 | public class AnimationCurve3dDrawer : JuicyPropertyDrawerBase 5 | { 6 | private SerializedProperty xCurve; 7 | private SerializedProperty yCurve; 8 | private SerializedProperty zCurve; 9 | private SerializedProperty type; 10 | 11 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 12 | { 13 | CacheProperty(ref xCurve, property, nameof(xCurve)); 14 | CacheProperty(ref yCurve, property, nameof(yCurve)); 15 | CacheProperty(ref zCurve, property, nameof(zCurve)); 16 | CacheProperty(ref type, property, nameof(type)); 17 | 18 | float height = (type.enumValueIndex + 1) * SingleLineHeight * StandardSpacing; 19 | 20 | return !property.isExpanded ? 21 | SingleLineHeight : SingleLineHeight + height; 22 | } 23 | 24 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 25 | { 26 | using (new EditorGUI.PropertyScope(position, label, property)) { 27 | 28 | int indent = 0; 29 | if (EditorGUIUtility.hierarchyMode) { 30 | indent = (EditorStyles.foldout.padding.left - EditorStyles.label.padding.left) - 4; 31 | position.xMin += indent; 32 | } 33 | 34 | float labelWidth = EditorGUIUtility.labelWidth; 35 | EditorGUIUtility.labelWidth -= indent; 36 | 37 | Rect foldoutRect = new Rect(position) { 38 | height = SingleLineHeight, 39 | }; 40 | 41 | property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, label, true); 42 | 43 | if (!property.isExpanded) { 44 | return; 45 | } 46 | 47 | using (new EditorGUI.IndentLevelScope()) { 48 | 49 | if (type.enumValueIndex >= 0) { 50 | Rect xRect = new Rect(position) { 51 | y = position.y + SingleLineHeight + StandardSpacing, 52 | height = EditorGUI.GetPropertyHeight(xCurve) 53 | }; 54 | EditorGUI.PropertyField(xRect, xCurve, 55 | new GUIContent(type.enumValueIndex == 0 ? "Curve" : "X")); 56 | 57 | if (type.enumValueIndex > 0) { 58 | Rect yRect = new Rect(position) { 59 | y = xRect.y + SingleLineHeight + StandardSpacing, 60 | height = EditorGUI.GetPropertyHeight(yCurve) 61 | }; 62 | EditorGUI.PropertyField(yRect, yCurve, new GUIContent("Y")); 63 | 64 | if (type.enumValueIndex > 1) { 65 | Rect zRect = new Rect(position) { 66 | y = yRect.y + SingleLineHeight + StandardSpacing, 67 | height = EditorGUI.GetPropertyHeight(zCurve) 68 | }; 69 | EditorGUI.PropertyField(zRect, zCurve, new GUIContent("Z")); 70 | } 71 | } 72 | } 73 | 74 | } 75 | 76 | EditorGUIUtility.labelWidth = labelWidth; 77 | } 78 | } 79 | }*/ 80 | } -------------------------------------------------------------------------------- /Juicy/Editor/Feedback/JuicyFeedbackBaseEditor.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace TinyTools.Juicy 6 | { 7 | [CustomEditor(typeof(JuicyFeedbackBase), true)] 8 | public class JuicyFeedbackBaseEditor : JuicyEditorBase 9 | { 10 | private const string Script = "m_Script"; 11 | 12 | private SerializedProperty timing; 13 | private SerializedProperty ease; 14 | 15 | private int childrenCount; 16 | 17 | protected override void Initialize() 18 | { 19 | CacheProperty(ref timing, nameof(timing)); 20 | CacheProperty(ref ease, nameof(ease)); 21 | 22 | childrenCount = GetChildren().ToList().Count; 23 | } 24 | 25 | public override void OnInspectorGUI() 26 | { 27 | serializedObject.Update(); 28 | 29 | if (timing != null) { 30 | timing.isExpanded = JuicyEditorUtils.FoldoutHeader(timing.isExpanded, "Timing", () => 31 | { 32 | EditorGUILayout.PropertyField(timing); 33 | }); 34 | } 35 | 36 | if (ContainsOnlyScriptAndTimingProperty()) { 37 | 38 | serializedObject.ApplyModifiedProperties(); 39 | return; 40 | } 41 | 42 | isExpanded.boolValue = JuicyEditorUtils.FoldoutHeader(isExpanded.boolValue, "Properties", () => 43 | { 44 | foreach (SerializedProperty child in GetChildren()) { 45 | if (FilterProperty(child)) { 46 | continue; 47 | } 48 | 49 | if (child.type.Contains("UnityEvent")) { 50 | 51 | using (new EditorGUILayout.HorizontalScope()) { 52 | GUILayout.Space(35); 53 | EditorGUILayout.PropertyField(child); 54 | } 55 | } else { 56 | if (SpecialFilterProperty(child)) { 57 | SpecialDraw(child); 58 | } else { 59 | EditorGUILayout.PropertyField(child); 60 | } 61 | } 62 | } 63 | 64 | // Draw ease always at the end 65 | if (ease != null) { 66 | EditorGUILayout.PropertyField(ease); 67 | } 68 | }); 69 | 70 | serializedObject.ApplyModifiedProperties(); 71 | } 72 | 73 | private bool ContainsOnlyScriptAndTimingProperty() 74 | { 75 | return childrenCount == 2; 76 | } 77 | 78 | protected virtual bool FilterProperty(SerializedProperty property) 79 | { 80 | return property.name.Equals(Script) || 81 | property.name.Equals(nameof(timing)) || 82 | property.name.Equals(nameof(ease)); 83 | } 84 | 85 | protected virtual bool SpecialFilterProperty(SerializedProperty property) 86 | { 87 | return false; 88 | } 89 | 90 | protected virtual void SpecialDraw(SerializedProperty child) {} 91 | } 92 | 93 | [CustomEditor(typeof(JuicyFeedbackLightBase), true)] 94 | public sealed class JuicyFeedbackLightBaseEditor : JuicyFeedbackBaseEditor {} 95 | 96 | [CustomEditor(typeof(JuicyFeedbackObjectBase), true)] 97 | public sealed class JuicyFeedbackObjectBaseEditor : JuicyFeedbackBaseEditor {} 98 | 99 | [CustomEditor(typeof(JuicyFeedbackTimeBase), true)] 100 | public sealed class JuicyFeedbackTimeBaseEditor : JuicyFeedbackBaseEditor {} 101 | } -------------------------------------------------------------------------------- /Juicy/Runtime/Utils/JuicyCameraShaker.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace TinyTools.Juicy 4 | { 5 | public sealed class JuicyCameraShaker : MonoBehaviour 6 | { 7 | private static JuicyCameraShaker instance; 8 | 9 | private Camera target = null; 10 | private Vector3 maximumStrength = Vector3.one; 11 | private float frequency = 25; 12 | private float fallOff = 0f; 13 | private float fallOffSmoothness = 1; 14 | private bool ignoreTimeScale = false; 15 | private float seed = 0f; 16 | private float elapsedTimePercentage = 0; 17 | private float recoveryRate = 0; 18 | private float recoverySpeed = 1f; 19 | private float stopTime; 20 | 21 | private Vector3 originalPosition; 22 | 23 | private AnimationCurve ease = AnimationCurve.Linear(0,0,1,1); 24 | private bool shaking = false; 25 | 26 | public static JuicyCameraShaker Instance(Camera camera) 27 | { 28 | if (instance != null) { 29 | return instance; 30 | } 31 | 32 | if (camera.TryGetComponent(out JuicyCameraShaker shaker)) { 33 | return instance; 34 | } 35 | 36 | instance = camera.gameObject.AddComponent(); 37 | instance.target = camera; 38 | instance.originalPosition = camera.transform.position; 39 | 40 | return instance; 41 | } 42 | 43 | private void Update() 44 | { 45 | if (!shaking) { 46 | return; 47 | } 48 | 49 | float deltaTime = JuicyUtils.DeltaTime(ignoreTimeScale); 50 | float time = JuicyUtils.Time(ignoreTimeScale); 51 | 52 | if (time > stopTime) { 53 | fallOff = Mathf.Clamp01(fallOff - recoverySpeed * deltaTime); 54 | } 55 | 56 | if (fallOff > 1) { 57 | target.transform.localPosition = originalPosition; 58 | shaking = false; 59 | return; 60 | } 61 | 62 | float shake = Mathf.Pow(fallOff, fallOffSmoothness); 63 | float easeMultiplier = ease.Evaluate(elapsedTimePercentage); 64 | 65 | target.transform.localPosition = originalPosition + new Vector3( 66 | maximumStrength.x * (Mathf.PerlinNoise(seed, time * frequency) * 2 - 1), 67 | maximumStrength.y * (Mathf.PerlinNoise(seed + 1, time * frequency) * 2 - 1), 68 | maximumStrength.z * (Mathf.PerlinNoise(seed + 2, time * frequency) * 2 - 1) 69 | ) * shake * easeMultiplier; 70 | 71 | elapsedTimePercentage += recoveryRate * deltaTime; 72 | } 73 | 74 | public void Shake(ShakeProperties properties) 75 | { 76 | if (Mathf.Approximately(properties.duration, 0)) { 77 | return; 78 | } 79 | maximumStrength = properties.maximumStrength; 80 | frequency = properties.frequency; 81 | fallOffSmoothness = properties.smoothness; 82 | recoveryRate = 1 / properties.duration; 83 | stopTime = JuicyUtils.Time(properties.ignoreTimeScale) + properties.duration; 84 | 85 | ease = properties.falloffCurve; 86 | ignoreTimeScale = properties.ignoreTimeScale; 87 | recoverySpeed = Mathf.Max(0.1f, properties.recoverySpeed); 88 | 89 | fallOff = properties.power; 90 | seed = Random.value; 91 | 92 | shaking = true; 93 | } 94 | 95 | public struct ShakeProperties 96 | { 97 | public float power; 98 | 99 | public bool ignoreTimeScale; 100 | public float frequency; 101 | public Vector3 maximumStrength; 102 | public float smoothness; 103 | public float duration; 104 | public AnimationCurve falloffCurve; 105 | public float recoverySpeed; 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /Juicy/Plugins/DOTween/Modules/DOTweenModuleSprite.cs: -------------------------------------------------------------------------------- 1 | // Author: Daniele Giardini - http://www.demigiant.com 2 | // Created: 2018/07/13 3 | 4 | #if true && (UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_5 || UNITY_2017_1_OR_NEWER) // MODULE_MARKER 5 | using System; 6 | using UnityEngine; 7 | using DG.Tweening.Core; 8 | using DG.Tweening.Plugins.Options; 9 | 10 | #pragma warning disable 1591 11 | namespace DG.Tweening 12 | { 13 | public static class DOTweenModuleSprite 14 | { 15 | #region Shortcuts 16 | 17 | #region SpriteRenderer 18 | 19 | /// Tweens a SpriteRenderer's color to the given value. 20 | /// Also stores the spriteRenderer as the tween's target so it can be used for filtered operations 21 | /// The end value to reachThe duration of the tween 22 | public static TweenerCore DOColor(this SpriteRenderer target, Color endValue, float duration) 23 | { 24 | TweenerCore t = DOTween.To(() => target.color, x => target.color = x, endValue, duration); 25 | t.SetTarget(target); 26 | return t; 27 | } 28 | 29 | /// Tweens a Material's alpha color to the given value. 30 | /// Also stores the spriteRenderer as the tween's target so it can be used for filtered operations 31 | /// The end value to reachThe duration of the tween 32 | public static TweenerCore DOFade(this SpriteRenderer target, float endValue, float duration) 33 | { 34 | TweenerCore t = DOTween.ToAlpha(() => target.color, x => target.color = x, endValue, duration); 35 | t.SetTarget(target); 36 | return t; 37 | } 38 | 39 | /// Tweens a SpriteRenderer's color using the given gradient 40 | /// (NOTE 1: only uses the colors of the gradient, not the alphas - NOTE 2: creates a Sequence, not a Tweener). 41 | /// Also stores the image as the tween's target so it can be used for filtered operations 42 | /// The gradient to useThe duration of the tween 43 | public static Sequence DOGradientColor(this SpriteRenderer target, Gradient gradient, float duration) 44 | { 45 | Sequence s = DOTween.Sequence(); 46 | GradientColorKey[] colors = gradient.colorKeys; 47 | int len = colors.Length; 48 | for (int i = 0; i < len; ++i) { 49 | GradientColorKey c = colors[i]; 50 | if (i == 0 && c.time <= 0) { 51 | target.color = c.color; 52 | continue; 53 | } 54 | float colorDuration = i == len - 1 55 | ? duration - s.Duration(false) // Verifies that total duration is correct 56 | : duration * (i == 0 ? c.time : c.time - colors[i - 1].time); 57 | s.Append(target.DOColor(c.color, colorDuration).SetEase(Ease.Linear)); 58 | } 59 | s.SetTarget(target); 60 | return s; 61 | } 62 | 63 | #endregion 64 | 65 | #region Blendables 66 | 67 | #region SpriteRenderer 68 | 69 | /// Tweens a SpriteRenderer's color to the given value, 70 | /// in a way that allows other DOBlendableColor tweens to work together on the same target, 71 | /// instead than fight each other as multiple DOColor would do. 72 | /// Also stores the SpriteRenderer as the tween's target so it can be used for filtered operations 73 | /// The value to tween toThe duration of the tween 74 | public static Tweener DOBlendableColor(this SpriteRenderer target, Color endValue, float duration) 75 | { 76 | endValue = endValue - target.color; 77 | Color to = new Color(0, 0, 0, 0); 78 | return DOTween.To(() => to, x => { 79 | Color diff = x - to; 80 | to = x; 81 | target.color += diff; 82 | }, endValue, duration) 83 | .Blendable().SetTarget(target); 84 | } 85 | 86 | #endregion 87 | 88 | #endregion 89 | 90 | #endregion 91 | } 92 | } 93 | #endif 94 | -------------------------------------------------------------------------------- /Juicy/Editor/Utils/AdvancedAnimationCurveDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [CustomPropertyDrawer(typeof(AdvancedAnimationCurve))] 7 | public class AdvancedAnimationCurveDrawer : JuicyPropertyDrawerBase 8 | { 9 | private SerializedProperty presets; 10 | private SerializedProperty curve; 11 | private AnimationCurve[] curves; 12 | 13 | private readonly string[] prefixes = { 14 | "Quad", 15 | "Cubic", 16 | "Expo", 17 | "Quart", 18 | "Quint", 19 | "Circ", 20 | "Sine", 21 | "Elastic", 22 | "Bounce", 23 | "Back", 24 | "Custom" 25 | }; 26 | 27 | private string[] names; 28 | 29 | private void Initialize(SerializedProperty property) 30 | { 31 | if (curve != null) { 32 | return; 33 | } 34 | 35 | string path = JuicyEditorUtils.GetPluginRootPath() + "Editor/EasingCurves.curves"; 36 | 37 | CacheProperty(ref curve, property, nameof(curve)); 38 | 39 | Object presetObject = AssetDatabase 40 | .LoadAssetAtPath(path); 41 | 42 | presets = new SerializedObject(presetObject) 43 | .FindProperty("m_Presets"); 44 | 45 | curves = new AnimationCurve[presets.arraySize]; 46 | names = new string[presets.arraySize]; 47 | 48 | for (int i = 0; i < presets.arraySize; i++) { 49 | 50 | SerializedProperty present = presets.GetArrayElementAtIndex(i); 51 | 52 | names[i] = BuildEaseMenu(present 53 | .FindPropertyRelative("m_Name").stringValue); 54 | 55 | curves[i] = new AnimationCurve(present 56 | .FindPropertyRelative("m_Curve").animationCurveValue.keys); 57 | } 58 | } 59 | 60 | private string BuildEaseMenu(string name) 61 | { 62 | foreach (var t in prefixes) { 63 | if (name.StartsWith(t)) { 64 | return $"{t}/{name.Substring(t.Length)}"; 65 | } 66 | } 67 | 68 | return name; 69 | } 70 | 71 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 72 | { 73 | using (new EditorGUI.PropertyScope(position, label, property)) { 74 | 75 | Initialize(property); 76 | 77 | Rect curveRect = new Rect(position) { 78 | width = position.width - JuicyStyles.PaneOptionsIcon.width, 79 | height = SingleLineHeight 80 | }; 81 | 82 | curve.animationCurveValue = EditorGUI.CurveField( 83 | curveRect, label, curve.animationCurveValue); 84 | 85 | Rect menuRect = new Rect(position) { 86 | x = position.x + curveRect.width, 87 | y = position.y + 2, 88 | width = JuicyStyles.PaneOptionsIcon.width, 89 | height = JuicyStyles.PaneOptionsIcon.height, 90 | }; 91 | 92 | if (EditorGUI.DropdownButton(menuRect, new GUIContent(JuicyStyles.PaneOptionsIcon), 93 | FocusType.Passive, JuicyStyles.IconButtonStyle)) { 94 | CreateContextMenu(); 95 | } 96 | } 97 | } 98 | 99 | private void CreateContextMenu() 100 | { 101 | var e = Event.current; 102 | Vector2 position = e.mousePosition; 103 | var menu = new GenericMenu(); 104 | 105 | for (int i = 0; i < names.Length; i++) { 106 | string name = names[i]; 107 | 108 | menu.AddItem( 109 | JuicyEditorUtils.GetContent(name), 110 | false, 111 | ChangeCurve, 112 | i); 113 | } 114 | 115 | menu.DropDown(new Rect(position, Vector2.zero)); 116 | e.Use(); 117 | } 118 | 119 | private void ChangeCurve(object index) 120 | { 121 | curve.animationCurveValue = new AnimationCurve(curves[(int) index].keys); 122 | curve.serializedObject.ApplyModifiedProperties(); 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /Juicy/Editor/Utils/ToggleGroupDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [CustomPropertyDrawer(typeof(ToggleGroup), true)] 7 | public sealed class ToggleGroupDrawer : JuicyPropertyDrawerBase 8 | { 9 | private SerializedProperty isActive; 10 | 11 | private float height; 12 | 13 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 14 | { 15 | CacheProperty(ref isActive, property, nameof(isActive)); 16 | 17 | return SingleLineHeight + 18 | (property.isExpanded ? height + StandardSpacing * 2 : 0); 19 | } 20 | 21 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 22 | { 23 | using (new EditorGUI.PropertyScope(position, label, property)) { 24 | 25 | //position.x -= 4; 26 | 27 | Rect foldoutRect = new Rect(position) { 28 | height = SingleLineHeight, 29 | }; 30 | 31 | Rect labelRect = new Rect(position) { 32 | x = position.x + 37f, 33 | y = foldoutRect.y, 34 | height = foldoutRect.height, 35 | width = 200f 36 | }; 37 | 38 | float width = EditorGUI.indentLevel > 1 ? 50 : 32; 39 | 40 | Rect toggleRect = new Rect(position) { 41 | x = position.x + width, 42 | y = foldoutRect.y, 43 | width = labelRect.width + width, 44 | height = foldoutRect.height 45 | }; 46 | 47 | var e = Event.current; 48 | 49 | if(e.isMouse && e.button == 0 && toggleRect.Contains(e.mousePosition)) 50 | { 51 | if(e.type == EventType.MouseDown) { 52 | isActive.boolValue = !isActive.boolValue; 53 | e.Use(); 54 | } 55 | } 56 | 57 | bool hierarchyMode = EditorGUIUtility.hierarchyMode; 58 | EditorGUIUtility.hierarchyMode = false; 59 | property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, GUIContent.none, true); 60 | EditorGUIUtility.hierarchyMode = hierarchyMode; 61 | 62 | using (var check = new EditorGUI.ChangeCheckScope()) { 63 | 64 | isActive.boolValue = GUI.Toggle(toggleRect, isActive.boolValue, string.Empty); 65 | 66 | if (check.changed && isActive.boolValue) { 67 | property.isExpanded = true; 68 | } 69 | } 70 | 71 | EditorGUI.LabelField(labelRect, property.displayName); 72 | 73 | if (property.isExpanded) { 74 | DrawChildren(position, property); 75 | } 76 | } 77 | } 78 | 79 | private void DrawChildren(Rect position, SerializedProperty property) 80 | { 81 | height = 0; 82 | 83 | using (new EditorGUI.IndentLevelScope()) 84 | using (new EditorGUI.DisabledScope(!isActive.boolValue)) 85 | { 86 | float yPos = SingleLineHeight; 87 | 88 | foreach (SerializedProperty child in GetChildren(property)) { 89 | 90 | if (child.name == nameof(isActive)) { 91 | continue; 92 | } 93 | 94 | float h = EditorGUI.GetPropertyHeight(child); 95 | Rect rect = new Rect(position) { 96 | y = position.y + yPos + StandardSpacing, 97 | height = h, 98 | width = position.width + 2f 99 | }; 100 | 101 | if (child.type.Contains("UnityEvent")) { 102 | rect.x += 32f; 103 | rect.width -= 32f; 104 | EditorGUI.PropertyField(rect, child); 105 | } 106 | 107 | EditorGUI.PropertyField(rect, child); 108 | 109 | yPos += h; 110 | height += h; 111 | } 112 | } 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /Juicy/Editor/Utils/FromToValueDrawerBase.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | public class FromToValueDrawerBase : JuicyPropertyDrawerBase 7 | { 8 | private SerializedProperty isFrom; 9 | private SerializedProperty value; 10 | 11 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 12 | { 13 | return EditorGUI.GetPropertyHeight(property); 14 | } 15 | 16 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 17 | { 18 | using (new EditorGUI.PropertyScope(position, label, property)) { 19 | 20 | CacheProperty(ref isFrom, property, nameof(isFrom)); 21 | CacheProperty(ref value, property, nameof(value)); 22 | 23 | float width = IsMinimalWidth ? 10 : 40; 24 | 25 | Rect valueRect = new Rect(position) { 26 | width = position.width - width, 27 | height = EditorGUIUtility.singleLineHeight 28 | }; 29 | 30 | Rect buttonRect = new Rect(position) { 31 | y = valueRect.y, 32 | x = position.x + valueRect.width, 33 | width = width, 34 | height = EditorGUIUtility.singleLineHeight 35 | }; 36 | 37 | DrawPropertyField(valueRect, property, value); 38 | 39 | if (GUI.Button(buttonRect, isFrom.boolValue ? "From" : "To")) { 40 | isFrom.boolValue = !isFrom.boolValue; 41 | } 42 | 43 | property.serializedObject.ApplyModifiedProperties(); 44 | } 45 | } 46 | 47 | protected virtual void DrawPropertyField(Rect position, 48 | SerializedProperty property, SerializedProperty valueProperty) 49 | { 50 | EditorGUI.PropertyField(position, valueProperty, 51 | new GUIContent(property.displayName.Contains("Value") ? 52 | valueProperty.displayName : property.displayName)); 53 | } 54 | } 55 | 56 | [CustomPropertyDrawer(typeof(BoolFromToValue))] 57 | public sealed class BoolFromToValueDrawer : FromToValueDrawerBase {} 58 | 59 | [CustomPropertyDrawer(typeof(FloatFromToValue))] 60 | public sealed class FloatFromToValueDrawer : FromToValueDrawerBase {} 61 | 62 | [CustomPropertyDrawer(typeof(Vector4FromToValue))] 63 | public sealed class Vector4FromToValueDrawer : FromToValueDrawerBase 64 | { 65 | protected override void DrawPropertyField(Rect position, SerializedProperty property, SerializedProperty valueProperty) 66 | { 67 | valueProperty.vector4Value = EditorGUI 68 | .Vector4Field(position, valueProperty.displayName, valueProperty.vector4Value); 69 | } 70 | } 71 | 72 | [CustomPropertyDrawer(typeof(Vector3FromToValue))] 73 | public sealed class Vector3FromToValueDrawer : FromToValueDrawerBase 74 | { 75 | protected override void DrawPropertyField(Rect position, SerializedProperty property, SerializedProperty valueProperty) 76 | { 77 | valueProperty.vector3Value = EditorGUI 78 | .Vector3Field(position, valueProperty.displayName, valueProperty.vector3Value); 79 | } 80 | } 81 | 82 | [CustomPropertyDrawer(typeof(ColorFromToValue))] 83 | public sealed class ColorFromToValueDrawer : FromToValueDrawerBase {} 84 | 85 | [CustomPropertyDrawer(typeof(ColorChooserFromToValue))] 86 | public sealed class ColorChooserFromToValueDrawer : FromToValueDrawerBase {} 87 | 88 | [CustomPropertyDrawer(typeof(SliderFromToValue))] 89 | public sealed class SliderFromToValueDrawer : FromToValueDrawerBase 90 | { 91 | private SerializedProperty range; 92 | 93 | protected override void DrawPropertyField(Rect position, 94 | SerializedProperty property, 95 | SerializedProperty valueProperty) 96 | { 97 | CacheProperty(ref range, property, nameof(range)); 98 | Vector3 rangeValue = range.vector3Value; 99 | 100 | EditorGUI.Slider(position, 101 | valueProperty, 102 | rangeValue.y, 103 | rangeValue.z, 104 | new GUIContent(valueProperty.displayName)); 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /Juicy/Plugins/DOTween/Editor/DOTweenEditor.XML: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DOTweenEditor 5 | 6 | 7 | 8 | 9 | Starts the update loop of tween in the editor. Has no effect during playMode. 10 | 11 | Eventual callback to call after every update 12 | 13 | 14 | 15 | Stops the update loop and clears the onPreviewUpdated callback. 16 | 17 | If TRUE also resets the tweened objects to their original state 18 | 19 | 20 | 21 | Readies the tween for editor preview by setting its UpdateType to Manual plus eventual extra settings. 22 | 23 | The tween to ready 24 | If TRUE (recommended) removes all callbacks (OnComplete/Rewind/etc) 25 | If TRUE prevents the tween from being auto-killed at completion 26 | If TRUE starts playing the tween immediately 27 | 28 | 29 | 30 | Checks that the given editor texture use the correct import settings, 31 | and applies them if they're incorrect. 32 | 33 | 34 | 35 | 36 | Returns TRUE if setup is required 37 | 38 | 39 | 40 | 41 | Returns TRUE if the file/directory at the given path exists. 42 | 43 | Path, relative to Unity's project folder 44 | 45 | 46 | 47 | 48 | Converts the given project-relative path to a full path, 49 | with backward (\) slashes). 50 | 51 | 52 | 53 | 54 | Converts the given full path to a path usable with AssetDatabase methods 55 | (relative to Unity's project folder, and with the correct Unity forward (/) slashes). 56 | 57 | 58 | 59 | 60 | Connects to a asset. 61 | If the asset already exists at the given path, loads it and returns it. 62 | Otherwise, either returns NULL or automatically creates it before loading and returning it 63 | (depending on the given parameters). 64 | 65 | Asset type 66 | File path (relative to Unity's project folder) 67 | If TRUE and the requested asset doesn't exist, forces its creation 68 | 69 | 70 | 71 | Full path for the given loaded assembly, assembly file included 72 | 73 | 74 | 75 | 76 | Adds the given global define if it's not already present 77 | 78 | 79 | 80 | 81 | Removes the given global define if it's present 82 | 83 | 84 | 85 | 86 | Returns TRUE if the given global define is present in all the 87 | or only in the given , depending on passed parameters. 88 | 89 | 90 | to use. Leave NULL to check in all of them. 91 | 92 | 93 | 94 | Not used as menu item anymore, but as a utiity function 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /Juicy/Editor/Utils/EaseDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | using Object = UnityEngine.Object; 4 | 5 | namespace TinyTools.Juicy 6 | { 7 | [CustomPropertyDrawer(typeof(Ease))] 8 | public sealed class EaseDrawer : JuicyPropertyDrawerBase 9 | { 10 | private const float ExpandedCurveHeight = 60f; 11 | 12 | private SerializedProperty presets; 13 | private SerializedProperty curve; 14 | private AnimationCurve[] curves; 15 | 16 | private readonly string[] prefixes = { 17 | "Quad", 18 | "Cubic", 19 | "Expo", 20 | "Quart", 21 | "Quint", 22 | "Circ", 23 | "Sine", 24 | "Elastic", 25 | "Bounce", 26 | "Back", 27 | "Custom" 28 | }; 29 | 30 | private string[] names; 31 | 32 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 33 | { 34 | return (property.isExpanded ? ExpandedCurveHeight : SingleLineHeight) + StandardSpacing; 35 | } 36 | 37 | private void Initialize(SerializedProperty property) 38 | { 39 | if (curve != null) { 40 | return; 41 | } 42 | 43 | string path = JuicyEditorUtils.GetPluginRootPath() + "Editor/EasingCurves.curves"; 44 | 45 | CacheProperty(ref curve, property, nameof(curve)); 46 | 47 | Object presetObject = AssetDatabase 48 | .LoadAssetAtPath(path); 49 | 50 | presets = new SerializedObject(presetObject) 51 | .FindProperty("m_Presets"); 52 | 53 | curves = new AnimationCurve[presets.arraySize]; 54 | names = new string[presets.arraySize]; 55 | 56 | for (int i = 0; i < presets.arraySize; i++) { 57 | 58 | SerializedProperty present = presets.GetArrayElementAtIndex(i); 59 | 60 | names[i] = BuildEaseMenu(present 61 | .FindPropertyRelative("m_Name").stringValue); 62 | 63 | curves[i] = new AnimationCurve(present 64 | .FindPropertyRelative("m_Curve").animationCurveValue.keys); 65 | } 66 | } 67 | 68 | private string BuildEaseMenu(string name) 69 | { 70 | foreach (var t in prefixes) { 71 | if (name.StartsWith(t)) { 72 | return $"{t}/{name.Substring(t.Length)}"; 73 | } 74 | } 75 | 76 | return name; 77 | } 78 | 79 | public override bool CanCacheInspectorGUI(SerializedProperty property) 80 | { 81 | return true; 82 | } 83 | 84 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 85 | { 86 | using (new EditorGUI.PropertyScope(position, label, property)) { 87 | 88 | if (EditorGUIUtility.hierarchyMode) { 89 | int num = EditorStyles.foldout.padding.left - EditorStyles.label.padding.left; 90 | position.xMin += (float)num - 4; 91 | } 92 | 93 | Initialize(property); 94 | 95 | Rect foldoutRect = new Rect(position) { 96 | height = SingleLineHeight, 97 | width = EditorGUIUtility.labelWidth - 25 98 | }; 99 | 100 | property.isExpanded = EditorGUI.Foldout( 101 | foldoutRect, 102 | property.isExpanded, label, true); 103 | 104 | DrawCurve(position, foldoutRect, property); 105 | } 106 | } 107 | 108 | private void DrawCurve(Rect position, Rect foldoutRect, SerializedProperty property) 109 | { 110 | Rect curveRect = new Rect(position) { 111 | x = position.x + foldoutRect.width, 112 | y = foldoutRect.y + StandardSpacing, 113 | width = position.width - foldoutRect.width - JuicyStyles.PaneOptionsIcon.width, 114 | height = property.isExpanded ? ExpandedCurveHeight : SingleLineHeight 115 | }; 116 | 117 | curve.animationCurveValue = EditorGUI.CurveField(curveRect, curve.animationCurveValue); 118 | 119 | Rect menuRect = new Rect(position) { 120 | x = curveRect.x + curveRect.width, 121 | y = curveRect.y, 122 | width = JuicyStyles.PaneOptionsIcon.width, 123 | height = JuicyStyles.PaneOptionsIcon.height, 124 | }; 125 | 126 | if (EditorGUI.DropdownButton(menuRect, new GUIContent(JuicyStyles.PaneOptionsIcon), 127 | FocusType.Passive, JuicyStyles.IconButtonStyle)) { 128 | CreateContextMenu(); 129 | } 130 | } 131 | 132 | private void CreateContextMenu() 133 | { 134 | var e = Event.current; 135 | Vector2 position = e.mousePosition; 136 | var menu = new GenericMenu(); 137 | 138 | for (int i = 0; i < names.Length; i++) { 139 | string name = names[i]; 140 | 141 | menu.AddItem( 142 | JuicyEditorUtils.GetContent(name), 143 | false, 144 | ChangeCurve, 145 | i); 146 | } 147 | 148 | menu.DropDown(new Rect(position, Vector2.zero)); 149 | e.Use(); 150 | } 151 | 152 | private void ChangeCurve(object index) 153 | { 154 | curve.animationCurveValue = new AnimationCurve(curves[(int) index].keys); 155 | curve.serializedObject.ApplyModifiedProperties(); 156 | } 157 | } 158 | } -------------------------------------------------------------------------------- /Juicy/Editor/Utils/FeedbackCopyHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using UnityEditor; 5 | 6 | namespace TinyTools.Juicy 7 | { 8 | internal static class FeedbackListCopyHelper 9 | { 10 | private static readonly List CopiedFeedbacks = 11 | new List(); 12 | 13 | private sealed class FeedbackCopyData 14 | { 15 | public Type type; 16 | public readonly List properties = 17 | new List(); 18 | } 19 | 20 | public static bool HasCopy => CopiedFeedbacks.Count > 0; 21 | 22 | public static void Copy(SerializedProperty feedbackList) 23 | { 24 | if (feedbackList == null) { 25 | return; 26 | } 27 | 28 | CopiedFeedbacks.Clear(); 29 | 30 | for (int i = 0; i < feedbackList.arraySize; i++) { 31 | SerializedProperty prop = feedbackList.GetArrayElementAtIndex(i); 32 | 33 | FeedbackCopyData data = new FeedbackCopyData { 34 | type = prop.objectReferenceValue.GetType(), 35 | }; 36 | 37 | SerializedProperty property = new SerializedObject(prop 38 | .objectReferenceValue).GetIterator(); 39 | property.Next(true); 40 | 41 | do { 42 | if (!FeedbackCopyHelper.IgnoreList.Contains(property.name)) { 43 | data.properties.Add(property.Copy()); 44 | } 45 | } while (property.Next(false)); 46 | 47 | CopiedFeedbacks.Add(data); 48 | } 49 | } 50 | 51 | public static void Paste(SerializedProperty feedbacks, Action removeAll, 52 | Func addAction) 53 | { 54 | if (feedbacks == null) { 55 | return; 56 | } 57 | 58 | removeAll.Invoke(); 59 | 60 | foreach (FeedbackCopyData data in CopiedFeedbacks) { 61 | addAction.Invoke(data.type); 62 | } 63 | 64 | for (int i = 0; i < feedbacks.arraySize; i++) { 65 | 66 | SerializedProperty feedback = feedbacks.GetArrayElementAtIndex(i); 67 | JuicyFeedbackBase f = feedback.objectReferenceValue as JuicyFeedbackBase; 68 | FeedbackCopyData data = CopiedFeedbacks[i]; 69 | 70 | JuicyEditorUtils.PasteFeedback(feedback, f); 71 | 72 | foreach (var property in data.properties) { 73 | SerializedObject obj = new SerializedObject(f); 74 | obj.CopyFromSerializedProperty(property); 75 | obj.ApplyModifiedProperties(); 76 | } 77 | } 78 | 79 | CopiedFeedbacks.Clear(); 80 | } 81 | } 82 | 83 | internal static class FeedbackCopyHelper 84 | { 85 | internal static Type Type { get; private set; } 86 | private static readonly List Properties = 87 | new List(); 88 | 89 | private static JuicyFeedbackBase copyReference; 90 | 91 | public static readonly string[] IgnoreList = 92 | { 93 | "m_ObjectHideFlags", 94 | "m_CorrespondingSourceObject", 95 | "m_PrefabInstance", 96 | "m_PrefabAsset", 97 | "m_GameObject", 98 | "m_Enabled", 99 | "m_EditorHideFlags", 100 | "m_Script", 101 | "m_Name", 102 | "m_EditorClassIdentifier" 103 | }; 104 | 105 | public static void RemoveCopyReference( 106 | JuicyFeedbackBase feedback, 107 | List feedbackList) 108 | { 109 | if(feedback.referenceCount > 0) 110 | feedback.referenceCount--; 111 | 112 | feedbackList.Remove(feedback); 113 | } 114 | 115 | public static void CopyReference(JuicyFeedbackBase feedback) 116 | { 117 | copyReference = feedback; 118 | } 119 | 120 | public static void PasteReference(List list) 121 | { 122 | Properties.Clear(); 123 | list.Add(copyReference); 124 | copyReference.referenceCount++; 125 | copyReference = null; 126 | } 127 | 128 | public static void Copy(SerializedObject serializedObject) 129 | { 130 | copyReference = (JuicyFeedbackBase) serializedObject.targetObject; 131 | 132 | Type = serializedObject.targetObject.GetType(); 133 | Properties.Clear(); 134 | 135 | SerializedProperty property = serializedObject.GetIterator(); 136 | property.Next(true); 137 | 138 | do { 139 | CacheCopy(property); 140 | } while (property.Next(false)); 141 | } 142 | 143 | private static void CacheCopy(SerializedProperty property) 144 | { 145 | if (!IgnoreList.Contains(property.name)) { 146 | Properties.Add(property.Copy()); 147 | } 148 | } 149 | 150 | public static void Paste(SerializedObject target) 151 | { 152 | if (target.targetObject.GetType() != Type) { 153 | return; 154 | } 155 | 156 | foreach (var property in Properties) { 157 | target.CopyFromSerializedProperty(property); 158 | } 159 | } 160 | 161 | public static void ClearCache() 162 | { 163 | Properties.Clear(); 164 | } 165 | 166 | public static bool HasCopy => Properties != null && Properties.Count > 0; 167 | } 168 | } -------------------------------------------------------------------------------- /Juicy/Plugins/DOTween/Modules/DOTweenModuleUtils.cs: -------------------------------------------------------------------------------- 1 | // Author: Daniele Giardini - http://www.demigiant.com 2 | // Created: 2018/07/13 3 | 4 | using System; 5 | using System.Reflection; 6 | using UnityEngine; 7 | using DG.Tweening.Core; 8 | using DG.Tweening.Plugins.Core.PathCore; 9 | using DG.Tweening.Plugins.Options; 10 | 11 | #pragma warning disable 1591 12 | namespace DG.Tweening 13 | { 14 | /// 15 | /// Utility functions that deal with available Modules. 16 | /// Modules defines: 17 | /// - DOTAUDIO 18 | /// - DOTPHYSICS 19 | /// - DOTPHYSICS2D 20 | /// - DOTSPRITE 21 | /// - DOTUI 22 | /// Extra defines set and used for implementation of external assets: 23 | /// - DOTWEEN_TMP ► TextMesh Pro 24 | /// - DOTWEEN_TK2D ► 2D Toolkit 25 | /// 26 | public static class DOTweenModuleUtils 27 | { 28 | static bool _initialized; 29 | 30 | #region Reflection 31 | 32 | /// 33 | /// Called via Reflection by DOTweenComponent on Awake 34 | /// 35 | #if UNITY_2018_1_OR_NEWER 36 | [UnityEngine.Scripting.Preserve] 37 | #endif 38 | public static void Init() 39 | { 40 | if (_initialized) return; 41 | 42 | _initialized = true; 43 | DOTweenExternalCommand.SetOrientationOnPath += Physics.SetOrientationOnPath; 44 | 45 | #if UNITY_EDITOR 46 | #if UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_5 || UNITY_2017_1 47 | UnityEditor.EditorApplication.playmodeStateChanged += PlaymodeStateChanged; 48 | #else 49 | UnityEditor.EditorApplication.playModeStateChanged += PlaymodeStateChanged; 50 | #endif 51 | #endif 52 | } 53 | 54 | #if UNITY_2018_1_OR_NEWER 55 | #pragma warning disable 56 | [UnityEngine.Scripting.Preserve] 57 | // Just used to preserve methods when building, never called 58 | static void Preserver() 59 | { 60 | Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); 61 | MethodInfo mi = typeof(MonoBehaviour).GetMethod("Stub"); 62 | } 63 | #pragma warning restore 64 | #endif 65 | 66 | #endregion 67 | 68 | #if UNITY_EDITOR 69 | // Fires OnApplicationPause in DOTweenComponent even when Editor is paused (otherwise it's only fired at runtime) 70 | #if UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_5 || UNITY_2017_1 71 | static void PlaymodeStateChanged() 72 | #else 73 | static void PlaymodeStateChanged(UnityEditor.PlayModeStateChange state) 74 | #endif 75 | { 76 | if (DOTween.instance == null) return; 77 | DOTween.instance.OnApplicationPause(UnityEditor.EditorApplication.isPaused); 78 | } 79 | #endif 80 | 81 | // █████████████████████████████████████████████████████████████████████████████████████████████████████████████████████ 82 | // ███ INTERNAL CLASSES ████████████████████████████████████████████████████████████████████████████████████████████████ 83 | // █████████████████████████████████████████████████████████████████████████████████████████████████████████████████████ 84 | 85 | public static class Physics 86 | { 87 | // Called via DOTweenExternalCommand callback 88 | public static void SetOrientationOnPath(PathOptions options, Tween t, Quaternion newRot, Transform trans) 89 | { 90 | #if true // PHYSICS_MARKER 91 | if (options.isRigidbody) ((Rigidbody)t.target).rotation = newRot; 92 | else trans.rotation = newRot; 93 | #else 94 | trans.rotation = newRot; 95 | #endif 96 | } 97 | 98 | // Returns FALSE if the DOTween's Physics2D Module is disabled, or if there's no Rigidbody2D attached 99 | public static bool HasRigidbody2D(Component target) 100 | { 101 | #if true // PHYSICS2D_MARKER 102 | return target.GetComponent() != null; 103 | #else 104 | return false; 105 | #endif 106 | } 107 | 108 | #region Called via Reflection 109 | 110 | 111 | // Called via Reflection by DOTweenPathInspector 112 | // Returns FALSE if the DOTween's Physics Module is disabled, or if there's no rigidbody attached 113 | #if UNITY_2018_1_OR_NEWER 114 | [UnityEngine.Scripting.Preserve] 115 | #endif 116 | public static bool HasRigidbody(Component target) 117 | { 118 | #if true // PHYSICS_MARKER 119 | return target.GetComponent() != null; 120 | #else 121 | return false; 122 | #endif 123 | } 124 | 125 | // Called via Reflection by DOTweenPath 126 | #if UNITY_2018_1_OR_NEWER 127 | [UnityEngine.Scripting.Preserve] 128 | #endif 129 | public static TweenerCore CreateDOTweenPathTween( 130 | MonoBehaviour target, bool tweenRigidbody, bool isLocal, Path path, float duration, PathMode pathMode 131 | ){ 132 | TweenerCore t; 133 | #if true // PHYSICS_MARKER 134 | Rigidbody rBody = tweenRigidbody ? target.GetComponent() : null; 135 | if (tweenRigidbody && rBody != null) { 136 | t = isLocal 137 | ? rBody.DOLocalPath(path, duration, pathMode) 138 | : rBody.DOPath(path, duration, pathMode); 139 | } else { 140 | t = isLocal 141 | ? target.transform.DOLocalPath(path, duration, pathMode) 142 | : target.transform.DOPath(path, duration, pathMode); 143 | } 144 | #else 145 | t = isLocal 146 | ? target.transform.DOLocalPath(path, duration, pathMode) 147 | : target.transform.DOPath(path, duration, pathMode); 148 | #endif 149 | return t; 150 | } 151 | 152 | #endregion 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Juicy/Editor/Attributes/TimingAttributeDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | using static TinyTools.Juicy.TimingAttribute.TimingStyle; 4 | 5 | namespace TinyTools.Juicy 6 | { 7 | [CustomPropertyDrawer(typeof(TimingAttribute))] 8 | public sealed class TimingAttributeDrawer : JuicyPropertyDrawerBase 9 | { 10 | private readonly PropertyData durationProperty = new PropertyData(HideDuration); 11 | private readonly PropertyData delayProperty = new PropertyData(HideDelay); 12 | private readonly PropertyData cooldownProperty = new PropertyData(HideCooldown); 13 | private readonly PropertyData ignoreTimeScaleProperty = new PropertyData(HideIgnoreTimeScale); 14 | 15 | private TimingAttribute TimingAttribute => (TimingAttribute)attribute; 16 | 17 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 18 | { 19 | float height = 0; 20 | float spacing = EditorGUIUtility.standardVerticalSpacing; 21 | 22 | height += durationProperty.GetHeight(TimingAttribute) + spacing; 23 | height += delayProperty.GetHeight(TimingAttribute) + spacing; 24 | height += cooldownProperty.GetHeight(TimingAttribute) + spacing; 25 | height += ignoreTimeScaleProperty.GetHeight(TimingAttribute) + spacing; 26 | 27 | return height + spacing; 28 | } 29 | 30 | public override bool CanCacheInspectorGUI(SerializedProperty property) 31 | { 32 | return true; 33 | } 34 | 35 | private void Initialise(SerializedProperty property) 36 | { 37 | if (durationProperty.Property == null) { 38 | durationProperty.SetProperty(property.FindPropertyRelative("duration")); 39 | } 40 | 41 | if (delayProperty.Property == null) { 42 | delayProperty.SetProperty(property.FindPropertyRelative("delay")); 43 | } 44 | 45 | if (cooldownProperty.Property == null) { 46 | cooldownProperty.SetProperty(property.FindPropertyRelative("cooldown")); 47 | } 48 | 49 | if (ignoreTimeScaleProperty.Property == null) { 50 | ignoreTimeScaleProperty.SetProperty(property.FindPropertyRelative("ignoreTimeScale")); 51 | } 52 | } 53 | 54 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 55 | { 56 | Initialise(property); 57 | 58 | float yPosition = 0f; 59 | float spacing = EditorGUIUtility.standardVerticalSpacing; 60 | 61 | using (new EditorGUI.PropertyScope(position, label, property)) { 62 | 63 | Rect duration = new Rect { 64 | x = position.x, 65 | y = position.y + yPosition, 66 | width = position.width, 67 | height = durationProperty.GetHeight(TimingAttribute) + spacing 68 | }; 69 | 70 | if (durationProperty.Draw(duration, TimingAttribute)) { 71 | yPosition += duration.height; 72 | } 73 | 74 | Rect delay = new Rect { 75 | x = position.x, 76 | y = position.y + yPosition, 77 | width = position.width, 78 | height = delayProperty.GetHeight(TimingAttribute) + spacing 79 | }; 80 | 81 | if (delayProperty.Draw(delay, TimingAttribute)) { 82 | yPosition += delay.height; 83 | } 84 | 85 | Rect cooldown = new Rect { 86 | x = position.x, 87 | y = position.y + yPosition, 88 | width = position.width, 89 | height = cooldownProperty.GetHeight(TimingAttribute) + spacing 90 | }; 91 | 92 | if (cooldownProperty.Draw(cooldown, TimingAttribute)) { 93 | yPosition += cooldown.height; 94 | } 95 | 96 | Rect ignoreTimeScale = new Rect { 97 | x = position.x, 98 | y = position.y + yPosition, 99 | width = position.width, 100 | height = ignoreTimeScaleProperty.GetHeight(TimingAttribute) + spacing 101 | }; 102 | 103 | ignoreTimeScaleProperty.Draw(ignoreTimeScale, TimingAttribute); 104 | } 105 | } 106 | 107 | private sealed class PropertyData 108 | { 109 | public SerializedProperty Property { get; private set; } 110 | 111 | private readonly TimingAttribute.TimingStyle hidingFlag; 112 | private float height; 113 | 114 | public PropertyData(TimingAttribute.TimingStyle hidingFlag) 115 | { 116 | this.hidingFlag = hidingFlag; 117 | } 118 | 119 | public void SetProperty(SerializedProperty property) 120 | { 121 | Property = property; 122 | height = EditorGUI.GetPropertyHeight(property); 123 | } 124 | 125 | public float GetHeight(TimingAttribute attribute) 126 | { 127 | if (Property == null || attribute.Style.HasFlag(hidingFlag)) { 128 | return 0; 129 | } 130 | 131 | return height; 132 | } 133 | 134 | public bool Draw(Rect position, TimingAttribute attribute) 135 | { 136 | if (attribute.Style.HasFlag(hidingFlag)) { 137 | return false; 138 | } 139 | 140 | EditorGUI.PropertyField(position, Property); 141 | 142 | Property.serializedObject.ApplyModifiedProperties(); 143 | 144 | return true; 145 | } 146 | } 147 | 148 | } 149 | } -------------------------------------------------------------------------------- /Juicy/Plugins/DOTween/Modules/DOTweenModulePhysics2D.cs: -------------------------------------------------------------------------------- 1 | // Author: Daniele Giardini - http://www.demigiant.com 2 | // Created: 2018/07/13 3 | 4 | #if true && (UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_5 || UNITY_2017_1_OR_NEWER) // MODULE_MARKER 5 | using System; 6 | using DG.Tweening.Core; 7 | using DG.Tweening.Plugins.Options; 8 | using UnityEngine; 9 | 10 | #pragma warning disable 1591 11 | namespace DG.Tweening 12 | { 13 | public static class DOTweenModulePhysics2D 14 | { 15 | #region Shortcuts 16 | 17 | #region Rigidbody2D Shortcuts 18 | 19 | /// Tweens a Rigidbody2D's position to the given value. 20 | /// Also stores the Rigidbody2D as the tween's target so it can be used for filtered operations 21 | /// The end value to reachThe duration of the tween 22 | /// If TRUE the tween will smoothly snap all values to integers 23 | public static TweenerCore DOMove(this Rigidbody2D target, Vector2 endValue, float duration, bool snapping = false) 24 | { 25 | TweenerCore t = DOTween.To(() => target.position, target.MovePosition, endValue, duration); 26 | t.SetOptions(snapping).SetTarget(target); 27 | return t; 28 | } 29 | 30 | /// Tweens a Rigidbody2D's X position to the given value. 31 | /// Also stores the Rigidbody2D as the tween's target so it can be used for filtered operations 32 | /// The end value to reachThe duration of the tween 33 | /// If TRUE the tween will smoothly snap all values to integers 34 | public static TweenerCore DOMoveX(this Rigidbody2D target, float endValue, float duration, bool snapping = false) 35 | { 36 | TweenerCore t = DOTween.To(() => target.position, target.MovePosition, new Vector2(endValue, 0), duration); 37 | t.SetOptions(AxisConstraint.X, snapping).SetTarget(target); 38 | return t; 39 | } 40 | 41 | /// Tweens a Rigidbody2D's Y position to the given value. 42 | /// Also stores the Rigidbody2D as the tween's target so it can be used for filtered operations 43 | /// The end value to reachThe duration of the tween 44 | /// If TRUE the tween will smoothly snap all values to integers 45 | public static TweenerCore DOMoveY(this Rigidbody2D target, float endValue, float duration, bool snapping = false) 46 | { 47 | TweenerCore t = DOTween.To(() => target.position, target.MovePosition, new Vector2(0, endValue), duration); 48 | t.SetOptions(AxisConstraint.Y, snapping).SetTarget(target); 49 | return t; 50 | } 51 | 52 | /// Tweens a Rigidbody2D's rotation to the given value. 53 | /// Also stores the Rigidbody2D as the tween's target so it can be used for filtered operations 54 | /// The end value to reachThe duration of the tween 55 | public static TweenerCore DORotate(this Rigidbody2D target, float endValue, float duration) 56 | { 57 | TweenerCore t = DOTween.To(() => target.rotation, target.MoveRotation, endValue, duration); 58 | t.SetTarget(target); 59 | return t; 60 | } 61 | 62 | #region Special 63 | 64 | /// Tweens a Rigidbody2D's position to the given value, while also applying a jump effect along the Y axis. 65 | /// Returns a Sequence instead of a Tweener. 66 | /// Also stores the Rigidbody2D as the tween's target so it can be used for filtered operations. 67 | /// IMPORTANT: a rigidbody2D can't be animated in a jump arc using MovePosition, so the tween will directly set the position 68 | /// The end value to reach 69 | /// Power of the jump (the max height of the jump is represented by this plus the final Y offset) 70 | /// Total number of jumps 71 | /// The duration of the tween 72 | /// If TRUE the tween will smoothly snap all values to integers 73 | public static Sequence DOJump(this Rigidbody2D target, Vector2 endValue, float jumpPower, int numJumps, float duration, bool snapping = false) 74 | { 75 | if (numJumps < 1) numJumps = 1; 76 | float startPosY = 0; 77 | float offsetY = -1; 78 | bool offsetYSet = false; 79 | Sequence s = DOTween.Sequence(); 80 | Tween yTween = DOTween.To(() => target.position, x => target.position = x, new Vector2(0, jumpPower), duration / (numJumps * 2)) 81 | .SetOptions(AxisConstraint.Y, snapping).SetEase(Ease.OutQuad).SetRelative() 82 | .SetLoops(numJumps * 2, LoopType.Yoyo) 83 | .OnStart(() => startPosY = target.position.y); 84 | s.Append(DOTween.To(() => target.position, x => target.position = x, new Vector2(endValue.x, 0), duration) 85 | .SetOptions(AxisConstraint.X, snapping).SetEase(Ease.Linear) 86 | ).Join(yTween) 87 | .SetTarget(target).SetEase(DOTween.defaultEaseType); 88 | yTween.OnUpdate(() => { 89 | if (!offsetYSet) { 90 | offsetYSet = true; 91 | offsetY = s.isRelative ? endValue.y : endValue.y - startPosY; 92 | } 93 | Vector3 pos = target.position; 94 | pos.y += DOVirtual.EasedValue(0, offsetY, yTween.ElapsedPercentage(), Ease.OutQuad); 95 | target.MovePosition(pos); 96 | }); 97 | return s; 98 | } 99 | 100 | #endregion 101 | 102 | #endregion 103 | 104 | #endregion 105 | } 106 | } 107 | #endif 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Juicy 2 | 3 | As part of my master thesis I created `Juicy` a Unity tool to easily add juice to quickly prototype games. 4 | 5 | ## Motivation 6 | 7 | The idea for `Juicy` came up during my studies when I saw the GDC talk Best Practices for fast game design in Unity - Unite LA. 8 | There Renaud Forestié described a tool they use internally. 9 | At that time, there was nothing comparable production-ready on the market. However, for our game Wild Woods we needed a good solution to add clean, efficient and extensible effects to the game. 10 | 11 | ## How it works? 12 | 13 | The basics implementation is just a list of `MonoBehaviours` which are hidden on a `GameObject` and styled with some editor magic. Additionaly some effects are done by using DoTween as a tweening libary and my own pooling solution PoolBoy. 14 | 15 | --- 16 | 17 | To start with you just have to add `JuicyFeedback` as a field to your script. 18 | 19 | ```cs 20 | public class JuicyFeedbackTest : MonoBehaviour 21 | { 22 | [SerializeField] private JuicyFeedback feedback; 23 | 24 | private void Update() 25 | { 26 | if (Input.GetKeyDown(KeyCode.Space)) { 27 | feedback.Play(); 28 | } 29 | } 30 | } 31 | ``` 32 | Feedback 33 | 34 | --- 35 | 36 | If you press the plus button a new feedback list will be created. 37 | 38 | Feedback List 39 | 40 | Now you have a total of 24 effects ordered by category at your disposal 41 | Feedbacks 42 | 43 | --- 44 | 45 | Here for example the "Shader" effect. A cool thing about this is that all possible properties of the shader are automatically listed in a dropdown. 46 | 47 | Feedback Shader 48 | 49 | --- 50 | 51 | If you add a bunch of effect like so, you get the following result 52 | 53 | 54 | 55 | 56 | 57 | 58 |
ExampleExample gif
59 | 60 | --- 61 | 62 | Or here an example of an `OnHit` feedback in Wild Woods. Here you can also see nicely the grass cutting particle effect created with the same system. 63 | 64 | ![bnYciEBS0g](https://user-images.githubusercontent.com/46827413/115109240-d98f4500-9f74-11eb-9543-250954730685.gif) 65 | 66 | But the best thing about this plugin is, that you can easily extent the system by creating your own effects. Just create a new class and inherit from `JuicyFeedbackBase` with the `[Feedback]` attribute that way you can change the menu path and name. Like in the following example: 67 | 68 | ```cs 69 | [Feedback("Debug/Log")] 70 | public class DebugLogFeedback : JuicyFeedbackBase 71 | { 72 | [SerializeField] private LogType logType = LogType.Log; 73 | [SerializeField] private string message; 74 | 75 | protected override void Play() 76 | { 77 | Debug.unityLogger.Log(logType, message); 78 | } 79 | } 80 | ``` 81 | 82 | ## Effect List 83 | 84 | Almost every effect comes with timing values, like duration, delay and cooldown to change values over time and with an ease. 85 | 86 | | Effect | Description | 87 | | ------------- |:-------------:| 88 | | Animator | Change animator values (trigger, float ...) | 89 | | Audio Oneshoot | Instantiates a oneshoot audio | 90 | | Audio Pooled | Instantiates a pooled audio | 91 | | Camera Shake | Shakes the camera | 92 | | Camera Zoom | Change camera FOV | 93 | | Event Simple| A single Unity Event | 94 | | Event Lifetime | Unity Events for the Unity lifecyles (Awake, Start ...) | 95 | | Light Color | Change the light color | 96 | | Light Intensity | Change the light intensity | 97 | | Object Create | Creates an object from a prefab | 98 | | Object Fade | Changes the alpha value of an renderer | 99 | | Object Move | Moves an object | 100 | | Object Punch | Punch an object scale to a value and back | 101 | | Object Rotate | Rotates an object | 102 | | Object Scale | Scales an object | 103 | | Object Shake | Changes objects rotation to a value and back | 104 | | Object Tint | Tint the objects material base color | 105 | | Particle Create | Instantiates a particle from a prefab | 106 | | Particle Pooled | Instantiates a pooled particle from a prefab | 107 | | Screen Flash | Overlays the screen with a color | 108 | | Shader | Effect to change shader properties | 109 | | Time Change | Change the time to a value and back | 110 | | Time Freeze | Freeze the time | 111 | 112 | ## Conclusion 113 | 114 | With the introduction of the `[SerializeReference]` attribute which allows you do serialize interfaces and handle inheritance, one could find a better solution to add effects than without hiding a multitude of `MonoBehaviours`. 115 | 116 | As mentioned at the beginning, Renaud Forestié was working on his own tool when I started my work and the idea for the topic of my master thesis. 117 | During the final phase of my thesis he published his tool MMFeedbacks in the Unity Asset Store. 118 | 119 | ## Outlook 120 | 121 | At the end of my master's thesis, I came up with an even more designer-friendly way. A Juicy Effect Graph that would consist of fixed assets that could even be reused. 122 | The following is a work in progress prototype of and effect graph build with the Unity Graph API with the help of NodeGraphProcessor. 123 | 124 | Over easter holidays I had a little time to create this little prototype (not included here). 125 | The idea is, that you can add parameters to a graph at runtime. Here for example on the right side with the `Juice` component. You can see the component has a reference to a graph asset. 126 | A editor script finds all possible properties and serializes the entered parameters. 127 | Feedback List 128 | 129 | ## Made with 130 | 131 | 132 | Tangle Toys 133 | 134 | 135 | Cuddle Waddle 136 | 137 | -------------------------------------------------------------------------------- /Juicy/Runtime/Feedback/JuicyFeedbackShader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using DG.Tweening; 4 | using UnityEngine; 5 | 6 | namespace TinyTools.Juicy 7 | { 8 | public enum ShaderKeyword 9 | { 10 | None, 11 | AlphaTestOn, 12 | AlphaBlendOn, 13 | AlphaPremultiplyOn, 14 | Emission, 15 | MetallicGlossMap, 16 | SpecGlossMap 17 | } 18 | 19 | [Feedback("Renderer/Shader")][AddComponentMenu("")] 20 | public class JuicyFeedbackShader : JuicyFeedbackBase 21 | { 22 | public static string Float = "Float"; 23 | public static string Vector = "Vector"; 24 | public static string Color = "Color"; 25 | public static string Range = "Range"; 26 | 27 | private static readonly Dictionary Keywords = new Dictionary { 28 | {ShaderKeyword.None, null}, 29 | {ShaderKeyword.AlphaTestOn, "_ALPHATEST_ON"}, 30 | {ShaderKeyword.AlphaBlendOn, "_ALPHABLEND_ON"}, 31 | {ShaderKeyword.AlphaPremultiplyOn, "_ALPHAPREMULTIPLY_ON"}, 32 | {ShaderKeyword.Emission, "_EMISSION"}, 33 | {ShaderKeyword.MetallicGlossMap, "_METALLICGLOSSMAP"}, 34 | {ShaderKeyword.SpecGlossMap, "_SPECGLOSSMAP"}, 35 | }; 36 | 37 | [SerializeField, Timing] private Timing timing = new Timing(); 38 | [SerializeField] private RendererTarget target = new RendererTarget(); 39 | 40 | [SerializeField] private string propertyName = ""; 41 | #pragma warning disable 0414 42 | [SerializeField] private int selected = 0; 43 | #pragma warning restore 0414 44 | [SerializeField] private string propertyType = ""; 45 | 46 | [SerializeField] private FloatFromToValue floatValue = new FloatFromToValue(); 47 | [SerializeField] private Vector4FromToValue vectorValue = new Vector4FromToValue(); 48 | [SerializeField] private ColorChooserFromToValue colorValue = new ColorChooserFromToValue(); 49 | [SerializeField] private SliderFromToValue sliderValue = new SliderFromToValue(); 50 | 51 | [SerializeField] private FloatReset floatResetValue = new FloatReset(); 52 | [SerializeField] private Vector4Reset vectorResetValue = new Vector4Reset(); 53 | [SerializeField] private ColorChooserReset colorResetValue = new ColorChooserReset(); 54 | [SerializeField] private FloatReset sliderResetValue = new FloatReset(); 55 | 56 | [SerializeField] private ShaderKeyword shaderKeyword = ShaderKeyword.None; 57 | [SerializeField] private Ease ease = new Ease(); 58 | 59 | private Material material; 60 | private string key; 61 | private float duration; 62 | 63 | protected override void Play() 64 | { 65 | if (!target.IsValid) { 66 | return; 67 | } 68 | 69 | timing.Invoke(this, PlayDelayed); 70 | } 71 | 72 | private void PlayDelayed() 73 | { 74 | duration = timing.duration; 75 | material = target.Value.material; 76 | 77 | HandleFloat(); 78 | HandleVector(); 79 | HandleColor(); 80 | HandleRange(); 81 | 82 | tween.SetEase(ease.curve); 83 | tween.SetUpdate(timing.ignoreTimeScale); 84 | } 85 | 86 | internal override void Stop() 87 | { 88 | base.Stop(); 89 | 90 | if (material == null) { 91 | return; 92 | } 93 | 94 | if (key != null) { 95 | material.DisableKeyword(key); 96 | } 97 | } 98 | 99 | private void HandleFloat() 100 | { 101 | CalcDuration(floatResetValue); 102 | Handle(ref Float, floatValue, floatResetValue, 103 | () => material.SetFloat( 104 | propertyName, floatResetValue.resetValue), 105 | () => material.DOFloat( 106 | floatValue.value, propertyName, duration)); 107 | 108 | } 109 | 110 | private void HandleRange() 111 | { 112 | CalcDuration(sliderResetValue); 113 | Handle(ref Range, sliderValue, sliderResetValue, 114 | () => material.SetFloat( 115 | propertyName, sliderResetValue.resetValue), 116 | () => material.DOFloat( 117 | sliderValue.value, propertyName, duration)); 118 | 119 | } 120 | 121 | private void HandleColor() 122 | { 123 | CalcDuration(colorResetValue); 124 | Handle(ref Color, colorValue, colorResetValue, 125 | () => material.SetColor( 126 | propertyName, colorResetValue.resetValue.Value), 127 | () => material.DOBlendableColor( 128 | colorValue.value.Value, propertyName, duration)); 129 | 130 | key = Keywords[shaderKeyword]; 131 | 132 | if (key == null) { 133 | return; 134 | } 135 | 136 | material.EnableKeyword(key); 137 | tween.onComplete += () => { material.DisableKeyword(key); }; 138 | } 139 | 140 | private void HandleVector() 141 | { 142 | CalcDuration(vectorResetValue); 143 | Handle(ref Vector, vectorValue, vectorResetValue, 144 | () => material.SetVector( 145 | propertyName, vectorResetValue.resetValue), 146 | () => material.DOBlendableVector4( 147 | propertyName, vectorValue.value, duration)); 148 | 149 | } 150 | 151 | private void CalcDuration(Reset reset) 152 | { 153 | if (reset.resetType == ResetType.Yoyo) { 154 | duration /= 2; 155 | } 156 | } 157 | 158 | private void AddIsFrom(FromToValue fromToValue) 159 | { 160 | if (fromToValue.isFrom) { 161 | tween.From(); 162 | } 163 | } 164 | 165 | private void Loop(Reset reset, Action resetCallback) 166 | { 167 | switch (reset.resetType) { 168 | case ResetType.Yoyo: 169 | tween.SetLoops(reset.loop ? -1 : 2, LoopType.Yoyo); 170 | break; 171 | case ResetType.ToValue: 172 | tween.SetLoops(reset.loop ? -1 : 1, LoopType.Restart); 173 | tween.onComplete += () => resetCallback(); 174 | break; 175 | } 176 | } 177 | 178 | private void Handle(ref string type, FromToValue fromToValue, Reset reset, 179 | Action callback, Func func) 180 | { 181 | if (!propertyType.Equals(type)) { 182 | return; 183 | } 184 | 185 | 186 | tween = func(); 187 | 188 | Loop(reset, callback); 189 | AddIsFrom(fromToValue); 190 | } 191 | } 192 | } -------------------------------------------------------------------------------- /Juicy/Editor/Feedback/JuicyFeedbackAnimatorEditor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using UnityEditor; 5 | using UnityEngine; 6 | 7 | namespace TinyTools.Juicy 8 | { 9 | [CustomEditor(typeof(JuicyFeedbackAnimator))] 10 | public sealed class JuicyFeedbackAnimatorEditor : JuicyEditorBase 11 | { 12 | private Animator animator; 13 | private readonly GUIContent valueLabelContent = new GUIContent("Value"); 14 | 15 | private IEnumerable AnimatorControllerParameters => 16 | animator == null ? null : animator.parameters; 17 | 18 | private SerializedProperty timing; 19 | private SerializedProperty targetProperty; 20 | private SerializedProperty selfTarget; 21 | private SerializedProperty otherTarget; 22 | private SerializedProperty targetIsSelf; 23 | private SerializedProperty type; 24 | private SerializedProperty selectedIndex; 25 | private SerializedProperty hash; 26 | private SerializedProperty boolValue; 27 | private SerializedProperty intValue; 28 | private SerializedProperty floatValue; 29 | private SerializedProperty ease; 30 | 31 | private ParameterData[] parameters; 32 | 33 | protected override void Initialize() 34 | { 35 | CacheProperty(ref timing, nameof(timing)); 36 | CacheProperty(ref targetProperty, "target"); 37 | 38 | CacheProperty(ref targetIsSelf, targetProperty, nameof(targetIsSelf)); 39 | CacheProperty(ref selfTarget, targetProperty, nameof(selfTarget)); 40 | CacheProperty(ref otherTarget, targetProperty, nameof(otherTarget)); 41 | 42 | CacheProperty(ref type, nameof(type)); 43 | CacheProperty(ref selectedIndex, nameof(selectedIndex)); 44 | CacheProperty(ref hash, nameof(hash)); 45 | 46 | CacheProperty(ref boolValue, nameof(boolValue)); 47 | CacheProperty(ref intValue, nameof(intValue)); 48 | CacheProperty(ref floatValue, nameof(floatValue)); 49 | 50 | CacheProperty(ref ease, nameof(ease)); 51 | 52 | SetupAnimator(); 53 | } 54 | 55 | public override void OnInspectorGUI() 56 | { 57 | serializedObject.Update(); 58 | 59 | SetupAnimator(); 60 | 61 | timing.isExpanded = 62 | JuicyEditorUtils.FoldoutHeader(timing.isExpanded, "Timing", () => 63 | { 64 | EditorGUILayout.PropertyField(timing); 65 | }); 66 | 67 | isExpanded.boolValue = 68 | JuicyEditorUtils.FoldoutHeader(isExpanded.boolValue, "Properties", DrawProperties); 69 | 70 | serializedObject.ApplyModifiedProperties(); 71 | } 72 | 73 | private void DrawProperties() 74 | { 75 | using (var check = new EditorGUI.ChangeCheckScope()) { 76 | EditorGUILayout.PropertyField(targetProperty); 77 | if (check.changed) { 78 | SetupAnimator(); 79 | } 80 | } 81 | 82 | bool isValid = false; 83 | 84 | if (targetIsSelf.boolValue) { 85 | isValid = (serializedObject.targetObject as Component)? 86 | .GetComponent() != null; 87 | 88 | } else { 89 | isValid = otherTarget.objectReferenceValue != null; 90 | } 91 | 92 | if (!isValid) { 93 | return; 94 | } 95 | 96 | if (animator != null && animator.runtimeAnimatorController == null) { 97 | EditorGUILayout.HelpBox("Controller of Animator Component is missing!", 98 | MessageType.Error); 99 | return; 100 | } 101 | 102 | if (parameters == null || parameters.Length == 0) { 103 | EditorGUILayout.HelpBox("No parameters found", 104 | MessageType.Info); 105 | return; 106 | } 107 | 108 | ParameterData data; 109 | 110 | using (var check = new EditorGUI.ChangeCheckScope()) { 111 | selectedIndex.intValue = 112 | EditorGUILayout.Popup("Parameter", selectedIndex.intValue, 113 | parameters.Select(x => x.MenuItem).ToArray()); 114 | 115 | selectedIndex.serializedObject.ApplyModifiedProperties(); 116 | 117 | data = parameters[selectedIndex.intValue]; 118 | 119 | hash.intValue = data.hash; 120 | 121 | if (check.changed) { 122 | type.enumValueIndex = Array.IndexOf(Enum.GetValues( 123 | typeof(AnimatorControllerParameterType)), 124 | data.type); 125 | } 126 | } 127 | 128 | switch (data.type) { 129 | case AnimatorControllerParameterType.Float: 130 | EditorGUILayout.PropertyField(floatValue, valueLabelContent); 131 | break; 132 | case AnimatorControllerParameterType.Int: 133 | EditorGUILayout.PropertyField(intValue, valueLabelContent); 134 | break; 135 | case AnimatorControllerParameterType.Bool: 136 | boolValue.boolValue = EditorGUILayout.Toggle(valueLabelContent, boolValue.boolValue); 137 | break; 138 | } 139 | 140 | EditorGUILayout.PropertyField(ease); 141 | } 142 | 143 | private void SetupAnimator() 144 | { 145 | animator = (Animator) (targetIsSelf.boolValue 146 | ? selfTarget.objectReferenceValue 147 | : otherTarget.objectReferenceValue); 148 | 149 | if (animator == null) { 150 | return; 151 | } 152 | 153 | if (animator.runtimeAnimatorController == null) { 154 | return; 155 | } 156 | 157 | animator.keepAnimatorControllerStateOnDisable = true; 158 | 159 | // Workaround to prevent unity to throw a warning 160 | animator.enabled = false; 161 | animator.enabled = true; 162 | 163 | parameters = 164 | AnimatorControllerParameters.Select(x => 165 | new ParameterData { 166 | type = x.type, 167 | name = x.name, 168 | hash = x.nameHash 169 | 170 | } 171 | ).ToArray(); 172 | 173 | if (selectedIndex.intValue > parameters.Length || parameters.Length == 0) { 174 | selectedIndex.intValue = 0; 175 | } 176 | } 177 | 178 | private struct ParameterData 179 | { 180 | public AnimatorControllerParameterType type; 181 | public string name; 182 | public int hash; 183 | 184 | public string MenuItem => $"{type}/{name}"; 185 | } 186 | } 187 | } -------------------------------------------------------------------------------- /Juicy/Editor/Utils/TargetDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | using Object = UnityEngine.Object; 4 | 5 | namespace TinyTools.Juicy 6 | { 7 | [CustomPropertyDrawer(typeof(Target<>))] 8 | public class TargetDrawer : JuicyPropertyDrawerBase where T : Object 9 | { 10 | private SerializedProperty targetIsSelf; 11 | private SerializedProperty selfTarget; 12 | private SerializedProperty otherTarget; 13 | 14 | private T targetComponent; 15 | 16 | private bool isValid; 17 | private bool isSelf; 18 | 19 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 20 | { 21 | return SingleLineHeight + 22 | (isValid ? 0 : SingleLineHeight * 2 + StandardSpacing * 2); 23 | } 24 | 25 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 26 | { 27 | using (new EditorGUI.PropertyScope(position, label, property)) { 28 | 29 | if (Event.current.type == EventType.Layout) { 30 | return; 31 | } 32 | 33 | targetComponent = null; 34 | 35 | CacheProperty(ref targetIsSelf, property, nameof(targetIsSelf)); 36 | CacheProperty(ref selfTarget, property, nameof(selfTarget)); 37 | CacheProperty(ref otherTarget, property, nameof(otherTarget)); 38 | 39 | float yPosition = 0f; 40 | 41 | if (isSelf != targetIsSelf.boolValue) { 42 | GUI.changed = true; 43 | } 44 | 45 | isSelf = targetIsSelf.boolValue; 46 | 47 | if (isSelf) { 48 | targetComponent = (property.serializedObject.targetObject as Component)? 49 | .GetComponent(); 50 | 51 | isValid = targetComponent != null && selfTarget.objectReferenceValue 52 | == targetComponent; 53 | 54 | } else { 55 | isValid = otherTarget.objectReferenceValue != null; 56 | } 57 | 58 | Rect targetRect = new Rect(position) { 59 | width = position.width - JuicyStyles.PaneOptionsIcon.width, 60 | height = EditorGUIUtility.singleLineHeight 61 | }; 62 | 63 | yPosition += EditorGUIUtility.singleLineHeight; 64 | 65 | Rect menuRect = new Rect(position) { 66 | x = position.x + targetRect.width, 67 | y = targetRect.y, 68 | width = JuicyStyles.PaneOptionsIcon.width, 69 | height = JuicyStyles.PaneOptionsIcon.height, 70 | }; 71 | 72 | if (EditorGUI.DropdownButton(menuRect, new GUIContent(JuicyStyles.PaneOptionsIcon), 73 | FocusType.Passive, JuicyStyles.IconButtonStyle)) { 74 | 75 | CreateContextMenu(); 76 | } 77 | 78 | if (isSelf) { 79 | selfTarget.objectReferenceValue = targetComponent; 80 | 81 | Rect labelRect = new Rect(position) { 82 | width = EditorGUIUtility.labelWidth, 83 | height = SingleLineHeight 84 | }; 85 | 86 | EditorGUI.LabelField(labelRect, new GUIContent("Target (This)", "Look for a valid component on this GameObject")); 87 | using (new EditorGUI.DisabledScope(true)) { 88 | 89 | float width = (EditorGUI.indentLevel * 16 - 5); 90 | 91 | Rect r = new Rect(position) { 92 | x = position.x + EditorGUIUtility.labelWidth - width, 93 | width = position.width - labelRect.width - menuRect.width + width, 94 | height = SingleLineHeight 95 | }; 96 | 97 | EditorGUI.PropertyField(r, selfTarget, GUIContent.none); 98 | } 99 | 100 | } else { 101 | EditorGUI.PropertyField(targetRect, otherTarget, 102 | new GUIContent("Target (Other)", "Look for a valid component on the referenced GameObject")); 103 | 104 | otherTarget.serializedObject.ApplyModifiedProperties(); 105 | } 106 | 107 | if (isValid) { 108 | return; 109 | } 110 | 111 | Rect helpBox = new Rect(position) { 112 | x = position.x, 113 | y = position.y + yPosition + StandardSpacing * 2, 114 | width = position.width, 115 | height = SingleLineHeight * 2 116 | }; 117 | 118 | // Workaround to fix weird padding 119 | if (EditorGUIUtility.hierarchyMode) { 120 | int num = EditorStyles.foldout.padding.left - EditorStyles.label.padding.left; 121 | helpBox.xMin += num; 122 | } 123 | 124 | EditorGUI.HelpBox(helpBox, $"No {typeof(T).Name} on {(isSelf ? "this" : "target")} GameObject found", 125 | MessageType.Error); 126 | 127 | } 128 | } 129 | 130 | private void CreateContextMenu() 131 | { 132 | var e = Event.current; 133 | Vector2 position = e.mousePosition; 134 | 135 | var menu = new GenericMenu(); 136 | bool isActive = targetIsSelf.boolValue; 137 | 138 | menu.AddItem( 139 | JuicyEditorUtils.GetContent("This"), 140 | isActive, 141 | SetTargetIsSelf, 142 | true); 143 | 144 | menu.AddItem( 145 | JuicyEditorUtils.GetContent("Other"), 146 | !isActive, 147 | SetTargetIsSelf, 148 | false); 149 | 150 | menu.DropDown(new Rect(position, Vector2.zero)); 151 | } 152 | 153 | private void SetTargetIsSelf(object flag) 154 | { 155 | targetIsSelf.boolValue = (bool)flag; 156 | targetIsSelf.serializedObject.ApplyModifiedProperties(); 157 | } 158 | } 159 | 160 | [CustomPropertyDrawer(typeof(RendererTarget))] 161 | public sealed class RendererTargetDrawer : TargetDrawer {} 162 | [CustomPropertyDrawer(typeof(AnimatorTarget))] 163 | public sealed class AnimatorTargetDrawer : TargetDrawer {} 164 | [CustomPropertyDrawer(typeof(TransformTarget))] 165 | public sealed class TransformTargetDrawer : TargetDrawer {} 166 | [CustomPropertyDrawer(typeof(AudioSourceTarget))] 167 | public sealed class AudioSourceTargetDrawer : TargetDrawer {} 168 | [CustomPropertyDrawer(typeof(LightTarget))] 169 | public sealed class LightTargetDrawer : TargetDrawer {} 170 | [CustomPropertyDrawer(typeof(CameraTarget))] 171 | public sealed class CameraTargetDrawer : TargetDrawer {} 172 | } -------------------------------------------------------------------------------- /Juicy/Editor/JuicyFeedbackDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | [CustomPropertyDrawer(typeof(JuicyFeedback))] 7 | public sealed class JuicyFeedbackDrawer : JuicyPropertyDrawerBase 8 | { 9 | private SerializedProperty feedbackList; 10 | private SerializedProperty feedbackName; 11 | 12 | private JuicyFeedbackList listReference; 13 | 14 | private float height = 0; 15 | 16 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 17 | { 18 | return base.GetPropertyHeight(property, label) + height; 19 | } 20 | 21 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 22 | { 23 | height = 0; 24 | CacheProperty(ref feedbackList, property, nameof(feedbackList)); 25 | 26 | Rect addRect = new Rect(position) { 27 | x = position.width - 6, 28 | width = 25 29 | }; 30 | 31 | bool hasReference = feedbackList.objectReferenceValue != null; 32 | 33 | Rect referenceRect = new Rect(position) { 34 | width = position.width - (hasReference ? 0 : addRect.width) 35 | }; 36 | 37 | string name = property.displayName; 38 | 39 | if (hasReference) { 40 | listReference = feedbackList.objectReferenceValue as JuicyFeedbackList; 41 | name = $"{name} ({listReference.displayName})"; 42 | } 43 | 44 | EditorGUI.PropertyField(referenceRect, feedbackList, 45 | new GUIContent(name)); 46 | 47 | if (hasReference) { 48 | return; 49 | } 50 | 51 | if (GUI.Button(addRect, new GUIContent(JuicyStyles.IconToolbarPlus.image, 52 | "Adds a feedback component"), 53 | JuicyStyles.RlPreButton)) { 54 | AddNewList(property); 55 | } 56 | } 57 | 58 | private void AddNewList(SerializedProperty property) 59 | { 60 | if (feedbackList.objectReferenceValue != null) { 61 | return; 62 | } 63 | 64 | GameObject gameObject = (property.serializedObject.targetObject as Component)? 65 | .gameObject; 66 | 67 | JuicyFeedbackList feedback = Undo.AddComponent(gameObject); 68 | feedback.displayName = property.displayName; 69 | 70 | feedbackList.objectReferenceValue = feedback; 71 | 72 | property.serializedObject.ApplyModifiedProperties(); 73 | } 74 | } 75 | 76 | /*private Editor editor; 77 | private GameObject gameObject; 78 | 79 | private Component component; 80 | 81 | private Object lastObjectReferenceValue; 82 | 83 | private SerializedProperty feedback; 84 | private JuicyFeedbackList feedbackList; 85 | 86 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 87 | { 88 | return EditorGUI.GetPropertyHeight(property, editor) - 89 | SingleLineHeight; 90 | //+ (property.isExpanded ? SingleLineHeight + StandardSpacing : 0); 91 | } 92 | 93 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 94 | { 95 | using (new EditorGUI.PropertyScope(position, label, property)) { 96 | 97 | CacheProperty(ref feedback, property, "feedbackList"); 98 | 99 | InitialiseFeedback(property); 100 | 101 | bool isExpanded = property.isExpanded; 102 | bool enabled = feedbackList.enabled; 103 | 104 | property.isExpanded = isExpanded; 105 | feedbackList.enabled = enabled; 106 | 107 | lastObjectReferenceValue = feedback.objectReferenceValue; 108 | DrawEditor(); 109 | 110 | //if (isExpanded) { 111 | //} 112 | } 113 | } 114 | 115 | private void DrawEditor() 116 | { 117 | if (!editor) { 118 | Editor.CreateCachedEditor( 119 | feedback.objectReferenceValue, 120 | typeof(JuicyFeedbackListEditor), ref editor); 121 | } else { 122 | editor.OnInspectorGUI(); 123 | } 124 | } 125 | 126 | private void InitialiseFeedback(SerializedProperty property) 127 | { 128 | feedbackList = feedback.objectReferenceValue as JuicyFeedbackList; 129 | component = property.serializedObject.targetObject as Component; 130 | gameObject = component != null ? component.gameObject : null; 131 | 132 | // Everything works like normal, do nothing but hiding the component 133 | if (feedbackList != null) { 134 | feedbackList.hideFlags = feedbackList.debugView ? HideFlags.None : HideFlags.HideInInspector; 135 | return; 136 | } 137 | 138 | bool debugView = false; 139 | 140 | // Unity Reset Button got clicked, so compare with the last reference you had and 141 | // reset the last editor 142 | if (IsResetByUnity()) { 143 | Debug.Log("Reset"); 144 | JuicyFeedbackList lastComponent = (JuicyFeedbackList) lastObjectReferenceValue; 145 | debugView = lastComponent.debugView; 146 | ((JuicyFeedbackListEditor)editor).ResetFeedbackByDefaultUnityFunction(); 147 | } 148 | 149 | if (gameObject == null) { 150 | return; 151 | } 152 | 153 | // Component got destroyed, redo action is called and we are looking for 154 | // a JuicyFeedbackList Component which has the same id like our property combination 155 | if (feedbackList == null) { 156 | foreach (var c in gameObject.GetComponents()) { 157 | if (!GetId(property).Equals(c.id)) { 158 | continue; 159 | } 160 | 161 | feedback.objectReferenceValue = c; 162 | lastObjectReferenceValue = c; 163 | return; 164 | } 165 | } 166 | 167 | feedbackList = Undo.AddComponent(gameObject); 168 | feedbackList.displayName = property.displayName; 169 | feedbackList.hideFlags = feedbackList.debugView ? HideFlags.None : HideFlags.HideInInspector; 170 | feedback.objectReferenceValue = feedbackList; 171 | feedbackList.debugView = debugView; 172 | feedbackList.id = GetId(property); 173 | feedbackList.callerComponent = component; 174 | 175 | if (editor == null || editor.target == feedback.objectReferenceValue) { 176 | return; 177 | } 178 | 179 | Editor.CreateCachedEditor( 180 | feedback.objectReferenceValue, 181 | typeof(JuicyFeedbackListEditor), ref editor); 182 | 183 | editor.Repaint(); 184 | } 185 | 186 | private string GetId(SerializedProperty property) 187 | { 188 | return $"{component.GetInstanceID()}{property.name}"; 189 | } 190 | 191 | private bool IsResetByUnity() 192 | { 193 | return lastObjectReferenceValue != null && feedback.objectReferenceValue == null; 194 | } 195 | }*/ 196 | } -------------------------------------------------------------------------------- /Juicy/Editor/Utils/JuicyStyles.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace TinyTools.Juicy 5 | { 6 | public static class JuicyStyles 7 | { 8 | public static GUIStyle IconButtonStyle { get; } 9 | public static GUIStyle BoxStyle { get; } 10 | public static GUIStyle FeedbackItemBoxStyle { get; } 11 | public static GUIStyle IconJuicyStyle { get; } 12 | public static GUIStyle FeedbackIconStyle { get; } 13 | public static GUIStyle FoldoutStyle { get; } 14 | public static GUIStyle FeedbackEditorStyle { get; } 15 | public static GUIStyle HeaderStyle { get; } 16 | public static GUIStyle HeaderToggleStyle { get; } 17 | public static GUIStyle SmallToggle { get; } 18 | public static GUIStyle HeaderFoldout { get; } 19 | public static GUIStyle ToggleGroupStyle { get; } 20 | public static GUIStyle ToggleGroupLabelStyle { get; } 21 | public static GUIStyle ToggleGroupLabelSelectedStyle { get; } 22 | 23 | public static Texture IconJuicy { get; } 24 | public static Texture DefaultFeedbackIcon { get; } 25 | public static Texture ValidIcon { get; } 26 | public static Texture InvalidIcon { get; } 27 | 28 | public static readonly GUIStyle RlDraggingHandle = "RL DragHandle"; 29 | public static readonly GUIStyle RlPreButton = "RL FooterButton"; 30 | public static readonly GUIStyle RlHeaderBackground = "RL Header"; 31 | public static readonly GUIStyle RlEmptyHeaderBackground = "RL Empty Header"; 32 | public static readonly GUIStyle RlFooterBackground = "RL Footer"; 33 | public static readonly GUIStyle RlBoxBackground = "RL Background"; 34 | public static readonly GUIStyle RlElementBackground = "RL Element"; 35 | public static GUIContent ListIsEmpty = EditorGUIUtility.TrTextContent("List is Empty"); 36 | 37 | public static GUIContent IconToolbarPlus = EditorGUIUtility 38 | .TrIconContent("Toolbar Plus"); 39 | public static GUIContent IconToolbarPlusMore = EditorGUIUtility.TrIconContent("Toolbar Plus More", "Add item to list"); 40 | 41 | private static readonly Texture2D PaneOptionsIconDark = (Texture2D) EditorGUIUtility 42 | .Load("Builtin Skins/DarkSkin/Images/pane options.png"); 43 | 44 | private static readonly Texture2D PaneOptionsIconLight = (Texture2D) EditorGUIUtility 45 | .Load("Builtin Skins/LightSkin/Images/pane options.png"); 46 | 47 | public static Texture2D PaneOptionsIcon => 48 | EditorGUIUtility.isProSkin ? PaneOptionsIconDark : PaneOptionsIconLight; 49 | 50 | public static Color HeaderBackground => 51 | EditorGUIUtility.isProSkin ? HeaderBackgroundDark : HeaderBackgroundLight; 52 | 53 | public static Color ReferenceColor => 54 | EditorGUIUtility.isProSkin ? ReferenceHighlightedColorDark : ReferenceHighlightedColorLight; 55 | 56 | private static readonly Color HeaderBackgroundDark = new Color(0.1f, 0.1f, 0.1f, 0.2f); 57 | private static readonly Color HeaderBackgroundLight = new Color(1f, 1f, 1f, 0.2f); 58 | private static readonly Color ReferenceHighlightedColorDark = new Color(0f,0.5f,1f, 1f); 59 | private static readonly Color ReferenceHighlightedColorLight = new Color(0f,0.5f,1f, 0.3f); 60 | 61 | public static readonly GUIContent IconToolbarMinus = 62 | EditorGUIUtility.TrIconContent("Toolbar Minus", "Remove item from list"); 63 | 64 | static JuicyStyles() 65 | { 66 | IconButtonStyle = new GUIStyle(GUI.skin.FindStyle("IconButton") ?? EditorGUIUtility 67 | .GetBuiltinSkin(EditorSkin.Inspector) 68 | .FindStyle("IconButton")) { 69 | margin = new RectOffset(0, 0, 2, 0) 70 | }; 71 | 72 | FeedbackEditorStyle = new GUIStyle { 73 | margin = new RectOffset(0, 17, 5, 5) 74 | }; 75 | 76 | BoxStyle = new GUIStyle(EditorStyles.toolbar) { 77 | padding = new RectOffset(3, 3, 4, 4), 78 | margin = new RectOffset(0, 3, 0, 0), 79 | stretchHeight = true, 80 | stretchWidth = true, 81 | fixedHeight = 0.0f 82 | }; 83 | 84 | FeedbackItemBoxStyle = new GUIStyle(EditorStyles.toolbar) { 85 | padding = new RectOffset(3, 3, 4, 4), 86 | margin = new RectOffset(0, 0, 0, 0), 87 | stretchHeight = true, 88 | stretchWidth = true, 89 | fixedHeight = 0.0f, 90 | }; 91 | 92 | IconJuicyStyle = new GUIStyle { 93 | margin = new RectOffset(10, 0, 0, 0), 94 | fixedWidth = EditorGUIUtility.singleLineHeight + 10, 95 | fixedHeight = EditorGUIUtility.singleLineHeight 96 | }; 97 | 98 | FeedbackIconStyle = new GUIStyle { 99 | margin = new RectOffset(0, 0, 4, 0), 100 | fixedWidth = EditorGUIUtility.singleLineHeight - 5, 101 | fixedHeight = EditorGUIUtility.singleLineHeight - 5, 102 | }; 103 | 104 | FoldoutStyle = new GUIStyle(EditorStyles.foldout) { 105 | //alignment = TextAnchor.MiddleLeft, 106 | wordWrap = true, 107 | //fontStyle = FontStyle.Bold, 108 | fontSize = 12 109 | }; 110 | 111 | SmallToggle = new GUIStyle("ShurikenToggle") { 112 | fixedWidth = EditorGUIUtility.singleLineHeight, 113 | fixedHeight = EditorGUIUtility.singleLineHeight 114 | }; 115 | 116 | HeaderStyle = new GUIStyle(EditorStyles.boldLabel); 117 | 118 | HeaderToggleStyle = new GUIStyle(EditorStyles.toggle) { 119 | fontStyle = FontStyle.Bold 120 | }; 121 | 122 | HeaderFoldout = new GUIStyle(EditorStyles.foldoutHeader) { 123 | margin = new RectOffset(16, 7, 0, 0), 124 | fixedHeight = 25, 125 | fontSize = 11, 126 | fontStyle = FontStyle.Bold 127 | }; 128 | 129 | ToggleGroupStyle = new GUIStyle(EditorStyles.foldoutHeader) { 130 | fontSize = 10, 131 | fontStyle = FontStyle.Normal, 132 | }; 133 | 134 | ToggleGroupLabelStyle = new GUIStyle(EditorStyles.boldLabel) { 135 | padding = new RectOffset(-10,0,0,0), 136 | fontSize = 10 137 | }; 138 | 139 | ToggleGroupLabelSelectedStyle = new GUIStyle(ToggleGroupLabelStyle) { 140 | normal = {textColor = EditorStyles.linkLabel.normal.textColor}, 141 | }; 142 | 143 | IconJuicy = AssetDatabase 144 | .LoadAssetAtPath(JuicyEditorUtils.GetPluginRootPath() + "Images/img_juicy.png"); 145 | 146 | DefaultFeedbackIcon = AssetDatabase 147 | .LoadAssetAtPath(JuicyEditorUtils.GetPluginRootPath() + $"Images/img_feedback_default.png"); 148 | 149 | InvalidIcon = EditorGUIUtility.IconContent("console.erroricon.sml").image; 150 | ValidIcon = EditorGUIUtility.IconContent("Collab").image; 151 | } 152 | } 153 | } -------------------------------------------------------------------------------- /Juicy/Editor/Utils/ReorderableListPropertyDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEditorInternal; 3 | using UnityEngine; 4 | 5 | namespace TinyTools.Juicy 6 | { 7 | public abstract class ReorderableListPropertyDrawer : JuicyPropertyDrawerBase where T : Object 8 | { 9 | private SerializedProperty listWrapperProperty; 10 | private SerializedObject serializedObject; 11 | 12 | private ReorderableList reorderableList; 13 | private Rect dragAndDropRect; 14 | private Object[] objects; 15 | 16 | protected abstract SerializedProperty GetListProperty(SerializedProperty property); 17 | 18 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 19 | { 20 | Initialize(property); 21 | 22 | float height = SingleLineHeight; 23 | 24 | return listWrapperProperty.isExpanded ? reorderableList.GetHeight() + height + StandardSpacing : height; 25 | } 26 | 27 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 28 | { 29 | using (new EditorGUI.PropertyScope(position, label, property)) { 30 | 31 | serializedObject.Update(); 32 | 33 | if (EditorGUIUtility.hierarchyMode) { 34 | int num = EditorStyles.foldout.padding.left - EditorStyles.label.padding.left; 35 | position.xMin += num - 4; 36 | } 37 | 38 | Rect foldoutRect = new Rect(position) { 39 | height = SingleLineHeight + StandardSpacing, 40 | }; 41 | 42 | listWrapperProperty.isExpanded = EditorGUI.Foldout(foldoutRect,listWrapperProperty.isExpanded, 43 | listWrapperProperty.displayName, true); 44 | 45 | if (!listWrapperProperty.isExpanded) { 46 | return; 47 | } 48 | 49 | position.y += SingleLineHeight + StandardSpacing; 50 | //position.x += 32; 51 | position.width -= 32; 52 | 53 | reorderableList.DoList(position); 54 | 55 | HandleDragAndDrop(); 56 | 57 | serializedObject.ApplyModifiedProperties(); 58 | } 59 | } 60 | 61 | private void Initialize(SerializedProperty property) 62 | { 63 | if (reorderableList != null) { 64 | return; 65 | } 66 | 67 | listWrapperProperty = GetListProperty(property); 68 | 69 | serializedObject = property.serializedObject; 70 | 71 | reorderableList = new ReorderableList(serializedObject, listWrapperProperty, 72 | true, false, true, false); 73 | 74 | InitializeHeaderCallback(); 75 | InitializeElementCallback(); 76 | InitializeRemoveCallback(); 77 | InitializeHeightCallback(); 78 | 79 | reorderableList.headerHeight = 3; 80 | reorderableList.footerHeight += 3; 81 | } 82 | 83 | private void InitializeHeaderCallback() 84 | { 85 | reorderableList.drawHeaderCallback = rect => 86 | { 87 | rect.y -= EditorGUIUtility.singleLineHeight; 88 | 89 | float height = EditorGUIUtility.singleLineHeight; 90 | 91 | rect.height = reorderableList.count == 0 ? height * 3: height; 92 | 93 | dragAndDropRect = rect; 94 | 95 | }; 96 | } 97 | 98 | private void InitializeElementCallback() 99 | { 100 | const float xShift = 0; 101 | const float deleteButtonWidth = 20; 102 | 103 | reorderableList.drawElementCallback = (rect, index, active, focused) => 104 | { 105 | if (index >= reorderableList.count) { 106 | return; 107 | } 108 | 109 | SerializedProperty property = listWrapperProperty.GetArrayElementAtIndex(index); 110 | 111 | Rect propertyRect = new Rect(rect) { 112 | x = rect.x - xShift, 113 | width = rect.width + xShift - deleteButtonWidth 114 | }; 115 | 116 | Rect buttonRect = new Rect(rect) { 117 | x = propertyRect.x + propertyRect.width, 118 | width = deleteButtonWidth 119 | }; 120 | 121 | EditorGUI.PropertyField(propertyRect, property, GUIContent.none); 122 | serializedObject.ApplyModifiedProperties(); 123 | 124 | if (GUI.Button(buttonRect,JuicyStyles.IconToolbarMinus, JuicyStyles.RlPreButton)) { 125 | RemoveItem(index); 126 | } 127 | }; 128 | } 129 | 130 | private void AddObject(T obj, int index) 131 | { 132 | listWrapperProperty.GetArrayElementAtIndex(index).objectReferenceValue = obj; 133 | 134 | serializedObject.ApplyModifiedProperties(); 135 | } 136 | 137 | private void AddObjects() 138 | { 139 | foreach (var t in objects) { 140 | listWrapperProperty.arraySize++; 141 | AddObject((T)t, listWrapperProperty.arraySize - 1); 142 | } 143 | } 144 | 145 | private void InitializeRemoveCallback() 146 | { 147 | reorderableList.onRemoveCallback = list => { RemoveItem(list.index); }; 148 | } 149 | 150 | private void RemoveItem(int index) 151 | { 152 | SerializedProperty property = listWrapperProperty 153 | .GetArrayElementAtIndex(index); 154 | 155 | if (property.objectReferenceValue != null) { 156 | listWrapperProperty.DeleteArrayElementAtIndex(index); 157 | listWrapperProperty.DeleteArrayElementAtIndex(index); 158 | } else { 159 | listWrapperProperty.DeleteArrayElementAtIndex(index); 160 | } 161 | 162 | serializedObject.ApplyModifiedProperties(); 163 | } 164 | 165 | private void InitializeHeightCallback() 166 | { 167 | reorderableList.elementHeightCallback = index => 168 | { 169 | if (index >= reorderableList.count) { 170 | return 0; 171 | } 172 | 173 | SerializedProperty property = listWrapperProperty.GetArrayElementAtIndex(index); 174 | 175 | if (property == null) { 176 | return 0; 177 | } 178 | 179 | return EditorGUI.GetPropertyHeight(property) + 180 | EditorGUIUtility.standardVerticalSpacing; 181 | }; 182 | } 183 | 184 | private void HandleDragAndDrop() 185 | { 186 | var e = Event.current; 187 | 188 | if (!dragAndDropRect.Contains(e.mousePosition)) { 189 | return; 190 | } 191 | 192 | objects = DragAndDrop.objectReferences; 193 | 194 | if (objects == null || objects.Length <= 0) { 195 | return; 196 | } 197 | 198 | if (!(objects[0] is AudioClip)) { 199 | return; 200 | } 201 | 202 | EventType eventType = e.type; 203 | 204 | if (eventType == EventType.DragUpdated || eventType == EventType.DragPerform){ 205 | DragAndDrop.visualMode = DragAndDropVisualMode.Copy; 206 | 207 | if (eventType == EventType.DragPerform) { 208 | DragAndDrop.AcceptDrag(); 209 | 210 | AddObjects(); 211 | } 212 | e.Use(); 213 | } 214 | } 215 | } 216 | 217 | [CustomPropertyDrawer(typeof(AudioClipList))] 218 | public sealed class ReorderableAudioClipListPropertyDrawer : ReorderableListPropertyDrawer 219 | { 220 | protected override SerializedProperty GetListProperty(SerializedProperty property) 221 | { 222 | return property.FindPropertyRelative("clips"); 223 | } 224 | } 225 | } --------------------------------------------------------------------------------