Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
<small>*From the Medieval Madness manual*</small>

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.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
| **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.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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<Player>();
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -41,12 +42,14 @@ private enum LampListColumn
FadingSteps = 6,
}

private readonly TableComponent _tableComponent;
private readonly List<GamelogicEngineLamp> _gleLamps;

private readonly ObjectReferencePicker<ILampDeviceComponent> _devicePicker;

public LampListViewItemRenderer(List<GamelogicEngineLamp> gleLamps, TableComponent tableComponent)
{
_tableComponent = tableComponent;
_gleLamps = gleLamps;
_devicePicker = new ObjectReferencePicker<ILampDeviceComponent>("Lamps", tableComponent, false);
}
Expand Down Expand Up @@ -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<Player>();
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<string, float> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.InputSystem;
using VisualPinball.Engine.Game.Engines;

namespace VisualPinball.Unity.Editor
Expand All @@ -35,6 +36,12 @@ public abstract class ListViewItemRenderer<TListData, TDeviceITem, TStatus> wher

protected abstract void RenderDeviceElement(TListData listData, Rect cellRect, Action<TListData> updateAction);

protected virtual void OnIconClick(TListData data, bool pressedDown)
{
}

protected bool MouseDownOnIcon;

protected void RenderId(Dictionary<string, TStatus> statuses, ref string id, Action<string> setId, TListData listData, Rect cellRect, Action<TListData> updateAction)
{
const float idWidth = 25f;
Expand All @@ -58,8 +65,19 @@ protected void RenderId(Dictionary<string, TStatus> 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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@

namespace VisualPinball.Unity.Editor
{
public class SwitchListViewItemRenderer : ListViewItemRenderer<SwitchListData, GamelogicEngineSwitch, bool>
public class SwitchListViewItemRenderer : ListViewItemRenderer<SwitchListData, GamelogicEngineSwitch, IApiSwitchStatus>
{
protected override List<GamelogicEngineSwitch> 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
{
Expand All @@ -49,19 +49,21 @@ private enum SwitchListColumn
private readonly InputManager _inputManager;

private readonly ObjectReferencePicker<ISwitchDeviceComponent> _devicePicker;
private readonly TableComponent _tableComponent;

public SwitchListViewItemRenderer(List<GamelogicEngineSwitch> gleSwitches, TableComponent tableComponent, InputManager inputManager)
{
_gleSwitches = gleSwitches;
_inputManager = inputManager;
_devicePicker = new ObjectReferencePicker<ISwitchDeviceComponent>("Switch Devices", tableComponent, false);
_tableComponent = tableComponent;
}

public void Render(TableComponent tableComponent, SwitchListData data, Rect cellRect, int column, Action<SwitchListData> updateAction)
{
EditorGUI.BeginDisabledGroup(Application.isPlaying);
var switchStatuses = Application.isPlaying
? tableComponent.gameObject.GetComponent<Player>()?.SwitchStatusesClosed
? tableComponent.gameObject.GetComponent<Player>()?.SwitchStatuses
: null;
switch ((SwitchListColumn)column)
{
Expand All @@ -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<IGamelogicEngine>();
var player = _tableComponent.GetComponent<Player>();
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<SwitchListData> updateAction)
{
// don't render for constants
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,12 @@ protected override bool SetupCompleted()
}

var gle = TableComponent.gameObject.GetComponent<IGamelogicEngine>();

if (gle == null)
{
if (gle == null) {
DisplayMessage("No gamelogic engine set.");
return false;
}

if (_needsAssetRefresh)
{
if (_needsAssetRefresh) {
AssetDatabase.Refresh();
_needsAssetRefresh = false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using Unity.Mathematics;
using UnityEditor;
using UnityEngine;
using UnityEngine.InputSystem;
using VisualPinball.Engine.Game.Engines;

namespace VisualPinball.Unity.Editor
Expand All @@ -46,13 +47,15 @@ private enum WireListColumn
PulseDelay = 5,
}

private readonly TableComponent _tableComponent;
private readonly InputManager _inputManager;

private readonly ObjectReferencePicker<ISwitchDeviceComponent> _sourceDevicePicker;
private readonly ObjectReferencePicker<IWireableComponent> _destDevicePicker;

public WireListViewItemRenderer(TableComponent tableComponent, InputManager inputManager)
{
_tableComponent = tableComponent;
_inputManager = inputManager;

_sourceDevicePicker = new ObjectReferencePicker<ISwitchDeviceComponent>("Wire Source", tableComponent, false);
Expand Down Expand Up @@ -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<Player>();
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<string, (bool, float)> statuses, WireListData listData, Rect cellRect, Action<WireListData> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Light>()) {
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);
Expand Down
3 changes: 3 additions & 0 deletions VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, float> LampStatuses { get; } = new Dictionary<string, float>();
internal void RegisterLamp(ILampDeviceComponent component, IApiLamp lampApi) => _lamps[component] = lampApi;

Expand Down
7 changes: 4 additions & 3 deletions VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, bool> SwitchStatusesClosed => _switchPlayer.SwitchStatusesClosed;
public Dictionary<string, IApiSwitchStatus> SwitchStatuses => _switchPlayer.SwitchStatuses;
public Dictionary<string, bool> CoilStatuses => _coilPlayer.CoilStatuses;
public Dictionary<string, float> LampStatuses => _lampPlayer.LampStatuses;
public Dictionary<string, (bool, float)> WireStatuses => _wirePlayer.WireStatuses;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
Loading