Files
ihob/Assets/ProCamera2D/Editor/Extensions/ProCamera2DParallaxEditor.cs
2026-02-21 17:04:05 -08:00

409 lines
14 KiB
C#

using System.Linq;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
#if USING_URP
using UnityEngine.Rendering.Universal;
#endif
namespace Com.LuisPedroFonseca.ProCamera2D
{
[CustomEditor(typeof(ProCamera2DParallax))]
public class ProCamera2DParallaxEditor : Editor
{
GUIContent _tooltip;
ReorderableList _parallaxLayersList;
ProCamera2D _proCamera2D;
ProCamera2DParallax _proCamera2DParallax;
MonoScript _script;
void OnEnable()
{
if (target == null)
return;
_proCamera2DParallax = (ProCamera2DParallax)target;
_proCamera2D = _proCamera2DParallax.ProCamera2D;
_script = MonoScript.FromMonoBehaviour(_proCamera2DParallax);
// Parallax layers List
_parallaxLayersList = new ReorderableList(serializedObject, serializedObject.FindProperty("ParallaxLayers"), false, true, true, true);
// Draw element callback
_parallaxLayersList.drawElementCallback = (rect, index, isActive, isFocused) =>
{
rect.y += 2;
var element = _parallaxLayersList.serializedProperty.GetArrayElementAtIndex(index);
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y, 65, 10), new GUIContent("Camera", "The parallax camera"));
EditorGUI.PropertyField(new Rect(
rect.x + 65,
rect.y,
80,
EditorGUIUtility.singleLineHeight),
element.FindPropertyRelative("ParallaxCamera"), GUIContent.none);
// Speed slider
EditorGUI.LabelField(new Rect(rect.x + 170, rect.y, 65, 20), new GUIContent(_proCamera2DParallax.UseIndependentAxisSpeeds ? "SpeedX" : "Speed", "The relative speed at which the camera should move in comparison to the main camera."));
if(_proCamera2DParallax.UseIndependentAxisSpeeds )
EditorGUI.LabelField(new Rect(rect.x + 170, rect.y + 15, 65, 20), new GUIContent("SpeedY", "The relative speed at which the camera should move in comparison to the main camera."));
if (_proCamera2DParallax.UseIndependentAxisSpeeds)
{
EditorGUI.PropertyField(new Rect(
rect.x + 215,
rect.y,
rect.width - 215,
EditorGUIUtility.singleLineHeight),
element.FindPropertyRelative("SpeedX"), GUIContent.none);
EditorGUI.PropertyField(new Rect(
rect.x + 215,
rect.y + 15,
rect.width - 215,
EditorGUIUtility.singleLineHeight),
element.FindPropertyRelative("SpeedY"), GUIContent.none);
}
else
{
EditorGUI.PropertyField(new Rect(
rect.x + 210,
rect.y,
rect.width - 210,
EditorGUIUtility.singleLineHeight),
element.FindPropertyRelative("Speed"), GUIContent.none);
}
if (_proCamera2DParallax.UseIndependentAxisSpeeds)
rect.y += 10;
// Layer mask
EditorGUI.PrefixLabel(new Rect(rect.x, rect.y + 25, 65, 10), new GUIContent("Culling Mask", "Which layers should this camera render?"));
EditorGUI.PropertyField(new Rect(
rect.x + 85,
rect.y + 25,
rect.width - 85,
EditorGUIUtility.singleLineHeight),
element.FindPropertyRelative("LayerMask"), GUIContent.none);
};
// Draw header callback
_parallaxLayersList.drawHeaderCallback = rect => EditorGUI.LabelField(rect, "Parallax layers");
// Add element callback
_parallaxLayersList.onAddCallback = list => AddParallaxLayer();
// Remove element callback
_parallaxLayersList.onRemoveCallback = list =>
{
if (EditorUtility.DisplayDialog("Warning!", "Are you sure you want to delete this layer?", "Yes", "No"))
{
var cam = list.serializedProperty.GetArrayElementAtIndex(list.index).FindPropertyRelative("ParallaxCamera").objectReferenceValue as Camera;
if (cam != null)
DestroyImmediate(cam.gameObject);
ReorderableList.defaultBehaviours.DoRemoveButton(list);
}
};
// Select element callback
_parallaxLayersList.onSelectCallback = list =>
{
EditorGUIUtility.PingObject(list.serializedProperty.GetArrayElementAtIndex(list.index).FindPropertyRelative("ParallaxCamera").objectReferenceValue as Camera);
};
_parallaxLayersList.onReorderCallback = list =>
{
if (_proCamera2DParallax.ParallaxLayers[0].ParallaxCamera.clearFlags != CameraClearFlags.SolidColor &&
_proCamera2DParallax.ParallaxLayers[0].ParallaxCamera.clearFlags != CameraClearFlags.Skybox)
_proCamera2DParallax.ParallaxLayers[0].ParallaxCamera.clearFlags = CameraClearFlags.SolidColor;
};
_parallaxLayersList.elementHeight = 70;
_parallaxLayersList.headerHeight = 18;
_parallaxLayersList.draggable = true;
}
public override void OnInspectorGUI()
{
if (target == null)
return;
serializedObject.Update();
// Show script link
GUI.enabled = false;
_script = EditorGUILayout.ObjectField("Script", _script, typeof(MonoScript), false) as MonoScript;
GUI.enabled = true;
// ProCamera2D
_tooltip = new GUIContent("Pro Camera 2D", "");
EditorGUILayout.PropertyField(serializedObject.FindProperty("_pc2D"), _tooltip);
if (_proCamera2DParallax.ProCamera2D == null)
EditorGUILayout.HelpBox("ProCamera2D is not set.", MessageType.Error, true);
_proCamera2D = _proCamera2DParallax.ProCamera2D;
if (_proCamera2D == null)
{
serializedObject.ApplyModifiedProperties();
return;
}
// Perspective camera
if (!_proCamera2D.GameCamera.orthographic)
EditorGUILayout.HelpBox("ProCamera2D Parallax needs an orthographic projection camera to work.", MessageType.Error, true);
// Parallax layers List
_parallaxLayersList.DoLayoutList();
var areSpeedsNotOrdered = false;
var isLayerMaskEmpty = false;
var isLayerMaskOverlapping = false;
for (var i = 0; i < _proCamera2DParallax.ParallaxLayers.Count; i++)
{
// Detect unordered speeds
if (i < _proCamera2DParallax.ParallaxLayers.Count - 1)
{
if (_proCamera2DParallax.ParallaxLayers[i].Speed > _proCamera2DParallax.ParallaxLayers[i + 1].Speed)
areSpeedsNotOrdered = true;
}
// Detect empty layer masks
if (_proCamera2DParallax.ParallaxLayers[i].LayerMask.value == 0)
isLayerMaskEmpty = true;
// Detect equal layer masks
for (var j = 0; j < _proCamera2DParallax.ParallaxLayers.Count; j++)
{
if (i != j && _proCamera2DParallax.ParallaxLayers[i].LayerMask.value == _proCamera2DParallax.ParallaxLayers[j].LayerMask.value)
isLayerMaskOverlapping = true;
}
// Remove layers with no camera assigned
if (_proCamera2DParallax.ParallaxLayers[i].ParallaxCamera == null)
{
_proCamera2DParallax.ParallaxLayers.RemoveAt(i);
GUIUtility.ExitGUI();
continue;
}
// Apply layer masks as camera culling mask
_proCamera2DParallax.ParallaxLayers[i].ParallaxCamera.cullingMask = _proCamera2DParallax.ParallaxLayers[i].LayerMask;
// Setup clear flags
if (_proCamera2DParallax.ParallaxLayers[0].Speed > 1)
{
_proCamera2D.GameCamera.clearFlags = CameraClearFlags.SolidColor;
_proCamera2DParallax.ParallaxLayers[i].ParallaxCamera.clearFlags = CameraClearFlags.Depth;
}
else
{
_proCamera2D.GameCamera.clearFlags = CameraClearFlags.Depth;
if (i > 0)
{
_proCamera2DParallax.ParallaxLayers[i].ParallaxCamera.clearFlags = CameraClearFlags.Depth;
}
else if (_proCamera2DParallax.ParallaxLayers[0].ParallaxCamera.clearFlags != CameraClearFlags.SolidColor &&
_proCamera2DParallax.ParallaxLayers[0].ParallaxCamera.clearFlags != CameraClearFlags.Skybox &&
_proCamera2DParallax.AutomaticallyConfigureCameraClearFlags)
{
_proCamera2DParallax.ParallaxLayers[0].ParallaxCamera.clearFlags = CameraClearFlags.SolidColor;
}
}
}
// Show warnings
if (areSpeedsNotOrdered)
EditorGUILayout.HelpBox("Parallax layer speeds are not ordered.", MessageType.Warning, true);
if (isLayerMaskEmpty)
EditorGUILayout.HelpBox("One or more layer mask is empty.", MessageType.Warning, true);
if (isLayerMaskOverlapping)
EditorGUILayout.HelpBox("Two or more cameras are rendering the same layers. Check your Layer Masks.", MessageType.Warning, true);
// Setup depths
var sortedNegativeParallaxLayers = _proCamera2DParallax.ParallaxLayers
.OrderByDescending(x => x.Speed)
.Where(p => p.Speed < 1)
.ToList();
for (var i = 0; i < sortedNegativeParallaxLayers.Count; i++)
{
var parallaxLayer = sortedNegativeParallaxLayers[i];
parallaxLayer.ParallaxCamera.depth = _proCamera2DParallax.BackDepthStart - i - 1;
}
var sortedPositiveParallaxLayers = _proCamera2DParallax.ParallaxLayers
.OrderBy(x => x.Speed)
.Where(p => p.Speed > 1)
.ToList();
for (var i = 0; i < sortedPositiveParallaxLayers.Count; i++)
{
var parallaxLayer = sortedPositiveParallaxLayers[i];
parallaxLayer.ParallaxCamera.depth = _proCamera2DParallax.FrontDepthStart + i + 1;
}
// Setup hierarchy order
var sortedParallaxLayers = _proCamera2DParallax.ParallaxLayers
.OrderBy(x => x.Speed)
.ToList();
for (var i = 0; i < sortedParallaxLayers.Count; i++)
{
var parallaxLayer = sortedParallaxLayers[i];
parallaxLayer.ParallaxCamera.transform.SetSiblingIndex(i);
}
#if USING_URP
sortedParallaxLayers.Add(new ProCamera2DParallaxLayer
{
ParallaxCamera = _proCamera2DParallax.ProCamera2D.GameCamera,
CameraTransform = _proCamera2DParallax.ProCamera2D.transform,
Speed = 1
});
sortedParallaxLayers = sortedParallaxLayers
.OrderBy(x => x.Speed)
.ToList();
for (var i = 0; i < sortedParallaxLayers.Count; i++)
{
var parallaxLayer = sortedParallaxLayers[i];
var cameraData = parallaxLayer.ParallaxCamera.GetUniversalAdditionalCameraData();
if (cameraData == null) continue;
cameraData.renderType = i == 0 ? CameraRenderType.Base : CameraRenderType.Overlay;
if (i != 0 || sortedParallaxLayers.Count <= 1) continue;
cameraData.cameraStack?.Clear();
for (var j = 1; j < sortedParallaxLayers.Count; j++)
{
cameraData.cameraStack?.Add(sortedParallaxLayers[j].ParallaxCamera);
}
}
#endif
// Show correct axis name
var hAxis = "";
var vAxis = "";
switch (_proCamera2D.Axis)
{
case MovementAxis.XY:
hAxis = "X";
vAxis = "Y";
break;
case MovementAxis.XZ:
hAxis = "X";
vAxis = "Z";
break;
case MovementAxis.YZ:
hAxis = "Y";
vAxis = "Z";
break;
}
// Follow X
_tooltip = new GUIContent("Parallax " + hAxis, "Should the parallax occur on the horizontal axis? On by default.");
EditorGUILayout.PropertyField(serializedObject.FindProperty("ParallaxHorizontal"), _tooltip);
// Follow Y
_tooltip = new GUIContent("Parallax " + vAxis, "Should the parallax occur on the vertical axis? On by default.");
EditorGUILayout.PropertyField(serializedObject.FindProperty("ParallaxVertical"), _tooltip);
// Parallax Zoom
_tooltip = new GUIContent("Parallax Zoom", "Should the parallax layers size be affected? On by default.");
EditorGUILayout.PropertyField(serializedObject.FindProperty("ParallaxZoom"), _tooltip);
// Use independent axis speeds
_tooltip = new GUIContent("Use Independent Axis Speeds", "Enable if you want your parallax layers to move at different horizontal and vertical speeds. May cause inconsistent parallax results. Use only if you know what you're doing.");
EditorGUILayout.PropertyField(serializedObject.FindProperty("UseIndependentAxisSpeeds"), _tooltip);
// Automatically configure camera clear flags
_tooltip = new GUIContent("Automatically Configure Clear Flags", "Disable only if you want to manually configure your camera clear flags.");
EditorGUILayout.PropertyField(serializedObject.FindProperty("AutomaticallyConfigureCameraClearFlags"), _tooltip);
// Back Depth Start
_tooltip = new GUIContent("Back Depth Start", "The depth at which the back cameras start");
EditorGUILayout.PropertyField(serializedObject.FindProperty("BackDepthStart"), _tooltip);
// Front Depth Start
_tooltip = new GUIContent("Front Depth Start", "The depth at which the front cameras start");
EditorGUILayout.PropertyField(serializedObject.FindProperty("FrontDepthStart"), _tooltip);
// Root Position
_tooltip = new GUIContent("Root Position", "The position at which your camera is going to start.");
EditorGUILayout.PropertyField(serializedObject.FindProperty("RootPosition"), _tooltip);
// Reset offset button
if (GUILayout.Button("Set Root Position"))
{
switch (_proCamera2D.Axis)
{
case MovementAxis.XY:
_proCamera2DParallax.RootPosition = new Vector3(_proCamera2D.transform.localPosition.x, _proCamera2D.transform.localPosition.y, 0);
break;
case MovementAxis.XZ:
_proCamera2DParallax.RootPosition = new Vector3(_proCamera2D.transform.localPosition.x, 0, _proCamera2D.transform.localPosition.z);
break;
case MovementAxis.YZ:
_proCamera2DParallax.RootPosition = new Vector3(0, _proCamera2D.transform.localPosition.y, _proCamera2D.transform.localPosition.z);
break;
}
}
// Limit back and front depth start
if (_proCamera2DParallax.FrontDepthStart < _proCamera2D.GameCamera.depth)
_proCamera2DParallax.FrontDepthStart = (int)_proCamera2D.GameCamera.depth;
if (_proCamera2DParallax.BackDepthStart > _proCamera2D.GameCamera.depth)
_proCamera2DParallax.BackDepthStart = (int)_proCamera2D.GameCamera.depth;
// Rename layers
if (serializedObject.ApplyModifiedProperties())
{
foreach (var parallaxLayer in _proCamera2DParallax.ParallaxLayers)
{
parallaxLayer.ParallaxCamera.gameObject.name = "ParallaxCamera (" + parallaxLayer.Speed + ")";
}
}
}
private void AddParallaxLayer()
{
var go = new GameObject("ParallaxCamera");
var cam = go.AddComponent<Camera>();
EditorUtility.CopySerialized(_proCamera2D.GameCamera, cam);
go.transform.SetParent(_proCamera2D.transform);
go.transform.localPosition = Vector3.zero;
cam.cullingMask = 0;
cam.clearFlags = CameraClearFlags.Depth;
var parallaxLayer = new ProCamera2DParallaxLayer
{
ParallaxCamera = cam,
Speed = 1
};
_proCamera2DParallax.ParallaxLayers.Add(parallaxLayer);
}
}
}