SHA256
Compare commits
8
Commits
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
63f38883c8
|
||
|
|
24ec6aa1d0
|
||
|
|
44caf92ada | ||
|
|
b3e50cd50e
|
||
|
|
1d4b1cdd46
|
||
|
|
98cf00578d | ||
|
|
b6f1c0e4d2 | ||
|
|
5e9c0272dd
|
No files matched your search
@@ -366,7 +366,7 @@ internal class RenderManager : MonoBehaviour
|
||||
{
|
||||
if (Configuration.AudioConfig.RenderAudio && Configuration.VideoConfig.Codec is Codec.VP9 or Codec.AV1 or Codec.VVC)
|
||||
{
|
||||
Configuration.AudioConfig.AudioSampleRate = 48000;
|
||||
if (Configuration.AudioConfig.AudioSampleRate == 44100) Configuration.AudioConfig.AudioSampleRate = 48000;
|
||||
}
|
||||
|
||||
for (; countdown > 0; countdown--)
|
||||
@@ -775,7 +775,7 @@ internal class RenderManager : MonoBehaviour
|
||||
_frameReader?.Dispose();
|
||||
AudioMixer?.Reset(); AudioMixer = null;
|
||||
SyncCalculator.Reset();
|
||||
|
||||
|
||||
RenderContext.Reset();
|
||||
|
||||
ADOBase.loader.LoadSceneWithTransition(WipeDirection.StartsFromRight, "scnEditor");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using ADOFAIRenderer.Util.FFmpeg;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ADOFAIRenderer.Data;
|
||||
|
||||
@@ -27,6 +28,10 @@ public class AdvancedConfig
|
||||
public bool ProcessAudio { get; set; } = true;
|
||||
public bool NonTile { get; set; } = false;
|
||||
public string FileExtension { get; set; } = "mkv";
|
||||
|
||||
public Color? firePlanetColor = null;
|
||||
public Color? icePlanetColor = null;
|
||||
public Color? windPlanetColor = null;
|
||||
}
|
||||
|
||||
public class VideoEncodingConfig
|
||||
@@ -44,7 +49,7 @@ public class VideoEncodingConfig
|
||||
|
||||
public class AudioEncodingConfig
|
||||
{
|
||||
private int _audioSampleRate = 44100;
|
||||
private int _audioSampleRate = 48000;
|
||||
private int _audioChannels = 2;
|
||||
private int _audioBitrate = 256000;
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using ADOFAIRenderer.Core;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ADOFAIRenderer.Patch.Game;
|
||||
|
||||
[HarmonyPatch(typeof(PlanetRenderer))]
|
||||
public static class PlanetRendererPatch
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch("SetColor")]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static void OverrideColor(PlanetRenderer __instance, ref PlanetColor planetColor)
|
||||
{
|
||||
if (RenderManager.Instance.stage == RenderStage.None) return;
|
||||
|
||||
var advConf = RenderManager.Instance.Configuration.AdvancedConfig;
|
||||
ApplyColor(ref planetColor, scrController.instance.planetRed, advConf.firePlanetColor);
|
||||
ApplyColor(ref planetColor, scrController.instance.planetBlue, advConf.icePlanetColor);
|
||||
ApplyColor(ref planetColor, scrController.instance.planetGreen, advConf.windPlanetColor);
|
||||
|
||||
return;
|
||||
|
||||
void ApplyColor(ref PlanetColor planetColor, scrPlanet targetPlanet, Color? customColor)
|
||||
{
|
||||
if (__instance == targetPlanet.planetRenderer && customColor != null)
|
||||
planetColor = new PlanetColor(customColor.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -9,7 +9,7 @@ using MelonLoader.Utils;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
[assembly: MelonInfo(typeof(RenderMod), "ADOFAI Renderer", "0.3.3-BETA", "ADOFAI Editor Forum")]
|
||||
[assembly: MelonInfo(typeof(RenderMod), "ADOFAI Renderer", "0.2.0-INDEV", "ADOFAI Editor Forum")]
|
||||
[assembly: MelonGame("7th Beat Games", "A Dance of Fire and Ice")]
|
||||
|
||||
namespace ADOFAIRenderer;
|
||||
@@ -18,7 +18,7 @@ internal class RenderMod : MelonMod
|
||||
{
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
public static bool IsFeatureDisabled = false;
|
||||
public static DateTime ExpirationTime = new(2026, 09, 21, 0, 0, 0, DateTimeKind.Utc);
|
||||
public static DateTime ExpirationTime = new(2026, 11, 21, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+23
-1
@@ -80,7 +80,29 @@ internal class RendererUIManager : MonoBehaviour
|
||||
MiddleClickToDefault = true,
|
||||
SliderSensitivity = 1.0f
|
||||
},
|
||||
theme: O5Theme.Dark,
|
||||
theme: O5Theme.Dark with
|
||||
{
|
||||
PanelBG = new Color(0.067f, 0.067f, 0.067f, 1f),
|
||||
TopBar = new Color(0f, 0f, 0f, 0.6f),
|
||||
MenuBG = new Color(0f, 0f, 0f, 0.6f),
|
||||
MenuHover = new Color(1.000f, 0.294f, 0.079f, .6f),
|
||||
ObjectBG = new Color(0f, 0f, 0f, 1f),
|
||||
ObjectButton = new Color(0f, 0f, 0f, 1f),
|
||||
ObjectActive = new Color(1.000f, 0.294f, 0.079f, 1f),
|
||||
ObjectActiveBright = new Color(0.812f, 0.827f, 1.000f, 1f),
|
||||
ControlOutlineIdle = new Color(1f, 1f, 1f, 0.4f),
|
||||
ButtonHover = new Color(0.133f, 0.133f, 0.133f, 1.0f),
|
||||
ControlOutlineHover = new Color(1f, 1f, 1f, 0.4f),
|
||||
CardHeader = new Color(0.157f, 0f, 0f, 1f),
|
||||
CardPanel = new Color(0, 0, 0, 0f),
|
||||
SoftRed = new Color(1.000f, 0.576f, 0.590f, 1f),
|
||||
MathOk = new Color(0.408f, 0.827f, 0.569f, 1f),
|
||||
MathWarn = new Color(0.964f, 0.647f, 0.315f, 1f),
|
||||
MathErr = new Color(1.000f, 0.576f, 0.590f, 1f),
|
||||
CornerRadius = 12, OutlineWidth = 2, ControlHeight = 40, FontSizeBody = 18, FontSizeH1 = 48,
|
||||
Outline = new Color(1f, 1f, 1f, 0.2f),
|
||||
TextDimAlpha = 1f
|
||||
},
|
||||
fonts: new RendererFontProvider()
|
||||
);
|
||||
|
||||
|
||||
+250
-228
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ADOFAIRenderer.API;
|
||||
using ADOFAIRenderer.Core;
|
||||
using ADOFAIRenderer.Data;
|
||||
@@ -21,11 +22,11 @@ internal static class RenderConfigWindow
|
||||
private static O5Window? _configWindow;
|
||||
|
||||
// Controls cache for validation & sync
|
||||
private static O5InputField? _renderFpsInput;
|
||||
private static O5InputField? _videoFpsInput;
|
||||
private static O5InputField? _renderWidthInput;
|
||||
private static O5InputField? _renderHeightInput;
|
||||
private static O5InputField? _delayAfterEndInput;
|
||||
private static O5Slider? _renderFpsSlider;
|
||||
private static O5Slider? _videoFPSSlider;
|
||||
private static O5Slider? _renderWidthSlider;
|
||||
private static O5Slider? _renderHeightSlider;
|
||||
private static O5Slider? _delayAfterEndSlider;
|
||||
private static O5Toggle? _showJudgementToggle;
|
||||
|
||||
private static O5Dropdown<Codec>? _codecDropdown;
|
||||
@@ -33,13 +34,13 @@ internal static class RenderConfigWindow
|
||||
private static O5InputField? _bitrateInput;
|
||||
private static O5InputField? _maxBitrateInput;
|
||||
private static O5InputField? _qpInput;
|
||||
private static O5InputField? _keyframeIntervalInput;
|
||||
private static O5Slider? _keyframeIntervalSlider;
|
||||
private static O5Toggle? _forceSoftwareToggle;
|
||||
|
||||
private static O5Toggle? _renderAudioToggle;
|
||||
private static O5Dropdown<int>? _sampleRateDropdown;
|
||||
private static O5Dropdown<int>? _channelsDropdown;
|
||||
private static O5InputField? _audioBitrateInput;
|
||||
private static O5Slider? _audioBitrateSlider;
|
||||
|
||||
private static O5InputField? _outputDirInput;
|
||||
|
||||
@@ -69,7 +70,8 @@ internal static class RenderConfigWindow
|
||||
|
||||
BuildUI(_configWindow.Content);
|
||||
}
|
||||
|
||||
|
||||
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
|
||||
var config = RenderManager.Instance.Configuration ??= new RenderConfig();
|
||||
ApplyConfigToUI(config);
|
||||
|
||||
@@ -83,64 +85,88 @@ internal static class RenderConfigWindow
|
||||
var (viewport, scrollContent, scroller) =
|
||||
O5Factory.ScrollView(ctx, content, spacing: 10f, padding: 8f);
|
||||
|
||||
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
|
||||
var config = RenderManager.Instance.Configuration ??= new RenderConfig();
|
||||
EnsureDefaultConfig(config);
|
||||
|
||||
// 1. RenderFPS & VideoFPS
|
||||
var (row1Left, row1Right) = CreateTwoColumnRow(scrollContent);
|
||||
_renderFpsInput = CreateInputField(
|
||||
ctx, row1Left,
|
||||
LocalizationManager.Get("ui.render_config.render_fps"),
|
||||
"60", config.RenderFPS.ToString("0.##"),
|
||||
"input_render_fps",
|
||||
TMPro.TMP_InputField.CharacterValidation.Decimal,
|
||||
val => { if (double.TryParse(val, out var d) && d > 0) config.RenderFPS = d; }
|
||||
double[] renderFpsSnap = { 23.976, 24, 29.97, 30, 59.94, 60, 144,
|
||||
120, 240, 360, 480, 600, 720, // +120
|
||||
288, 576, 864, 1152, 1440, 1728, // +288
|
||||
3000 // MAX
|
||||
};
|
||||
double[] videoFpsSnap = { 23.976, 24, 29.97, 30, 59.94, 60, 72,
|
||||
120, 180, 240, 300, 360, 420, 480, 540, // +60
|
||||
144, 216, 288, 432, 504, 576, // +72
|
||||
600 // MAX
|
||||
};
|
||||
|
||||
double[] audioBitrateSnap = { 64, 96, 128, 192, 256, 320, 384 };
|
||||
|
||||
// 1-3. Top section: 좌/우 컨테이너 1개씩으로 3행을 쌓는다 (중앙선 1개)
|
||||
var (topLeft, topRight) = CreateTwoColumnPanel(scrollContent, spacing: 8f, columnSpacing: 10f);
|
||||
|
||||
_renderFpsSlider = O5Factory.Slider(
|
||||
ctx: ctx, parent: CreateFieldSlot(topLeft),
|
||||
defaultValue: 60, min: 0.01, max: 3000, value: 60,
|
||||
format: "F3", clampMode: ClampMode.All,
|
||||
|
||||
sliderFilter: value => renderFpsSnap.OrderBy(n => Math.Abs(n - value)).First(),
|
||||
inputFilter: null, onComplete: null, onChanged: val => { config.RenderFPS = val; },
|
||||
text: LocalizationManager.Get("ui.render_config.render_fps"),
|
||||
id: "slider_render_fps", showFill: true
|
||||
);
|
||||
_videoFpsInput = CreateInputField(
|
||||
ctx, row1Right,
|
||||
LocalizationManager.Get("ui.render_config.video_fps"),
|
||||
"60", config.VideoFPS.ToString("0.##"),
|
||||
"input_video_fps",
|
||||
TMPro.TMP_InputField.CharacterValidation.Decimal,
|
||||
val => { if (double.TryParse(val, out var d) && d > 0) config.VideoFPS = d; }
|
||||
|
||||
_videoFPSSlider = O5Factory.Slider(
|
||||
ctx: ctx, parent: CreateFieldSlot(topRight),
|
||||
defaultValue: 60, min: 0.01, max: 600, value: 60,
|
||||
format: "F3", clampMode: ClampMode.All,
|
||||
|
||||
sliderFilter: value => videoFpsSnap.OrderBy(n => Math.Abs(n - value)).First(),
|
||||
inputFilter: null, onComplete: null, onChanged: val => { config.VideoFPS = val; },
|
||||
text: LocalizationManager.Get("ui.render_config.video_fps"),
|
||||
id: "slider_video_fps", showFill: true
|
||||
);
|
||||
|
||||
// 2. RenderWidth & RenderHeight
|
||||
var (row2Left, row2Right) = CreateTwoColumnRow(scrollContent);
|
||||
_renderWidthInput = CreateInputField(
|
||||
ctx, row2Left,
|
||||
LocalizationManager.Get("ui.render_config.render_width"),
|
||||
"1920", config.RenderWidth.ToString(),
|
||||
"input_render_width",
|
||||
TMPro.TMP_InputField.CharacterValidation.Integer,
|
||||
val => { if (int.TryParse(val, out var i) && i > 0) config.RenderWidth = i; }
|
||||
_renderWidthSlider = O5Factory.Slider(
|
||||
ctx: ctx, parent: CreateFieldSlot(topLeft),
|
||||
defaultValue: 2560, min: 0, max: 15360, value: 2560,
|
||||
format: "F0", clampMode: ClampMode.All,
|
||||
|
||||
sliderFilter: value => Math.Max(value, 2), inputFilter: value => Math.Max(value, 2),
|
||||
onComplete: null, onChanged: val => { config.RenderWidth = (int)Math.Round(val); },
|
||||
text: LocalizationManager.Get("ui.render_config.render_width"),
|
||||
id: "slider_render_width", showFill: true
|
||||
);
|
||||
_renderHeightInput = CreateInputField(
|
||||
ctx, row2Right,
|
||||
LocalizationManager.Get("ui.render_config.render_height"),
|
||||
"1080", config.RenderHeight.ToString(),
|
||||
"input_render_height",
|
||||
TMPro.TMP_InputField.CharacterValidation.Integer,
|
||||
val => { if (int.TryParse(val, out var i) && i > 0) config.RenderHeight = i; }
|
||||
|
||||
_renderHeightSlider = O5Factory.Slider(
|
||||
ctx: ctx, parent: CreateFieldSlot(topRight),
|
||||
defaultValue: 1440, min: 0, max: 8640, value: 1440,
|
||||
format: "F0", clampMode: ClampMode.All,
|
||||
|
||||
sliderFilter: value => Math.Max(value, 2), inputFilter: value => Math.Max(value, 2),
|
||||
onComplete: null, onChanged: val => { config.RenderWidth = (int)Math.Round(val); },
|
||||
text: LocalizationManager.Get("ui.render_config.render_height"),
|
||||
id: "slider_render_height", showFill: true
|
||||
);
|
||||
|
||||
// 3. Delay After Level End & Show Judgement
|
||||
var (row3Left, row3Right) = CreateTwoColumnRow(scrollContent);
|
||||
_delayAfterEndInput = CreateInputField(
|
||||
ctx, row3Left,
|
||||
LocalizationManager.Get("ui.render_config.delay_after_end"),
|
||||
"1.0", config.DelayAfterEnd.ToString("0.##"),
|
||||
"input_delay_after_end",
|
||||
TMPro.TMP_InputField.CharacterValidation.Decimal,
|
||||
val => { if (double.TryParse(val, out var d) && d >= 0) config.DelayAfterEnd = d; }
|
||||
|
||||
_delayAfterEndSlider = O5Factory.Slider(
|
||||
ctx: ctx, parent: CreateFieldSlot(topLeft),
|
||||
defaultValue: 15, min: 0, max: 9999.999, value: 15,
|
||||
format: "F3", clampMode: ClampMode.All,
|
||||
|
||||
sliderFilter: null, inputFilter: null,
|
||||
onComplete: null, onChanged: val => { config.DelayAfterEnd = val; },
|
||||
text: LocalizationManager.Get("ui.render_config.delay_after_end"),
|
||||
id: "slider_delay_after_end", showFill: true
|
||||
);
|
||||
|
||||
_showJudgementToggle = CreateToggleField(
|
||||
ctx, row3Right,
|
||||
LocalizationManager.Get("ui.render_config.show_judgement"),
|
||||
ctx, CreateFieldSlot(topRight), "",
|
||||
false, config.ShowJudgementText,
|
||||
LocalizationManager.Get("ui.render_config.show_judgement"),
|
||||
"toggle_show_judgement",
|
||||
val => config.ShowJudgementText = val
|
||||
val => config.ShowJudgementText = val,
|
||||
generateLabel: false
|
||||
);
|
||||
|
||||
// 4. Video Encoding Config (Collapsible Card)
|
||||
@@ -154,69 +180,69 @@ internal static class RenderConfigWindow
|
||||
showActiveToggle: false
|
||||
);
|
||||
|
||||
var (vRow1Left, vRow1Right) = CreateTwoColumnRow(videoContent);
|
||||
var (videoLeft, videoRight) = CreateTwoColumnPanel(videoContent);
|
||||
var codecs = (Codec[])Enum.GetValues(typeof(Codec));
|
||||
_codecDropdown = CreateDropdownField(
|
||||
ctx, vRow1Left,
|
||||
ctx, CreateFieldSlot(videoLeft),
|
||||
LocalizationManager.Get("ui.render_config.codec"),
|
||||
Codec.H264, config.VideoConfig.Codec,
|
||||
codecs, c => c.ToString(),
|
||||
"dd_video_codec",
|
||||
c => config.VideoConfig.Codec = c
|
||||
);
|
||||
|
||||
var rateModes = (RateControlMode[])Enum.GetValues(typeof(RateControlMode));
|
||||
_rateControlDropdown = CreateDropdownField(
|
||||
ctx, vRow1Right,
|
||||
LocalizationManager.Get("ui.render_config.rate_control"),
|
||||
RateControlMode.CBR, config.VideoConfig.RateControlMode,
|
||||
rateModes, m => m.ToString(),
|
||||
"dd_rate_control",
|
||||
m => config.VideoConfig.RateControlMode = m
|
||||
);
|
||||
|
||||
var (vRow2Left, vRow2Right) = CreateTwoColumnRow(videoContent);
|
||||
_bitrateInput = CreateInputField(
|
||||
ctx, vRow2Left,
|
||||
ctx, CreateFieldSlot(videoLeft),
|
||||
LocalizationManager.Get("ui.render_config.bitrate"),
|
||||
"30000", config.VideoConfig.Bitrate.ToString(),
|
||||
"input_bitrate",
|
||||
TMPro.TMP_InputField.CharacterValidation.Integer,
|
||||
val => { if (int.TryParse(val, out var b) && b > 0) config.VideoConfig.Bitrate = b; }
|
||||
);
|
||||
_maxBitrateInput = CreateInputField(
|
||||
ctx, vRow2Right,
|
||||
LocalizationManager.Get("ui.render_config.max_bitrate"),
|
||||
"50000", config.VideoConfig.MaxBitrate.ToString(),
|
||||
"input_max_bitrate",
|
||||
TMPro.TMP_InputField.CharacterValidation.Integer,
|
||||
val => { if (int.TryParse(val, out var mb) && mb > 0) config.VideoConfig.MaxBitrate = mb; }
|
||||
);
|
||||
|
||||
var (vRow3Left, vRow3Right) = CreateTwoColumnRow(videoContent);
|
||||
_qpInput = CreateInputField(
|
||||
ctx, vRow3Left,
|
||||
ctx, CreateFieldSlot(videoLeft),
|
||||
LocalizationManager.Get("ui.render_config.qp"),
|
||||
"18", config.VideoConfig.QP.ToString(),
|
||||
"input_qp",
|
||||
TMPro.TMP_InputField.CharacterValidation.Integer,
|
||||
val => { if (int.TryParse(val, out var qp) && qp >= 0) config.VideoConfig.QP = qp; }
|
||||
);
|
||||
_keyframeIntervalInput = CreateInputField(
|
||||
ctx, vRow3Right,
|
||||
LocalizationManager.Get("ui.render_config.keyframe_interval"),
|
||||
"2.0", config.VideoConfig.KeyframeInterval.ToString("0.##"),
|
||||
"input_keyframe_interval",
|
||||
TMPro.TMP_InputField.CharacterValidation.Decimal,
|
||||
val => { if (double.TryParse(val, out var kf) && kf > 0) config.VideoConfig.KeyframeInterval = kf; }
|
||||
|
||||
var rateModes = (RateControlMode[])Enum.GetValues(typeof(RateControlMode));
|
||||
_rateControlDropdown = CreateDropdownField(
|
||||
ctx, CreateFieldSlot(videoRight),
|
||||
LocalizationManager.Get("ui.render_config.rate_control"),
|
||||
RateControlMode.CBR, config.VideoConfig.RateControlMode,
|
||||
rateModes, m => m.ToString(),
|
||||
"dd_rate_control",
|
||||
m => config.VideoConfig.RateControlMode = m
|
||||
);
|
||||
_maxBitrateInput = CreateInputField(
|
||||
ctx, CreateFieldSlot(videoRight),
|
||||
LocalizationManager.Get("ui.render_config.max_bitrate"),
|
||||
"50000", config.VideoConfig.MaxBitrate.ToString(),
|
||||
"input_max_bitrate",
|
||||
TMPro.TMP_InputField.CharacterValidation.Integer,
|
||||
val => { if (int.TryParse(val, out var mb) && mb > 0) config.VideoConfig.MaxBitrate = mb; }
|
||||
);
|
||||
|
||||
_forceSoftwareToggle = CreateToggleField(
|
||||
ctx, CreateFieldSlot(videoRight), "",
|
||||
true, config.VideoConfig.ForceSoftwareEncoder,
|
||||
LocalizationManager.Get("ui.render_config.force_software"),
|
||||
"toggle_force_software",
|
||||
val => config.AudioConfig.RenderAudio = val,
|
||||
generateLabel: true
|
||||
);
|
||||
|
||||
_forceSoftwareToggle = O5Factory.Toggle(
|
||||
ctx, videoContent,
|
||||
false, config.VideoConfig.ForceSoftwareEncoder,
|
||||
val => config.VideoConfig.ForceSoftwareEncoder = val,
|
||||
LocalizationManager.Get("ui.render_config.force_software"),
|
||||
"toggle_force_software"
|
||||
_keyframeIntervalSlider = O5Factory.Slider(
|
||||
ctx: ctx, parent: videoContent,
|
||||
defaultValue: 2, min: 0.1, max: 10, value: 2,
|
||||
format: "F1", clampMode: ClampMode.All,
|
||||
|
||||
sliderFilter: null, inputFilter: null,
|
||||
onComplete: null, onChanged: val => { config.VideoConfig.KeyframeInterval = val; },
|
||||
text: LocalizationManager.Get("ui.render_config.keyframe_interval"),
|
||||
id: "slider_keyframe_interval", showFill: true
|
||||
);
|
||||
|
||||
// 5. Audio Encoding Config (Collapsible Card)
|
||||
@@ -230,43 +256,45 @@ internal static class RenderConfigWindow
|
||||
showActiveToggle: false
|
||||
);
|
||||
|
||||
var (aRow1Left, aRow1Right) = CreateTwoColumnRow(audioContent);
|
||||
var (audioLeft, audioRight) = CreateTwoColumnPanel(audioContent);
|
||||
_renderAudioToggle = CreateToggleField(
|
||||
ctx, aRow1Left,
|
||||
LocalizationManager.Get("ui.render_config.render_audio"),
|
||||
ctx, CreateFieldSlot(audioLeft), "",
|
||||
true, config.AudioConfig.RenderAudio,
|
||||
LocalizationManager.Get("ui.render_config.render_audio"),
|
||||
"toggle_render_audio",
|
||||
val => config.AudioConfig.RenderAudio = val
|
||||
val => config.AudioConfig.RenderAudio = val,
|
||||
generateLabel: false
|
||||
);
|
||||
|
||||
_audioBitrateSlider = O5Factory.Slider(
|
||||
ctx: ctx, parent: CreateFieldSlot(audioRight),
|
||||
defaultValue: 256, min: 64, max: 384, value: 256,
|
||||
format: "F0", clampMode: ClampMode.All,
|
||||
|
||||
var sampleRates = new[] { 44100, 48000 };
|
||||
_sampleRateDropdown = CreateDropdownField(
|
||||
ctx, aRow1Right,
|
||||
LocalizationManager.Get("ui.render_config.sample_rate"),
|
||||
44100, config.AudioConfig.AudioSampleRate,
|
||||
sampleRates, r => $"{r} Hz",
|
||||
"dd_sample_rate",
|
||||
r => config.AudioConfig.AudioSampleRate = r
|
||||
sliderFilter: value => audioBitrateSnap.OrderBy(n => Math.Abs(n - value)).First(),
|
||||
inputFilter: null, onComplete: null, onChanged: val => { config.AudioConfig.AudioBitrate = (int)Math.Round(val) * 1000; },
|
||||
text: LocalizationManager.Get("ui.render_config.audio_bitrate"),
|
||||
id: "slider_audio_bitrate", showFill: true
|
||||
);
|
||||
|
||||
var (aRow2Left, aRow2Right) = CreateTwoColumnRow(audioContent);
|
||||
|
||||
var channels = new[] { 1, 2 };
|
||||
_channelsDropdown = CreateDropdownField(
|
||||
ctx, aRow2Left,
|
||||
ctx, CreateFieldSlot(audioLeft),
|
||||
LocalizationManager.Get("ui.render_config.channels"),
|
||||
2, config.AudioConfig.AudioChannels,
|
||||
channels, c => c == 1 ? "1 (Mono)" : "2 (Stereo)",
|
||||
"dd_channels",
|
||||
c => config.AudioConfig.AudioChannels = c
|
||||
);
|
||||
_audioBitrateInput = CreateInputField(
|
||||
ctx, aRow2Right,
|
||||
LocalizationManager.Get("ui.render_config.audio_bitrate"),
|
||||
"256", (config.AudioConfig.AudioBitrate / 1000).ToString(),
|
||||
"input_audio_bitrate",
|
||||
TMPro.TMP_InputField.CharacterValidation.Integer,
|
||||
val => { if (int.TryParse(val, out var ab) && ab > 0) config.AudioConfig.AudioBitrate = ab * 1000; }
|
||||
|
||||
var sampleRates = new[] { 44100, 48000, 96000 };
|
||||
_sampleRateDropdown = CreateDropdownField(
|
||||
ctx, CreateFieldSlot(audioRight),
|
||||
LocalizationManager.Get("ui.render_config.sample_rate"),
|
||||
48000, config.AudioConfig.AudioSampleRate,
|
||||
sampleRates, r => $"{r/1000.0} kHz",
|
||||
"dd_sample_rate",
|
||||
r => config.AudioConfig.AudioSampleRate = r
|
||||
);
|
||||
|
||||
// 6. Global Settings (Collapsible Card)
|
||||
@@ -301,9 +329,9 @@ internal static class RenderConfigWindow
|
||||
}
|
||||
);
|
||||
|
||||
var (gBtnLeft, gBtnRight) = CreateTwoColumnRow(globalContent);
|
||||
var (globalLeft, globalRight) = CreateTwoColumnPanel(globalContent);
|
||||
O5Factory.Button(
|
||||
ctx, gBtnLeft,
|
||||
ctx, globalLeft,
|
||||
() =>
|
||||
{
|
||||
var initial = string.IsNullOrWhiteSpace(_outputDirInput.Value)
|
||||
@@ -324,7 +352,7 @@ internal static class RenderConfigWindow
|
||||
);
|
||||
|
||||
O5Factory.Button(
|
||||
ctx, gBtnRight,
|
||||
ctx, globalRight,
|
||||
() =>
|
||||
{
|
||||
_outputDirInput.Set(GlobalSettings.DefaultOutputDirectory);
|
||||
@@ -346,19 +374,27 @@ internal static class RenderConfigWindow
|
||||
showActiveToggle: false
|
||||
);
|
||||
|
||||
var (adRow1Left, adRow1Right) = CreateTwoColumnRow(advContent);
|
||||
var (advLeft, advRight) = CreateTwoColumnPanel(advContent);
|
||||
var extensions = new[] { "mkv", "mp4", "mov", "webm", "avi" };
|
||||
_fileExtensionDropdown = CreateDropdownField(
|
||||
ctx, adRow1Left,
|
||||
ctx, CreateFieldSlot(advLeft),
|
||||
LocalizationManager.Get("ui.render_config.file_extension"),
|
||||
"mkv", config.AdvancedConfig.FileExtension,
|
||||
extensions, e => e,
|
||||
"dd_adv_file_extension",
|
||||
e => { if (!string.IsNullOrEmpty(e)) config.AdvancedConfig.FileExtension = e; }
|
||||
);
|
||||
_processAudioToggle = CreateToggleField(
|
||||
ctx, CreateFieldSlot(advLeft), "",
|
||||
true, config.AdvancedConfig.ProcessAudio,
|
||||
LocalizationManager.Get("ui.render_config.process_audio"),
|
||||
"toggle_process_audio",
|
||||
val => config.AdvancedConfig.ProcessAudio = val,
|
||||
generateLabel: false
|
||||
);
|
||||
|
||||
_randomSeedInput = CreateInputField(
|
||||
ctx, adRow1Right,
|
||||
ctx, CreateFieldSlot(advRight),
|
||||
LocalizationManager.Get("ui.render_config.random_seed"),
|
||||
"", config.AdvancedConfig.RandomSeed?.ToString() ?? "",
|
||||
"input_random_seed",
|
||||
@@ -369,23 +405,13 @@ internal static class RenderConfigWindow
|
||||
else if (string.IsNullOrWhiteSpace(val)) config.AdvancedConfig.RandomSeed = null;
|
||||
}
|
||||
);
|
||||
|
||||
var (adRow2Left, adRow2Right) = CreateTwoColumnRow(advContent);
|
||||
_processAudioToggle = CreateToggleField(
|
||||
ctx, adRow2Left,
|
||||
LocalizationManager.Get("ui.render_config.process_audio"),
|
||||
true, config.AdvancedConfig.ProcessAudio,
|
||||
LocalizationManager.Get("ui.render_config.process_audio"),
|
||||
"toggle_process_audio",
|
||||
val => config.AdvancedConfig.ProcessAudio = val
|
||||
);
|
||||
_nonTileToggle = CreateToggleField(
|
||||
ctx, adRow2Right,
|
||||
LocalizationManager.Get("ui.render_config.non_tile"),
|
||||
ctx, CreateFieldSlot(advRight), "",
|
||||
false, config.AdvancedConfig.NonTile,
|
||||
LocalizationManager.Get("ui.render_config.non_tile"),
|
||||
"toggle_non_tile",
|
||||
val => config.AdvancedConfig.NonTile = val
|
||||
val => config.AdvancedConfig.NonTile = val,
|
||||
generateLabel: false
|
||||
);
|
||||
|
||||
// 8. Go Render Button
|
||||
@@ -400,22 +426,22 @@ internal static class RenderConfigWindow
|
||||
|
||||
private static void EnsureDefaultConfig(RenderConfig config)
|
||||
{
|
||||
if (config.RenderWidth <= 0) config.RenderWidth = Screen.width > 0 ? Screen.width : 1920;
|
||||
if (config.RenderHeight <= 0) config.RenderHeight = Screen.height > 0 ? Screen.height : 1080;
|
||||
if (config.RenderWidth <= 0) config.RenderWidth = Screen.width > 0 ? Screen.width : 2560;
|
||||
if (config.RenderHeight <= 0) config.RenderHeight = Screen.height > 0 ? Screen.height : 1440;
|
||||
if (config.RenderFPS <= 0) config.RenderFPS = 60.0;
|
||||
if (config.VideoFPS <= 0) config.VideoFPS = 60.0;
|
||||
if (config.DelayAfterEnd < 0) config.DelayAfterEnd = 1.0;
|
||||
if (config.DelayAfterEnd < 0) config.DelayAfterEnd = 15.0;
|
||||
}
|
||||
|
||||
private static void ApplyConfigToUI(RenderConfig config)
|
||||
{
|
||||
EnsureDefaultConfig(config);
|
||||
|
||||
_renderFpsInput?.Set(config.RenderFPS.ToString("0.##"), false);
|
||||
_videoFpsInput?.Set(config.VideoFPS.ToString("0.##"), false);
|
||||
_renderWidthInput?.Set(config.RenderWidth.ToString(), false);
|
||||
_renderHeightInput?.Set(config.RenderHeight.ToString(), false);
|
||||
_delayAfterEndInput?.Set(config.DelayAfterEnd.ToString("0.##"), false);
|
||||
_renderFpsSlider?.Set((float) config.RenderFPS, false);
|
||||
_videoFPSSlider?.Set((float) config.VideoFPS, false);
|
||||
_renderWidthSlider?.Set(config.RenderWidth, false);
|
||||
_renderHeightSlider?.Set(config.RenderHeight, false);
|
||||
_delayAfterEndSlider?.Set((float) config.DelayAfterEnd, false);
|
||||
_showJudgementToggle?.Set(config.ShowJudgementText, false);
|
||||
|
||||
_codecDropdown?.Set(config.VideoConfig.Codec, false);
|
||||
@@ -423,13 +449,13 @@ internal static class RenderConfigWindow
|
||||
_bitrateInput?.Set(config.VideoConfig.Bitrate.ToString(), false);
|
||||
_maxBitrateInput?.Set(config.VideoConfig.MaxBitrate.ToString(), false);
|
||||
_qpInput?.Set(config.VideoConfig.QP.ToString(), false);
|
||||
_keyframeIntervalInput?.Set(config.VideoConfig.KeyframeInterval.ToString("0.##"), false);
|
||||
_keyframeIntervalSlider?.Set(config.VideoConfig.KeyframeInterval, false);
|
||||
_forceSoftwareToggle?.Set(config.VideoConfig.ForceSoftwareEncoder, false);
|
||||
|
||||
_renderAudioToggle?.Set(config.AudioConfig.RenderAudio, false);
|
||||
_sampleRateDropdown?.Set(config.AudioConfig.AudioSampleRate, false);
|
||||
_channelsDropdown?.Set(config.AudioConfig.AudioChannels, false);
|
||||
_audioBitrateInput?.Set((config.AudioConfig.AudioBitrate / 1000).ToString(), false);
|
||||
_audioBitrateSlider?.Set(config.AudioConfig.AudioBitrate / 1000.0, false);
|
||||
|
||||
_outputDirInput?.Set(GlobalSettings.EffectiveOutputDirectory, false);
|
||||
|
||||
@@ -443,46 +469,6 @@ internal static class RenderConfigWindow
|
||||
{
|
||||
var config = RenderManager.Instance.Configuration;
|
||||
|
||||
// Validate RenderFPS
|
||||
if (!double.TryParse(_renderFpsInput?.Value, out var renderFps) || renderFps <= 0)
|
||||
{
|
||||
ShowValidationError("Render FPS must be a positive number.");
|
||||
return;
|
||||
}
|
||||
config.RenderFPS = renderFps;
|
||||
|
||||
// Validate VideoFPS
|
||||
if (!double.TryParse(_videoFpsInput?.Value, out var videoFps) || videoFps <= 0)
|
||||
{
|
||||
ShowValidationError("Video FPS must be a positive number.");
|
||||
return;
|
||||
}
|
||||
config.VideoFPS = videoFps;
|
||||
|
||||
// Validate RenderWidth
|
||||
if (!int.TryParse(_renderWidthInput?.Value, out var renderWidth) || renderWidth <= 0)
|
||||
{
|
||||
ShowValidationError("Render Width must be an integer greater than 0.");
|
||||
return;
|
||||
}
|
||||
config.RenderWidth = renderWidth;
|
||||
|
||||
// Validate RenderHeight
|
||||
if (!int.TryParse(_renderHeightInput?.Value, out var renderHeight) || renderHeight <= 0)
|
||||
{
|
||||
ShowValidationError("Render Height must be an integer greater than 0.");
|
||||
return;
|
||||
}
|
||||
config.RenderHeight = renderHeight;
|
||||
|
||||
// Validate DelayAfterEnd
|
||||
if (!double.TryParse(_delayAfterEndInput?.Value, out var delay) || delay < 0)
|
||||
{
|
||||
ShowValidationError("Delay After End must be a non-negative number.");
|
||||
return;
|
||||
}
|
||||
config.DelayAfterEnd = delay;
|
||||
|
||||
// Sync Video Config
|
||||
if (_bitrateInput != null && int.TryParse(_bitrateInput.Value, out var bitrate) && bitrate > 0)
|
||||
{
|
||||
@@ -496,16 +482,6 @@ internal static class RenderConfigWindow
|
||||
{
|
||||
config.VideoConfig.QP = qp;
|
||||
}
|
||||
if (_keyframeIntervalInput != null && double.TryParse(_keyframeIntervalInput.Value, out var kf) && kf > 0)
|
||||
{
|
||||
config.VideoConfig.KeyframeInterval = kf;
|
||||
}
|
||||
|
||||
// Sync Audio Config
|
||||
if (_audioBitrateInput != null && int.TryParse(_audioBitrateInput.Value, out var ab) && ab > 0)
|
||||
{
|
||||
config.AudioConfig.AudioBitrate = ab * 1000;
|
||||
}
|
||||
|
||||
// Sync Advanced Config
|
||||
if (_randomSeedInput != null)
|
||||
@@ -564,41 +540,84 @@ internal static class RenderConfigWindow
|
||||
);
|
||||
}
|
||||
|
||||
private static (RectTransform left, RectTransform right) CreateTwoColumnRow(Transform parent, float spacing = 8f)
|
||||
// 카드(또는 상단 섹션)당 하나의 좌/우 컨테이너를 만들어
|
||||
// 가로 한 줄 두 필드 사이 중앙이 행마다 어긋나지 않도록 한다.
|
||||
// - 패널 1개 = 중앙선 1개 (행마다 HLG를 만들던 기존 방식의 P1-P2 어긋남 방지)
|
||||
// - 좌/우 컬럼은 preferredWidth=0 + flexibleWidth=1 로 항상 50:50
|
||||
// - 각 필드는 Field 슬롯(label 18 + 간격 3 + 컨트롤 50)으로 높이를 고정해
|
||||
// 좌/우 스택을 써도 행 단위 Y 정렬이 유지되도록 한다.
|
||||
private static (RectTransform left, RectTransform right) CreateTwoColumnPanel(
|
||||
Transform parent, float spacing = 8f, float columnSpacing = 8f)
|
||||
{
|
||||
var rowObj = new GameObject("TwoColRow");
|
||||
rowObj.transform.SetParent(parent, false);
|
||||
var panelObj = new GameObject("TwoColPanel");
|
||||
panelObj.transform.SetParent(parent, false);
|
||||
|
||||
var rowRect = rowObj.AddComponent<RectTransform>();
|
||||
panelObj.AddComponent<RectTransform>();
|
||||
|
||||
var hLayout = rowObj.AddComponent<HorizontalLayoutGroup>();
|
||||
var hLayout = panelObj.AddComponent<HorizontalLayoutGroup>();
|
||||
hLayout.spacing = spacing;
|
||||
hLayout.padding = new RectOffset(0, 0, 0, 0);
|
||||
hLayout.childAlignment = TextAnchor.UpperCenter;
|
||||
hLayout.childControlWidth = true;
|
||||
hLayout.childControlHeight = true;
|
||||
hLayout.childForceExpandWidth = true;
|
||||
hLayout.childForceExpandHeight = false;
|
||||
|
||||
var col1Obj = new GameObject("Col1");
|
||||
col1Obj.transform.SetParent(rowObj.transform, false);
|
||||
var col1Rect = col1Obj.AddComponent<RectTransform>();
|
||||
var col1Layout = col1Obj.AddComponent<VerticalLayoutGroup>();
|
||||
col1Layout.spacing = 3f;
|
||||
col1Layout.childControlWidth = true;
|
||||
col1Layout.childControlHeight = true;
|
||||
col1Layout.childForceExpandWidth = true;
|
||||
col1Layout.childForceExpandHeight = false;
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var panelLE = panelObj.AddComponent<LayoutElement>();
|
||||
panelLE.flexibleWidth = 1f;
|
||||
|
||||
var col2Obj = new GameObject("Col2");
|
||||
col2Obj.transform.SetParent(rowObj.transform, false);
|
||||
var col2Rect = col2Obj.AddComponent<RectTransform>();
|
||||
var col2Layout = col2Obj.AddComponent<VerticalLayoutGroup>();
|
||||
col2Layout.spacing = 3f;
|
||||
col2Layout.childControlWidth = true;
|
||||
col2Layout.childControlHeight = true;
|
||||
col2Layout.childForceExpandWidth = true;
|
||||
col2Layout.childForceExpandHeight = false;
|
||||
var left = CreateColumn(panelObj.transform, "LeftCol", columnSpacing);
|
||||
var right = CreateColumn(panelObj.transform, "RightCol", columnSpacing);
|
||||
|
||||
return (col1Rect, col2Rect);
|
||||
return (left, right);
|
||||
}
|
||||
|
||||
private static RectTransform CreateColumn(Transform parent, string name, float spacing)
|
||||
{
|
||||
var colObj = new GameObject(name);
|
||||
colObj.transform.SetParent(parent, false);
|
||||
var colRect = colObj.AddComponent<RectTransform>();
|
||||
|
||||
var colLayout = colObj.AddComponent<VerticalLayoutGroup>();
|
||||
colLayout.spacing = spacing;
|
||||
colLayout.padding = new RectOffset(0, 0, 0, 0);
|
||||
colLayout.childControlWidth = true;
|
||||
colLayout.childControlHeight = true;
|
||||
colLayout.childForceExpandWidth = true;
|
||||
colLayout.childForceExpandHeight = false;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
// 핵심: 좌/우 선호 너비를 0으로 강제해 항상 정확히 절반씩 나누도록 한다.
|
||||
// (기존 행마다 HLG 방식은 라벨/값 텍스트 길이에 따라 preferred가 달라져
|
||||
// 중앙이 (W + P1 - P2) / 2 로 행마다 틀어졌음)
|
||||
var colLE = colObj.AddComponent<LayoutElement>();
|
||||
colLE.minWidth = 0f;
|
||||
colLE.preferredWidth = 0f;
|
||||
colLE.flexibleWidth = 1f;
|
||||
|
||||
return colRect;
|
||||
}
|
||||
|
||||
private static RectTransform CreateFieldSlot(Transform column)
|
||||
{
|
||||
var slotObj = new GameObject("Field");
|
||||
slotObj.transform.SetParent(column, false);
|
||||
var slotRect = slotObj.AddComponent<RectTransform>();
|
||||
|
||||
var slotLayout = slotObj.AddComponent<VerticalLayoutGroup>();
|
||||
slotLayout.spacing = 3f;
|
||||
slotLayout.padding = new RectOffset(0, 0, 0, 0);
|
||||
slotLayout.childControlWidth = true;
|
||||
slotLayout.childControlHeight = true;
|
||||
slotLayout.childForceExpandWidth = true;
|
||||
slotLayout.childForceExpandHeight = false;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var slotLE = slotObj.AddComponent<LayoutElement>();
|
||||
slotLE.flexibleWidth = 1f;
|
||||
|
||||
return slotRect;
|
||||
}
|
||||
|
||||
private static O5InputField CreateInputField(
|
||||
@@ -686,16 +705,20 @@ internal static class RenderConfigWindow
|
||||
bool value,
|
||||
string toggleText,
|
||||
string id,
|
||||
Action<bool>? onChanged = null)
|
||||
Action<bool>? onChanged = null,
|
||||
bool generateLabel = true)
|
||||
{
|
||||
var label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
|
||||
label.text = labelText;
|
||||
label.color = ctx.Theme.TextDim;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var labelLE = label.gameObject.AddComponent<LayoutElement>();
|
||||
labelLE.preferredHeight = 18f;
|
||||
labelLE.minHeight = 18f;
|
||||
if (generateLabel)
|
||||
{
|
||||
var label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
|
||||
label.text = labelText;
|
||||
label.color = ctx.Theme.TextDim;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var labelLE = label.gameObject.AddComponent<LayoutElement>();
|
||||
labelLE.preferredHeight = 18f;
|
||||
labelLE.minHeight = 18f;
|
||||
}
|
||||
|
||||
return O5Factory.Toggle(
|
||||
ctx: ctx,
|
||||
@@ -730,12 +753,11 @@ internal static class RenderConfigWindow
|
||||
|
||||
var ddContainer = new GameObject($"DD_{id}");
|
||||
ddContainer.transform.SetParent(colParent, false);
|
||||
var ddRect = ddContainer.AddComponent<RectTransform>();
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
var ddLE = ddContainer.AddComponent<LayoutElement>();
|
||||
ddLE.preferredHeight = 50f;
|
||||
ddLE.minHeight = 50f;
|
||||
ddLE.minHeight = 0f;
|
||||
|
||||
return O5Factory.DropDown(
|
||||
ctx: ctx,
|
||||
|
||||
Reference in new issue
Block a user