Add Unity Pipeline package support
This commit is contained in:
+116
@@ -0,0 +1,116 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Pipeline.Commands;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Commands.ProjectSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Get/set the project Audio settings (CLI-202). The AudioManager has no scripting API, so this
|
||||
/// reads/writes its serialized properties (global volume, rolloff scale, doppler factor) via
|
||||
/// <see cref="ProjectSettingsAsset"/>. Properties absent in a given Unity version are skipped.
|
||||
/// </summary>
|
||||
public static class AudioSettingsCommands
|
||||
{
|
||||
const string Group = "audio";
|
||||
const string AssetPath = "ProjectSettings/AudioManager.asset";
|
||||
|
||||
// Serialized property name -> response/plan label.
|
||||
const string VolumeProp = "m_Volume";
|
||||
const string RolloffProp = "Rolloff Scale";
|
||||
const string DopplerProp = "Doppler Factor";
|
||||
|
||||
[CliCommand("get_audio_settings", "Read project Audio settings (volume, rolloff scale, doppler factor).", MainThreadRequired = true)]
|
||||
public static ProjectSettingsResponse Get() => ProjectSettingsCommand.Get(Group, Read);
|
||||
|
||||
[CliCommand("set_audio_settings", "Change project Audio settings. Requires confirm=true; use dry_run to preview. Not undoable via Ctrl+Z.", MainThreadRequired = true)]
|
||||
public static ProjectSettingsResponse Set(
|
||||
[CliArg("settings", "Fields to change; omitted fields are left unchanged.")] AudioSettingsInput settings = null,
|
||||
[CliArg("confirm", "Apply the change. Without it the call is refused.")] bool confirm = false,
|
||||
[CliArg("dry_run", "Preview the change without applying it.")] bool dryRun = false)
|
||||
{
|
||||
if (settings == null)
|
||||
return ProjectSettingsResponse.Fail(Group, "No 'settings' object provided.");
|
||||
|
||||
var so = ProjectSettingsAsset.Load(AssetPath);
|
||||
var changes = new List<string>();
|
||||
AddFloatChange(so, VolumeProp, "volume", settings.Volume, changes);
|
||||
AddFloatChange(so, RolloffProp, "rolloffScale", settings.RolloffScale, changes);
|
||||
AddFloatChange(so, DopplerProp, "dopplerFactor", settings.DopplerFactor, changes);
|
||||
|
||||
if (changes.Count == 0)
|
||||
return NoChanges();
|
||||
|
||||
var planText = "Set audio settings: " + string.Join("; ", changes);
|
||||
|
||||
void Apply()
|
||||
{
|
||||
var obj = ProjectSettingsAsset.Load(AssetPath);
|
||||
SetFloat(obj, VolumeProp, settings.Volume);
|
||||
SetFloat(obj, RolloffProp, settings.RolloffScale);
|
||||
SetFloat(obj, DopplerProp, settings.DopplerFactor);
|
||||
obj.ApplyModifiedProperties();
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
return ProjectSettingsCommand.Apply(Group, confirm, dryRun, settings, () => planText, Apply, Read);
|
||||
}
|
||||
|
||||
static ProjectSettingsResponse NoChanges()
|
||||
{
|
||||
var response = ProjectSettingsCommand.Get(Group, Read);
|
||||
response.Message = "No changes specified; nothing to apply.";
|
||||
return response;
|
||||
}
|
||||
|
||||
static Dictionary<string, object> Read()
|
||||
{
|
||||
var so = ProjectSettingsAsset.Load(AssetPath);
|
||||
var values = new Dictionary<string, object>();
|
||||
AddFloat(so, VolumeProp, "volume", values);
|
||||
AddFloat(so, RolloffProp, "rolloffScale", values);
|
||||
AddFloat(so, DopplerProp, "dopplerFactor", values);
|
||||
return values;
|
||||
}
|
||||
|
||||
static void AddFloat(SerializedObject so, string prop, string label, Dictionary<string, object> values)
|
||||
{
|
||||
var p = so.FindProperty(prop);
|
||||
if (p != null)
|
||||
values[label] = p.floatValue;
|
||||
}
|
||||
|
||||
static void AddFloatChange(SerializedObject so, string prop, string label, float? newValue, List<string> changes)
|
||||
{
|
||||
if (!newValue.HasValue)
|
||||
return;
|
||||
var p = so.FindProperty(prop);
|
||||
if (p == null)
|
||||
return; // not present in this Unity version
|
||||
if (!Mathf.Approximately(p.floatValue, newValue.Value))
|
||||
changes.Add($"{label} {p.floatValue} -> {newValue.Value}");
|
||||
}
|
||||
|
||||
static void SetFloat(SerializedObject so, string prop, float? value)
|
||||
{
|
||||
if (!value.HasValue)
|
||||
return;
|
||||
var p = so.FindProperty(prop);
|
||||
if (p != null)
|
||||
p.floatValue = value.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Audio settings to change. Null/omitted fields are left unchanged.</summary>
|
||||
public class AudioSettingsInput : IStructuredCommandInput
|
||||
{
|
||||
[CliArg("volume", "Global audio volume (0..1).")]
|
||||
public float? Volume { get; set; }
|
||||
|
||||
[CliArg("rolloffScale", "Global rolloff scale.")]
|
||||
public float? RolloffScale { get; set; }
|
||||
|
||||
[CliArg("dopplerFactor", "Global doppler factor.")]
|
||||
public float? DopplerFactor { get; set; }
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a7abd0c1722ba34dae96408746d2c9a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Pipeline.Commands;
|
||||
using Unity.Pipeline.Editor.Authoring;
|
||||
using Unity.Pipeline.Models;
|
||||
using UnityEditor;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Commands.ProjectSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Get/set the project's default render pipeline (CLI-202). The settable field is a handle to a
|
||||
/// <see cref="RenderPipelineAsset"/>; an empty reference selects the built-in pipeline (no SRP).
|
||||
/// </summary>
|
||||
public static class GraphicsSettingsCommands
|
||||
{
|
||||
const string Group = "graphics";
|
||||
|
||||
[CliCommand("get_graphics_settings", "Read GraphicsSettings (default render pipeline).", MainThreadRequired = true)]
|
||||
public static ProjectSettingsResponse Get() => ProjectSettingsCommand.Get(Group, Read);
|
||||
|
||||
[CliCommand("set_graphics_settings", "Set the default render pipeline asset. Requires confirm=true; use dry_run to preview. Not undoable via Ctrl+Z.", MainThreadRequired = true)]
|
||||
public static ProjectSettingsResponse Set(
|
||||
[CliArg("settings", "Fields to change; omitted fields are left unchanged.")] GraphicsSettingsInput settings = null,
|
||||
[CliArg("confirm", "Apply the change. Without it the call is refused.")] bool confirm = false,
|
||||
[CliArg("dry_run", "Preview the change without applying it.")] bool dryRun = false)
|
||||
{
|
||||
if (settings == null)
|
||||
return ProjectSettingsResponse.Fail(Group, "No 'settings' object provided.");
|
||||
|
||||
// omitted (null) = leave unchanged; an empty reference = built-in pipeline; otherwise a
|
||||
// handle to a RenderPipelineAsset resolved through the shared ObjectResolver.
|
||||
if (settings.RenderPipelineAsset == null)
|
||||
return NoChanges();
|
||||
|
||||
RenderPipelineAsset asset = null;
|
||||
if (!settings.RenderPipelineAsset.IsEmpty)
|
||||
{
|
||||
if (!ObjectResolver.TryResolve(settings.RenderPipelineAsset, out var obj, out var resolveError))
|
||||
return ProjectSettingsResponse.Fail(Group, resolveError);
|
||||
asset = obj as RenderPipelineAsset;
|
||||
if (asset == null)
|
||||
return ProjectSettingsResponse.Fail(Group, $"'{settings.RenderPipelineAsset}' is not a RenderPipelineAsset.");
|
||||
}
|
||||
|
||||
var current = GraphicsSettings.defaultRenderPipeline;
|
||||
if (current == asset)
|
||||
return NoChanges();
|
||||
|
||||
var currentName = current != null ? current.name : "<built-in>";
|
||||
var newName = asset != null ? asset.name : "<built-in>";
|
||||
var planText = $"Set defaultRenderPipeline {currentName} -> {newName}";
|
||||
|
||||
void Apply()
|
||||
{
|
||||
GraphicsSettings.defaultRenderPipeline = asset;
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
return ProjectSettingsCommand.Apply(Group, confirm, dryRun, settings, () => planText, Apply, Read);
|
||||
}
|
||||
|
||||
static ProjectSettingsResponse NoChanges()
|
||||
{
|
||||
var response = ProjectSettingsCommand.Get(Group, Read);
|
||||
response.Message = "No changes specified; nothing to apply.";
|
||||
return response;
|
||||
}
|
||||
|
||||
static Dictionary<string, object> Read()
|
||||
{
|
||||
var srp = GraphicsSettings.defaultRenderPipeline;
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
["defaultRenderPipeline"] = srp != null ? srp.name : null,
|
||||
["defaultRenderPipelinePath"] = srp != null ? AssetDatabase.GetAssetPath(srp) : null,
|
||||
["usingScriptableRenderPipeline"] = srp != null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Graphics settings to change. Null/omitted fields are left unchanged.</summary>
|
||||
public class GraphicsSettingsInput : IStructuredCommandInput
|
||||
{
|
||||
[CliArg("renderPipelineAsset", "Reference (path / guid / globalId) to a RenderPipelineAsset to set as the default. Pass an empty reference ({}) to select the built-in pipeline; omit to leave unchanged.")]
|
||||
public ObjectRef RenderPipelineAsset { get; set; }
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5095c024b3a03e94996b73e4e42c4e4c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Pipeline.Commands;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Commands.ProjectSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Get/set the legacy Input Manager (CLI-202). The InputManager has no scripting API, so this
|
||||
/// reads/writes <c>ProjectSettings/InputManager.asset</c> via serialized properties:
|
||||
/// <c>get</c> lists the configured axis names; <c>set</c> tunes a named axis's
|
||||
/// sensitivity/gravity/dead values.
|
||||
///
|
||||
/// "Input (legacy / Input System where feasible)" per the ticket: the legacy InputManager is
|
||||
/// always present, so it is the portable target. Projects on the new Input System package store
|
||||
/// their config in a separate input-actions asset, which would be a follow-up.
|
||||
/// </summary>
|
||||
public static class InputSettingsCommands
|
||||
{
|
||||
const string Group = "input";
|
||||
const string AssetPath = "ProjectSettings/InputManager.asset";
|
||||
|
||||
[CliCommand("get_input_settings", "Read the legacy Input Manager axes (names and count).", MainThreadRequired = true)]
|
||||
public static ProjectSettingsResponse Get() => ProjectSettingsCommand.Get(Group, Read);
|
||||
|
||||
[CliCommand("set_input_settings", "Tune a legacy Input Manager axis (sensitivity/gravity/dead) by name. Requires confirm=true; use dry_run to preview. Not undoable via Ctrl+Z.", MainThreadRequired = true)]
|
||||
public static ProjectSettingsResponse Set(
|
||||
[CliArg("settings", "Axis change. 'axis' selects the axis by name; omitted numeric fields are left unchanged.")] InputAxisInput settings = null,
|
||||
[CliArg("confirm", "Apply the change. Without it the call is refused.")] bool confirm = false,
|
||||
[CliArg("dry_run", "Preview the change without applying it.")] bool dryRun = false)
|
||||
{
|
||||
if (settings == null)
|
||||
return ProjectSettingsResponse.Fail(Group, "No 'settings' object provided.");
|
||||
if (string.IsNullOrEmpty(settings.Axis))
|
||||
return ProjectSettingsResponse.Fail(Group, "'axis' is required.");
|
||||
|
||||
var so = ProjectSettingsAsset.Load(AssetPath);
|
||||
var axes = so.FindProperty("m_Axes");
|
||||
var index = FindAxisIndex(axes, settings.Axis);
|
||||
if (index < 0)
|
||||
return ProjectSettingsResponse.Fail(Group, $"No axis named '{settings.Axis}'.");
|
||||
|
||||
var axis = axes.GetArrayElementAtIndex(index);
|
||||
var changes = new List<string>();
|
||||
AddFloatChange(axis, "sensitivity", settings.Sensitivity, changes);
|
||||
AddFloatChange(axis, "gravity", settings.Gravity, changes);
|
||||
AddFloatChange(axis, "dead", settings.Dead, changes);
|
||||
|
||||
if (changes.Count == 0)
|
||||
return NoChanges();
|
||||
|
||||
var planText = $"Set input axis '{settings.Axis}': " + string.Join("; ", changes);
|
||||
|
||||
void Apply()
|
||||
{
|
||||
var obj = ProjectSettingsAsset.Load(AssetPath);
|
||||
var arr = obj.FindProperty("m_Axes");
|
||||
var idx = FindAxisIndex(arr, settings.Axis);
|
||||
if (idx < 0)
|
||||
return;
|
||||
|
||||
var ax = arr.GetArrayElementAtIndex(idx);
|
||||
SetFloat(ax, "sensitivity", settings.Sensitivity);
|
||||
SetFloat(ax, "gravity", settings.Gravity);
|
||||
SetFloat(ax, "dead", settings.Dead);
|
||||
obj.ApplyModifiedProperties();
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
return ProjectSettingsCommand.Apply(Group, confirm, dryRun, settings, () => planText, Apply, Read);
|
||||
}
|
||||
|
||||
static ProjectSettingsResponse NoChanges()
|
||||
{
|
||||
var response = ProjectSettingsCommand.Get(Group, Read);
|
||||
response.Message = "No changes specified; nothing to apply.";
|
||||
return response;
|
||||
}
|
||||
|
||||
static int FindAxisIndex(SerializedProperty axes, string name)
|
||||
{
|
||||
if (axes == null)
|
||||
return -1;
|
||||
for (var i = 0; i < axes.arraySize; i++)
|
||||
{
|
||||
var nameProp = axes.GetArrayElementAtIndex(i).FindPropertyRelative("m_Name");
|
||||
if (nameProp != null && nameProp.stringValue == name)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void AddFloatChange(SerializedProperty axis, string prop, float? newValue, List<string> changes)
|
||||
{
|
||||
if (!newValue.HasValue)
|
||||
return;
|
||||
var p = axis.FindPropertyRelative(prop);
|
||||
if (p == null)
|
||||
return;
|
||||
if (!Mathf.Approximately(p.floatValue, newValue.Value))
|
||||
changes.Add($"{prop} {p.floatValue} -> {newValue.Value}");
|
||||
}
|
||||
|
||||
static void SetFloat(SerializedProperty axis, string prop, float? value)
|
||||
{
|
||||
if (!value.HasValue)
|
||||
return;
|
||||
var p = axis.FindPropertyRelative(prop);
|
||||
if (p != null)
|
||||
p.floatValue = value.Value;
|
||||
}
|
||||
|
||||
static Dictionary<string, object> Read()
|
||||
{
|
||||
var so = ProjectSettingsAsset.Load(AssetPath);
|
||||
var axes = so.FindProperty("m_Axes");
|
||||
var names = new List<string>();
|
||||
if (axes != null)
|
||||
{
|
||||
for (var i = 0; i < axes.arraySize; i++)
|
||||
{
|
||||
var nameProp = axes.GetArrayElementAtIndex(i).FindPropertyRelative("m_Name");
|
||||
names.Add(nameProp != null ? nameProp.stringValue : null);
|
||||
}
|
||||
}
|
||||
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
["axisCount"] = names.Count,
|
||||
["axes"] = names
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Selects a legacy input axis by name and the numeric fields to change (omitted = unchanged).</summary>
|
||||
public class InputAxisInput : IStructuredCommandInput
|
||||
{
|
||||
[CliArg("axis", "Name of the axis to modify (e.g. 'Horizontal').", Required = true)]
|
||||
public string Axis { get; set; }
|
||||
|
||||
[CliArg("sensitivity", "New sensitivity.")]
|
||||
public float? Sensitivity { get; set; }
|
||||
|
||||
[CliArg("gravity", "New gravity.")]
|
||||
public float? Gravity { get; set; }
|
||||
|
||||
[CliArg("dead", "New dead-zone size.")]
|
||||
public float? Dead { get; set; }
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27a9903daaf869948aa6dd45e9735f6f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Pipeline.Commands;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Commands.ProjectSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Get/set a representative slice of 3D <see cref="Physics"/> settings (CLI-202): gravity (by
|
||||
/// component), default solver iterations, and the bounce threshold.
|
||||
/// </summary>
|
||||
public static class PhysicsSettingsCommands
|
||||
{
|
||||
const string Group = "physics";
|
||||
|
||||
[CliCommand("get_physics_settings", "Read Physics settings (gravity, solver iterations, bounce threshold).", MainThreadRequired = true)]
|
||||
public static ProjectSettingsResponse Get() => ProjectSettingsCommand.Get(Group, Read);
|
||||
|
||||
[CliCommand("set_physics_settings", "Change Physics settings. Requires confirm=true; use dry_run to preview. Not undoable via Ctrl+Z.", MainThreadRequired = true)]
|
||||
public static ProjectSettingsResponse Set(
|
||||
[CliArg("settings", "Fields to change; omitted fields are left unchanged.")] PhysicsSettingsInput settings = null,
|
||||
[CliArg("confirm", "Apply the change. Without it the call is refused.")] bool confirm = false,
|
||||
[CliArg("dry_run", "Preview the change without applying it.")] bool dryRun = false)
|
||||
{
|
||||
if (settings == null)
|
||||
return ProjectSettingsResponse.Fail(Group, "No 'settings' object provided.");
|
||||
|
||||
var changes = new List<string>();
|
||||
|
||||
var gravity = Physics.gravity;
|
||||
var newGravity = gravity;
|
||||
if (settings.GravityX.HasValue) newGravity.x = settings.GravityX.Value;
|
||||
if (settings.GravityY.HasValue) newGravity.y = settings.GravityY.Value;
|
||||
if (settings.GravityZ.HasValue) newGravity.z = settings.GravityZ.Value;
|
||||
if (newGravity != gravity)
|
||||
changes.Add($"gravity {gravity} -> {newGravity}");
|
||||
|
||||
if (settings.DefaultSolverIterations.HasValue && settings.DefaultSolverIterations.Value != Physics.defaultSolverIterations)
|
||||
changes.Add($"defaultSolverIterations {Physics.defaultSolverIterations} -> {settings.DefaultSolverIterations.Value}");
|
||||
if (settings.BounceThreshold.HasValue && !Mathf.Approximately(settings.BounceThreshold.Value, Physics.bounceThreshold))
|
||||
changes.Add($"bounceThreshold {Physics.bounceThreshold} -> {settings.BounceThreshold.Value}");
|
||||
|
||||
if (changes.Count == 0)
|
||||
return NoChanges();
|
||||
|
||||
var planText = "Set physics settings: " + string.Join("; ", changes);
|
||||
|
||||
void Apply()
|
||||
{
|
||||
Physics.gravity = newGravity;
|
||||
if (settings.DefaultSolverIterations.HasValue) Physics.defaultSolverIterations = settings.DefaultSolverIterations.Value;
|
||||
if (settings.BounceThreshold.HasValue) Physics.bounceThreshold = settings.BounceThreshold.Value;
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
return ProjectSettingsCommand.Apply(Group, confirm, dryRun, settings, () => planText, Apply, Read);
|
||||
}
|
||||
|
||||
static ProjectSettingsResponse NoChanges()
|
||||
{
|
||||
var response = ProjectSettingsCommand.Get(Group, Read);
|
||||
response.Message = "No changes specified; nothing to apply.";
|
||||
return response;
|
||||
}
|
||||
|
||||
static Dictionary<string, object> Read()
|
||||
{
|
||||
var g = Physics.gravity;
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
["gravityX"] = g.x,
|
||||
["gravityY"] = g.y,
|
||||
["gravityZ"] = g.z,
|
||||
["defaultSolverIterations"] = Physics.defaultSolverIterations,
|
||||
["bounceThreshold"] = Physics.bounceThreshold
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Physics settings to change. Null/omitted fields are left unchanged.</summary>
|
||||
public class PhysicsSettingsInput : IStructuredCommandInput
|
||||
{
|
||||
[CliArg("gravityX", "Gravity X component.")]
|
||||
public float? GravityX { get; set; }
|
||||
|
||||
[CliArg("gravityY", "Gravity Y component (e.g. -9.81).")]
|
||||
public float? GravityY { get; set; }
|
||||
|
||||
[CliArg("gravityZ", "Gravity Z component.")]
|
||||
public float? GravityZ { get; set; }
|
||||
|
||||
[CliArg("defaultSolverIterations", "Default solver iteration count.")]
|
||||
public int? DefaultSolverIterations { get; set; }
|
||||
|
||||
[CliArg("bounceThreshold", "Bounce threshold velocity.")]
|
||||
public float? BounceThreshold { get; set; }
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06de808693ce8c24891baa1026490d3d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Pipeline.Commands;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Commands.ProjectSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Get/set a representative slice of <see cref="PlayerSettings"/> (CLI-202): identity
|
||||
/// (company/product/version) and the two domain-reload-triggering toggles — scripting backend and
|
||||
/// API compatibility level. Scripting-backend / API-level changes are read and written against the
|
||||
/// active build target's <see cref="NamedBuildTarget"/>.
|
||||
///
|
||||
/// Icons and splash are intentionally out of this MVP set: they take <c>Texture2D</c> assets, not
|
||||
/// scalar JSON, so they need a dedicated asset-reference convention.
|
||||
/// </summary>
|
||||
public static class PlayerSettingsCommands
|
||||
{
|
||||
const string Group = "player";
|
||||
|
||||
[CliCommand("get_player_settings", "Read PlayerSettings (company/product/version, scripting backend, API level).", MainThreadRequired = true)]
|
||||
public static ProjectSettingsResponse Get() => ProjectSettingsCommand.Get(Group, Read);
|
||||
|
||||
[CliCommand("set_player_settings", "Change PlayerSettings. Requires confirm=true; use dry_run to preview. Not undoable via Ctrl+Z. Scripting backend / API level changes trigger a domain reload.", MainThreadRequired = true)]
|
||||
public static ProjectSettingsResponse Set(
|
||||
[CliArg("settings", "Fields to change; omitted fields are left unchanged.")] PlayerSettingsInput settings = null,
|
||||
[CliArg("confirm", "Apply the change. Without it the call is refused.")] bool confirm = false,
|
||||
[CliArg("dry_run", "Preview the change without applying it.")] bool dryRun = false)
|
||||
{
|
||||
if (settings == null)
|
||||
return ProjectSettingsResponse.Fail(Group, "No 'settings' object provided.");
|
||||
|
||||
var target = NamedBuildTarget.FromBuildTargetGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
|
||||
var changes = new List<string>();
|
||||
var reload = false;
|
||||
|
||||
if (settings.CompanyName != null && settings.CompanyName != PlayerSettings.companyName)
|
||||
changes.Add($"companyName '{PlayerSettings.companyName}' -> '{settings.CompanyName}'");
|
||||
if (settings.ProductName != null && settings.ProductName != PlayerSettings.productName)
|
||||
changes.Add($"productName '{PlayerSettings.productName}' -> '{settings.ProductName}'");
|
||||
if (settings.BundleVersion != null && settings.BundleVersion != PlayerSettings.bundleVersion)
|
||||
changes.Add($"bundleVersion '{PlayerSettings.bundleVersion}' -> '{settings.BundleVersion}'");
|
||||
|
||||
if (settings.ScriptingBackend.HasValue)
|
||||
{
|
||||
var current = PlayerSettings.GetScriptingBackend(target);
|
||||
if (settings.ScriptingBackend.Value != current)
|
||||
{
|
||||
changes.Add($"scriptingBackend {current} -> {settings.ScriptingBackend.Value} (domain reload)");
|
||||
reload = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.ApiCompatibilityLevel.HasValue)
|
||||
{
|
||||
var current = PlayerSettings.GetApiCompatibilityLevel(target);
|
||||
if (settings.ApiCompatibilityLevel.Value != current)
|
||||
{
|
||||
changes.Add($"apiCompatibilityLevel {current} -> {settings.ApiCompatibilityLevel.Value} (domain reload)");
|
||||
reload = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changes.Count == 0)
|
||||
return NoChanges();
|
||||
|
||||
var planText = "Set player settings: " + string.Join("; ", changes);
|
||||
|
||||
void Apply()
|
||||
{
|
||||
if (settings.CompanyName != null) PlayerSettings.companyName = settings.CompanyName;
|
||||
if (settings.ProductName != null) PlayerSettings.productName = settings.ProductName;
|
||||
if (settings.BundleVersion != null) PlayerSettings.bundleVersion = settings.BundleVersion;
|
||||
if (settings.ScriptingBackend.HasValue) PlayerSettings.SetScriptingBackend(target, settings.ScriptingBackend.Value);
|
||||
if (settings.ApiCompatibilityLevel.HasValue) PlayerSettings.SetApiCompatibilityLevel(target, settings.ApiCompatibilityLevel.Value);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
return ProjectSettingsCommand.Apply(Group, confirm, dryRun, settings, () => planText, Apply, Read, requiresDomainReload: reload);
|
||||
}
|
||||
|
||||
static ProjectSettingsResponse NoChanges()
|
||||
{
|
||||
var response = ProjectSettingsCommand.Get(Group, Read);
|
||||
response.Message = "No changes specified; nothing to apply.";
|
||||
return response;
|
||||
}
|
||||
|
||||
static Dictionary<string, object> Read()
|
||||
{
|
||||
var target = NamedBuildTarget.FromBuildTargetGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
["companyName"] = PlayerSettings.companyName,
|
||||
["productName"] = PlayerSettings.productName,
|
||||
["bundleVersion"] = PlayerSettings.bundleVersion,
|
||||
["buildTarget"] = target.TargetName,
|
||||
["scriptingBackend"] = PlayerSettings.GetScriptingBackend(target).ToString(),
|
||||
["apiCompatibilityLevel"] = PlayerSettings.GetApiCompatibilityLevel(target).ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Player settings to change. Null/omitted fields are left unchanged.</summary>
|
||||
public class PlayerSettingsInput : IStructuredCommandInput
|
||||
{
|
||||
[CliArg("companyName", "Company name.")]
|
||||
public string CompanyName { get; set; }
|
||||
|
||||
[CliArg("productName", "Product name.")]
|
||||
public string ProductName { get; set; }
|
||||
|
||||
[CliArg("bundleVersion", "Bundle/application version string.")]
|
||||
public string BundleVersion { get; set; }
|
||||
|
||||
[CliArg("scriptingBackend", "Scripting backend (e.g. Mono2x, IL2CPP). Triggers a domain reload.")]
|
||||
public ScriptingImplementation? ScriptingBackend { get; set; }
|
||||
|
||||
[CliArg("apiCompatibilityLevel", "API compatibility level (e.g. NET_Standard_2_0, NET_Unity_4_8). Triggers a domain reload.")]
|
||||
public ApiCompatibilityLevel? ApiCompatibilityLevel { get; set; }
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4fcf409834ac0bf4bb18a7b7fadad8dd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Commands.ProjectSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads a <see cref="SerializedObject"/> for a singleton settings asset under
|
||||
/// <c>ProjectSettings/</c> (e.g. <c>InputManager.asset</c>, <c>TagManager.asset</c>,
|
||||
/// <c>AudioManager.asset</c>) that has no first-class scripting API. Used by the settings-group
|
||||
/// commands that must read/write those managers via serialized properties.
|
||||
/// </summary>
|
||||
internal static class ProjectSettingsAsset
|
||||
{
|
||||
public static SerializedObject Load(string assetPath)
|
||||
{
|
||||
var objects = AssetDatabase.LoadAllAssetsAtPath(assetPath);
|
||||
if (objects == null || objects.Length == 0 || objects[0] == null)
|
||||
throw new InvalidOperationException($"Could not load settings asset at '{assetPath}'.");
|
||||
|
||||
return new SerializedObject(objects[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5646d5f43f10f344ab42eecab65f08c8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Commands.ProjectSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Shared plumbing the per-group project-settings commands (CLI-202) wrap, so each group's
|
||||
/// <c>get</c>/<c>set</c> handler stays thin and the policy is applied uniformly:
|
||||
/// <list type="bullet">
|
||||
/// <item><description><see cref="Get"/> — read a group's values into a <see cref="ProjectSettingsResponse"/>.</description></item>
|
||||
/// <item><description><see cref="Apply"/> — gate a write with the <c>confirm</c>/<c>dry_run</c> convention and
|
||||
/// apply it (project-settings writes are not part of Unity's Undo), then re-read the group so the response
|
||||
/// reflects the post-change state, and surface domain-reload / reimport signals.</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// Group handlers supply two callbacks: <c>readValues</c> (snapshot the group's current values)
|
||||
/// and, for writes, <c>apply</c> (perform the mutation) plus a <c>plan</c> describing the intended
|
||||
/// effect. All run on the main thread (these commands are <c>MainThreadRequired = true</c>).
|
||||
/// </summary>
|
||||
public static class ProjectSettingsCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Read-only path: build a success response carrying the group's current values.
|
||||
/// </summary>
|
||||
public static ProjectSettingsResponse Get(string group, Func<Dictionary<string, object>> readValues)
|
||||
{
|
||||
if (readValues == null)
|
||||
throw new ArgumentNullException(nameof(readValues));
|
||||
|
||||
if (!TryReadValues(readValues, out var values, out var readError))
|
||||
return ProjectSettingsResponse.Fail(group, $"Failed to read {group} settings: {readError}");
|
||||
|
||||
return new ProjectSettingsResponse
|
||||
{
|
||||
Success = true,
|
||||
Group = group,
|
||||
Values = values,
|
||||
Message = $"Read {group} settings."
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write path: gate the change with the <c>confirm</c>/<c>dry_run</c> convention and apply it
|
||||
/// (project-settings writes are not undoable via Ctrl+Z), then re-read the
|
||||
/// group. <paramref name="requiresDomainReload"/> / <paramref name="requiresReimport"/> declare
|
||||
/// side effects of this specific change (Unity defers them, so the response returns first);
|
||||
/// they are reported for an applied change and for a dry-run preview alike, and suppressed when
|
||||
/// the operation is refused or fails.
|
||||
/// </summary>
|
||||
public static ProjectSettingsResponse Apply(
|
||||
string group,
|
||||
bool confirm,
|
||||
bool dryRun,
|
||||
object auditParams,
|
||||
Func<string> plan,
|
||||
Action apply,
|
||||
Func<Dictionary<string, object>> readValues,
|
||||
bool requiresDomainReload = false,
|
||||
bool requiresReimport = false)
|
||||
{
|
||||
if (plan == null)
|
||||
throw new ArgumentNullException(nameof(plan));
|
||||
if (apply == null)
|
||||
throw new ArgumentNullException(nameof(apply));
|
||||
if (readValues == null)
|
||||
throw new ArgumentNullException(nameof(readValues));
|
||||
|
||||
string planText;
|
||||
try
|
||||
{
|
||||
planText = plan();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ProjectSettingsResponse.Fail(group, $"Failed to compute the operation preview: {ex.Message}");
|
||||
}
|
||||
|
||||
// Inline confirm / dry_run gate + Undo grouping (the codebase-wide mutating convention).
|
||||
var response = new ProjectSettingsResponse { Group = group };
|
||||
if (dryRun)
|
||||
{
|
||||
response.Success = true;
|
||||
response.DryRun = true;
|
||||
response.Applied = false;
|
||||
response.Message = $"Dry run — no changes made. Intended effect: {planText}";
|
||||
}
|
||||
else if (!confirm)
|
||||
{
|
||||
response.Success = false;
|
||||
response.Applied = false;
|
||||
response.Message =
|
||||
"Refused: this operation mutates the project. Re-run with confirm=true to apply, " +
|
||||
"or dry_run=true to preview the change.";
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
apply();
|
||||
response.Success = true;
|
||||
response.Applied = true;
|
||||
response.Message = planText;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
response.Success = false;
|
||||
response.Applied = false;
|
||||
response.Message = $"Operation failed: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
// Side-effect signals are only meaningful when the operation went through (applied) or was
|
||||
// previewed (dry run); suppress them for a refusal or failure.
|
||||
response.RequiresDomainReload = response.Success && requiresDomainReload;
|
||||
response.RequiresReimport = response.Success && requiresReimport;
|
||||
|
||||
// Re-read so the caller sees the resulting (or, for a dry run / refusal, the unchanged)
|
||||
// state. A read failure must not mask the write outcome, so it is non-fatal here.
|
||||
if (TryReadValues(readValues, out var values, out var readError))
|
||||
response.Values = values;
|
||||
else
|
||||
Debug.LogWarning($"[pipeline] Read-back of {group} settings failed: {readError}");
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private static bool TryReadValues(Func<Dictionary<string, object>> readValues,
|
||||
out Dictionary<string, object> values, out string error)
|
||||
{
|
||||
try
|
||||
{
|
||||
values = readValues() ?? new Dictionary<string, object>();
|
||||
error = null;
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
values = null;
|
||||
error = ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00e8d8eb6857c9d40babc72bb711b9c4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Commands.ProjectSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Shared response for the project-settings get/set commands (CLI-202). One type backs every
|
||||
/// settings group (PlayerSettings, Quality, Graphics, Physics, Time, Input, Tags & Layers,
|
||||
/// Audio) so agents get a uniform shape across them.
|
||||
/// </para>
|
||||
///
|
||||
/// <para><see cref="Values"/> carries the current settings for the group as a flat
|
||||
/// name → value map — returned by a <c>get</c>, and re-read after a <c>set</c> so the caller can
|
||||
/// confirm the change without a second round-trip.</para>
|
||||
///
|
||||
/// <para>Writes follow the shared <c>confirm</c>/<c>dry_run</c> convention, so
|
||||
/// <see cref="Applied"/> / <see cref="DryRun"/> mirror its semantics: a change only mutates when
|
||||
/// it actually ran (not a preview, not refused). <see cref="RequiresDomainReload"/> and
|
||||
/// <see cref="RequiresReimport"/> flag side effects an agent must wait out — e.g. switching the
|
||||
/// scripting backend or API level triggers a domain reload; some graphics/texture changes trigger
|
||||
/// an asset reimport. They describe the operation, so a <c>dry_run</c> still reports them.</para>
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ProjectSettingsResponse
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>Settings group this response is for (e.g. "player", "quality", "time").</summary>
|
||||
[JsonProperty("group")]
|
||||
public string Group { get; set; }
|
||||
|
||||
/// <summary>True only when a write actually mutated the project (false for get/dry-run/refused/failed).</summary>
|
||||
[JsonProperty("applied")]
|
||||
public bool Applied { get; set; }
|
||||
|
||||
/// <summary>True when a write was a preview only (no mutation).</summary>
|
||||
[JsonProperty("dryRun")]
|
||||
public bool DryRun { get; set; }
|
||||
|
||||
/// <summary>The operation triggers a domain reload; the agent should wait for the editor to settle.</summary>
|
||||
[JsonProperty("requiresDomainReload")]
|
||||
public bool RequiresDomainReload { get; set; }
|
||||
|
||||
/// <summary>The operation triggers an asset reimport; the agent should wait for it to finish.</summary>
|
||||
[JsonProperty("requiresReimport")]
|
||||
public bool RequiresReimport { get; set; }
|
||||
|
||||
/// <summary>Current settings for the group as a name → value map.</summary>
|
||||
[JsonProperty("values")]
|
||||
public Dictionary<string, object> Values { get; set; }
|
||||
|
||||
[JsonProperty("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
public static ProjectSettingsResponse Fail(string group, string message) => new ProjectSettingsResponse
|
||||
{
|
||||
Success = false,
|
||||
Group = group,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f8c1356c54a05248b34aff8e0490c0c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Pipeline.Commands;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Commands.ProjectSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Get/set <see cref="QualitySettings"/> (CLI-202): the active quality level plus a couple of
|
||||
/// representative per-level toggles (vSync, anti-aliasing). Level changes apply expensive changes
|
||||
/// so the switch takes full effect.
|
||||
/// </summary>
|
||||
public static class QualitySettingsCommands
|
||||
{
|
||||
const string Group = "quality";
|
||||
|
||||
[CliCommand("get_quality_settings", "Read QualitySettings (current level, level names, vSync, anti-aliasing).", MainThreadRequired = true)]
|
||||
public static ProjectSettingsResponse Get() => ProjectSettingsCommand.Get(Group, Read);
|
||||
|
||||
[CliCommand("set_quality_settings", "Change QualitySettings. Requires confirm=true; use dry_run to preview. Not undoable via Ctrl+Z.", MainThreadRequired = true)]
|
||||
public static ProjectSettingsResponse Set(
|
||||
[CliArg("settings", "Fields to change; omitted fields are left unchanged.")] QualitySettingsInput settings = null,
|
||||
[CliArg("confirm", "Apply the change. Without it the call is refused.")] bool confirm = false,
|
||||
[CliArg("dry_run", "Preview the change without applying it.")] bool dryRun = false)
|
||||
{
|
||||
if (settings == null)
|
||||
return ProjectSettingsResponse.Fail(Group, "No 'settings' object provided.");
|
||||
|
||||
var names = QualitySettings.names;
|
||||
var changes = new List<string>();
|
||||
|
||||
if (settings.Level.HasValue)
|
||||
{
|
||||
if (settings.Level.Value < 0 || settings.Level.Value >= names.Length)
|
||||
return ProjectSettingsResponse.Fail(Group, $"level {settings.Level.Value} out of range (0..{names.Length - 1}).");
|
||||
if (settings.Level.Value != QualitySettings.GetQualityLevel())
|
||||
changes.Add($"level {QualitySettings.GetQualityLevel()} -> {settings.Level.Value}");
|
||||
}
|
||||
|
||||
if (settings.VSyncCount.HasValue && settings.VSyncCount.Value != QualitySettings.vSyncCount)
|
||||
changes.Add($"vSyncCount {QualitySettings.vSyncCount} -> {settings.VSyncCount.Value}");
|
||||
if (settings.AntiAliasing.HasValue && settings.AntiAliasing.Value != QualitySettings.antiAliasing)
|
||||
changes.Add($"antiAliasing {QualitySettings.antiAliasing} -> {settings.AntiAliasing.Value}");
|
||||
|
||||
if (changes.Count == 0)
|
||||
return NoChanges();
|
||||
|
||||
var planText = "Set quality settings: " + string.Join("; ", changes);
|
||||
|
||||
void Apply()
|
||||
{
|
||||
if (settings.Level.HasValue) QualitySettings.SetQualityLevel(settings.Level.Value, true);
|
||||
if (settings.VSyncCount.HasValue) QualitySettings.vSyncCount = settings.VSyncCount.Value;
|
||||
if (settings.AntiAliasing.HasValue) QualitySettings.antiAliasing = settings.AntiAliasing.Value;
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
return ProjectSettingsCommand.Apply(Group, confirm, dryRun, settings, () => planText, Apply, Read);
|
||||
}
|
||||
|
||||
static ProjectSettingsResponse NoChanges()
|
||||
{
|
||||
var response = ProjectSettingsCommand.Get(Group, Read);
|
||||
response.Message = "No changes specified; nothing to apply.";
|
||||
return response;
|
||||
}
|
||||
|
||||
static Dictionary<string, object> Read()
|
||||
{
|
||||
var names = QualitySettings.names;
|
||||
var level = QualitySettings.GetQualityLevel();
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
["level"] = level,
|
||||
["levelName"] = (level >= 0 && level < names.Length) ? names[level] : null,
|
||||
["levelNames"] = names,
|
||||
["vSyncCount"] = QualitySettings.vSyncCount,
|
||||
["antiAliasing"] = QualitySettings.antiAliasing
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Quality settings to change. Null/omitted fields are left unchanged.</summary>
|
||||
public class QualitySettingsInput : IStructuredCommandInput
|
||||
{
|
||||
[CliArg("level", "Quality level index (see levelNames from get_quality_settings).")]
|
||||
public int? Level { get; set; }
|
||||
|
||||
[CliArg("vSyncCount", "VSync count (0 = off, 1, 2).")]
|
||||
public int? VSyncCount { get; set; }
|
||||
|
||||
[CliArg("antiAliasing", "MSAA sample count (0, 2, 4, 8).")]
|
||||
public int? AntiAliasing { get; set; }
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b4bc1a976dcac514796a70fd1b91a2be
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Pipeline.Commands;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Commands.ProjectSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Get/set project tags and layers (CLI-202). Tags use the
|
||||
/// <see cref="InternalEditorUtility"/> add/remove API; user layers (indices 8..31; 0..7 are
|
||||
/// reserved) are assigned via the TagManager's serialized <c>layers</c> array.
|
||||
/// </summary>
|
||||
public static class TagsLayersCommands
|
||||
{
|
||||
const string Group = "tags_layers";
|
||||
const string TagManagerPath = "ProjectSettings/TagManager.asset";
|
||||
const int FirstUserLayer = 8;
|
||||
const int LastUserLayer = 31;
|
||||
|
||||
[CliCommand("get_tags_layers", "Read the project's tags and (named) layers.", MainThreadRequired = true)]
|
||||
public static ProjectSettingsResponse Get() => ProjectSettingsCommand.Get(Group, Read);
|
||||
|
||||
[CliCommand("set_tags_layers", "Add/remove tags and assign user layer names (index 8-31). Requires confirm=true; use dry_run to preview. Not undoable via Ctrl+Z.", MainThreadRequired = true)]
|
||||
public static ProjectSettingsResponse Set(
|
||||
[CliArg("settings", "Tag/layer changes to make.")] TagsLayersInput settings = null,
|
||||
[CliArg("confirm", "Apply the change. Without it the call is refused.")] bool confirm = false,
|
||||
[CliArg("dry_run", "Preview the change without applying it.")] bool dryRun = false)
|
||||
{
|
||||
if (settings == null)
|
||||
return ProjectSettingsResponse.Fail(Group, "No 'settings' object provided.");
|
||||
|
||||
if (settings.SetLayers != null)
|
||||
{
|
||||
foreach (var layer in settings.SetLayers)
|
||||
{
|
||||
if (layer == null)
|
||||
continue;
|
||||
if (layer.Index < FirstUserLayer || layer.Index > LastUserLayer)
|
||||
return ProjectSettingsResponse.Fail(Group,
|
||||
$"layer index {layer.Index} is reserved or out of range; user layers are {FirstUserLayer}..{LastUserLayer}.");
|
||||
}
|
||||
}
|
||||
|
||||
var existingTags = new HashSet<string>(InternalEditorUtility.tags);
|
||||
var existingLayers = InternalEditorUtility.layers;
|
||||
var changes = new List<string>();
|
||||
|
||||
if (settings.AddTags != null)
|
||||
foreach (var tag in settings.AddTags)
|
||||
if (!string.IsNullOrEmpty(tag) && !existingTags.Contains(tag))
|
||||
changes.Add($"add tag '{tag}'");
|
||||
|
||||
if (settings.RemoveTags != null)
|
||||
foreach (var tag in settings.RemoveTags)
|
||||
if (!string.IsNullOrEmpty(tag) && existingTags.Contains(tag))
|
||||
changes.Add($"remove tag '{tag}'");
|
||||
|
||||
if (settings.SetLayers != null)
|
||||
foreach (var layer in settings.SetLayers)
|
||||
if (layer != null)
|
||||
{
|
||||
var desired = layer.Name ?? string.Empty;
|
||||
var current = (existingLayers != null && layer.Index >= 0 && layer.Index < existingLayers.Length)
|
||||
? (existingLayers[layer.Index] ?? string.Empty)
|
||||
: string.Empty;
|
||||
if (current != desired)
|
||||
changes.Add($"layer[{layer.Index}] = '{desired}'");
|
||||
}
|
||||
|
||||
if (changes.Count == 0)
|
||||
return NoChanges();
|
||||
|
||||
var planText = "Set tags/layers: " + string.Join("; ", changes);
|
||||
|
||||
void Apply()
|
||||
{
|
||||
// Remove before add so a remove+re-add in one call behaves predictably.
|
||||
if (settings.RemoveTags != null)
|
||||
foreach (var tag in settings.RemoveTags)
|
||||
if (!string.IsNullOrEmpty(tag))
|
||||
InternalEditorUtility.RemoveTag(tag);
|
||||
|
||||
if (settings.AddTags != null)
|
||||
foreach (var tag in settings.AddTags)
|
||||
if (!string.IsNullOrEmpty(tag))
|
||||
InternalEditorUtility.AddTag(tag);
|
||||
|
||||
if (settings.SetLayers != null && settings.SetLayers.Length > 0)
|
||||
{
|
||||
var so = ProjectSettingsAsset.Load(TagManagerPath);
|
||||
var layersProp = so.FindProperty("layers");
|
||||
foreach (var layer in settings.SetLayers)
|
||||
{
|
||||
if (layer == null)
|
||||
continue;
|
||||
layersProp.GetArrayElementAtIndex(layer.Index).stringValue = layer.Name ?? string.Empty;
|
||||
}
|
||||
so.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
return ProjectSettingsCommand.Apply(Group, confirm, dryRun, settings, () => planText, Apply, Read);
|
||||
}
|
||||
|
||||
static ProjectSettingsResponse NoChanges()
|
||||
{
|
||||
var response = ProjectSettingsCommand.Get(Group, Read);
|
||||
response.Message = "No changes specified; nothing to apply.";
|
||||
return response;
|
||||
}
|
||||
|
||||
static Dictionary<string, object> Read() => new Dictionary<string, object>
|
||||
{
|
||||
["tags"] = InternalEditorUtility.tags,
|
||||
["layers"] = InternalEditorUtility.layers
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Tag/layer changes. All fields optional; omitted ones make no change.</summary>
|
||||
public class TagsLayersInput : IStructuredCommandInput
|
||||
{
|
||||
[CliArg("addTags", "Tag names to add.")]
|
||||
public string[] AddTags { get; set; }
|
||||
|
||||
[CliArg("removeTags", "Tag names to remove.")]
|
||||
public string[] RemoveTags { get; set; }
|
||||
|
||||
[CliArg("setLayers", "User layer assignments (index 8-31).")]
|
||||
public LayerAssignment[] SetLayers { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>Assigns a name to a single user layer slot.</summary>
|
||||
public class LayerAssignment : IStructuredCommandInput
|
||||
{
|
||||
[CliArg("index", "Layer index (8-31 for user layers).", Required = true)]
|
||||
public int Index { get; set; }
|
||||
|
||||
[CliArg("name", "Layer name (empty string clears the slot).", Required = true)]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c6cb0f1d0a407f498fc141814f2d329
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Pipeline.Commands;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Pipeline.Editor.Commands.ProjectSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Get/set the project Time settings (CLI-202): fixed timestep, maximum allowed timestep, and the
|
||||
/// time scale.
|
||||
/// </summary>
|
||||
public static class TimeSettingsCommands
|
||||
{
|
||||
const string Group = "time";
|
||||
|
||||
[CliCommand("get_time_settings", "Read Time settings (fixedDeltaTime, maximumDeltaTime, timeScale).", MainThreadRequired = true)]
|
||||
public static ProjectSettingsResponse Get() => ProjectSettingsCommand.Get(Group, Read);
|
||||
|
||||
[CliCommand("set_time_settings", "Change Time settings. Requires confirm=true; use dry_run to preview. Not undoable via Ctrl+Z.", MainThreadRequired = true)]
|
||||
public static ProjectSettingsResponse Set(
|
||||
[CliArg("settings", "Fields to change; omitted fields are left unchanged.")] TimeSettingsInput settings = null,
|
||||
[CliArg("confirm", "Apply the change. Without it the call is refused.")] bool confirm = false,
|
||||
[CliArg("dry_run", "Preview the change without applying it.")] bool dryRun = false)
|
||||
{
|
||||
if (settings == null)
|
||||
return ProjectSettingsResponse.Fail(Group, "No 'settings' object provided.");
|
||||
|
||||
var changes = new List<string>();
|
||||
|
||||
if (settings.FixedDeltaTime.HasValue && !Mathf.Approximately(settings.FixedDeltaTime.Value, Time.fixedDeltaTime))
|
||||
changes.Add($"fixedDeltaTime {Time.fixedDeltaTime} -> {settings.FixedDeltaTime.Value}");
|
||||
if (settings.MaximumDeltaTime.HasValue && !Mathf.Approximately(settings.MaximumDeltaTime.Value, Time.maximumDeltaTime))
|
||||
changes.Add($"maximumDeltaTime {Time.maximumDeltaTime} -> {settings.MaximumDeltaTime.Value}");
|
||||
if (settings.TimeScale.HasValue && !Mathf.Approximately(settings.TimeScale.Value, Time.timeScale))
|
||||
changes.Add($"timeScale {Time.timeScale} -> {settings.TimeScale.Value}");
|
||||
|
||||
if (changes.Count == 0)
|
||||
return NoChanges();
|
||||
|
||||
var planText = "Set time settings: " + string.Join("; ", changes);
|
||||
|
||||
void Apply()
|
||||
{
|
||||
if (settings.FixedDeltaTime.HasValue) Time.fixedDeltaTime = settings.FixedDeltaTime.Value;
|
||||
if (settings.MaximumDeltaTime.HasValue) Time.maximumDeltaTime = settings.MaximumDeltaTime.Value;
|
||||
if (settings.TimeScale.HasValue) Time.timeScale = settings.TimeScale.Value;
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
return ProjectSettingsCommand.Apply(Group, confirm, dryRun, settings, () => planText, Apply, Read);
|
||||
}
|
||||
|
||||
static ProjectSettingsResponse NoChanges()
|
||||
{
|
||||
var response = ProjectSettingsCommand.Get(Group, Read);
|
||||
response.Message = "No changes specified; nothing to apply.";
|
||||
return response;
|
||||
}
|
||||
|
||||
static Dictionary<string, object> Read() => new Dictionary<string, object>
|
||||
{
|
||||
["fixedDeltaTime"] = Time.fixedDeltaTime,
|
||||
["maximumDeltaTime"] = Time.maximumDeltaTime,
|
||||
["timeScale"] = Time.timeScale
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Time settings to change. Null/omitted fields are left unchanged.</summary>
|
||||
public class TimeSettingsInput : IStructuredCommandInput
|
||||
{
|
||||
[CliArg("fixedDeltaTime", "Fixed timestep in seconds (e.g. 0.02).")]
|
||||
public float? FixedDeltaTime { get; set; }
|
||||
|
||||
[CliArg("maximumDeltaTime", "Maximum allowed timestep in seconds.")]
|
||||
public float? MaximumDeltaTime { get; set; }
|
||||
|
||||
[CliArg("timeScale", "Time scale (1 = real-time).")]
|
||||
public float? TimeScale { get; set; }
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5973463d405c064687fe56fee5d011a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user