Modify: 카메라 컨트롤러 관련 스크립트 업데이트
This commit is contained in:
parent
7a4e05d575
commit
2cfe3d7f5f
@ -439,6 +439,9 @@ public class StreamDeckServerManager : MonoBehaviour
|
|||||||
case "get_drone_state":
|
case "get_drone_state":
|
||||||
HandleGetDroneState(service);
|
HandleGetDroneState(service);
|
||||||
break;
|
break;
|
||||||
|
case "toggle_default_blend":
|
||||||
|
HandleToggleDefaultBlend();
|
||||||
|
break;
|
||||||
|
|
||||||
// 아이템
|
// 아이템
|
||||||
case "toggle_item":
|
case "toggle_item":
|
||||||
@ -642,6 +645,13 @@ public class StreamDeckServerManager : MonoBehaviour
|
|||||||
HandleGetDroneState(service);
|
HandleGetDroneState(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleToggleDefaultBlend()
|
||||||
|
{
|
||||||
|
if (cameraManager == null) return;
|
||||||
|
cameraManager.ToggleDefaultBlend();
|
||||||
|
// ToggleDefaultBlend 내부에서 NotifyCameraChanged를 호출하여 모든 클라이언트에 브로드캐스트됩니다.
|
||||||
|
}
|
||||||
|
|
||||||
private void HandleGetDroneState(StreamDeckService service)
|
private void HandleGetDroneState(StreamDeckService service)
|
||||||
{
|
{
|
||||||
if (cameraManager == null) return;
|
if (cameraManager == null) return;
|
||||||
|
|||||||
@ -365,6 +365,61 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
streamDeckManager.NotifyCameraChanged();
|
streamDeckManager.NotifyCameraChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Main Camera에 달린 CinemachineBrain의 Default Blend 스타일을 Cut ↔ EaseInOut 로 토글합니다.
|
||||||
|
/// </summary>
|
||||||
|
public void ToggleDefaultBlend()
|
||||||
|
{
|
||||||
|
var brain = GetCinemachineBrain();
|
||||||
|
if (brain == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[CameraManager] CinemachineBrain을 찾을 수 없습니다. Main Camera에 추가되어 있어야 합니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var blend = brain.DefaultBlend;
|
||||||
|
blend.Style = (blend.Style == CinemachineBlendDefinition.Styles.Cut)
|
||||||
|
? CinemachineBlendDefinition.Styles.EaseInOut
|
||||||
|
: CinemachineBlendDefinition.Styles.Cut;
|
||||||
|
brain.DefaultBlend = blend;
|
||||||
|
|
||||||
|
NotifyStreamDeckCameraStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsDefaultBlendCut
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var brain = GetCinemachineBrain();
|
||||||
|
return brain != null && brain.DefaultBlend.Style == CinemachineBlendDefinition.Styles.Cut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string DefaultBlendStyleName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var brain = GetCinemachineBrain();
|
||||||
|
return brain != null ? brain.DefaultBlend.Style.ToString() : "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CinemachineBrain cachedBrain;
|
||||||
|
|
||||||
|
public CinemachineBrain GetCinemachineBrain()
|
||||||
|
{
|
||||||
|
if (cachedBrain != null) return cachedBrain;
|
||||||
|
|
||||||
|
var mainCam = Camera.main;
|
||||||
|
if (mainCam != null)
|
||||||
|
{
|
||||||
|
cachedBrain = mainCam.GetComponent<CinemachineBrain>();
|
||||||
|
if (cachedBrain != null) return cachedBrain;
|
||||||
|
}
|
||||||
|
cachedBrain = FindFirstObjectByType<CinemachineBrain>();
|
||||||
|
return cachedBrain;
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Initialization
|
#region Initialization
|
||||||
@ -1175,7 +1230,9 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
camera_name = currentPreset.virtualCamera?.gameObject.name ?? "Unknown",
|
camera_name = currentPreset.virtualCamera?.gameObject.name ?? "Unknown",
|
||||||
preset_name = currentPreset.presetName,
|
preset_name = currentPreset.presetName,
|
||||||
total_cameras = cameraPresets.Count,
|
total_cameras = cameraPresets.Count,
|
||||||
is_drone_mode = IsDroneModeActive
|
is_drone_mode = IsDroneModeActive,
|
||||||
|
default_blend_style = DefaultBlendStyleName,
|
||||||
|
is_default_blend_cut = IsDefaultBlendCut
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1203,6 +1260,8 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
public string preset_name;
|
public string preset_name;
|
||||||
public int total_cameras;
|
public int total_cameras;
|
||||||
public bool is_drone_mode;
|
public bool is_drone_mode;
|
||||||
|
public string default_blend_style;
|
||||||
|
public bool is_default_blend_cut;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -1253,6 +1312,10 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
// 드론 상태는 GetCurrentCameraState()의 is_drone_mode로 반환됨
|
// 드론 상태는 GetCurrentCameraState()의 is_drone_mode로 반환됨
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "toggle_default_blend":
|
||||||
|
ToggleDefaultBlend();
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Debug.LogWarning($"[CameraManager] 알 수 없는 액션: {actionId}");
|
Debug.LogWarning($"[CameraManager] 알 수 없는 액션: {actionId}");
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -213,6 +213,23 @@ public class RuntimeControlPanelManager
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default Blend 토글 (Cinemachine Brain)
|
||||||
|
var blendRow = new VisualElement();
|
||||||
|
blendRow.AddToClassList("action-row");
|
||||||
|
|
||||||
|
bool isCut = cam.IsDefaultBlendCut;
|
||||||
|
string styleName = cam.DefaultBlendStyleName;
|
||||||
|
var blendBtn = MakeButton(
|
||||||
|
$"Blend: {styleName} ▶ {(isCut ? "EaseInOut" : "Cut")}로 전환",
|
||||||
|
isCut ? "action-btn--secondary" : "action-btn--success");
|
||||||
|
blendBtn.clicked += () =>
|
||||||
|
{
|
||||||
|
cam.ToggleDefaultBlend();
|
||||||
|
SwitchCategory("camera");
|
||||||
|
};
|
||||||
|
blendRow.Add(blendBtn);
|
||||||
|
actionList.Add(blendRow);
|
||||||
|
|
||||||
var data = cam.GetCameraListData();
|
var data = cam.GetCameraListData();
|
||||||
if (data?.presets == null) return;
|
if (data?.presets == null) return;
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine.UIElements;
|
using UnityEngine.UIElements;
|
||||||
@ -11,8 +12,13 @@ public class CameraManagerEditor : Editor
|
|||||||
private const string UssPath = "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/CameraManagerEditor.uss";
|
private const string UssPath = "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/CameraManagerEditor.uss";
|
||||||
private const string CommonUssPath = "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/StreamingleCommon.uss";
|
private const string CommonUssPath = "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/StreamingleCommon.uss";
|
||||||
|
|
||||||
|
private VisualElement presetsSection;
|
||||||
private VisualElement presetsContainer;
|
private VisualElement presetsContainer;
|
||||||
|
private VisualElement dropZone;
|
||||||
private Label presetsTitleLabel;
|
private Label presetsTitleLabel;
|
||||||
|
private Button defaultBlendButton;
|
||||||
|
private Label defaultBlendStatusLabel;
|
||||||
|
private HelpBox brainNotFoundWarning;
|
||||||
private CameraManager manager;
|
private CameraManager manager;
|
||||||
|
|
||||||
public override VisualElement CreateInspectorGUI()
|
public override VisualElement CreateInspectorGUI()
|
||||||
@ -36,6 +42,9 @@ public class CameraManagerEditor : Editor
|
|||||||
SetupRotationTargetLogic(root);
|
SetupRotationTargetLogic(root);
|
||||||
SetupBlendTransitionLogic(root);
|
SetupBlendTransitionLogic(root);
|
||||||
|
|
||||||
|
// Cinemachine Brain default blend toggle
|
||||||
|
SetupDefaultBlendSection(root);
|
||||||
|
|
||||||
// Preset list (dynamic)
|
// Preset list (dynamic)
|
||||||
BuildPresetsSection(root);
|
BuildPresetsSection(root);
|
||||||
|
|
||||||
@ -162,12 +171,104 @@ public class CameraManagerEditor : Editor
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Cinemachine Brain
|
||||||
|
|
||||||
|
private void SetupDefaultBlendSection(VisualElement root)
|
||||||
|
{
|
||||||
|
var group = root.Q("defaultBlendGroup");
|
||||||
|
brainNotFoundWarning = root.Q<HelpBox>("brainNotFoundWarning");
|
||||||
|
if (group == null) return;
|
||||||
|
|
||||||
|
var row = new VisualElement();
|
||||||
|
row.AddToClassList("blend-toggle-row");
|
||||||
|
|
||||||
|
defaultBlendStatusLabel = new Label("현재 Default Blend:");
|
||||||
|
defaultBlendStatusLabel.AddToClassList("blend-toggle-label");
|
||||||
|
row.Add(defaultBlendStatusLabel);
|
||||||
|
|
||||||
|
defaultBlendButton = new Button(ToggleEditorDefaultBlend) { text = "-" };
|
||||||
|
defaultBlendButton.AddToClassList("blend-toggle-btn");
|
||||||
|
row.Add(defaultBlendButton);
|
||||||
|
|
||||||
|
group.Add(row);
|
||||||
|
|
||||||
|
// 인스펙터가 포커스를 가질 때 주기적으로 UI 동기화 (씬에서 직접 편집한 경우 반영)
|
||||||
|
root.schedule.Execute(UpdateDefaultBlendUI).Every(500);
|
||||||
|
UpdateDefaultBlendUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
private CinemachineBrain FindBrain()
|
||||||
|
{
|
||||||
|
var mainCam = Camera.main;
|
||||||
|
if (mainCam != null)
|
||||||
|
{
|
||||||
|
var brain = mainCam.GetComponent<CinemachineBrain>();
|
||||||
|
if (brain != null) return brain;
|
||||||
|
}
|
||||||
|
return FindFirstObjectByType<CinemachineBrain>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateDefaultBlendUI()
|
||||||
|
{
|
||||||
|
if (defaultBlendButton == null) return;
|
||||||
|
|
||||||
|
var brain = FindBrain();
|
||||||
|
if (brain == null)
|
||||||
|
{
|
||||||
|
defaultBlendButton.text = "Brain 없음";
|
||||||
|
defaultBlendButton.SetEnabled(false);
|
||||||
|
defaultBlendButton.RemoveFromClassList("blend-toggle-btn--cut");
|
||||||
|
defaultBlendButton.RemoveFromClassList("blend-toggle-btn--ease");
|
||||||
|
|
||||||
|
if (defaultBlendStatusLabel != null)
|
||||||
|
defaultBlendStatusLabel.text = "CinemachineBrain을 찾을 수 없음";
|
||||||
|
|
||||||
|
if (brainNotFoundWarning != null)
|
||||||
|
brainNotFoundWarning.style.display = DisplayStyle.Flex;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (brainNotFoundWarning != null)
|
||||||
|
brainNotFoundWarning.style.display = DisplayStyle.None;
|
||||||
|
|
||||||
|
var style = brain.DefaultBlend.Style;
|
||||||
|
bool isCut = style == CinemachineBlendDefinition.Styles.Cut;
|
||||||
|
bool isEase = style == CinemachineBlendDefinition.Styles.EaseInOut;
|
||||||
|
|
||||||
|
if (defaultBlendStatusLabel != null)
|
||||||
|
defaultBlendStatusLabel.text = $"현재 Default Blend: {style}";
|
||||||
|
|
||||||
|
defaultBlendButton.SetEnabled(true);
|
||||||
|
defaultBlendButton.text = isCut ? "▶ EaseInOut 으로 전환" : "▶ Cut 으로 전환";
|
||||||
|
|
||||||
|
defaultBlendButton.EnableInClassList("blend-toggle-btn--cut", isCut);
|
||||||
|
defaultBlendButton.EnableInClassList("blend-toggle-btn--ease", isEase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleEditorDefaultBlend()
|
||||||
|
{
|
||||||
|
var brain = FindBrain();
|
||||||
|
if (brain == null) return;
|
||||||
|
|
||||||
|
Undo.RecordObject(brain, "Toggle CinemachineBrain Default Blend");
|
||||||
|
var blend = brain.DefaultBlend;
|
||||||
|
blend.Style = (blend.Style == CinemachineBlendDefinition.Styles.Cut)
|
||||||
|
? CinemachineBlendDefinition.Styles.EaseInOut
|
||||||
|
: CinemachineBlendDefinition.Styles.Cut;
|
||||||
|
brain.DefaultBlend = blend;
|
||||||
|
EditorUtility.SetDirty(brain);
|
||||||
|
|
||||||
|
UpdateDefaultBlendUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Preset List
|
#region Preset List
|
||||||
|
|
||||||
private void BuildPresetsSection(VisualElement root)
|
private void BuildPresetsSection(VisualElement root)
|
||||||
{
|
{
|
||||||
var section = root.Q("presetsSection");
|
presetsSection = root.Q("presetsSection");
|
||||||
if (section == null) return;
|
if (presetsSection == null) return;
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
var header = new VisualElement();
|
var header = new VisualElement();
|
||||||
@ -177,19 +278,88 @@ public class CameraManagerEditor : Editor
|
|||||||
presetsTitleLabel.AddToClassList("presets-title");
|
presetsTitleLabel.AddToClassList("presets-title");
|
||||||
header.Add(presetsTitleLabel);
|
header.Add(presetsTitleLabel);
|
||||||
|
|
||||||
var addBtn = new Button(AddPreset) { text = "+ 프리셋 추가" };
|
var addBtn = new Button(AddPreset)
|
||||||
|
{
|
||||||
|
text = "+ 프리셋 추가",
|
||||||
|
tooltip = "씬의 첫 CinemachineCamera를 추가합니다. 아래 영역으로 드래그해서 추가할 수도 있습니다."
|
||||||
|
};
|
||||||
addBtn.AddToClassList("preset-add-btn");
|
addBtn.AddToClassList("preset-add-btn");
|
||||||
header.Add(addBtn);
|
header.Add(addBtn);
|
||||||
|
|
||||||
section.Add(header);
|
presetsSection.Add(header);
|
||||||
|
|
||||||
// Container
|
// Container
|
||||||
presetsContainer = new VisualElement();
|
presetsContainer = new VisualElement();
|
||||||
section.Add(presetsContainer);
|
presetsSection.Add(presetsContainer);
|
||||||
|
|
||||||
|
// Drop zone
|
||||||
|
dropZone = new Label("CinemachineCamera를 여기로 드래그하여 추가");
|
||||||
|
dropZone.AddToClassList("preset-drop-zone");
|
||||||
|
presetsSection.Add(dropZone);
|
||||||
|
|
||||||
|
SetupDragAndDrop(presetsSection);
|
||||||
|
|
||||||
RebuildPresetList();
|
RebuildPresetList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetupDragAndDrop(VisualElement target)
|
||||||
|
{
|
||||||
|
target.RegisterCallback<DragUpdatedEvent>(evt =>
|
||||||
|
{
|
||||||
|
if (GetDraggedCameras().Count > 0)
|
||||||
|
{
|
||||||
|
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
|
||||||
|
dropZone?.AddToClassList("preset-drop-zone--active");
|
||||||
|
evt.StopPropagation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
target.RegisterCallback<DragLeaveEvent>(_ =>
|
||||||
|
{
|
||||||
|
dropZone?.RemoveFromClassList("preset-drop-zone--active");
|
||||||
|
});
|
||||||
|
|
||||||
|
target.RegisterCallback<DragExitedEvent>(_ =>
|
||||||
|
{
|
||||||
|
dropZone?.RemoveFromClassList("preset-drop-zone--active");
|
||||||
|
});
|
||||||
|
|
||||||
|
target.RegisterCallback<DragPerformEvent>(evt =>
|
||||||
|
{
|
||||||
|
var cams = GetDraggedCameras();
|
||||||
|
dropZone?.RemoveFromClassList("preset-drop-zone--active");
|
||||||
|
if (cams.Count == 0) return;
|
||||||
|
|
||||||
|
DragAndDrop.AcceptDrag();
|
||||||
|
Undo.RecordObject(this.target, "Add Camera Preset (Drop)");
|
||||||
|
foreach (var cam in cams)
|
||||||
|
{
|
||||||
|
manager.cameraPresets.Add(new CameraManager.CameraPreset(cam));
|
||||||
|
}
|
||||||
|
EditorUtility.SetDirty(this.target);
|
||||||
|
RebuildPresetList();
|
||||||
|
evt.StopPropagation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<CinemachineCamera> GetDraggedCameras()
|
||||||
|
{
|
||||||
|
var list = new List<CinemachineCamera>();
|
||||||
|
foreach (var obj in DragAndDrop.objectReferences)
|
||||||
|
{
|
||||||
|
if (obj is CinemachineCamera cc)
|
||||||
|
{
|
||||||
|
if (!list.Contains(cc)) list.Add(cc);
|
||||||
|
}
|
||||||
|
else if (obj is GameObject go)
|
||||||
|
{
|
||||||
|
var cam = go.GetComponent<CinemachineCamera>();
|
||||||
|
if (cam != null && !list.Contains(cam)) list.Add(cam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
private void RebuildPresetList()
|
private void RebuildPresetList()
|
||||||
{
|
{
|
||||||
if (presetsContainer == null || manager == null) return;
|
if (presetsContainer == null || manager == null) return;
|
||||||
@ -226,14 +396,32 @@ public class CameraManagerEditor : Editor
|
|||||||
var headerRow = new VisualElement();
|
var headerRow = new VisualElement();
|
||||||
headerRow.AddToClassList("preset-item-header");
|
headerRow.AddToClassList("preset-item-header");
|
||||||
|
|
||||||
var indexLabel = new Label($"{index + 1}");
|
int idx = index;
|
||||||
indexLabel.AddToClassList("preset-index");
|
|
||||||
headerRow.Add(indexLabel);
|
var indexField = new IntegerField
|
||||||
|
{
|
||||||
|
value = index + 1,
|
||||||
|
isDelayed = true,
|
||||||
|
tooltip = "카메라 순서 번호. 숫자를 입력 후 Enter를 누르면 해당 위치로 이동합니다."
|
||||||
|
};
|
||||||
|
indexField.AddToClassList("preset-index-field");
|
||||||
|
indexField.RegisterValueChangedCallback(evt =>
|
||||||
|
{
|
||||||
|
int newIndex = Mathf.Clamp(evt.newValue - 1, 0, manager.cameraPresets.Count - 1);
|
||||||
|
if (newIndex != idx)
|
||||||
|
{
|
||||||
|
MovePreset(idx, newIndex);
|
||||||
|
}
|
||||||
|
else if (evt.newValue != idx + 1)
|
||||||
|
{
|
||||||
|
indexField.SetValueWithoutNotify(idx + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
headerRow.Add(indexField);
|
||||||
|
|
||||||
var nameField = new TextField();
|
var nameField = new TextField();
|
||||||
nameField.value = preset.presetName;
|
nameField.value = preset.presetName;
|
||||||
nameField.AddToClassList("preset-name-field");
|
nameField.AddToClassList("preset-name-field");
|
||||||
int idx = index;
|
|
||||||
nameField.RegisterValueChangedCallback(evt =>
|
nameField.RegisterValueChangedCallback(evt =>
|
||||||
{
|
{
|
||||||
Undo.RecordObject(target, "Rename Camera Preset");
|
Undo.RecordObject(target, "Rename Camera Preset");
|
||||||
@ -250,19 +438,24 @@ public class CameraManagerEditor : Editor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Up button
|
// Up button
|
||||||
var upBtn = new Button(() => SwapPresets(index, index - 1)) { text = "\u25B2" };
|
var upBtn = new Button(() => SwapPresets(index, index - 1)) { text = "\u25B2", tooltip = "위로 이동" };
|
||||||
upBtn.AddToClassList("preset-reorder-btn");
|
upBtn.AddToClassList("preset-reorder-btn");
|
||||||
upBtn.SetEnabled(index > 0);
|
upBtn.SetEnabled(index > 0);
|
||||||
headerRow.Add(upBtn);
|
headerRow.Add(upBtn);
|
||||||
|
|
||||||
// Down button
|
// Down button
|
||||||
var downBtn = new Button(() => SwapPresets(index, index + 1)) { text = "\u25BC" };
|
var downBtn = new Button(() => SwapPresets(index, index + 1)) { text = "\u25BC", tooltip = "아래로 이동" };
|
||||||
downBtn.AddToClassList("preset-reorder-btn");
|
downBtn.AddToClassList("preset-reorder-btn");
|
||||||
downBtn.SetEnabled(index < manager.cameraPresets.Count - 1);
|
downBtn.SetEnabled(index < manager.cameraPresets.Count - 1);
|
||||||
headerRow.Add(downBtn);
|
headerRow.Add(downBtn);
|
||||||
|
|
||||||
|
// Duplicate button
|
||||||
|
var duplicateBtn = new Button(() => DuplicatePreset(index)) { text = "\u29C9", tooltip = "프리셋 복제" };
|
||||||
|
duplicateBtn.AddToClassList("preset-duplicate-btn");
|
||||||
|
headerRow.Add(duplicateBtn);
|
||||||
|
|
||||||
// Delete button
|
// Delete button
|
||||||
var deleteBtn = new Button(() => DeletePreset(index)) { text = "X" };
|
var deleteBtn = new Button(() => DeletePreset(index)) { text = "X", tooltip = "프리셋 삭제" };
|
||||||
deleteBtn.AddToClassList("preset-delete-btn");
|
deleteBtn.AddToClassList("preset-delete-btn");
|
||||||
headerRow.Add(deleteBtn);
|
headerRow.Add(deleteBtn);
|
||||||
|
|
||||||
@ -278,11 +471,10 @@ public class CameraManagerEditor : Editor
|
|||||||
allowSceneObjects = true,
|
allowSceneObjects = true,
|
||||||
value = preset.virtualCamera
|
value = preset.virtualCamera
|
||||||
};
|
};
|
||||||
int ci = index;
|
|
||||||
cameraField.RegisterValueChangedCallback(evt =>
|
cameraField.RegisterValueChangedCallback(evt =>
|
||||||
{
|
{
|
||||||
Undo.RecordObject(target, "Change Camera Preset Camera");
|
Undo.RecordObject(target, "Change Camera Preset Camera");
|
||||||
manager.cameraPresets[ci].virtualCamera = evt.newValue as CinemachineCamera;
|
manager.cameraPresets[idx].virtualCamera = evt.newValue as CinemachineCamera;
|
||||||
EditorUtility.SetDirty(target);
|
EditorUtility.SetDirty(target);
|
||||||
});
|
});
|
||||||
fields.Add(cameraField);
|
fields.Add(cameraField);
|
||||||
@ -292,11 +484,10 @@ public class CameraManagerEditor : Editor
|
|||||||
tooltip = "이 카메라에서 마우스 조작(회전, 팬, 줌)을 허용할지 여부",
|
tooltip = "이 카메라에서 마우스 조작(회전, 팬, 줌)을 허용할지 여부",
|
||||||
value = preset.allowMouseControl
|
value = preset.allowMouseControl
|
||||||
};
|
};
|
||||||
int mi = index;
|
|
||||||
mouseToggle.RegisterValueChangedCallback(evt =>
|
mouseToggle.RegisterValueChangedCallback(evt =>
|
||||||
{
|
{
|
||||||
Undo.RecordObject(target, "Toggle Mouse Control");
|
Undo.RecordObject(target, "Toggle Mouse Control");
|
||||||
manager.cameraPresets[mi].allowMouseControl = evt.newValue;
|
manager.cameraPresets[idx].allowMouseControl = evt.newValue;
|
||||||
EditorUtility.SetDirty(target);
|
EditorUtility.SetDirty(target);
|
||||||
});
|
});
|
||||||
fields.Add(mouseToggle);
|
fields.Add(mouseToggle);
|
||||||
@ -337,6 +528,36 @@ public class CameraManagerEditor : Editor
|
|||||||
RebuildPresetList();
|
RebuildPresetList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void MovePreset(int from, int to)
|
||||||
|
{
|
||||||
|
if (from == to) return;
|
||||||
|
if (from < 0 || from >= manager.cameraPresets.Count) return;
|
||||||
|
if (to < 0 || to >= manager.cameraPresets.Count) return;
|
||||||
|
|
||||||
|
Undo.RecordObject(target, "Move Camera Preset");
|
||||||
|
var preset = manager.cameraPresets[from];
|
||||||
|
manager.cameraPresets.RemoveAt(from);
|
||||||
|
manager.cameraPresets.Insert(to, preset);
|
||||||
|
EditorUtility.SetDirty(target);
|
||||||
|
RebuildPresetList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DuplicatePreset(int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= manager.cameraPresets.Count) return;
|
||||||
|
|
||||||
|
Undo.RecordObject(target, "Duplicate Camera Preset");
|
||||||
|
var source = manager.cameraPresets[index];
|
||||||
|
var copy = new CameraManager.CameraPreset(source.virtualCamera)
|
||||||
|
{
|
||||||
|
presetName = $"{source.presetName} (복사)",
|
||||||
|
allowMouseControl = source.allowMouseControl
|
||||||
|
};
|
||||||
|
manager.cameraPresets.Insert(index + 1, copy);
|
||||||
|
EditorUtility.SetDirty(target);
|
||||||
|
RebuildPresetList();
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Play Mode State
|
#region Play Mode State
|
||||||
|
|||||||
@ -1,5 +1,54 @@
|
|||||||
/* Camera Manager Editor styles */
|
/* Camera Manager Editor styles */
|
||||||
|
|
||||||
|
.blend-toggle-row {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blend-toggle-label {
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #d4d4d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blend-toggle-btn {
|
||||||
|
min-width: 160px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-width: 0;
|
||||||
|
font-size: 11px;
|
||||||
|
-unity-font-style: bold;
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
color: #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blend-toggle-btn:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blend-toggle-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blend-toggle-btn--cut {
|
||||||
|
background-color: rgba(234, 179, 8, 0.25);
|
||||||
|
color: #fde68a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blend-toggle-btn--cut:hover {
|
||||||
|
background-color: rgba(234, 179, 8, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blend-toggle-btn--ease {
|
||||||
|
background-color: rgba(34, 197, 94, 0.25);
|
||||||
|
color: #bbf7d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blend-toggle-btn--ease:hover {
|
||||||
|
background-color: rgba(34, 197, 94, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
.presets-section {
|
.presets-section {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
@ -63,6 +112,18 @@
|
|||||||
color: #94a3b8;
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.preset-index-field {
|
||||||
|
width: 42px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-index-field > .unity-integer-field__input {
|
||||||
|
-unity-text-align: middle-center;
|
||||||
|
-unity-font-style: bold;
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.preset-name-field {
|
.preset-name-field {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
@ -97,6 +158,24 @@
|
|||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.preset-duplicate-btn {
|
||||||
|
width: 24px;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 1px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border-width: 0;
|
||||||
|
background-color: rgba(99, 102, 241, 0.15);
|
||||||
|
color: #a5b4fc;
|
||||||
|
font-size: 12px;
|
||||||
|
-unity-text-align: middle-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-duplicate-btn:hover {
|
||||||
|
background-color: rgba(99, 102, 241, 0.35);
|
||||||
|
color: #c7d2fe;
|
||||||
|
}
|
||||||
|
|
||||||
.preset-delete-btn {
|
.preset-delete-btn {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
@ -126,3 +205,22 @@
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
-unity-font-style: italic;
|
-unity-font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.preset-drop-zone {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.03);
|
||||||
|
-unity-text-align: middle-center;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-drop-zone--active {
|
||||||
|
border-color: #6366f1;
|
||||||
|
background-color: rgba(99, 102, 241, 0.15);
|
||||||
|
color: #c7d2fe;
|
||||||
|
-unity-font-style: bold;
|
||||||
|
}
|
||||||
|
|||||||
@ -60,6 +60,14 @@
|
|||||||
</ui:Foldout>
|
</ui:Foldout>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
|
|
||||||
|
<!-- Cinemachine Brain Default Blend -->
|
||||||
|
<ui:VisualElement class="section">
|
||||||
|
<ui:Foldout text="Cinemachine Brain" value="true" class="section-foldout">
|
||||||
|
<ui:VisualElement name="defaultBlendGroup"/>
|
||||||
|
<ui:HelpBox name="brainNotFoundWarning" message-type="Warning" text="Main Camera에 CinemachineBrain이 없습니다. Main Camera를 설정하거나 Camera에 CinemachineBrain 컴포넌트를 추가하세요."/>
|
||||||
|
</ui:Foldout>
|
||||||
|
</ui:VisualElement>
|
||||||
|
|
||||||
<!-- 카메라 프리셋 (C#에서 동적 생성) -->
|
<!-- 카메라 프리셋 (C#에서 동적 생성) -->
|
||||||
<ui:VisualElement name="presetsSection" class="section presets-section"/>
|
<ui:VisualElement name="presetsSection" class="section presets-section"/>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user