diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f50f6ae1..fb5cfd54c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,9 @@ Built with Unity 2020.3. ### Added +- Editor: Enable manual trigger for coils, switches, lamps and wires during gameplay ([#332](https://github.com/freezy/VisualPinball.Engine/pull/332)) - Support for dynamic wires, also known as *Fast Flip* ([#330](https://github.com/freezy/VisualPinball.Engine/pull/330), [Documentation](https://docs.visualpinball.org/creators-guide/editor/wire-manager.html#dynamic)). -- Component for light groups, allowing easy grouping of GI lamps. ([#330](https://github.com/freezy/VisualPinball.Engine/pull/330) ([Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/light-groups.html))). +- Component for light groups, allowing easy grouping of GI lamps. ([#330](https://github.com/freezy/VisualPinball.Engine/pull/330) [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/light-groups.html)). - Slingshot component ([#329](https://github.com/freezy/VisualPinball.Engine/pull/329), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/slingshots.html)). - Create insert meshes ([#320](https://github.com/freezy/VisualPinball.Engine/pull/320)). - Full support for custom playfield meshes. diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/coil-manager.md b/VisualPinball.Unity/Documentation~/creators-guide/editor/coil-manager.md index 4dbe2d771..3bc03ce3b 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/editor/coil-manager.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/editor/coil-manager.md @@ -41,24 +41,6 @@ The **Element** column is where you choose which specifc element in the destinat > [!note] > Bumpers are currently hard-wired, i.e. their switch will directly trigger the coil without going through the gamelogic engine. That means they don't need to be configured in the switch or coil manager. VPE will make this configurable in the future. -### Type +## Runtime -In the **Type** column you can define whether the coil is single-wound or dual-wound. There's an excellent page about the differences in [MPF's documentation](https://docs.missionpinball.org/en/latest/mechs/coils/dual_vs_single_wound.html). In short, dual-wound coils have two circuits, one for powering the coil, and one for holding it, while single-wound coils only have one. - -This changes how the coil powers off: - -- For **single-wound** coils, VPE uses the same coil's events for powering on and off. -- For **dual-wound** coils, it uses the *on* event from the main coil and the *off* event from the hold coil. - -### Hold Coil - -When the coil type is set to *Dual-Wound*, this column defines the hold coil event, i.e. the event on which the coil powers off. - -Dual-wound coils are fairly common. For example, *Medieval Madness* has the following dual-wound coils: - -![Medieval Madness dual-wound coils](dual-wound-coils.png) -*From the Medieval Madness manual* - -In VPE, the two flippers would map to the following configuration: - -![Dual-wound example configuration](switch-manager-dual-wound.png) +During runtime, you'll see the coil statuses update in real-time. Clicking on the icon manually toggles the coil, which can be handy for debugging. diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/dual-wound-coils.png b/VisualPinball.Unity/Documentation~/creators-guide/editor/dual-wound-coils.png deleted file mode 100644 index a892327a5..000000000 Binary files a/VisualPinball.Unity/Documentation~/creators-guide/editor/dual-wound-coils.png and /dev/null differ diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager.md b/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager.md index 2c38c3d7e..cc19ff34e 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager.md @@ -90,8 +90,10 @@ In Visual Pinball, you can put GI lamps into a collection and address the whole ## Editor vs Runtime -While editing the table in the Unity editor, you may find it helpful to disable lamps you're not editing. During runtime, VPE first turns all lamps off, then turns on the constant lamps, and then transfers control of the lamps to the gamelogic engine. +While editing the table in the Unity editor, you may find it helpful to disable lamps you're not editing. You can quickly do that with the top-right controls, which allow you to toggle selectively GIs, inserts, flashers, lamps selected in the manager, or all lamps. You can also quickly select the light source to quickly edit multiple lights. -If you are running the game in the editor, the lamp manager shows the lamp statuses in real time: +During runtime, VPE first turns all lamps off, then turns on the constant lamps, and then transfers control of the lamps to the gamelogic engine. If you are running the game in the editor, the lamp manager shows the lamp statuses in real time: ![Lamps runtime](lamp-manager-gameplay.gif) + +During gameplay you can also click on the lamp icon to toggle the lamp. This can be handy for debugging. diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/switch-manager-dual-wound.png b/VisualPinball.Unity/Documentation~/creators-guide/editor/switch-manager-dual-wound.png deleted file mode 100644 index 1fc6695fd..000000000 Binary files a/VisualPinball.Unity/Documentation~/creators-guide/editor/switch-manager-dual-wound.png and /dev/null differ diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/switch-manager.md b/VisualPinball.Unity/Documentation~/creators-guide/editor/switch-manager.md index 3a31ddc94..b315a612b 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/editor/switch-manager.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/editor/switch-manager.md @@ -95,4 +95,8 @@ Below a list of game mechanisms that contain built-in switches. | **Target** | On collision | *After pulse delay* | | **Kicker** | When ball enters the kicker | When ball's outside the kicker | | **Spinner** | On each spin | *After pulse delay* | -| **Trigger** | When the ball rolls over the trigger | When the ball is outside of the trigger | \ No newline at end of file +| **Trigger** | When the ball rolls over the trigger | When the ball is outside of the trigger | + +## Runtime + +During runtime, you'll see the switch statuses update in real-time. Clicking on the icon manually toggles the switch, which can be handy for debugging. diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/wire-manager.md b/VisualPinball.Unity/Documentation~/creators-guide/editor/wire-manager.md index 2f258b3fb..dd7a4f246 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/editor/wire-manager.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/editor/wire-manager.md @@ -80,3 +80,7 @@ However, by design, there are two caveats: Internally, VPE connects switches to events. Some switchable game items only emit the *switch closed* event. Such items are spinners and targets. These are elements where the re-opening of the switch does not have any semantic value. In order for those to not stay closed forever, VPE closes them after a given delay. We call this the **Pulse Delay**. This field is only visible if the input source is a pulse-driven source. + +## Runtime + +During runtime, you'll see the wire statuses update in real-time. Clicking on the icon manually toggles the wire, which can be handy for debugging. diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilListViewItemRenderer.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilListViewItemRenderer.cs index 30618a602..798db5eac 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilListViewItemRenderer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilListViewItemRenderer.cs @@ -73,6 +73,19 @@ public void Render(TableComponent tableComponent, CoilListData data, Rect cellRe EditorGUI.EndDisabledGroup(); } + protected override void OnIconClick(CoilListData data, bool pressedDown) + { + var player = _tableComponent.GetComponent(); + if (player == null || data.Device == null || string.IsNullOrEmpty(data.DeviceItem)) { + return; + } + var coil = player.Coil(data.Device, data.DeviceItem); + coil?.OnCoil(pressedDown); + if (player.CoilStatuses.ContainsKey(data.Id)) { + player.CoilStatuses[data.Id] = pressedDown; + } + } + private void UpdateId(CoilListData data, string id) { if (data.Destination == CoilDestination.Lamp) { diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListViewItemRenderer.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListViewItemRenderer.cs index 0cd142f56..81916a25a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListViewItemRenderer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListViewItemRenderer.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using UnityEditor; using UnityEngine; +using UnityEngine.InputSystem; using VisualPinball.Engine.Game.Engines; using VisualPinball.Engine.Math; using Color = UnityEngine.Color; @@ -41,12 +42,14 @@ private enum LampListColumn FadingSteps = 6, } + private readonly TableComponent _tableComponent; private readonly List _gleLamps; private readonly ObjectReferencePicker _devicePicker; public LampListViewItemRenderer(List gleLamps, TableComponent tableComponent) { + _tableComponent = tableComponent; _gleLamps = gleLamps; _devicePicker = new ObjectReferencePicker("Lamps", tableComponent, false); } @@ -89,17 +92,40 @@ public void Render(TableComponent tableComponent, LampListData data, Rect cellRe EditorGUI.EndDisabledGroup(); } + protected override void OnIconClick(LampListData data, bool pressedDown) + { + var player = _tableComponent.GetComponent(); + if (player == null || data.Device == null || string.IsNullOrEmpty(data.DeviceItem)) { + return; + } + var lamp = player.Lamp(data.Device); + lamp?.OnChange(pressedDown); + if (player.LampStatuses.ContainsKey(data.Id)) { + player.LampStatuses[data.Id] = pressedDown ? 1 : 0; + } + } + private void RenderCoilId(Dictionary lampStatuses, LampListData lampListData, Rect cellRect) { // add some padding cellRect.x = cellRect.width - 45; cellRect.width -= 4; + var iconRect = cellRect; + iconRect.width = 20; + + if (Mouse.current.leftButton.wasPressedThisFrame && !MouseDownOnIcon && iconRect.Contains(Event.current.mousePosition)) { + OnIconClick(lampListData, true); + MouseDownOnIcon = true; + } + if (Mouse.current.leftButton.wasReleasedThisFrame && MouseDownOnIcon && iconRect.Contains(Event.current.mousePosition)) { + OnIconClick(lampListData, false); + MouseDownOnIcon = false; + } + var statusAvail = Application.isPlaying && lampStatuses != null && lampStatuses.ContainsKey(lampListData.Id); var icon = Icons.Coil(IconSize.Small, statusAvail && lampStatuses[lampListData.Id] > 0 ? IconColor.Orange : IconColor.Gray); if (icon != null) { - var iconRect = cellRect; - iconRect.width = 20; var guiColor = GUI.color; GUI.color = Color.clear; EditorGUI.DrawTextureTransparent(iconRect, icon, ScaleMode.ScaleToFit); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/ListViewItemRenderer.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/ListViewItemRenderer.cs index 908de9251..3f394d97e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/ListViewItemRenderer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/ListViewItemRenderer.cs @@ -19,6 +19,7 @@ using System.Linq; using UnityEditor; using UnityEngine; +using UnityEngine.InputSystem; using VisualPinball.Engine.Game.Engines; namespace VisualPinball.Unity.Editor @@ -35,6 +36,12 @@ public abstract class ListViewItemRenderer wher protected abstract void RenderDeviceElement(TListData listData, Rect cellRect, Action updateAction); + protected virtual void OnIconClick(TListData data, bool pressedDown) + { + } + + protected bool MouseDownOnIcon; + protected void RenderId(Dictionary statuses, ref string id, Action setId, TListData listData, Rect cellRect, Action updateAction) { const float idWidth = 25f; @@ -58,8 +65,19 @@ protected void RenderId(Dictionary statuses, ref string id, Act options.Add("Add..."); if (Application.isPlaying && statuses != null) { + var iconRect = cellRect; iconRect.width = 20; + + if (Mouse.current.leftButton.wasPressedThisFrame && !MouseDownOnIcon && iconRect.Contains(Event.current.mousePosition)) { + OnIconClick(listData, true); + MouseDownOnIcon = true; + } + if (Mouse.current.leftButton.wasReleasedThisFrame && MouseDownOnIcon && iconRect.Contains(Event.current.mousePosition)) { + OnIconClick(listData, false); + MouseDownOnIcon = false; + } + dropdownRect.x += 25; dropdownRect.width -= 25; if (statuses.ContainsKey(id)) { diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Switch/SwitchListViewItemRenderer.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Switch/SwitchListViewItemRenderer.cs index 868c585fc..c07d0257f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Switch/SwitchListViewItemRenderer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Switch/SwitchListViewItemRenderer.cs @@ -23,11 +23,11 @@ namespace VisualPinball.Unity.Editor { - public class SwitchListViewItemRenderer : ListViewItemRenderer + public class SwitchListViewItemRenderer : ListViewItemRenderer { protected override List GleItems => _gleSwitches; protected override GamelogicEngineSwitch InstantiateGleItem(string id) => new GamelogicEngineSwitch(id); - protected override Texture2D StatusIcon(bool status) => Icons.Switch(status, IconSize.Small, status ? IconColor.Orange : IconColor.Gray); + protected override Texture2D StatusIcon(IApiSwitchStatus status) => Icons.Switch(status.IsSwitchClosed, IconSize.Small, status.IsSwitchClosed ? IconColor.Orange : IconColor.Gray); private struct InputSystemEntry { @@ -49,19 +49,21 @@ private enum SwitchListColumn private readonly InputManager _inputManager; private readonly ObjectReferencePicker _devicePicker; + private readonly TableComponent _tableComponent; public SwitchListViewItemRenderer(List gleSwitches, TableComponent tableComponent, InputManager inputManager) { _gleSwitches = gleSwitches; _inputManager = inputManager; _devicePicker = new ObjectReferencePicker("Switch Devices", tableComponent, false); + _tableComponent = tableComponent; } public void Render(TableComponent tableComponent, SwitchListData data, Rect cellRect, int column, Action updateAction) { EditorGUI.BeginDisabledGroup(Application.isPlaying); var switchStatuses = Application.isPlaying - ? tableComponent.gameObject.GetComponent()?.SwitchStatusesClosed + ? tableComponent.gameObject.GetComponent()?.SwitchStatuses : null; switch ((SwitchListColumn)column) { @@ -87,6 +89,16 @@ public void Render(TableComponent tableComponent, SwitchListData data, Rect cell EditorGUI.EndDisabledGroup(); } + protected override void OnIconClick(SwitchListData data, bool pressedDown) + { + var gle = _tableComponent.GetComponent(); + var player = _tableComponent.GetComponent(); + gle?.Switch(data.Id, pressedDown); + if (player != null && player.SwitchStatuses.ContainsKey(data.Id)) { + player.SwitchStatuses[data.Id].IsSwitchClosed = pressedDown; + } + } + private void RenderNc(SwitchListData switchListData, Rect cellRect, Action updateAction) { // don't render for constants diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Switch/SwitchManager.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Switch/SwitchManager.cs index 8e0524b9b..931df5d0b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Switch/SwitchManager.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Switch/SwitchManager.cs @@ -77,15 +77,12 @@ protected override bool SetupCompleted() } var gle = TableComponent.gameObject.GetComponent(); - - if (gle == null) - { + if (gle == null) { DisplayMessage("No gamelogic engine set."); return false; } - if (_needsAssetRefresh) - { + if (_needsAssetRefresh) { AssetDatabase.Refresh(); _needsAssetRefresh = false; } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Wire/WireListViewItemRenderer.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Wire/WireListViewItemRenderer.cs index 89ef4ecc7..9d75818ee 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Wire/WireListViewItemRenderer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Wire/WireListViewItemRenderer.cs @@ -20,6 +20,7 @@ using Unity.Mathematics; using UnityEditor; using UnityEngine; +using UnityEngine.InputSystem; using VisualPinball.Engine.Game.Engines; namespace VisualPinball.Unity.Editor @@ -46,6 +47,7 @@ private enum WireListColumn PulseDelay = 5, } + private readonly TableComponent _tableComponent; private readonly InputManager _inputManager; private readonly ObjectReferencePicker _sourceDevicePicker; @@ -53,6 +55,7 @@ private enum WireListColumn public WireListViewItemRenderer(TableComponent tableComponent, InputManager inputManager) { + _tableComponent = tableComponent; _inputManager = inputManager; _sourceDevicePicker = new ObjectReferencePicker("Wire Source", tableComponent, false); @@ -88,11 +91,34 @@ public void Render(TableComponent tableComponent, WireListData data, Rect cellRe EditorGUI.EndDisabledGroup(); } + protected override void OnIconClick(WireListData data, bool pressedDown) + { + var player = _tableComponent.GetComponent(); + if (player == null || data.DestinationDevice == null || string.IsNullOrEmpty(data.DestinationDeviceItem)) { + return; + } + var wire = player.WireDevice(data.DestinationDevice); + wire?.Wire(data.DestinationDeviceItem).OnChange(pressedDown); + if (player.WireStatuses.ContainsKey(data.Id)) { + player.WireStatuses[data.Id] = (pressedDown, Time.realtimeSinceStartup); + } + } + private void RenderDescription(Dictionary statuses, WireListData listData, Rect cellRect, Action updateAction) { if (Application.isPlaying && statuses != null) { var iconRect = cellRect; iconRect.width = 20; + + if (Mouse.current.leftButton.wasPressedThisFrame && !MouseDownOnIcon && iconRect.Contains(Event.current.mousePosition)) { + OnIconClick(listData, true); + MouseDownOnIcon = true; + } + if (Mouse.current.leftButton.wasReleasedThisFrame && MouseDownOnIcon && iconRect.Contains(Event.current.mousePosition)) { + OnIconClick(listData, false); + MouseDownOnIcon = false; + } + if (statuses.ContainsKey(listData.Id)) { var status = statuses[listData.Id]; var icon = StatusIcon(status.Item1); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/Terminator2.cs b/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/Terminator2.cs index f4efcefae..3c1f7adba 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/Terminator2.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/Terminator2.cs @@ -370,9 +370,9 @@ public void FixShip(GameObject go) [NameMatch("F127a", FloatParam = 10000f)] public void FlasherIntensities(GameObject go, LightComponent lc, float param) { + lc.FadeSpeedUp = 0f; + lc.FadeSpeedDown = 0.15f; foreach (var l in go.GetComponentsInChildren()) { - lc.FadeSpeedUp = 0f; - lc.FadeSpeedDown = 0f; RenderPipeline.Current.LightConverter.SetIntensity(l, param); RenderPipeline.Current.LightConverter.SetTemperature(l, 3000); RenderPipeline.Current.LightConverter.SetShadow(l, true, true, 0.001f); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs index 435a3a5ae..416ffac69 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs @@ -46,6 +46,9 @@ public class LampPlayer private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + internal IApiLamp Lamp(ILampDeviceComponent component) + => _lamps.ContainsKey(component) ? _lamps[component] : null; + internal Dictionary LampStatuses { get; } = new Dictionary(); internal void RegisterLamp(ILampDeviceComponent component, IApiLamp lampApi) => _lamps[component] = lampApi; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs index ecdf5dd3a..13f7b650b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs @@ -94,11 +94,12 @@ public class Player : MonoBehaviour #region Access internal IApiSwitch Switch(ISwitchDeviceComponent component, string switchItem) => component != null ? _switchPlayer.Switch(component, switchItem) : null; - internal IApiCoil Coil(ICoilDeviceComponent component, string coilItem) => component != null ? _coilPlayer.Coil(component, coilItem) : null; - internal IApiWireDeviceDest WireDevice(IWireableComponent c) => _wirePlayer.WireDevice(c); + public IApiCoil Coil(ICoilDeviceComponent component, string coilItem) => component != null ? _coilPlayer.Coil(component, coilItem) : null; + public IApiLamp Lamp(ILampDeviceComponent component) => component != null ? _lampPlayer.Lamp(component) : null; + public IApiWireDeviceDest WireDevice(IWireableComponent c) => _wirePlayer.WireDevice(c); internal void HandleWireSwitchChange(WireDestConfig wireConfig, bool isEnabled) => _wirePlayer.HandleSwitchChange(wireConfig, isEnabled); - public Dictionary SwitchStatusesClosed => _switchPlayer.SwitchStatusesClosed; + public Dictionary SwitchStatuses => _switchPlayer.SwitchStatuses; public Dictionary CoilStatuses => _coilPlayer.CoilStatuses; public Dictionary LampStatuses => _lampPlayer.LampStatuses; public Dictionary WireStatuses => _wirePlayer.WireStatuses; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchHandler.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchHandler.cs index 055d7f05b..564ade289 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchHandler.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchHandler.cs @@ -179,7 +179,10 @@ internal class ItemSwitchStatus : IApiSwitchStatus private readonly bool _isNormallyClosed; public bool IsSwitchEnabled { get; set; } - public bool IsSwitchClosed => _isNormallyClosed ? !IsSwitchEnabled : IsSwitchEnabled; + public bool IsSwitchClosed { + get => _isNormallyClosed ? !IsSwitchEnabled : IsSwitchEnabled; + set => IsSwitchEnabled = _isNormallyClosed ? !value : value; + } public ItemSwitchStatus(bool normallyClosed) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchPlayer.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchPlayer.cs index 644d1391e..d3ebd798c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchPlayer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchPlayer.cs @@ -31,7 +31,7 @@ public class SwitchPlayer /// /// Maps the switch configuration ID to a switch status. /// - private readonly Dictionary _switchStatuses = new Dictionary(); + internal readonly Dictionary SwitchStatuses = new Dictionary(); /// /// Maps the input action to a list of switch statuses @@ -44,8 +44,6 @@ public class SwitchPlayer private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - internal Dictionary SwitchStatusesClosed - => _switchStatuses.ToDictionary(s => s.Key, s => s.Value.IsSwitchClosed); internal IApiSwitch Switch(ISwitchDeviceComponent component, string deviceSwitchId) => _switchDevices.ContainsKey(component) ? _switchDevices[component].Switch(deviceSwitchId) : null; internal void RegisterSwitchDevice(ISwitchDeviceComponent component, IApiSwitchDevice switchDeviceApi) @@ -88,7 +86,7 @@ public void OnStart() var deviceSwitch = device.Switch(switchMapping.DeviceItem); if (deviceSwitch != null) { var switchStatus = deviceSwitch.AddSwitchDest(new SwitchConfig(switchMapping)); - _switchStatuses[switchMapping.Id] = switchStatus; + SwitchStatuses[switchMapping.Id] = switchStatus; } else { Logger.Error($"Unknown switch \"{switchMapping.DeviceItem}\" in switch device \"{switchMapping.Device}\"."); @@ -103,11 +101,11 @@ public void OnStart() } var keyboardSwitch = new KeyboardSwitch(switchMapping.Id, switchMapping.IsNormallyClosed); _keySwitchAssignments[switchMapping.InputAction].Add(keyboardSwitch); - _switchStatuses[switchMapping.Id] = keyboardSwitch; + SwitchStatuses[switchMapping.Id] = keyboardSwitch; break; case SwitchSource.Constant: - _switchStatuses[switchMapping.Id] = new ConstantSwitch(switchMapping.Constant == SwitchConstant.Closed); + SwitchStatuses[switchMapping.Id] = new ConstantSwitch(switchMapping.Constant == SwitchConstant.Closed); break; default: @@ -156,7 +154,11 @@ internal class KeyboardSwitch : IApiSwitchStatus private readonly bool _isNormallyClosed; public bool IsSwitchEnabled { get; set; } - public bool IsSwitchClosed => _isNormallyClosed ? !IsSwitchEnabled : IsSwitchEnabled; + public bool IsSwitchClosed + { + get => _isNormallyClosed ? !IsSwitchEnabled : IsSwitchEnabled; + set => IsSwitchEnabled = _isNormallyClosed ? !value : value; + } public KeyboardSwitch(string switchId, bool normallyClosed) { @@ -167,8 +169,11 @@ public KeyboardSwitch(string switchId, bool normallyClosed) internal class ConstantSwitch : IApiSwitchStatus { - public bool IsSwitchEnabled { get; } - public bool IsSwitchClosed => IsSwitchEnabled; + public bool IsSwitchEnabled { get; set; } + public bool IsSwitchClosed { + get => IsSwitchEnabled; + set => IsSwitchEnabled = value; + } public ConstantSwitch(bool isSwitchClosed) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs index c76362684..ec934cce7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs @@ -173,17 +173,17 @@ internal interface IApiSwitch /// /// This interface abstracts objects that can represent a switch status (keyboard, constant, items). /// - internal interface IApiSwitchStatus + public interface IApiSwitchStatus { /// /// True if switch is enabled, false otherwise. Note that enabled != closed. /// - bool IsSwitchEnabled { get; } + bool IsSwitchEnabled { get; set; } /// /// True if switch is closed, false otherwise. /// - bool IsSwitchClosed { get; } + bool IsSwitchClosed { get; set; } } /// @@ -211,7 +211,7 @@ internal interface IApiSwitchDevice /// /// This interface makes the implementation act as a coil that consumes coil events. /// - internal interface IApiCoil : IApiWireDest + public interface IApiCoil : IApiWireDest { /// /// The coil status changed. @@ -237,7 +237,7 @@ internal interface IApiCoilDevice /// /// A game item that acts a lamp that can either receive a float value or a color. /// - internal interface IApiLamp : IApiWireDest + public interface IApiLamp : IApiWireDest { /// /// Sets the color of the light. @@ -261,7 +261,7 @@ internal interface IApiLamp : IApiWireDest /// /// This interface groups devices that can receive binary data, currently coils and lamps. /// - internal interface IApiWireDest + public interface IApiWireDest { /// /// Changes the status at the destination. @@ -273,7 +273,7 @@ internal interface IApiWireDest /// /// A game item that can act as one or more wire destinations. /// - internal interface IApiWireDeviceDest + public interface IApiWireDeviceDest { /// /// Which wire destination to return. diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightApi.cs index be14b0ae4..edbde7288 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightApi.cs @@ -97,22 +97,13 @@ private void Set(int lightStatus, float value) { switch (lightStatus) { case LightStatus.LightStateOff: { - if (MainComponent.FadeSpeedDown > 0) { - _lightComponent.FadeTo(0); - - } else { - _lightComponent.Enabled = false; - } + _lightComponent.FadeTo(0); break; } case LightStatus.LightStateOn: { - if (MainComponent.FadeSpeedUp > 0) { - _lightComponent.FadeTo(value); + _lightComponent.FadeTo(value); - } else { - _lightComponent.Enabled = true; - } break; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs index 77708df20..478e372ec 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs @@ -56,9 +56,15 @@ public class LightComponent : MainRenderableComponent, ILampDeviceCom public string BlinkPattern; public int BlinkInterval; + [Min(0)] + [Tooltip("Time in seconds the lamp takes to reach 100% of intensity.")] public float FadeSpeedUp; + + [Min(0)] + [Tooltip("Time in seconds the lamp takes to turn off.")] public float FadeSpeedDown; - private bool IsFlasher => FadeSpeedUp == 0f && FadeSpeedDown == 0f; + + private bool FadeEnabled => FadeSpeedUp > 0f || FadeSpeedDown > 0f; #endregion @@ -178,19 +184,23 @@ private void Awake() } // enable at 0 foreach (var unityLight in _unityLights) { - if (IsFlasher) { - unityLight.enabled = false; - - } else { + if (FadeEnabled) { unityLight.enabled = true; unityLight.intensity = 0; + + } else { + unityLight.enabled = false; } } } public void FadeTo(float value) { - if (IsFlasher) { + if (FadeEnabled) { + StopAllCoroutines(); + StartCoroutine(nameof(Fade), value); + + } else { foreach (var unityLight in _unityLights) { if (value > 0) { unityLight.intensity = value * _fullIntensity; @@ -200,10 +210,6 @@ public void FadeTo(float value) unityLight.enabled = false; } } - - } else { - StopAllCoroutines(); - StartCoroutine(nameof(Fade), value); } } @@ -242,12 +248,19 @@ private IEnumerator Fade(float value) ? FadeSpeedUp * (_fullIntensity - a) / _fullIntensity : FadeSpeedDown * (1 - (_fullIntensity - a) / _fullIntensity); - while (counter < duration) { - counter += Time.deltaTime; + if (duration == 0) { foreach (var unityLight in _unityLights) { - unityLight.intensity = Mathf.Lerp(a, b, counter / duration); + unityLight.intensity = b; + } + + } else { + while (counter <= duration) { + counter += Time.deltaTime; + foreach (var unityLight in _unityLights) { + unityLight.intensity = Mathf.Lerp(a, b, counter / duration); + } + yield return null; } - yield return null; } }